diff options
Diffstat (limited to 'ROADMAP.txt')
| -rw-r--r-- | ROADMAP.txt | 121 |
1 files changed, 81 insertions, 40 deletions
diff --git a/ROADMAP.txt b/ROADMAP.txt index 6c4765e..4352b9e 100644 --- a/ROADMAP.txt +++ b/ROADMAP.txt @@ -27,6 +27,15 @@ Things the schema does not tell you, each of which has already cost real time. `git clone --depth 1 https://git.sr.ht/~sircmpwn/<service>.sr.ht` and look at `api/graph/schema.graphqls`. Regenerating the dumps with a full introspection query would remove the trap. +- **`MailingList.subscription` does not report your subscription.** The field + exists and is typed `MailingListSubscription`, but it returns null even + immediately after a `mailingListSubscribe` that hands you back a real + subscription id — verified live against `~hutch`, for both owned and + non-owned lists. Do not gate subscribe-state on it. The authoritative source + is membership in the `subscriptions` query (correct: true after subscribe, + false after unsubscribe); the mutations take `listID: Int!`, read from + `list(rid:){ id }`. Cost the v3.11.0 subscribe toggle a full afternoon of the + "looks right, isn't" variety. ## Phase 0: Unblock CI — done (v3.5.0) @@ -79,13 +88,14 @@ were never called. Each removes a "why can't I do this here?" moment. - ~~`updatePreferences`~~ (todo.sr.ht and lists.sr.ht) — `notifySelf` and `copySelf`, surfaced as an Email section in Settings. -`mailingListSubscribe` is deliberately not wired up. `MailingList` has no -`subscription` field, unlike `Ticket` and `Tracker`, so per-list state is only -knowable from the `subscriptions` query — which by definition lists what the -user is already subscribed to. Subscribing needs a list the user is *not* -subscribed to, and sr.ht exposes no discovery API to find one (see -[SCOPE.txt](SCOPE.txt) on hub.sr.ht). Revisit if hub.sr.ht ever gains an API, or -alongside Phase 2, which surfaces lists through patchsets. +`mailingListSubscribe` was left unwired here on the view that per-list state was +only knowable from the `subscriptions` query, and subscribing needs a list you +are *not* subscribed to. **Shipped in v3.11.0** once live testing clarified two +things: a specific list is reachable without a discovery API (Lookup, a project's +lists, patchsets), and the `subscriptions` query *is* the reliable state source — +membership in it answers "am I subscribed to this rid?". `MailingList.subscription` +looked like a shortcut but is a trap (see API traps); it is not used. See +"mailing list subscribe" below. ### Refactors folded in @@ -144,10 +154,11 @@ so "breaking change" does not apply. These buckets track *user-visible scale*. | v3.8.1 | SonarCloud triage; housekeeping | No behaviour change at all | | v3.8.2 | Home system status moved to a title-bar status badge | Small UI relocation, no new surface | | v3.9.0 | ~~hub.sr.ht project writes + discovery (#12–#15); multi-language highlighting (#16); App Intents expansion (#17); man-page catalog sync (#7); checklist / recent-activity / pull-to-refresh fixes (#18, #11, #9)~~ | Shipped — the cut this session | -| v3.10.0 | "What's cooking" ingest; doc truth-up; deploy keys; revisit `mailingListSubscribe` | Ships one feature, corrects the map | -| v3.11.0 | Accessibility | Independent, device-verified | +| v3.10.0 | ~~git.sr.ht deploy keys~~ (shipped); ~~"What's cooking" ingest + doc truth-up~~ (done) | Ships one feature, corrects the map | +| v3.11.0 | ~~Mailing list subscribe/unsubscribe toggle~~ (shipped) | Ingest-surfaced; state via the `subscriptions` query (the `subscription` field is a trap) | +| v3.12.0 | Accessibility | Independent, device-verified | | v4.0.0 | Localization *with* translations | The only true re-presentation | -| — | Swift 6 language mode; cache reads | Internal; ride along, no tag | +| — | ~~Swift 6 language mode~~ (done); cache reads | Internal; ride along, no tag | 3.9.0 was cut this session, bundling the hub.sr.ht writes with the other features listed. That reorders the original plan: the "What's cooking" ingest @@ -276,7 +287,7 @@ exception earns the release its own line: the `forceRefresh` fix changes what pull-to-refresh does, so it needs a manual pass on a device before v3.8.1 ships, not just a green suite. -### Ingest "What's cooking on SourceHut?" — v3.9.0 +### Ingest "What's cooking on SourceHut?" — v3.10.0 sr.ht posts a quarterly update to `~sircmpwn/sr.ht-announce`, mirrored at <https://sourcehut.org/blog/>. Nothing in Hutch tracks it, so the API grows and @@ -294,19 +305,42 @@ flags two openings: - **hub.sr.ht gained a writable GraphQL API** for managing projects and project resources. ~~Rechecked and shipped~~: project create/edit, resource link/unlink, and public discovery landed (#12–#15) — see "hub.sr.ht writes" - below. `SCOPE.txt` still needs its "hub has no public API / no discovery" - claim corrected. `mailingListSubscribe` was *not* unblocked — that needs a - per-list subscription field lists.sr.ht still lacks. -- **git.sr.ht deploy keys are complete** (`createDeployKey` / `deleteDeployKey` - are in the SDL). Hutch never calls them. + below. `SCOPE.txt`'s "hub has no public API / no discovery" claim has since + been corrected. +- ~~git.sr.ht deploy keys are complete~~ — **shipped** (v3.10.0). + `createDeployKey` / `deleteDeployKey` (and `Repository.deployKeys`) are wired + into the repository actions menu, owner-only, alongside ACLs. Start from Q1 2026 forward — that is roughly when the current `Docs/API` dumps were captured. -Research does not ship, so v3.9.0 pairs the ingest with **deploy keys** — the one -self-contained feature it has already surfaced and that the SDL confirms exists. -That gives the release something a user can see. Everything else the ingest turns -up gets filed, not built, and hub.sr.ht gets its own bucket below. +Deploy keys — the one self-contained feature the ingest had already surfaced and +that the SDL confirmed — shipped in v3.10.0. + +**Ingest run (2026-08, Q1–Q2 posts + live schema introspection with a test +token):** everything else the posts flagged is already in Hutch — RIDs (used +throughout), pronouns and avatars (queried and displayed, avatar upload/delete +in Settings), hub project writes and discovery (shipped), deploy keys (shipped). +Planned-but-not-yet-shipped upstream, so nothing to build: anonymous API access +and "standardized / connections-spec" GraphQL (Q2 named both as future work). +The one *new* opening the introspection turned up is below. + +### Mailing list subscribe — done (v3.11.0) + +A subscribe / unsubscribe toggle now sits in the mailing-list detail toolbar +(`MailingListDetailView`, which backs both Lookup results and +`ProjectMailingListView`). It is hidden for lists you own and while state is +unknown. + +Live testing rewrote the plan. `MailingList.subscription` looked like the state +source but is a trap — it returns null even right after a successful +`mailingListSubscribe` that hands back a subscription id (see API traps). So +state comes from membership in the `subscriptions` query, which *is* reliable, +and the numeric `listID` the mutations require comes from `list(rid:){ id }`. +The mutations themselves (`mailingListSubscribe` / `mailingListUnsubscribe`, +`listID: Int!`) work as expected. Phase 1's "no discovery API" worry was moot: +a specific list is reachable via Lookup, a project's lists, or patchsets, and +that is all subscribing needs. ### hub.sr.ht writes — projects and discovery done @@ -325,11 +359,10 @@ confirmed without a token (introspection there is auth-gated), so the mutations degrade to a visible error rather than a crash if a field is not yet deployed. Verify on a signed-in device. -`mailingListSubscribe` stays out. `MailingList` still has no `subscription` -field, and public *project* discovery does not help find a mailing list the user -is *not* subscribed to — the reason Phase 1 declined it. Revisit only if -lists.sr.ht gains per-list subscription state. `SCOPE.txt` still needs its -"hub has no public API / no discovery" claim corrected. +`mailingListSubscribe` is now buildable: the ingest found `MailingList` gained a +`subscription` field, so per-list state is readable and the subscribe/unsubscribe +toggle can reflect it — see the "mailing list subscribe" bucket below. `SCOPE.txt` +has since had its "hub has no public API / no discovery" claim corrected. ### App Intent gaps — unscheduled @@ -351,21 +384,29 @@ rather than a bare `TODO`.) saved-search store for an intent to open. Add the intent once global saved-search persistence exists. -### Swift 6 language mode — no release of its own - -The project builds in Swift 5 language mode with -`SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`. Moving to Swift 6 is blocked on -concurrency diagnostics that are warnings today and errors there: - -- `APICacheTests` and `BundleUserAgentTests` call main-actor-isolated - initialisers and properties from nonisolated contexts, and `await` a few - expressions without marking them. Roughly 20 warnings, all in tests. -- Response types are implicitly `@MainActor` under the default isolation, so - their `Decodable` conformances are too. Decoding one from a nonisolated - context — an `async let` over a raw `client.execute`, say — warns now and - fails then. The pattern that avoids it is `async let` over `@MainActor` - methods, as in `HomeViewModel.loadDashboard` and - `NotificationPreferencesViewModel.load`. +### Swift 6 language mode — done + +`SWIFT_VERSION` is now `6.0` (keeping `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`). +The migration was mostly mechanical, in a few buckets: + +- **Models are `nonisolated`.** Under MainActor-default, every value-type model + was implicitly `@MainActor`; the pure data types in `Models/` (and utility + extensions like `Date.relativeDescription`, `DateFormatter+SRHT`, `SRHTWebURL`) + are now `nonisolated`, so the nonisolated networking layer can use them. +- **App Intents statics.** `AppIntent`/`AppEntity`/`AppEnum` `static var`s were + "global shared mutable state"; the stored ones are now `static let`. +- **Sendable dictionaries.** `nil as String? as Any` in `[String: any Sendable]` + GraphQL variable dicts became `nil as String? as any Sendable`. +- **`UserDefaults`** gets a retroactive `@unchecked Sendable` (documented + thread-safe) since it threads through account sessions and stores. +- **WidgetKit** completion handlers are rebound `nonisolated(unsafe)` to cross + into their `Task {}`; the `@Observable` `TipStoreViewModel` task handle is + `@ObservationIgnored nonisolated(unsafe)` for its nonisolated `deinit`. +- **Tests** run on `@MainActor` (they exercise MainActor app code), with a few + constant fixtures marked `nonisolated` for use inside `@Sendable` stub + closures. + +Builds and the full suite are clean in Swift 6 mode with no behaviour change. ### Cache reads that bypass the client — no release of its own |
