summaryrefslogtreecommitdiff
path: root/ROADMAP.md
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-15 23:36:36 -0500
committerChristian Cleberg <[email protected]>2026-07-15 23:36:36 -0500
commit9e6d3d5f16c23ada226477f3ed1e2b957b4d95f6 (patch)
tree6e50ea2a6125db337dcb005f2ae881a4a9038fe2 /ROADMAP.md
parent0ad430c79eedac4140680e3aaca238969ac711b1 (diff)
downloadhutch-9e6d3d5f16c23ada226477f3ed1e2b957b4d95f6.tar.gz
hutch-9e6d3d5f16c23ada226477f3ed1e2b957b4d95f6.tar.bz2
hutch-9e6d3d5f16c23ada226477f3ed1e2b957b4d95f6.zip
docs: record the API traps and Swift 6 blockers
Adds a SourceHut API traps section for the two things that cost this work real time and that the schema will not tell you: - Thread.updated is the root email's insert time, not thread activity. It never advances on a reply, while the schema describes threads as ordered "most recently bumped". Every inbox surface trusted it. - The schema dumps in Docs/API omit inputFields and enumValues, so they answer "what shape is this mutation's input" and "what does this enum accept" with an empty array rather than an error. Both questions have to go to the real SDL. Phase 3 picks up the Swift 6 language mode blockers and the three view models still reading client.responseCache directly, which was previously buried as a footnote inside completed Phase 1. Also corrects the Phase 0 record. It credited eff81f3 with fixing an Identifiable collision between same-subject threads. There is no such collision to fix: deduplicateThreads merges those threads before anything renders, and the test that motivated the change built its summaries by hand and skipped that step. The change stands on clarity; the claimed bug was not real.
Diffstat (limited to 'ROADMAP.md')
-rw-r--r--ROADMAP.md66
1 files changed, 58 insertions, 8 deletions
diff --git a/ROADMAP.md b/ROADMAP.md
index 66e56cf..f08981d 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -6,6 +6,28 @@ call sites in the Swift source.
See [SCOPE.md](SCOPE.md) for features that are intentionally out of scope.
+## SourceHut API traps
+
+Things the schema does not tell you, each of which has already cost real time.
+
+- **`Thread.updated` is not the thread's activity.** It is the root email's
+ insert time and never advances when a reply arrives, despite the name and
+ despite the schema describing `MailingList.threads` as ordered "most recently
+ bumped". sr.ht returns `updated` seven seconds after `root.date` on a thread
+ carrying four replies. Anything built on it silently treats thread creation as
+ activity. Use `MailingList.emails`, which is reverse-chronological arrival
+ data — see `MailingListActivity`. Prefer `Email.received` over `Email.date`:
+ `received` is server-side and non-null, `date` comes from the sender's header
+ and is neither.
+- **The schema dumps in `Docs/API` are partial.** They were captured with an
+ introspection query that omits `inputFields` and `enumValues`, so they cannot
+ answer what a mutation's input looks like or what an enum accepts — both come
+ back as empty arrays rather than as an error. For input shapes and enum cases,
+ read the real SDL instead:
+ `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.
+
## Phase 0: Unblock CI — done (v3.5.0)
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:
`request.httpBody` read inside a `URLProtocol` (always nil; the body lives on
`httpBodyStream`), an incident fixture contradicting its own RSS input, and an
image assertion that treated the correct `&amp;` attribute encoding as a bug.
-- Four were real bugs the suite had been right about all along: repository
+- Three were real bugs the suite had been right about all along: repository
descriptions could not be cleared (a nil subscript assignment drops the key
instead of sending JSON null), `serviceNotProvisioned` was unreachable behind
- a broader `no such` match, code spans rendered their contents as live markup,
- and inbox threads keyed `id` on a subject-derived grouping key so two threads
- sharing a subject on one list collided under `Identifiable`.
+ a broader `no such` match, and code spans rendered their contents as live
+ markup.
+- One was neither. `keepsDistinctThreadsDistinctByRootMessageID` asserted that
+ two same-subject threads get distinct `id`s, and `eff81f3` obliged by keying
+ `id` on the root Message-ID. The commit message claims this fixed an
+ `Identifiable` collision; it did not, because `deduplicateThreads` merges
+ same-subject threads into one summary before anything renders, so the
+ collision is unreachable. The test constructed summaries by hand and skipped
+ that step. The change is harmless and separating identity from grouping reads
+ better, but the stated reason was wrong.
## Phase 1: Close the write gaps — done (v3.6.0)
@@ -69,10 +98,8 @@ alongside Phase 2, which surfaces lists through patchsets.
TTL-aware path — so both were removed rather than merged. `responseCache`
remains as the in-memory layer behind `cachedPayload`.
-Known follow-up: `BuildListViewModel`, `RepositoryListViewModel`, and
-`PasteService` still read `client.responseCache` directly, falling back across
-two different cache keys. That predates `APICacheKeys` and should be folded into
-`cachedPayload`.
+Known follow-up: three view models still read `client.responseCache` directly.
+Tracked under Phase 3.
## Phase 2: Patchsets — done (v3.7.0)
@@ -121,6 +148,29 @@ GraphQL mutation. Treat that boundary as explicit rather than half-building it.
`deleteMailingList`).
- `events` feed (todo.sr.ht) and `archiveMessage` (lists.sr.ht).
+### Swift 6 language mode
+
+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`.
+
+### Cache reads that bypass the client
+
+`BuildListViewModel`, `RepositoryListViewModel`, and `PasteService` still read
+`client.responseCache` directly, each falling back across two different cache
+keys. That predates `APICacheKeys` and should be folded into `cachedPayload`,
+which already consults the persistent cache before the memory layer.
+
## Housekeeping
- `Hutch/Hutch/App/AccountSession.swift` sits in a stray nested directory;