Commit d0156e05fd

d0156e05fde8d489ef45f8637c92a352a9e89a3c

parent: 9fa0534e7d

Unsigned

cmc <hello@cleberg.net> · 2026-08-07 09:27 UTC

Bump to 3.9.0; reflect shipped work in roadmap

Cut 3.9.0 (build 94), bundling this session's features. Update ROADMAP.txt:
mark hub.sr.ht project writes + discovery (#12-#15) done, correct the Q2
ingest note and App Intent gaps, and reconcile the release plan (3.9.0 is
the shipped cut; ingest/deploy keys move to 3.10.0).

Layout: unified · split

Hutch.xcodeproj/project.pbxproj +12 −12
@@ -597,7 +597,7 @@
597 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 597 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
598 CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements; 598 CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements;
599 CODE_SIGN_STYLE = Automatic; 599 CODE_SIGN_STYLE = Automatic;
600 CURRENT_PROJECT_VERSION = 93; 600 CURRENT_PROJECT_VERSION = 94;
601 DEVELOPMENT_TEAM = ZCNAX3VL9D; 601 DEVELOPMENT_TEAM = ZCNAX3VL9D;
602 ENABLE_PREVIEWS = YES; 602 ENABLE_PREVIEWS = YES;
603 GENERATE_INFOPLIST_FILE = YES; 603 GENERATE_INFOPLIST_FILE = YES;
@@ -614,7 +614,7 @@
614 "$(inherited)", 614 "$(inherited)",
615 "@executable_path/Frameworks", 615 "@executable_path/Frameworks",
616 ); 616 );
617 MARKETING_VERSION = 3.8.5; 617 MARKETING_VERSION = 3.9.0;
618 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; 618 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
619 PRODUCT_NAME = "$(TARGET_NAME)"; 619 PRODUCT_NAME = "$(TARGET_NAME)";
620 STRING_CATALOG_GENERATE_SYMBOLS = YES; 620 STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -634,7 +634,7 @@
634 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 634 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
635 CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements; 635 CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements;
636 CODE_SIGN_STYLE = Automatic; 636 CODE_SIGN_STYLE = Automatic;
637 CURRENT_PROJECT_VERSION = 93; 637 CURRENT_PROJECT_VERSION = 94;
638 DEVELOPMENT_TEAM = ZCNAX3VL9D; 638 DEVELOPMENT_TEAM = ZCNAX3VL9D;
639 ENABLE_PREVIEWS = YES; 639 ENABLE_PREVIEWS = YES;
640 GENERATE_INFOPLIST_FILE = YES; 640 GENERATE_INFOPLIST_FILE = YES;
@@ -651,7 +651,7 @@
651 "$(inherited)", 651 "$(inherited)",
652 "@executable_path/Frameworks", 652 "@executable_path/Frameworks",
653 ); 653 );
654 MARKETING_VERSION = 3.8.5; 654 MARKETING_VERSION = 3.9.0;
655 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; 655 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
656 PRODUCT_NAME = "$(TARGET_NAME)"; 656 PRODUCT_NAME = "$(TARGET_NAME)";
657 STRING_CATALOG_GENERATE_SYMBOLS = YES; 657 STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -714,7 +714,7 @@
714 APPLICATION_EXTENSION_API_ONLY = YES; 714 APPLICATION_EXTENSION_API_ONLY = YES;
715 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; 715 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements;
716 CODE_SIGN_STYLE = Automatic; 716 CODE_SIGN_STYLE = Automatic;
717 CURRENT_PROJECT_VERSION = 93; 717 CURRENT_PROJECT_VERSION = 94;
718 DEVELOPMENT_TEAM = ZCNAX3VL9D; 718 DEVELOPMENT_TEAM = ZCNAX3VL9D;
719 GENERATE_INFOPLIST_FILE = NO; 719 GENERATE_INFOPLIST_FILE = NO;
720 INFOPLIST_FILE = HutchWidgetExtension/Info.plist; 720 INFOPLIST_FILE = HutchWidgetExtension/Info.plist;
@@ -724,7 +724,7 @@
724 "@executable_path/Frameworks", 724 "@executable_path/Frameworks",
725 "@executable_path/../../Frameworks", 725 "@executable_path/../../Frameworks",
726 ); 726 );
727 MARKETING_VERSION = 3.8.5; 727 MARKETING_VERSION = 3.9.0;
728 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; 728 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension;
729 PRODUCT_NAME = "$(TARGET_NAME)"; 729 PRODUCT_NAME = "$(TARGET_NAME)";
730 SKIP_INSTALL = YES; 730 SKIP_INSTALL = YES;
@@ -743,7 +743,7 @@
743 APPLICATION_EXTENSION_API_ONLY = YES; 743 APPLICATION_EXTENSION_API_ONLY = YES;
744 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; 744 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements;
745 CODE_SIGN_STYLE = Automatic; 745 CODE_SIGN_STYLE = Automatic;
746 CURRENT_PROJECT_VERSION = 93; 746 CURRENT_PROJECT_VERSION = 94;
747 DEVELOPMENT_TEAM = ZCNAX3VL9D; 747 DEVELOPMENT_TEAM = ZCNAX3VL9D;
748 GENERATE_INFOPLIST_FILE = NO; 748 GENERATE_INFOPLIST_FILE = NO;
749 INFOPLIST_FILE = HutchWidgetExtension/Info.plist; 749 INFOPLIST_FILE = HutchWidgetExtension/Info.plist;
@@ -753,7 +753,7 @@
753 "@executable_path/Frameworks", 753 "@executable_path/Frameworks",
754 "@executable_path/../../Frameworks", 754 "@executable_path/../../Frameworks",
755 ); 755 );
756 MARKETING_VERSION = 3.8.5; 756 MARKETING_VERSION = 3.9.0;
757 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; 757 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension;
758 PRODUCT_NAME = "$(TARGET_NAME)"; 758 PRODUCT_NAME = "$(TARGET_NAME)";
759 SKIP_INSTALL = YES; 759 SKIP_INSTALL = YES;
@@ -772,7 +772,7 @@
772 APPLICATION_EXTENSION_API_ONLY = YES; 772 APPLICATION_EXTENSION_API_ONLY = YES;
773 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 773 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
774 CODE_SIGN_STYLE = Automatic; 774 CODE_SIGN_STYLE = Automatic;
775 CURRENT_PROJECT_VERSION = 93; 775 CURRENT_PROJECT_VERSION = 94;
776 DEVELOPMENT_TEAM = ZCNAX3VL9D; 776 DEVELOPMENT_TEAM = ZCNAX3VL9D;
777 GENERATE_INFOPLIST_FILE = NO; 777 GENERATE_INFOPLIST_FILE = NO;
778 INFOPLIST_FILE = HutchSafariExtension/Info.plist; 778 INFOPLIST_FILE = HutchSafariExtension/Info.plist;
@@ -782,7 +782,7 @@
782 "@executable_path/Frameworks", 782 "@executable_path/Frameworks",
783 "@executable_path/../../Frameworks", 783 "@executable_path/../../Frameworks",
784 ); 784 );
785 MARKETING_VERSION = 3.8.5; 785 MARKETING_VERSION = 3.9.0;
786 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchSafariExtension; 786 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchSafariExtension;
787 PRODUCT_NAME = "$(TARGET_NAME)"; 787 PRODUCT_NAME = "$(TARGET_NAME)";
788 SKIP_INSTALL = YES; 788 SKIP_INSTALL = YES;
@@ -801,7 +801,7 @@
801 APPLICATION_EXTENSION_API_ONLY = YES; 801 APPLICATION_EXTENSION_API_ONLY = YES;
802 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 802 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
803 CODE_SIGN_STYLE = Automatic; 803 CODE_SIGN_STYLE = Automatic;
804 CURRENT_PROJECT_VERSION = 93; 804 CURRENT_PROJECT_VERSION = 94;
805 DEVELOPMENT_TEAM = ZCNAX3VL9D; 805 DEVELOPMENT_TEAM = ZCNAX3VL9D;
806 GENERATE_INFOPLIST_FILE = NO; 806 GENERATE_INFOPLIST_FILE = NO;
807 INFOPLIST_FILE = HutchSafariExtension/Info.plist; 807 INFOPLIST_FILE = HutchSafariExtension/Info.plist;
@@ -811,7 +811,7 @@
811 "@executable_path/Frameworks", 811 "@executable_path/Frameworks",
812 "@executable_path/../../Frameworks", 812 "@executable_path/../../Frameworks",
813 ); 813 );
814 MARKETING_VERSION = 3.8.5; 814 MARKETING_VERSION = 3.9.0;
815 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchSafariExtension; 815 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchSafariExtension;
816 PRODUCT_NAME = "$(TARGET_NAME)"; 816 PRODUCT_NAME = "$(TARGET_NAME)";
817 SKIP_INSTALL = YES; 817 SKIP_INSTALL = YES;
ROADMAP.txt +41 −26
@@ -143,17 +143,19 @@ so "breaking change" does not apply. These buckets track *user-visible scale*.
143| --- | --- | --- | 143| --- | --- | --- |
144| v3.8.1 | SonarCloud triage; housekeeping | No behaviour change at all | 144| v3.8.1 | SonarCloud triage; housekeeping | No behaviour change at all |
145| v3.8.2 | Home system status moved to a title-bar status badge | Small UI relocation, no new surface | 145| v3.8.2 | Home system status moved to a title-bar status badge | Small UI relocation, no new surface |
146| v3.9.0 | "What's cooking" ingest; doc truth-up; deploy keys | Ships one feature, corrects the map | 146| 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 |
147| v3.10.0 | hub.sr.ht writes: projects, discovery, `mailingListSubscribe` | Provisional — gated on what v3.9.0 finds | 147| v3.10.0 | "What's cooking" ingest; doc truth-up; deploy keys; revisit `mailingListSubscribe` | Ships one feature, corrects the map |
148| v3.11.0 | Accessibility | Independent, device-verified | 148| v3.11.0 | Accessibility | Independent, device-verified |
149| v4.0.0 | Localization *with* translations | The only true re-presentation | 149| v4.0.0 | Localization *with* translations | The only true re-presentation |
150| — | Swift 6 language mode; cache reads | Internal; ride along, no tag | 150| — | Swift 6 language mode; cache reads | Internal; ride along, no tag |
151 151
152Ordering is by dependency, not size. v3.9.0 leads because it is the only item 1523.9.0 was cut this session, bundling the hub.sr.ht writes with the other
153that corrects the others' inputs: the ingest's real output is a `SCOPE.txt` that 153features listed. That reorders the original plan: the "What's cooking" ingest
154is true, and v3.10.0 rests entirely on one unverified sentence in a blog post. 154and deploy keys — once slated for 3.9.0 — move to 3.10.0, and the hub.sr.ht
155Do not commit v3.10.0's number until the SDL has been read — the bucket may turn 155writes that were provisionally 3.10.0 landed early, because the SDL, once
156out to be empty, which is the point of sequencing it second. 156actually read, turned out to have the mutations (it was not the empty bucket the
157sequencing had guarded against). The ingest still leads 3.10.0: its real output
158is a `SCOPE.txt` that is true.
157 159
158`KeychainHelper` is deliberately unbucketed; see the SonarCloud hotspots below. 160`KeychainHelper` is deliberately unbucketed; see the SonarCloud hotspots below.
159 161
@@ -290,10 +292,11 @@ and it has been discouraging work that is in fact already done.
290flags two openings: 292flags two openings:
291 293
292- **hub.sr.ht gained a writable GraphQL API** for managing projects and project 294- **hub.sr.ht gained a writable GraphQL API** for managing projects and project
293 resources. Hutch's projects are read-only, and `SCOPE.txt` still rules out 295 resources. ~~Rechecked and shipped~~: project create/edit, resource
294 discovery on the grounds that hub has no public API. Both claims need 296 link/unlink, and public discovery landed (#12–#15) — see "hub.sr.ht writes"
295 rechecking — this may also unblock `mailingListSubscribe`, which Phase 1 left 297 below. `SCOPE.txt` still needs its "hub has no public API / no discovery"
296 out for exactly that reason. 298 claim corrected. `mailingListSubscribe` was *not* unblocked — that needs a
299 per-list subscription field lists.sr.ht still lacks.
297- **git.sr.ht deploy keys are complete** (`createDeployKey` / `deleteDeployKey` 300- **git.sr.ht deploy keys are complete** (`createDeployKey` / `deleteDeployKey`
298 are in the SDL). Hutch never calls them. 301 are in the SDL). Hutch never calls them.
299 302
@@ -305,18 +308,28 @@ self-contained feature it has already surfaced and that the SDL confirms exists.
305That gives the release something a user can see. Everything else the ingest turns 308That gives the release something a user can see. Everything else the ingest turns
306up gets filed, not built, and hub.sr.ht gets its own bucket below. 309up gets filed, not built, and hub.sr.ht gets its own bucket below.
307 310
308### hub.sr.ht writes — v3.10.0, provisional 311### hub.sr.ht writes — projects and discovery done
309 312
310Everything here rests on a single sentence in the Q2 2026 post: that hub.sr.ht 313Reading `api/graph/schema.graphqls` in `hub.sr.ht` settled the Q2 2026 claim:
311gained a writable GraphQL API. If true, three things unblock at once — 314the master schema does expose the project write API — `createProject`,
312project writes (Hutch's projects are read-only), discovery (which `SCOPE.txt` 315`updateProject`, `deleteProject`, the `link*` / `unlink*` resource mutations,
313rules out on the grounds hub has no public API), and `mailingListSubscribe`, 316and a public `projects` discovery query. Two of the three items this bucket
314which Phase 1 declined for exactly that reason. 317tracked shipped against it:
315 318
316All three live or die on the same unverified claim, which is why this is 319- ~~Project writes~~ — create (#13), edit (#14), and manage linked
317sequenced after the ingest rather than planned now. Read 320 repositories, trackers, and mailing lists (#15).
318`api/graph/schema.graphqls` in `hub.sr.ht` before committing the version number. 321- ~~Discovery~~ — a browsable directory of public projects (#12).
319The bucket may be empty. 322
323Built against the master SDL; live deployment on `sr.ht/query` could not be
324confirmed without a token (introspection there is auth-gated), so the mutations
325degrade to a visible error rather than a crash if a field is not yet deployed.
326Verify on a signed-in device.
327
328`mailingListSubscribe` stays out. `MailingList` still has no `subscription`
329field, and public *project* discovery does not help find a mailing list the user
330is *not* subscribed to — the reason Phase 1 declined it. Revisit only if
331lists.sr.ht gains per-list subscription state. `SCOPE.txt` still needs its
332"hub has no public API / no discovery" claim corrected.
320 333
321### App Intent gaps — unscheduled 334### App Intent gaps — unscheduled
322 335
@@ -326,11 +339,13 @@ search/persistence layer — so neither is schedulable until that lands. (These
326were the two `swift:S1135` TODOs; promoted here so the code carries a reference 339were the two `swift:S1135` TODOs; promoted here so the code carries a reference
327rather than a bare `TODO`.) 340rather than a bare `TODO`.)
328 341
329- **Global content search.** `SearchHutchIntent` accepts a query but routes to 342- **Global content search.** `SearchHutchIntent` accepts a query — and now a
330 the Lookup screen — sourcehut entity resolution — because Hutch has no 343 search *type* (#17) — but still routes to the Lookup screen, sourcehut entity
331 full-text search across tickets, repos, and lists. Its own description says 344 resolution, because Hutch has no full-text search across tickets, repos, and
332 "Opens Hutch lookup with a search query." When a real search exists, repoint 345 lists. When a real search exists, repoint the `.search` route in
333 the `.search` route in `SearchHutchIntent.route`. 346 `SearchHutchIntent.route`. (#17 also completed Check Status / Check Builds
347 dialogs and added the Clear Recent Activity and Unpin Resource mutating
348 intents; those were shipped, not gaps.)
334- **`OpenSavedSearchIntent`.** Saved searches are per-tracker only 349- **`OpenSavedSearchIntent`.** Saved searches are per-tracker only
335 (`TicketSavedFilterStore`, `ScopedSearchHistoryStore`); there is no global 350 (`TicketSavedFilterStore`, `ScopedSearchHistoryStore`); there is no global
336 saved-search store for an intent to open. Add the intent once global 351 saved-search store for an intent to open. Add the intent once global