Commit d5dec7be8a

d5dec7be8a88165dcb81c88c6fa23aa7e7360a03

parent: 073fca4510

Unsigned

cmc <hello@cleberg.net> · 2026-07-16 15:04 UTC

docs: bucket the Phase 3 backlog into semantic versions

Layout: unified · split

ROADMAP.md +67 −8
@@ -134,6 +134,28 @@ GraphQL mutation. Treat that boundary as explicit rather than half-building it.
134134Unlike Phases 1 and 2, this is not one shippable thing. It is several, and they
135135are sized very differently — measure before committing to one.
136136
137### Release plan
138
139Hutch is an app with a `MARKETING_VERSION`, not a library with an API contract,
140so "breaking change" does not apply. These buckets track *user-visible scale*.
141
142| Version | Contents | Why here |
143| --- | --- | --- |
144| v3.8.1 | SonarCloud triage; housekeeping | No behaviour change at all |
145| v3.9.0 | "What's cooking" ingest; doc truth-up; deploy keys | Ships one feature, corrects the map |
146| v3.10.0 | hub.sr.ht writes: projects, discovery, `mailingListSubscribe` | Provisional — gated on what v3.9.0 finds |
147| v3.11.0 | Accessibility | Independent, device-verified |
148| v4.0.0 | Localization *with* translations | The only true re-presentation |
149| — | Swift 6 language mode; cache reads | Internal; ride along, no tag |
150
151Ordering is by dependency, not size. v3.9.0 leads because it is the only item
152that corrects the others' inputs: the ingest's real output is a `SCOPE.md` that
153is true, and v3.10.0 rests entirely on one unverified sentence in a blog post.
154Do not commit v3.10.0's number until the SDL has been read — the bucket may turn
155out to be empty, which is the point of sequencing it second.
156
157`KeychainHelper` is deliberately unbucketed; see the SonarCloud hotspots below.
158
137159### API features — done (v3.8.0)
138160
139161- ~~`uploadArtifact` / `deleteArtifact`~~ — artifacts were read-only.
@@ -152,7 +174,7 @@ Three of the six planned. The other three did not survive contact:
152174 on judgement — see [SCOPE.md](SCOPE.md) for the reasoning, so they do not get
153175 re-proposed.
154176
155### Localization
177### Localization — v4.0.0, and only with translations
156178
157179The project sets `LOCALIZATION_PREFERS_STRING_CATALOGS = YES` but ships no
158180string catalog, so every user-facing string is hardcoded English. Roughly 634
@@ -164,12 +186,19 @@ for users until translations exist. It is groundwork, and it is the largest diff
164186in the roadmap — it touches nearly every view, with the regression risk that
165187implies.
166188
167### Accessibility
189That combination is why this is bucketed at v4.0.0 *bundled with at least one
190real translation*, rather than shipped alone. An English-only catalog would earn
191the major number on regression risk while delivering nothing — the wrong trade.
192Hold the catalog until a translation lands. If it ever ships unbundled, it is
193groundwork and belongs in a quiet minor, not a 4.0.
194
195### Accessibility — v3.11.0
168196
169197Labels and hints appear in 17 of 89 view files. Mechanical and low-risk, but it
170198cannot be verified from a build — it needs VoiceOver driven on a device.
199Independent of every other bucket, so it can move if a device pass is convenient.
171200
172### SonarCloud backlog
201### SonarCloud backlog — v3.8.1
173202
17420351 open issues: **0 bugs, 0 vulnerabilities, 51 code smells**, plus 3 security
175204hotspots. The headline number is misleading, so trust the breakdown before
@@ -193,7 +222,11 @@ The 3 hotspots are the part actually worth thought:
193222 `kSecAttrAccessibleWhenUnlockedThisDeviceOnly` with no
194223 `SecAccessControl`, so it does not require biometric or passcode
195224 authentication to read. That is a genuine product decision — should a stolen,
196 unlocked phone hand over a sr.ht token? — not a lint nit.
225 unlocked phone hand over a sr.ht token? — not a lint nit. **Unbucketed on
226 purpose:** adding `SecAccessControl` changes what a user must do to read their
227 own token, so it needs a decision first. If the answer is yes, it is a minor
228 bump of its own — a visible auth change should not hide inside a feature
229 release.
197230- `ReadmeView:1922` (**LOW**) — unrestricted WebView navigation. Probably a false
198231 positive: `isAllowedReadmeNavigationURL` enforces a scheme allowlist. Verify,
199232 then annotate.
@@ -201,7 +234,12 @@ The 3 hotspots are the part actually worth thought:
201234Query it with:
202235`https://sonarcloud.io/api/issues/search?componentKeys=zerolabsco_hutch&resolved=false`
203236
204### Ingest "What's cooking on SourceHut?"
237This is a patch because nothing executes differently afterwards. The 35 hardcoded-URI
238issues are resolved as *Won't Fix* in SonarCloud's web UI — not a commit at all — and
239the rest is three comments and one annotation. If it produces a diff that changes a
240runtime path, something has gone wrong.
241
242### Ingest "What's cooking on SourceHut?" — v3.9.0
205243
206244sr.ht posts a quarterly update to `~sircmpwn/sr.ht-announce`, mirrored at
207245<https://sourcehut.org/blog/>. Nothing in Hutch tracks it, so the API grows and
@@ -227,7 +265,25 @@ flags two openings:
227265Start from Q1 2026 forward — that is roughly when the current `Docs/API` dumps
228266were captured.
229267
230### Swift 6 language mode
268Research does not ship, so v3.9.0 pairs the ingest with **deploy keys** — the one
269self-contained feature it has already surfaced and that the SDL confirms exists.
270That gives the release something a user can see. Everything else the ingest turns
271up gets filed, not built, and hub.sr.ht gets its own bucket below.
272
273### hub.sr.ht writes — v3.10.0, provisional
274
275Everything here rests on a single sentence in the Q2 2026 post: that hub.sr.ht
276gained a writable GraphQL API. If true, three things unblock at once —
277project writes (Hutch's projects are read-only), discovery (which `SCOPE.md`
278rules out on the grounds hub has no public API), and `mailingListSubscribe`,
279which Phase 1 declined for exactly that reason.
280
281All three live or die on the same unverified claim, which is why this is
282sequenced after the ingest rather than planned now. Read
283`api/graph/schema.graphqls` in `hub.sr.ht` before committing the version number.
284The bucket may be empty.
285
286### Swift 6 language mode — no release of its own
231287
232288The project builds in Swift 5 language mode with
233289`SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`. Moving to Swift 6 is blocked on
@@ -243,14 +299,17 @@ concurrency diagnostics that are warnings today and errors there:
243299 methods, as in `HomeViewModel.loadDashboard` and
244300 `NotificationPreferencesViewModel.load`.
245301
246### Cache reads that bypass the client
302### Cache reads that bypass the client — no release of its own
247303
248304`BuildListViewModel`, `RepositoryListViewModel`, and `PasteService` still read
249305`client.responseCache` directly, each falling back across two different cache
250306keys. That predates `APICacheKeys` and should be folded into `cachedPayload`,
251307which already consults the persistent cache before the memory layer.
252308
253## Housekeeping
309Like Swift 6 above, this is internal and rides along with whatever release
310already touches that area. Neither justifies a tag.
311
312## Housekeeping — v3.8.1
254313
255314- `Hutch/Hutch/App/AccountSession.swift` sits in a stray nested directory;
256315 `Hutch/HutchTests/` is empty.