<feed xmlns='http://www.w3.org/2005/Atom'>
<title>hutch.git/Hutch, branch v3.8.2</title>
<subtitle>ios client for sourcehut. swiftui.
</subtitle>
<id>http://git.krz.sh/krz/hutch.git/atom?h=v3.8.2</id>
<link rel='self' href='http://git.krz.sh/krz/hutch.git/atom?h=v3.8.2'/>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/'/>
<updated>2026-07-16T21:59:55+00:00</updated>
<entry>
<title>Move Home system status into a title-bar status badge</title>
<updated>2026-07-16T21:59:55+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T21:59:55+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=7b817a1d2aed0e8b89f36b410f2779f4a6b35407'/>
<id>urn:sha1:7b817a1d2aed0e8b89f36b410f2779f4a6b35407</id>
<content type='text'>
Replace the disruption-only banner on the Home tab with a persistent
circular status indicator in the navigation bar, next to the "Home"
title. The badge reflects the existing status logic — green/check when
operational, orange/exclamation on disruption, gray otherwise — and taps
through to System Status. SystemStatusSummaryRow is unchanged and still
used by the More tab.

Resolve the system-status result first in HomeViewModel.loadDashboard so
the badge settles from cache immediately instead of waiting on the
slower projects/jobs/tickets/inbox loads, which had left it spinning for
several seconds.

Bump MARKETING_VERSION to 3.8.2 and record it in ROADMAP.
</content>
</entry>
<entry>
<title>chore: track the two HutchIntents TODOs in the roadmap (S1135)</title>
<updated>2026-07-16T16:17:45+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T16:17:45+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=f3c70e04148c57dcafead89b4e02b83e5f5635f8'/>
<id>urn:sha1:f3c70e04148c57dcafead89b4e02b83e5f5635f8</id>
<content type='text'>
SearchHutchIntent's route-to-Lookup stopgap and the absent
OpenSavedSearchIntent are both gated on a global search/persistence
layer Hutch lacks. Promote both to ROADMAP § "App Intent gaps" and
replace the inline TODOs with plain references, clearing those two
S1135 issues without losing the design intent.
</content>
</entry>
<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>chore: move AccountSession.swift out of the stray nested Hutch/Hutch dir</title>
<updated>2026-07-16T15:18:27+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T15:18:27+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=9834b780b24a1dd617fa761155712920159f3d8d'/>
<id>urn:sha1:9834b780b24a1dd617fa761155712920159f3d8d</id>
<content type='text'>
It sat at Hutch/Hutch/App/AccountSession.swift, one level too deep. The
target is a PBXFileSystemSynchronizedRootGroup rooted at Hutch/, so the
file compiled by path with no pbxproj reference; moving it beside the
rest of App/ needs no project-file change. Also removed the emptied
Hutch/Hutch tree and the stray empty Hutch/HutchTests dir.
</content>
</entry>
<entry>
<title>fix: honor forceRefresh for projects and system status on the dashboard</title>
<updated>2026-07-16T15:13:46+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T15:13:46+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=65412ee9818bf251fd5caac231746b04c7eca23f'/>
<id>urn:sha1:65412ee9818bf251fd5caac231746b04c7eca23f</id>
<content type='text'>
loadDashboard(forceRefresh:) fanned the flag out to five loaders, but
loadProjects and loadSystemStatusSnapshot dropped it: they called
fetchProjects() and snapshotResult() with no policy, so pull-to-refresh
returned cached projects and status while the other three sections
refreshed. SonarCloud flagged both params as unused (swift:S1172).

Thread forceRefresh through ProjectService.fetchProjects into the page
policy (refreshIgnoringCache when forced), and pass it to
snapshotResult, which already accepted it. ProjectsListView carried the
same latent bug via its own .refreshable — fixed there too now that
fetchProjects can force.
</content>
</entry>
<entry>
<title>chore: remove the artifacts debug probe</title>
<updated>2026-07-16T06:21:33+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T06:21:33+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=21be03e6ca54c1a15607faab905b119eda9a548f'/>
<id>urn:sha1:21be03e6ca54c1a15607faab905b119eda9a548f</id>
<content type='text'>
It did its job: tags=1 confirmed the menu was disabled by its own tags check
while the explanation sat unreachable inside it.
</content>
</entry>
<entry>
<title>fix: deleted repositories linger until the cache expires</title>
<updated>2026-07-16T06:16:39+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T06:16:39+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=ad108c76d062099af9e698e1f0ba0d8cecfeab1f'/>
<id>urn:sha1:ad108c76d062099af9e698e1f0ba0d8cecfeab1f</id>
<content type='text'>
Deleting a repository left it on the list, and pulling to refresh did not shift
it. Two independent reasons, both cache-related.

deleteRepository never invalidated anything. Creation invalidates the
repositories and home prefixes; deletion was written without it, so the list and
Home kept serving a repository that no longer exists.

And forceRefresh only ever reached the build statuses — its own doc comment says
so — while the repository list itself was pinned to useCache: true. So a pull to
refresh re-served the same cache it already had. fetchPage already takes
useCache and falls through to an uncached fetch; it was simply never told.
</content>
</entry>
<entry>
<title>fix: download artifacts through the API instead of handing them to Safari</title>
<updated>2026-07-16T06:06:46+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T06:06:46+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=871b04159aa47c0b0e62e2520c2f30e81f0f024b'/>
<id>urn:sha1:871b04159aa47c0b0e62e2520c2f30e81f0f024b</id>
<content type='text'>
Tapping download opened Artifact.url in the browser, which answered with
"Authorization header is required". That URL is not a web page: git.sr.ht
resolves it to &lt;api origin&gt;/query/artifact/&lt;checksum&gt;/&lt;filename&gt;, which demands
a bearer token. Safari has none and no way to get one, so the download could
never have worked — this predates the upload work.

Fetch it with the client that already holds the token and hand the user the file
through a share sheet. fetchData mirrors fetchText, including its host guard, so
an authenticated request still cannot be aimed anywhere but *.sr.ht over https.

Also guards zero-byte uploads. sr.ht streams into S3, which rejects a zero-part
multipart completion with "MalformedXML: UnknownError" — an error that says
nothing about the cause and cost a round of testing to identify. Empty files are
now refused by name before the request is made.
</content>
</entry>
<entry>
<title>fix: make artifact upload actually fire, and show it when it fails</title>
<updated>2026-07-16T05:54:59+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T05:54:59+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=d32ad837ac9eb153598c10c3cf47fbdb4e51e3ba'/>
<id>urn:sha1:d32ad837ac9eb153598c10c3cf47fbdb4e51e3ba</id>
<content type='text'>
Picking a file did nothing. The fileImporter's isPresented binding was derived
from uploadTargetRef and nilled it on dismissal, but dismissal happens before
the completion runs — so the completion read nil and returned without
uploading. Presentation state and payload state cannot be the same state. A
plain isImporting bool drives presentation now; the tag survives in
uploadTargetRef until the completion consumes it.

The upload menu was also disabled when the repository has no tags, while the
explanation for that state lived inside the menu — unreachable exactly when it
applies, so the tap died with no reason given. sr.ht requires revspec to match a
tag, so having none is a real state worth explaining rather than hiding.

Failures were invisible too. uploadArtifact and deleteArtifact set error, but
the overlay only renders it when the list is empty, so a rejection on a
repository that already has artifacts — a duplicate filename is the likely one,
since sr.ht requires filenames to be unique per repository — set an error nobody
saw. The tab carries an error banner now.
</content>
</entry>
</feed>
