krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

v3.7.0: ROADMAP.md · raw

  1# Roadmap
  2
  3Planned work for Hutch, ordered by dependency. Feature gaps below were
  4identified by diffing the GraphQL schema dumps in `Docs/API` against actual
  5call sites in the Swift source.
  6
  7See [SCOPE.md](SCOPE.md) for features that are intentionally out of scope.
  8
  9## Phase 0: Unblock CI — done (v3.5.0)
 10
 11Nothing downstream is trustworthy until the build badge means something.
 12
 13- ~~Fix `repo-structure-check` in `builds/swift-ci.yml`~~. It asserted
 14  `test -d "website"`, but `website/` was removed in `24c8bc6` (2026-04-10), so
 15  the check had failed since then.
 16- ~~Add a macOS CI job that runs `xcodebuild test`~~. builds.sr.ht has no macOS
 17  image and its maintainer has ruled them out, so `xcodebuild` cannot run there.
 18  The test plan now runs on the GitHub mirror via `.github/workflows/test.yml`;
 19  builds.sr.ht keeps secret scanning and structure checks.
 20
 21Turning the gate on first required making the suite green. All 214 tests had
 22been running only on demand in Xcode, and ten had rotted:
 23
 24- The `Hutch` scheme referenced `container:HutchTests` without the
 25  `.xctestplan` extension, so `xcodebuild test -scheme Hutch` — the path the
 26  README sends contributors down — could not run at all.
 27- Five were test-side rot: uppercase GraphQL enum rawValues asserted as
 28  lowercase, an ordering expectation predating `sortBuildItemsForTriage`,
 29  `request.httpBody` read inside a `URLProtocol` (always nil; the body lives on
 30  `httpBodyStream`), an incident fixture contradicting its own RSS input, and an
 31  image assertion that treated the correct `&` attribute encoding as a bug.
 32- Four were real bugs the suite had been right about all along: repository
 33  descriptions could not be cleared (a nil subscript assignment drops the key
 34  instead of sending JSON null), `serviceNotProvisioned` was unreachable behind
 35  a broader `no such` match, code spans rendered their contents as live markup,
 36  and inbox threads keyed `id` on a subject-derived grouping key so two threads
 37  sharing a subject on one list collided under `Identifiable`.
 38
 39## Phase 1: Close the write gaps — done (v3.6.0)
 40
 41Small, independently shippable mutations that already existed in the API but
 42were never called. Each removes a "why can't I do this here?" moment.
 43
 44- ~~`updateTicket`~~ — edit a ticket's subject and body after creation.
 45- ~~`deleteTicket`~~ — delete a ticket, behind a confirmation.
 46- ~~`ticketSubscribe` / `ticketUnsubscribe`, `trackerSubscribe` /
 47  `trackerUnsubscribe`~~ — `Ticket.subscription` and `Tracker.subscription` are
 48  null when not subscribed, so both toggles reflect real server state.
 49- ~~`mailingListUnsubscribe`~~ — see the caveat below.
 50- ~~`updatePreferences`~~ (todo.sr.ht and lists.sr.ht) — `notifySelf` and
 51  `copySelf`, surfaced as an Email section in Settings.
 52
 53`mailingListSubscribe` is deliberately not wired up. `MailingList` has no
 54`subscription` field, unlike `Ticket` and `Tracker`, so per-list state is only
 55knowable from the `subscriptions` query — which by definition lists what the
 56user is already subscribed to. Subscribing needs a list the user is *not*
 57subscribed to, and sr.ht exposes no discovery API to find one (see
 58[SCOPE.md](SCOPE.md) on hub.sr.ht). Revisit if hub.sr.ht ever gains an API, or
 59alongside Phase 2, which surfaces lists through patchsets.
 60
 61### Refactors folded in
 62
 63- ~~Collapse `SRHTClient`'s duplicated request paths~~. Extracted
 64  `makeAuthorizedRequest`, `send`, and `encodedGraphQLBody`; `executeMultipart`
 65  became the single-file case of `executeMultipartFiles`. The `#if DEBUG`
 66  logging block went from five copies to one. 938 lines to 569.
 67- ~~Unify the two `executeCached` overloads~~. The memory-only overload and
 68  `executeAndCache` turned out to be dead — all 38 call sites already used the
 69  TTL-aware path — so both were removed rather than merged. `responseCache`
 70  remains as the in-memory layer behind `cachedPayload`.
 71
 72Known follow-up: `BuildListViewModel`, `RepositoryListViewModel`, and
 73`PasteService` still read `client.responseCache` directly, falling back across
 74two different cache keys. That predates `APICacheKeys` and should be folded into
 75`cachedPayload`.
 76
 77## Phase 2: Patchsets — done (v3.7.0)
 78
 79The flagship gap. Sending and reviewing patches over email is the SourceHut
 80contribution model, and Hutch had no reference to `patchset` anywhere.
 81
 82Scoped as review-and-triage, not submission:
 83
 84- ~~Patchset list per mailing list~~ — see the caveat below.
 85- ~~Patchset detail~~: cover letter, per-patch diffs (via the existing
 86  `DiffView`), checks, and the version / superseded-by chain.
 87- ~~Status transitions via `updatePatchset`~~.
 88
 89Two schema facts shaped the result, and are worth knowing before extending this:
 90
 91- **`MailingList` has no `patchsets` field.** A list's patchsets cannot be
 92  queried directly; they are reachable only through thread roots. The existing
 93  threads query now also selects `root.patchset`, so the Patches tab costs no
 94  extra request — but it also means patchsets cannot be filtered by status
 95  server-side, and only patchsets whose thread appears in the current page are
 96  listed.
 97- **`Patch` carries no diff.** It has only `index`, `count`, `version`,
 98  `prefix`, `subject`, and `trailers`. The diff exists solely inside the email
 99  body, so it is recovered with `InboxThreadUtilities.segmentMessageBody` — the
100  same splitter the inbox thread view uses.
101
102Patch *submission* remains out of reach: it is a `git send-email` flow, not a
103GraphQL mutation. Treat that boundary as explicit rather than half-building it.
104
105## Phase 3: Polish and reach
106
107- **Localization.** The project sets `LOCALIZATION_PREFERS_STRING_CATALOGS =
108  YES` but ships no string catalog, so every user-facing string is hardcoded
109  English.
110- **Accessibility.** Labels and hints appear in only 16 of roughly 130 view
111  files.
112- `uploadArtifact` / `deleteArtifact` — artifacts are read-only today.
113- Webhook management. Zero calls to any `create*Webhook` across every service.
114  Push notifications are out of scope because they need a relay server (see
115  [SCOPE.md](SCOPE.md)), but webhook management is client-side only and is a
116  prerequisite if that relay ever ships.
117- `auditLog` (meta.sr.ht) — unused security surface.
118- Build groups (`createGroup`, `startGroup`) and secret management
119  (`shareSecret`, the `secrets` query). Today `secrets` is only a submit toggle.
120- Mailing list creation and settings (`createMailingList`, `updateMailingList`,
121  `deleteMailingList`).
122- `events` feed (todo.sr.ht) and `archiveMessage` (lists.sr.ht).
123
124## Housekeeping
125
126- `Hutch/Hutch/App/AccountSession.swift` sits in a stray nested directory;
127  `Hutch/HutchTests/` is empty.