krz/hutch

an ios client for sourcehut

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

v3.5.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
 40
 41Small, independently shippable mutations that already exist in the API but are
 42never called. Each removes a "why can't I do this here?" moment.
 43
 44- `updateTicket` — edit ticket title and description after creation. Currently
 45  a ticket can be created and its status changed, but never edited.
 46- `deleteTicket` — delete a ticket.
 47- `trackerSubscribe` / `trackerUnsubscribe`, `ticketSubscribe` /
 48  `ticketUnsubscribe`, `mailingListSubscribe` / `mailingListUnsubscribe` 49  subscriptions are currently read-only. `MailingListListView` reads the
 50  `subscriptions` query, but nothing can subscribe or unsubscribe.
 51- `updatePreferences` (todo.sr.ht and lists.sr.ht) — email notification
 52  preferences.
 53
 54### Refactors to fold in
 55
 56These are touched by everything in later phases, so they belong here rather
 57than as standalone work.
 58
 59- `SRHTClient` has four near-identical request-and-decode paths (`execute`,
 60  `executeMultipart`, `executeMultipartFiles`, `executeAndCache`, plus the
 61  private `performGraphQLRequest`). The token guard, header setup, status-code
 62  handling, and a ~35-line `#if DEBUG` logging block are each duplicated about
 63  five times. Collapse to one request builder and one decode helper.
 64- Two caches overlap: the in-memory `responseCache` and the persistent `cache`,
 65  reached through two different `executeCached` overloads with different return
 66  types and semantics (one does stale-while-revalidate with TTLs, the other only
 67  checks memory). Unify on the TTL-aware path.
 68
 69## Phase 2: Patchsets
 70
 71The flagship gap. There is currently no reference to `patchset` anywhere in the
 72Swift source, yet lists.sr.ht exposes a full `Patchset` type (subject, version,
 73prefix, status, coverLetter, patches, tools, mbox), a `patchset` query, and an
 74`updatePatchset` mutation. Sending and reviewing patches over email is the
 75SourceHut contribution model, and Hutch cannot currently participate in it.
 76
 77Scope this as review-and-triage, not submission:
 78
 79- Patchset list per mailing list.
 80- Patchset detail: cover letter, per-patch diffs (reuse the existing
 81  `DiffView`), version and superseded-by chain.
 82- Status transitions via `updatePatchset`.
 83
 84Patch *submission* is an email / `git send-email` flow and is likely out of
 85reach from the app. Treat that boundary as explicit rather than half-building
 86it.
 87
 88## Phase 3: Polish and reach
 89
 90- **Localization.** The project sets `LOCALIZATION_PREFERS_STRING_CATALOGS =
 91  YES` but ships no string catalog, so every user-facing string is hardcoded
 92  English.
 93- **Accessibility.** Labels and hints appear in only 16 of roughly 130 view
 94  files.
 95- `uploadArtifact` / `deleteArtifact` — artifacts are read-only today.
 96- Webhook management. Zero calls to any `create*Webhook` across every service.
 97  Push notifications are out of scope because they need a relay server (see
 98  [SCOPE.md](SCOPE.md)), but webhook management is client-side only and is a
 99  prerequisite if that relay ever ships.
100- `auditLog` (meta.sr.ht) — unused security surface.
101- Build groups (`createGroup`, `startGroup`) and secret management
102  (`shareSecret`, the `secrets` query). Today `secrets` is only a submit toggle.
103- Mailing list creation and settings (`createMailingList`, `updateMailingList`,
104  `deleteMailingList`).
105- `events` feed (todo.sr.ht) and `archiveMessage` (lists.sr.ht).
106
107## Housekeeping
108
109- `Hutch/Hutch/App/AccountSession.swift` sits in a stray nested directory;
110  `Hutch/HutchTests/` is empty.
111</content>
112</invoke>