Commit f9e5b19cff
Unsigned
Layout: unified · split
ROADMAP.md +58 −8
| @@ -6,6 +6,28 @@ call sites in the Swift source. | |||
| 6 | 6 | ||
| 7 | See [SCOPE.md](SCOPE.md) for features that are intentionally out of scope. | 7 | See [SCOPE.md](SCOPE.md) for features that are intentionally out of scope. |
| 8 | 8 | ||
| 9 | ## SourceHut API traps | ||
| 10 | |||
| 11 | Things the schema does not tell you, each of which has already cost real time. | ||
| 12 | |||
| 13 | - **`Thread.updated` is not the thread's activity.** It is the root email's | ||
| 14 | insert time and never advances when a reply arrives, despite the name and | ||
| 15 | despite the schema describing `MailingList.threads` as ordered "most recently | ||
| 16 | bumped". sr.ht returns `updated` seven seconds after `root.date` on a thread | ||
| 17 | carrying four replies. Anything built on it silently treats thread creation as | ||
| 18 | activity. Use `MailingList.emails`, which is reverse-chronological arrival | ||
| 19 | data — see `MailingListActivity`. Prefer `Email.received` over `Email.date`: | ||
| 20 | `received` is server-side and non-null, `date` comes from the sender's header | ||
| 21 | and is neither. | ||
| 22 | - **The schema dumps in `Docs/API` are partial.** They were captured with an | ||
| 23 | introspection query that omits `inputFields` and `enumValues`, so they cannot | ||
| 24 | answer what a mutation's input looks like or what an enum accepts — both come | ||
| 25 | back as empty arrays rather than as an error. For input shapes and enum cases, | ||
| 26 | read the real SDL instead: | ||
| 27 | `git clone --depth 1 https://git.sr.ht/~sircmpwn/<service>.sr.ht` and look at | ||
| 28 | `api/graph/schema.graphqls`. Regenerating the dumps with a full introspection | ||
| 29 | query would remove the trap. | ||
| 30 | |||
| 9 | ## Phase 0: Unblock CI — done (v3.5.0) | 31 | ## Phase 0: Unblock CI — done (v3.5.0) |
| 10 | 32 | ||
| 11 | Nothing downstream is trustworthy until the build badge means something. | 33 | Nothing downstream is trustworthy until the build badge means something. |
| @@ -29,12 +51,19 @@ been running only on demand in Xcode, and ten had rotted: | |||
| 29 | `request.httpBody` read inside a `URLProtocol` (always nil; the body lives on | 51 | `request.httpBody` read inside a `URLProtocol` (always nil; the body lives on |
| 30 | `httpBodyStream`), an incident fixture contradicting its own RSS input, and an | 52 | `httpBodyStream`), an incident fixture contradicting its own RSS input, and an |
| 31 | image assertion that treated the correct `&` attribute encoding as a bug. | 53 | 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 | 54 | - Three 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 | 55 | descriptions could not be cleared (a nil subscript assignment drops the key |
| 34 | instead of sending JSON null), `serviceNotProvisioned` was unreachable behind | 56 | instead of sending JSON null), `serviceNotProvisioned` was unreachable behind |
| 35 | a broader `no such` match, code spans rendered their contents as live markup, | 57 | a broader `no such` match, and code spans rendered their contents as live |
| 36 | and inbox threads keyed `id` on a subject-derived grouping key so two threads | 58 | markup. |
| 37 | sharing a subject on one list collided under `Identifiable`. | 59 | - One was neither. `keepsDistinctThreadsDistinctByRootMessageID` asserted that |
| 60 | two same-subject threads get distinct `id`s, and `eff81f3` obliged by keying | ||
| 61 | `id` on the root Message-ID. The commit message claims this fixed an | ||
| 62 | `Identifiable` collision; it did not, because `deduplicateThreads` merges | ||
| 63 | same-subject threads into one summary before anything renders, so the | ||
| 64 | collision is unreachable. The test constructed summaries by hand and skipped | ||
| 65 | that step. The change is harmless and separating identity from grouping reads | ||
| 66 | better, but the stated reason was wrong. | ||
| 38 | 67 | ||
| 39 | ## Phase 1: Close the write gaps — done (v3.6.0) | 68 | ## Phase 1: Close the write gaps — done (v3.6.0) |
| 40 | 69 | ||
| @@ -69,10 +98,8 @@ alongside Phase 2, which surfaces lists through patchsets. | |||
| 69 | TTL-aware path — so both were removed rather than merged. `responseCache` | 98 | TTL-aware path — so both were removed rather than merged. `responseCache` |
| 70 | remains as the in-memory layer behind `cachedPayload`. | 99 | remains as the in-memory layer behind `cachedPayload`. |
| 71 | 100 | ||
| 72 | Known follow-up: `BuildListViewModel`, `RepositoryListViewModel`, and | 101 | Known follow-up: three view models still read `client.responseCache` directly. |
| 73 | `PasteService` still read `client.responseCache` directly, falling back across | 102 | Tracked under Phase 3. |
| 74 | two different cache keys. That predates `APICacheKeys` and should be folded into | ||
| 75 | `cachedPayload`. | ||
| 76 | 103 | ||
| 77 | ## Phase 2: Patchsets — done (v3.7.0) | 104 | ## Phase 2: Patchsets — done (v3.7.0) |
| 78 | 105 | ||
| @@ -121,6 +148,29 @@ GraphQL mutation. Treat that boundary as explicit rather than half-building it. | |||
| 121 | `deleteMailingList`). | 148 | `deleteMailingList`). |
| 122 | - `events` feed (todo.sr.ht) and `archiveMessage` (lists.sr.ht). | 149 | - `events` feed (todo.sr.ht) and `archiveMessage` (lists.sr.ht). |
| 123 | 150 | ||
| 151 | ### Swift 6 language mode | ||
| 152 | |||
| 153 | The project builds in Swift 5 language mode with | ||
| 154 | `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`. Moving to Swift 6 is blocked on | ||
| 155 | concurrency diagnostics that are warnings today and errors there: | ||
| 156 | |||
| 157 | - `APICacheTests` and `BundleUserAgentTests` call main-actor-isolated | ||
| 158 | initialisers and properties from nonisolated contexts, and `await` a few | ||
| 159 | expressions without marking them. Roughly 20 warnings, all in tests. | ||
| 160 | - Response types are implicitly `@MainActor` under the default isolation, so | ||
| 161 | their `Decodable` conformances are too. Decoding one from a nonisolated | ||
| 162 | context — an `async let` over a raw `client.execute`, say — warns now and | ||
| 163 | fails then. The pattern that avoids it is `async let` over `@MainActor` | ||
| 164 | methods, as in `HomeViewModel.loadDashboard` and | ||
| 165 | `NotificationPreferencesViewModel.load`. | ||
| 166 | |||
| 167 | ### Cache reads that bypass the client | ||
| 168 | |||
| 169 | `BuildListViewModel`, `RepositoryListViewModel`, and `PasteService` still read | ||
| 170 | `client.responseCache` directly, each falling back across two different cache | ||
| 171 | keys. That predates `APICacheKeys` and should be folded into `cachedPayload`, | ||
| 172 | which already consults the persistent cache before the memory layer. | ||
| 173 | |||
| 124 | ## Housekeeping | 174 | ## Housekeeping |
| 125 | 175 | ||
| 126 | - `Hutch/Hutch/App/AccountSession.swift` sits in a stray nested directory; | 176 | - `Hutch/Hutch/App/AccountSession.swift` sits in a stray nested directory; |