<feed xmlns='http://www.w3.org/2005/Atom'>
<title>hutch.git/Hutch/Views/Patchsets, branch v3.8.1</title>
<subtitle>ios client for sourcehut. swiftui.
</subtitle>
<id>http://git.krz.sh/krz/hutch.git/atom?h=v3.8.1</id>
<link rel='self' href='http://git.krz.sh/krz/hutch.git/atom?h=v3.8.1'/>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/'/>
<updated>2026-07-16T16:13:13+00:00</updated>
<entry>
<title>fix: move the S1186 empty-block comments inside the braces</title>
<updated>2026-07-16T16:13:13+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T16:13:13+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=d73c6ac381baac0ae8b0d5dfc551165591115905'/>
<id>urn:sha1:d73c6ac381baac0ae8b0d5dfc551165591115905</id>
<content type='text'>
Trailing // comments after {} left the block lexically empty, so
SonarCloud kept flagging stopLoading (and would have re-flagged the two
Cancel buttons). S1186 wants a *nested* comment; use /* ... */ inside.
</content>
</entry>
<entry>
<title>chore: clear the actionable SonarCloud code smells</title>
<updated>2026-07-16T15:18:40+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T15:18:40+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=e93972f39150e5e590e49aaf46a369c463277c30'/>
<id>urn:sha1:e93972f39150e5e590e49aaf46a369c463277c30</id>
<content type='text'>
- S1871: merge the identical .home / .recentActivity deep-link cases in
  RootView — recent activity is a section of Home, not its own screen.
- S1186: comment the two intentionally-empty Cancel buttons
  (PatchsetDetailView, TicketDetailView) and the empty URLProtocol
  stopLoading override in APICacheTests.
- S108: comment the expected-miss catch block in APICacheTests.
- S1172: rename the unused url parameter in mimeType(for:) to _.
- S4624: extract the nested template literal in the deep-link builders
  (background.js, content.js) to a pathSegment variable.

Left as Won't Fix, with reasons: the 35 hardcoded-URI warnings (a
one-forge client and its literal-URL tests), executeCached's 8 params
(38 call sites, no benefit), the forceRefresh S1172 pair (fixed as a
real bug instead), S1481 on ArtifactsView (false positive — $vm.error
is used), and S7785 (top-level await would break a classic content
script).
</content>
</entry>
<entry>
<title>fix: render patchsets on a plain list</title>
<updated>2026-07-16T03:08:53+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T03:08:53+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=1d2769fc7a347939275e9130ee174d61d96ea401'/>
<id>urn:sha1:1d2769fc7a347939275e9130ee174d61d96ea401</id>
<content type='text'>
Collapsing patches shrank the layout loop from 3674pt/1647pt to 718pt/600pt but
did not end it. The oscillating item is section 1 item 0 — the cover letter, not
a patch — so size alone was not the cause.

The log shows the cell laid out at width 390.0 while the content reports its
preferred size at 390.333. That is inset grouped's 20pt insets landing on a
fractional width: the Text reflows to a different height than the cell was sized
for, each size triggers the other, and it never settles.

ThreadDetailView renders the same bodies through the same DiffView with the same
modifiers and does not loop. The difference is .listStyle(.plain), which this
view never set and so inherited inset grouped.
</content>
</entry>
<entry>
<title>fix: collapse patches to stop a recursive layout loop</title>
<updated>2026-07-16T03:02:55+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T03:02:55+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=2913d950a5c82d80226050e05bd64be7e30ff87d'/>
<id>urn:sha1:2913d950a5c82d80226050e05bd64be7e30ff87d</id>
<content type='text'>
Opening a patchset wedged the app. UICollectionView reported a row oscillating
between 3674pt and 1647pt and trapped in a recursive layout loop, leaving the
UI unresponsive.

The detail view rendered every patch in the series expanded, so a List held one
enormous self-sizing row per patch, each with a full diff. Self-sizing cells
that large do not settle.

Patches now start collapsed and expand on tap, so at most the ones a reviewer
opens are measured. This is what ThreadDetailView already does — it collapses
every message but the last, and renders the same diffs through the same
DiffView without trouble. Reviewing a series one patch at a time is also closer
to how the reading actually goes.

The rendering of a block list is shared between the cover letter and patches
rather than duplicated.
</content>
</entry>
<entry>
<title>fix: push patchset views directly instead of by route</title>
<updated>2026-07-16T02:56:56+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T02:56:56+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=575e62f6dab44b0c9836623fe8b7d17a219f8e0f'/>
<id>urn:sha1:575e62f6dab44b0c9836623fe8b7d17a219f8e0f</id>
<content type='text'>
Tapping a patch failed with "no matching navigationDestination declaration
visible from the location of the link".

MailingListDetailView is presented from four places, but only the More tab and
Lookup declare a MoreRoute destination. Reached from a project, via
ProjectMailingListView, there is no such destination in the surrounding stack,
so a NavigationLink carrying MoreRoute.patchset had nowhere to resolve. The
thread rows beside it already use the closure form for exactly this reason.

Push PatchsetDetailView directly, from the rows and from the version-chain
links inside the detail view, which inherits whatever stack presented it. That
leaves MoreRoute.patchset with no users, so it and its two destinations are
removed rather than left as a route nothing links to.

Neither the compiler nor the tests catch this: it is a runtime SwiftUI
resolution failure.
</content>
</entry>
<entry>
<title>feat: review patchsets</title>
<updated>2026-07-16T02:48:58+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T02:48:58+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=0eceec357b7ef5251ed0ae9d42b513c17380e0af'/>
<id>urn:sha1:0eceec357b7ef5251ed0ae9d42b513c17380e0af</id>
<content type='text'>
Patchsets are how contributions reach sourcehut, and Hutch had no reference to
them anywhere. This adds review and triage: read a series, see its checks and
version chain, and set its status.

Two schema facts shaped the design.

MailingList exposes no patchsets field, so a list's patchsets cannot be queried
directly. They are reachable only through thread roots, so the existing threads
query now also selects root.patchset — no extra request — and the Patches tab is
derived from that. It appears only on lists that actually carry patches.

Patch carries no diff. index, count, version, prefix, subject, and trailers are
all it has; the diff exists only inside the email body. Patch bodies are split
with the same InboxThreadUtilities.segmentMessageBody the inbox uses and
rendered through the existing DiffView.

Patches are ordered by their [PATCH n/m] index rather than receipt order, since
mail arrives out of sequence. Patches with no index are kept at the end rather
than dropped, because a one-off patch has no prefix.

updatePatchset is nullable, so a null response is treated as a declined change
and the local status is left alone rather than advanced optimistically. UNKNOWN
and SUPERSEDED are not offered: the first is a sentinel, the second is set by
the server when a newer version lands.

Patch submission stays out of scope. It is a git send-email flow, not a GraphQL
mutation.
</content>
</entry>
</feed>
