krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

22c7a0e45a5cc344e2fa0635431d95c23024ad63

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-07-16T02:50:30Z

chore: bump to 3.7.0 and record Phase 2

MARKETING_VERSION 3.6.0 -> 3.7.0, build 88 -> 89.

The README feature list also picks up Phase 1's ticket editing, subscriptions,
and email preferences, which it never gained.
 Hutch.xcodeproj/project.pbxproj | 24 ++++++++++++------------
 README.md                       |  3 +++
 ROADMAP.md                      | 37 +++++++++++++++++++++++--------------
 3 files changed, 38 insertions(+), 26 deletions(-)

diff --git a/Hutch.xcodeproj/project.pbxproj b/Hutch.xcodeproj/project.pbxproj
index 4d1898a..8f71248 100644
--- a/Hutch.xcodeproj/project.pbxproj
+++ b/Hutch.xcodeproj/project.pbxproj
@@ -597,7 +597,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 88;
+				CURRENT_PROJECT_VERSION = 89;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -614,7 +614,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 3.6.0;
+				MARKETING_VERSION = 3.7.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -634,7 +634,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 88;
+				CURRENT_PROJECT_VERSION = 89;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				ENABLE_PREVIEWS = YES;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -651,7 +651,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 3.6.0;
+				MARKETING_VERSION = 3.7.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -714,7 +714,7 @@
 				APPLICATION_EXTENSION_API_ONLY = YES;
 				CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 88;
+				CURRENT_PROJECT_VERSION = 89;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = HutchWidgetExtension/Info.plist;
@@ -724,7 +724,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 3.6.0;
+				MARKETING_VERSION = 3.7.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SKIP_INSTALL = YES;
@@ -743,7 +743,7 @@
 				APPLICATION_EXTENSION_API_ONLY = YES;
 				CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 88;
+				CURRENT_PROJECT_VERSION = 89;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = HutchWidgetExtension/Info.plist;
@@ -753,7 +753,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 3.6.0;
+				MARKETING_VERSION = 3.7.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SKIP_INSTALL = YES;
@@ -772,7 +772,7 @@
 				APPLICATION_EXTENSION_API_ONLY = YES;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 88;
+				CURRENT_PROJECT_VERSION = 89;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = HutchSafariExtension/Info.plist;
@@ -782,7 +782,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 3.6.0;
+				MARKETING_VERSION = 3.7.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchSafariExtension;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SKIP_INSTALL = YES;
@@ -801,7 +801,7 @@
 				APPLICATION_EXTENSION_API_ONLY = YES;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 88;
+				CURRENT_PROJECT_VERSION = 89;
 				DEVELOPMENT_TEAM = ZCNAX3VL9D;
 				GENERATE_INFOPLIST_FILE = NO;
 				INFOPLIST_FILE = HutchSafariExtension/Info.plist;
@@ -811,7 +811,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/../../Frameworks",
 				);
-				MARKETING_VERSION = 3.6.0;
+				MARKETING_VERSION = 3.7.0;
 				PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchSafariExtension;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SKIP_INSTALL = YES;
diff --git a/README.md b/README.md
index c81a7c4..fd553a8 100644
--- a/README.md
+++ b/README.md
@@ -15,10 +15,13 @@ The app currently includes:
 - Repository browsing for Git and Mercurial repositories
 - Repository details including README, references, commits, diffs, files, artifacts, and settings
 - Tracker and ticket browsing, ticket detail views, and tracker creation
+- Ticket editing and deletion, with subscriptions for tickets and trackers
 - Build job browsing, build detail views, and build submission
 - Inbox and mailing list reading flows
+- Patchset review: cover letters, per-patch diffs, checks, version chains, and status changes
 - Paste browsing, creation, and detail views
 - Profile and account settings, including SSH keys, PGP keys, and personal access token management
+- Email preferences for todo.sr.ht and lists.sr.ht
 - Deep links for repositories, tickets, and build jobs
 
 Some SourceHut services are still browser-only from within Hutch. Unsupported areas currently open in Safari instead of rendering in-app.
diff --git a/ROADMAP.md b/ROADMAP.md
index e86ddb0..66e56cf 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -74,24 +74,33 @@ Known follow-up: `BuildListViewModel`, `RepositoryListViewModel`, and
 two different cache keys. That predates `APICacheKeys` and should be folded into
 `cachedPayload`.
 
-## Phase 2: Patchsets
+## Phase 2: Patchsets — done (v3.7.0)
 
-The flagship gap. There is currently no reference to `patchset` anywhere in the
-Swift source, yet lists.sr.ht exposes a full `Patchset` type (subject, version,
-prefix, status, coverLetter, patches, tools, mbox), a `patchset` query, and an
-`updatePatchset` mutation. Sending and reviewing patches over email is the
-SourceHut contribution model, and Hutch cannot currently participate in it.
+The flagship gap. Sending and reviewing patches over email is the SourceHut
+contribution model, and Hutch had no reference to `patchset` anywhere.
 
-Scope this as review-and-triage, not submission:
+Scoped as review-and-triage, not submission:
 
-- Patchset list per mailing list.
-- Patchset detail: cover letter, per-patch diffs (reuse the existing
-  `DiffView`), version and superseded-by chain.
-- Status transitions via `updatePatchset`.
+- ~~Patchset list per mailing list~~ — see the caveat below.
+- ~~Patchset detail~~: cover letter, per-patch diffs (via the existing
+  `DiffView`), checks, and the version / superseded-by chain.
+- ~~Status transitions via `updatePatchset`~~.
 
-Patch *submission* is an email / `git send-email` flow and is likely out of
-reach from the app. Treat that boundary as explicit rather than half-building
-it.
+Two schema facts shaped the result, and are worth knowing before extending this:
+
+- **`MailingList` has no `patchsets` field.** A list's patchsets cannot be
+  queried directly; they are reachable only through thread roots. The existing
+  threads query now also selects `root.patchset`, so the Patches tab costs no
+  extra request — but it also means patchsets cannot be filtered by status
+  server-side, and only patchsets whose thread appears in the current page are
+  listed.
+- **`Patch` carries no diff.** It has only `index`, `count`, `version`,
+  `prefix`, `subject`, and `trailers`. The diff exists solely inside the email
+  body, so it is recovered with `InboxThreadUtilities.segmentMessageBody` — the
+  same splitter the inbox thread view uses.
+
+Patch *submission* remains out of reach: it is a `git send-email` flow, not a
+GraphQL mutation. Treat that boundary as explicit rather than half-building it.
 
 ## Phase 3: Polish and reach