31 Commits
Author SHA1 Message Date
Hermes bae531f850 Add push-equivalent notifications via background polling; bump to 3.6
Build iOS App / Build .ipa and publish source (push) Successful in 31s
Sideloaded apps can't use APNs (no push entitlement without Apple signing),
so deliver notifications via an iOS BGAppRefreshTask that polls the monitored
repos every ~15 min and fires local notifications (no Apple push needed):
- new PRs that appear in the open set
- CI status changes on existing PRs

Persistence: NotificationState tracks announced PR ids + last-known CI status
so we only fire on actual changes, not every poll. AppDelegate registers the
background task and reschedules the next one. Info.plist gets UIBackgroundModes
fetch + the BGTask identifier. Settings gains a Notifications toggle; also
poll after a foreground inbox load so state seeds and changes are caught
promptly. Bump to 3.6.
2026-08-24 08:18:29 +00:00
Hermes 6ed50ecc4a Ignore URLError.cancelled so pull-to-refresh doesn't show a spurious error; bump to 3.5
Build iOS App / Build .ipa and publish source (push) Successful in 1m44s
Pull-to-refresh cancels the still-in-flight request (URLError.cancelled,
localizedDescription 'cancelled') to run the refresh, which surfaced as an
error dialog. Add an isCancellation helper and skip cancellations in the PR
load and CI-runs paths. Bump to 3.5.
2026-08-24 07:18:35 +00:00
Hermes a045a36125 Reload inbox after merge + pull-to-refresh on PR content; bump to 3.4
Build iOS App / Build .ipa and publish source (push) Successful in 34s
- After a merge, the app returns to the landing page but the inbox kept stale
  PRs. Add an inboxNeedsRefresh flag set on merge success; InboxView reloads
  on next appearance when it's set.
- The PR content (incl. the Checks/workflow tab) is now pull-to-refreshable
  via .refreshable on the content ScrollView, so workflow status can be
  updated by pulling down. Bump to 3.4.
2026-08-17 13:23:10 +00:00
Hermes a9d408ee85 Fix compile errors: guard unwrap + ViewBuilder var mutation
Build iOS App / Build .ipa and publish source (push) Successful in 25s
- PRDetailView: guard let current = selectedTab ?? .overview fails because
  ?? already unwraps to non-optional. Use let current = selectedTab ?? .overview.
- MarkdownText: var inCodeBlock inside ViewBuilder is not allowed. Precompute
  lines with their code-block state as a computed property on a private struct,
  then iterate via ForEach in the builder. Bump to 3.3.
2026-08-14 09:45:44 +00:00
Hermes dd0fc84238 Restore vertical scroll + line-based markdown; swipe on bottom bar; bump to 3.3
Build iOS App / Build .ipa and publish source (push) Failing after 16s
The horizontal paging ScrollView squished the markdown text and the TabView
broke navigation. Revert to the plain vertical ScrollView + switch (always
rendered correctly). Swipe-to-change now lives on the bottom tab bar itself
(simultaneous DragGesture), so tapping tabs still works. Rewrite MarkdownText
line-based: each source line renders on its own line (AttributedString
collapses single newlines into spaces), with heading/bullet/code handling.
Bump to 3.3.
2026-08-14 09:43:39 +00:00
Hermes 221d23a6a3 Fix compile: scrollPosition needs optional binding; selectedTab is now DetailTab?
Build iOS App / Build .ipa and publish source (push) Successful in 25s
scrollPosition(id:) requires Binding<some Hashable?>, but selectedTab was a
non-optional DetailTab — the build failed with 'generic parameter some
Hashable could not be inferred'. Make selectedTab optional (DetailTab? =
.overview); bottom-bar == comparisons still work via optional promotion.
2026-08-14 07:30:18 +00:00
Hermes 1817422836 Replace page TabView with paging ScrollView; fix diff + centering; bump to 3.2
Build iOS App / Build .ipa and publish source (push) Failing after 16s
The TabView with .page style broke NavigationLink inside tabs (the page
gesture swallows taps) and centered content. Replace with a horizontal paging
ScrollView using containerRelativeFrame and scrollTargetBehavior(.paging):
same swipe-to-change behavior, no gesture conflicts, diff view works again.
Bump to 3.2.
2026-08-14 06:46:25 +00:00
Hermes ed36ed72de Merge spinner + swipeable PR sections; bump to 3.1
Build iOS App / Build .ipa and publish source (push) Successful in 25s
- MergeSheet: show a spinner in the toolbar while the merge is confirming/
  rejecting, lock Cancel and interactive dismissal during the request, and
  prevent double-taps.
- PR detail: replace the ScrollView-switch with a page-style TabView so you
  can swipe left/right to change sections (iOS 26 style); bottom bar taps
  still select. Make DetailTab Hashable. Bump to 3.1.
2026-08-13 22:03:14 +00:00
Hermes 2301b8a861 Merge success animation + auto-return to landing page; bump to 3.0
Build iOS App / Build .ipa and publish source (push) Successful in 23s
On a successful merge, show a brief full-screen green checkmark animation
('Merged'), then automatically pop the PR detail view back to the landing
page. MergeSheet now calls an onSuccess callback; PRDetailView shows the
overlay and dismisses after ~1.2s. Bump to 3.0.
2026-08-13 16:35:06 +00:00
Hermes 5e9ee9e5cf Prevent duplicate approve reviews + redesign bottom bar; bump to 2.9
Build iOS App / Build .ipa and publish source (push) Successful in 22s
- Approve: rapid taps fired multiple submitReview calls that each appended a
  review, stacking duplicate entries. Add a busy guard (skip while one is in
  flight) and dedupe reviews per user so the latest replaces earlier ones;
  disable the review menu while submitting.
- Bottom bar: replace the full-width bar with a floating frosted-glass pill
  inset from the edges (like SideStore), with a highlighted capsule behind
  the active tab. Bump to 2.9.
2026-08-13 16:00:32 +00:00
Hermes ed22fbc351 Empty state pull-to-refresh + inline title aligned with gear; bump to 2.8
Build iOS App / Build .ipa and publish source (push) Successful in 22s
- Empty state ('No open PRs') was a bare ContentUnavailableView with no
  pull-to-refresh, so you couldn't reload when the list was empty. Wrap it in
  a refreshable ScrollView so pulling down reloads even with no PRs.
- Switch the landing title from .large to .inline so 'Pull Requests' sits on
  the same row as the settings gear (better space use). Bump to 2.8.
2026-08-13 15:48:13 +00:00
Hermes ba3cda06bb Add iOS app icon (git merge symbol) + upload icon in CI; bump to 2.7
Build iOS App / Build .ipa and publish source (push) Failing after 4m46s
Generate a clean, centered git merge/PR icon via ComfyUI (DreamShaper on
wilkes) and bundle it as the app icon. Update the CI workflow to extract the
icon from the built bundle and upload it as icon.png so SideStore shows it.
Bump to 2.7.
2026-08-13 13:31:18 +00:00
Hermes a1ffcaffd2 Auto-update branch when merge conflicts (branch behind main); bump to 2.6
Build iOS App / Build .ipa and publish source (push) Successful in 22s
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.
2026-08-13 13:02:45 +00:00
Hermes 0d2c4a3cb6 Match Apple Music: full-width tab bar + large title; bump to 2.5
Build iOS App / Build .ipa and publish source (push) Successful in 36s
- 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.
2026-08-13 07:13:48 +00:00
Hermes 7399187fb4 Slim bottom tab bar to Apple height; remove redundant reload button; bump to 2.4
Build iOS App / Build .ipa and publish source (push) Successful in 25s
- 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.
2026-08-13 06:59:09 +00:00
Hermes 880cff9e68 Remove in-list spinner + add monitored-repos setting; bump to 2.3
Build iOS App / Build .ipa and publish source (push) Successful in 22s
- 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.
2026-08-13 06:43:46 +00:00
Hermes 75f5f3bc64 Load inbox only on first appearance; refresh via pull-to-refresh; bump to 2.2
Build iOS App / Build .ipa and publish source (push) Successful in 22s
.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.
2026-08-13 06:35:20 +00:00
Hermes 2b929e65e9 Speed up landing screen: drop per-PR enrichment + add inbox cache; bump to 2.1
Build iOS App / Build .ipa and publish source (push) Successful in 23s
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.
2026-08-13 06:27:32 +00:00
Hermes 53b3f69d0f Merge icon, delete-branch-after-merge setting, floating tab bar; bump to 2.0
Build iOS App / Build .ipa and publish source (push) Successful in 22s
- 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.
2026-08-12 22:37:42 +00:00
Hermes fce0385708 Restructure PR detail: remove Commits tab, bottom icon tab bar, toolbar actions, markdown overview; bump to 1.9
Build iOS App / Build .ipa and publish source (push) Successful in 21s
- 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.
2026-08-12 22:28:18 +00:00
Hermes de9729b898 Hide Logs button for unrun jobs; remove inbox filter bar; bump to 1.8
Build iOS App / Build .ipa and publish source (push) Successful in 23s
- 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.
2026-08-12 22:06:09 +00:00
Hermes 9f3fb95cbe Fix commits tab + add CI job logs; bump to 1.7
Build iOS App / Build .ipa and publish source (push) Successful in 21s
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.
2026-08-12 21:55:24 +00:00
Hermes a3b8eefdb2 Fix diff text not rendering: LazyVStack in dual-axis ScrollView collapses row width; bump to 1.6
Build iOS App / Build .ipa and publish source (push) Successful in 21s
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.
2026-08-12 21:46:14 +00:00
Hermes db694543c1 Fix diff screen hang/crash: stable DiffLine id + request timeout; bump to 1.5
Build iOS App / Build .ipa and publish source (push) Successful in 22s
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.
2026-08-12 21:36:34 +00:00
Hermes 8be8f3ac50 Fix diff viewer: fetch PR .diff instead of relying on per-file patch; bump to 1.4
Build iOS App / Build .ipa and publish source (push) Successful in 21s
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.
2026-08-12 21:18:01 +00:00
Hermes 9e71b6a8b9 Make PR detail load resilient to per-endpoint 403; bump to 1.3
Build iOS App / Build .ipa and publish source (push) Successful in 23s
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.
2026-08-12 21:00:22 +00:00
Hermes 58eb145717 Fix compile: await LogStore calls from non-MainActor GiteaClient
Build iOS App / Build .ipa and publish source (push) Successful in 44s
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.
2026-08-12 19:51:32 +00:00
Hermes b51b2ee5c8 Add in-app diagnostic logging + resilient inbox; bump to 1.2
Build iOS App / Build .ipa and publish source (push) Failing after 5m44s
- 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.
2026-08-12 18:46:08 +00:00
Hermes b2311d8007 Fix PR inbox: decode Gitea ISO8601 dates with timezone offset
Build iOS App / Build .ipa and publish source (push) Successful in 28s
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.
2026-08-12 17:51:46 +00:00
Hermes ddb6983a99 Rename model Label to PRLabel to avoid shadowing SwiftUI Label
Build iOS App / Build .ipa and publish source (push) Successful in 23s
2026-08-12 17:19:51 +00:00
Hermes 1025634978 GiteaPR: native iOS app to manage Gitea pull requests
Build iOS App / Build .ipa and publish source (push) Failing after 35s
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.
2026-08-12 17:07:58 +00:00