When a PR's head branch is behind main (or conflicts), Gitea's merge returns
409. Previously the app just showed an error. Now: on a 409 the app calls
POST /pulls/{index}/update (merges base into head), then retries the merge
once. Add APIError.mergeConflict for a clear message and map 409 in the
client. Bump to 2.6.
- Bottom tab bar: replace the floating capsule (which had space below it and
was too thin) with a full-width frosted-glass bar at the standard iOS tab
bar height, sitting flush above the home indicator — matching Apple Music.
- Landing page title: use .large navigation title so 'Pull Requests' aligns at
the top like Apple Music's 'Inicio', instead of the inline title.
- Bump to 2.5.
- Bottom tab bar: reduce vertical padding (8→4), icon size (20→18), and
bottom inset (8→4) so it matches Apple's standard compact tab bar height
(Apple Music etc.) instead of a tall bar.
- Remove the manual reload button from the loaded PR toolbar — the detail
view auto-loads on appearance, so it was redundant clutter. The reload
button is kept only on the error/retry state. Bump to 2.4.
- Remove the in-list loading wheel overlay; the landing page just shows the
cached list or the 'No open PRs' screen.
- Add a 'Monitored repos' setting (Settings > Inbox) that limits the inbox to
specific repos, cutting the number of API calls dramatically. Empty =
monitor all accessible repos. Bump to 2.3.
.task fires on every appearance, including popping back from a PR detail, so
the landing page re-ran the full network refresh on each back navigation.
Guard the initial load with didInitialLoad so the list loads once and then
only refreshes when the user pulls down. Bump to 2.2.
The inbox was slow because it enriched every PR with CI runs + reviews (2
extra API calls per PR, sequential). Drop that from the list — CI/review
status now shows only in the PR detail screen. Add a disk cache so the landing
screen renders instantly from the last load, then refreshes in the background
and keeps the cached list on network failure. Bump to 2.1.
- Merge button: use the proper SF Symbol 'arrow.triangle.merge' instead of
the download icon 'arrow.down.circle'.
- Add a Settings toggle 'Delete branch after merge'. When on, the merge call
passes delete_branch_after_merge=true and the head branch is deleted after
a successful merge (best-effort).
- PR detail bottom tab bar: floating capsule style (regularMaterial + shadow)
like the latest iOS tab bars, instead of a full-width bar.
- Bump to 2.0.
- Remove the Commits tab (not useful).
- Move the section switcher to a bottom tab bar with icons (Overview /
Conversation / Files / Checks) instead of a top segmented control.
- Move Review (menu) and Merge into the navigation toolbar, removing the
bottom action bar.
- Add an Overview tab that renders the PR body as markdown via SwiftUI's
built-in AttributedString(markdown:) (no third-party dependency).
- Bump to 1.9.
- Checks: only show the Logs button once a job has actually run (terminal
conclusion). Queued/running jobs are grey with no logs, so the button was
useless there.
- Inbox: remove the segmented filter bar (All/Assigned/By me/Review) — not
useful yet. Bump to 1.8.
Commits tab was empty: Gitea PR commits have no 'id' field (only sha), but
PRCommit.id was a non-optional let decoded from 'id', so decoding [PRCommit]
always threw and the tab silently showed nothing. Make PRCommit.id a computed
sha-based identity so the array decodes and renders.
CI: add a Logs button per job that fetches the job's raw log text
(/actions/jobs/{id}/logs) into a viewable, shareable screen. Bump to 1.7.
The diff data was fine, but the render collapsed: a LazyVStack inside a
dual-axis ScrollView([.horizontal, .vertical]) gets an unbounded proposal and
rows collapse to near-zero width — the green addition background rendered but
the monospaced text was laid out at ~0 width and disappeared. Use a plain
VStack inside nested vertical→horizontal ScrollViews (the stable pattern for
diff text) and give each line fixedSize(horizontal:) so the text isn't
clipped. Bump to 1.6.
DiffLine.id was a computed property returning a fresh UUID() on every access.
SwiftUI's ForEach requires stable identities; regenerating each read churned
the main thread, hanging the diff screen and eventually crashing. Store the
UUID once per line. Also add a 30s URLRequest timeout so a hung request can't
leave the 'Loading diff...' spinner forever. Bump to 1.5.
This Gitea instance never returns a 'patch' field from /pulls/{n}/files (the
field is absent for all files, regardless of size), so the diff viewer always
fell back to 'Diff too large to preview'. Now DiffView fetches the PR's .diff
endpoint (plain-text unified diff), extracts the hunks for the selected file,
and renders them colour-coded. Bump to 1.4.
Opening a PR could fail the whole screen when any one detail sub-request
returns 401/403 (e.g. a token scope the repo's Actions endpoint needs).
Refactor load() so the PR fetch is the only fatal call; comments/reviews/
files/commits/runs each fail independently, are logged, and the rest still
render. Also log actions/runs failures for visibility. Bump to 1.3.
LogStore is @MainActor; GiteaClient.request is not main-actor-isolated, so the
5 LogStore.shared.log calls in the request handler need 'await' under Swift 5
strict concurrency. Without them the build fails at compile time.
- LogStore: bounded in-memory + persisted log of API calls and errors,
viewable and shareable from Settings → View logs.
- GiteaClient: log every request/response status (esp. 404s).
- Inbox: a single failing repo no longer wipes the whole inbox — per-repo
errors are caught, logged, and the rest still load.
- Bump to 1.2 so SideStore offers the update.
SideStore only shows an update when the version changes; the previous build
stayed at 1.0 so no update appeared. Bump MARKETING_VERSION to 1.1 and have
the workflow read CFBundleShortVersionString from the built app's Info.plist
instead of a hardcoded VERSION, so the metadata can never drift from the
pbxproj again.
Gitea returns dates like 2026-08-12T11:09:13+02:00. The default JSONDecoder
date strategy expects a Unix timestamp number, so decoding any Date? field
threw and the whole [PullRequest] array failed — the inbox showed no PRs.
Add a shared decoder with a custom ISO8601 strategy (with and without
fractional seconds) and use it for all get/post decoding.
Multi-repo PR inbox, review (approve/request changes/comment), merge with
gating toggle, read-only diff viewer, and Gitea Actions check status with
one-tap re-run. Ships unsigned via the iMac runner to the SideStore source.