krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.6.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
78
79The flagship gap. There is currently no reference to `patchset` anywhere in the
80Swift source, yet lists.sr.ht exposes a full `Patchset` type (subject, version,
81prefix, status, coverLetter, patches, tools, mbox), a `patchset` query, and an
82`updatePatchset` mutation. Sending and reviewing patches over email is the
83SourceHut contribution model, and Hutch cannot currently participate in it.
84
85Scope this as review-and-triage, not submission:
86
87- Patchset list per mailing list.
88- Patchset detail: cover letter, per-patch diffs (reuse the existing
89 `DiffView`), version and superseded-by chain.
90- Status transitions via `updatePatchset`.
91
92Patch *submission* is an email / `git send-email` flow and is likely out of
93reach from the app. Treat that boundary as explicit rather than half-building
94it.
95
96## Phase 3: Polish and reach
97
98- **Localization.** The project sets `LOCALIZATION_PREFERS_STRING_CATALOGS =
99 YES` but ships no string catalog, so every user-facing string is hardcoded
100 English.
101- **Accessibility.** Labels and hints appear in only 16 of roughly 130 view
102 files.
103- `uploadArtifact` / `deleteArtifact` — artifacts are read-only today.
104- Webhook management. Zero calls to any `create*Webhook` across every service.
105 Push notifications are out of scope because they need a relay server (see
106 [SCOPE.md](SCOPE.md)), but webhook management is client-side only and is a
107 prerequisite if that relay ever ships.
108- `auditLog` (meta.sr.ht) — unused security surface.
109- Build groups (`createGroup`, `startGroup`) and secret management
110 (`shareSecret`, the `secrets` query). Today `secrets` is only a submit toggle.
111- Mailing list creation and settings (`createMailingList`, `updateMailingList`,
112 `deleteMailingList`).
113- `events` feed (todo.sr.ht) and `archiveMessage` (lists.sr.ht).
114
115## Housekeeping
116
117- `Hutch/Hutch/App/AccountSession.swift` sits in a stray nested directory;
118 `Hutch/HutchTests/` is empty.