<feed xmlns='http://www.w3.org/2005/Atom'>
<title>hutch.git, branch v3.5.0</title>
<subtitle>ios client for sourcehut. swiftui.
</subtitle>
<id>http://git.krz.sh/krz/hutch.git/atom?h=v3.5.0</id>
<link rel='self' href='http://git.krz.sh/krz/hutch.git/atom?h=v3.5.0'/>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/'/>
<updated>2026-07-16T00:33:51+00:00</updated>
<entry>
<title>docs: add roadmap</title>
<updated>2026-07-16T00:33:51+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T00:33:51+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=a065fc5751ca015c2191e91440402d5703102efa'/>
<id>urn:sha1:a065fc5751ca015c2191e91440402d5703102efa</id>
<content type='text'>
Four phases ordered by dependency, with feature gaps identified by diffing the
schema dumps in Docs/API against actual call sites. Records Phase 0 as done and
what unblocking CI turned up.
</content>
</entry>
<entry>
<title>ci: run the test plan on macOS</title>
<updated>2026-07-16T00:33:30+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T00:33:30+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=33bc142df56011877a9c85118eb4df63fd9ca3aa'/>
<id>urn:sha1:33bc142df56011877a9c85118eb4df63fd9ca3aa</id>
<content type='text'>
The builds.sr.ht job runs on Ubuntu and can only do secret scanning and
structure checks, so the 214 tests ran only when someone remembered to run
them in Xcode. That is why the suite had rotted to ten failures.

builds.sr.ht has no macOS image and its maintainer has ruled them out, so
xcodebuild cannot run there. Add a job on the GitHub mirror instead.

Pinned to macos-26 because macos-latest still points at macOS 15, which lacks
the iOS 26 SDK. The simulator is resolved at runtime rather than pinned by
name, since device names shift between runner images.
</content>
</entry>
<entry>
<title>fix: give inbox threads identity distinct from their grouping key</title>
<updated>2026-07-16T00:31:17+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T00:31:17+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=eff81f34bb51ecc6e0aa5d831567db2cea0527a9'/>
<id>urn:sha1:eff81f34bb51ecc6e0aa5d831567db2cea0527a9</id>
<content type='text'>
InboxThreadSummary.id returned threadGroupingKey, which is listRID plus the
subject with Re:/Fwd: stripped. Two unrelated threads on one list sharing a
subject therefore shared an id — common on sourcehut, where "[PATCH] test" is
an ordinary subject — which collides under Identifiable in every list that
renders these summaries.

Key id on the root Message-ID, which is unique per thread, and leave
threadGroupingKey subject-based so replies still collapse into one
conversation.

Read state moves to threadGroupingKey at each call site. It was already
keyed on that string via id, so persisted keys are unchanged and marking a
conversation read still covers the whole subject group, matching how
HomeViewModel already builds the key for isUnread.
</content>
</entry>
<entry>
<title>fix: render code span contents literally</title>
<updated>2026-07-16T00:29:57+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T00:29:57+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=c5247f7021090358e8db70daa9ed09521e9f206a'/>
<id>urn:sha1:c5247f7021090358e8db70daa9ed09521e9f206a</id>
<content type='text'>
processInline protected allowlisted HTML tags before it handled code spans, so
a `&lt;b&gt;` written inside backticks was carried through as a live tag and applied
formatting instead of rendering as text. Every other inline pass ran against
code span contents for the same reason, so `**x**` in backticks was emitted as
bold.

Protect code spans first with their contents escaped, which takes them out of
reach of the tag, emphasis, and link passes.
</content>
</entry>
<entry>
<title>test: match cached incident fixture to its RSS source</title>
<updated>2026-07-16T00:28:21+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T00:28:21+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=07b269c82f4d45af44ab7097e4a7cd5953c2af94'/>
<id>urn:sha1:07b269c82f4d45af44ab7097e4a7cd5953c2af94</id>
<content type='text'>
The expected incident declared url: nil while the cachedIncidentRSS it stands
in for carries &lt;link&gt;https://status.sr.ht/issues/1/&lt;/link&gt;, so the fixture
contradicted its own input. The repository parses and persists the link
correctly.
</content>
</entry>
<entry>
<title>fix: stop shadowing serviceNotProvisioned classification</title>
<updated>2026-07-16T00:26:27+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T00:26:27+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=3c99c01ad1c0c855ef1793d4b74a9c77e7445811'/>
<id>urn:sha1:3c99c01ad1c0c855ef1793d4b74a9c77e7445811</id>
<content type='text'>
"No such repository or user found" matched the broad "no such" test for
.notFound, which ran first and made the .serviceNotProvisioned rule below it
unreachable. Users hitting a service they have not activated were told the
content was no longer available rather than that the account needs to enable
the service.

Order the specific check ahead of the general one.
</content>
</entry>
<entry>
<title>fix: allow clearing a repository description</title>
<updated>2026-07-16T00:25:27+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T00:25:27+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=7ffef07278e94bd3c268ee5cbd5c9c11cbfa3270'/>
<id>urn:sha1:7ffef07278e94bd3c268ee5cbd5c9c11cbfa3270</id>
<content type='text'>
metadataInputForSave assigned nil through the dictionary subscript to clear a
description. Swift removes the key on a nil subscript assignment, so the
mutation was sent with no `description` field and the old value survived —
clearing a description silently did nothing.

Store the nil with updateValue so the key is retained; AnyCodable already
encodes an unmatched value as a JSON null.
</content>
</entry>
<entry>
<title>test: assert image URLs are not double-escaped</title>
<updated>2026-07-16T00:22:12+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T00:22:12+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=63d0491b161457913c938d620dc1f26f29c798a9'/>
<id>urn:sha1:63d0491b161457913c938d620dc1f26f29c798a9</id>
<content type='text'>
markdownImageQueryStringPreservesAmpersands rejected any "amp;metric" in the
rendered HTML, but `&amp;amp;` is the correct encoding for `&amp;` in an attribute
value and is what a browser needs to request a literal `&amp;`. The assertion
conflated the URL with its HTML encoding.

Target the real failure mode instead: double-escaping, which would send
"&amp;amp;" through as part of the query string and break badge images.
</content>
</entry>
<entry>
<title>test: repair stale expectations and request-body capture</title>
<updated>2026-07-16T00:19:43+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T00:19:43+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=4346adc50f01be7f7322249d32745d119c757ff2'/>
<id>urn:sha1:4346adc50f01be7f7322249d32745d119c757ff2</id>
<content type='text'>
These tests drifted from the code and went unnoticed because CI never ran
them. All four are test-side errors; no app behavior is involved.

- TicketListViewModelTests asserted lowercase "resolved"/"fixed"/"reported"
  against TicketStatus/TicketResolution rawValues, which are uppercase to
  match the todo.sr.ht GraphQL enums.
- HomeViewModelTests expected failedBuilds in ascending id order. Ordering
  moved to newest-first when sortBuildItemsForTriage landed in 5f6d545; the
  filtering the test covers is unchanged.
- SettingsViewModelTests read request.httpBody inside a URLProtocol, where it
  is always nil because URLSession moves the body onto httpBodyStream. The
  stub now reads the body off the stream at capture time.
</content>
</entry>
<entry>
<title>fix: repair test plan reference in Hutch scheme</title>
<updated>2026-07-16T00:11:56+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T00:11:56+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=ed572a864debd7df840ea5873173be18ff27e809'/>
<id>urn:sha1:ed572a864debd7df840ea5873173be18ff27e809</id>
<content type='text'>
The Hutch scheme referenced `container:HutchTests`, omitting the
`.xctestplan` extension, so `xcodebuild test -scheme Hutch` failed with
"the test plan HutchTests could not be read". HutchTests.xcscheme already
used the correct reference.

The README directs contributors to the Hutch scheme, so this path needs to
work before it can be wired into CI.
</content>
</entry>
</feed>
