<feed xmlns='http://www.w3.org/2005/Atom'>
<title>hutch.git/HutchTests, branch v3.9.0</title>
<subtitle>ios client for sourcehut. swiftui.
</subtitle>
<id>http://git.krz.sh/krz/hutch.git/atom?h=v3.9.0</id>
<link rel='self' href='http://git.krz.sh/krz/hutch.git/atom?h=v3.9.0'/>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/'/>
<updated>2026-08-07T09:12:50+00:00</updated>
<entry>
<title>Projects: discovery, create, edit, manage linked resources (#12, #13, #14, #15)</title>
<updated>2026-08-07T09:12:50+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-08-07T09:12:50+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=1f8f651a9c2ac2d6be231ff9f71ee3bcce9936d8'/>
<id>urn:sha1:1f8f651a9c2ac2d6be231ff9f71ee3bcce9936d8</id>
<content type='text'>
hub.sr.ht's GraphQL now exposes the project write API (create/update/
link/unlink) and a public `projects` discovery query, so these
previously-blocked issues can be built.

ProjectService gains: fetchPublicProjects (#12); createProject (#13);
updateProject with ProjectInput (#14); link/unlink for sources, trackers,
and mailing lists plus linkable-resource candidate fetches (#15); and hub
cache invalidation after every mutation.

UI: a Discover browser and a create form reached from the projects list;
an Edit form and a Manage Resources sheet on project detail, gated to
projects the user owns. Mutations degrade to a visible error if a field
isn't live yet.

Built against the master schema; live deployment on sr.ht/query is not yet
confirmed (introspection there requires a token).
</content>
</entry>
<entry>
<title>Sync bundled man page catalog from man.sr.ht (#7)</title>
<updated>2026-08-07T08:27:37+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-08-07T08:27:37+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=89c90d11d80fde63997ca029e14806df0319a541'/>
<id>urn:sha1:89c90d11d80fde63997ca029e14806df0319a541</id>
<content type='text'>
The More tab's official man-page list was a hardcoded array, updated by
hand. Move it to a checked-in Hutch/Resources/man-pages.json (bundled via
the synchronized group) loaded by a new ManPageCatalog, with the previous
list kept as a built-in fallback.

Add scripts/sync_man_pages.py, which re-derives the catalog from the
man.sr.ht landing page, and a weekly scheduled workflow that runs it and
opens a pull request when the list diverges upstream. The script refuses to
write a suspiciously short list so a markup change can't gut the catalog.

This first sync also picks up chat.sr.ht, which upstream added since the
list was last hand-edited. Tests cover catalog loading, the fallback, and
JSON decoding.
</content>
</entry>
<entry>
<title>Expand App Intents: dialogs, search type, mutating intents (#17)</title>
<updated>2026-08-07T08:06:34+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-08-07T08:06:34+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=9c11d69002d71556fea1e2a938e02d4f4d503ea5'/>
<id>urn:sha1:9c11d69002d71556fea1e2a938e02d4f4d503ea5</id>
<content type='text'>
Addresses the three open items on the App Intents ticket:

- Check Status / Check Builds now return a spoken/visible dialog alongside
  their value, so they surface a result in Siri instead of appearing to do
  nothing. Logic consolidated to always produce a message.
- Search Hutch gains a Search Type parameter (user, git/hg repo, mailing
  list, tracker, build job) threaded through the .search route into Lookup,
  which pre-selects the type. LookupType now conforms to AppEnum.
- Adds mutating intents (not just navigation): Clear Recent Activity and
  Unpin Resource, backed by RecentActivityStore.clear and a new
  HomePinStore.removePin. Both act on the active account's storage.

Tests cover search-type routing, the blank-query fallback, and pin removal.
</content>
</entry>
<entry>
<title>Add multi-language syntax highlighting (#16)</title>
<updated>2026-08-07T07:45:31+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-08-07T07:45:31+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=b410fd16b76146f4116538c5b82330818dd47da9'/>
<id>urn:sha1:b410fd16b76146f4116538c5b82330818dd47da9</id>
<content type='text'>
Splash (Swift-only, since removed) left code rendering unhighlighted
everywhere. Add Highlightr (highlight.js) behind a small SyntaxHighlighter
service and use it on both surfaces:

- Native file viewer: highlight NSAttributedString by language inferred
  from the filename, picking a light/dark theme from the trait style and
  re-highlighting on style changes. Falls back to plain text for unknown
  languages or very large files.
- Markdown/org code blocks in the WKWebView (READMEs, tickets, comments):
  the web view disables JavaScript, so highlight server-side into inline
  color-styled spans. The color scheme is now part of the render/cache
  key since colors are baked into the HTML.

Covered by unit tests for language mapping and highlighted output.
</content>
</entry>
<entry>
<title>Let users clear recent activity on Home (#11)</title>
<updated>2026-08-07T06:44:17+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-08-07T06:44:17+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=a0ed11a3c4ddb2f532fa70884dc2829a23a5eec8'/>
<id>urn:sha1:a0ed11a3c4ddb2f532fa70884dc2829a23a5eec8</id>
<content type='text'>
Add a Clear button in the Home Recent section header to drop all
entries, plus swipe-to-delete on each row to forget a single item
(gated on the existing swipe-actions setting). Back it with
RecentActivityStore.clear and remove(id:), with unit tests.
</content>
</entry>
<entry>
<title>Update test fixtures to root@krz.sh email</title>
<updated>2026-08-02T14:28:56+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-08-02T14:28:04+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=04fae8d7fb3726b4d1b4c2576ece04766d2afe74'/>
<id>urn:sha1:04fae8d7fb3726b4d1b4c2576ece04766d2afe74</id>
<content type='text'>
</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>Merge pull request #5 from zerolabsco/inbox-unread-baseline</title>
<updated>2026-07-16T04:27:03+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T04:27:03+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=0ad430c79eedac4140680e3aaca238969ac711b1'/>
<id>urn:sha1:0ad430c79eedac4140680e3aaca238969ac711b1</id>
<content type='text'>
Start a new account at zero unread</content>
</entry>
<entry>
<title>fix: derive inbox activity from mail, not thread.updated</title>
<updated>2026-07-16T04:18:20+00:00</updated>
<author>
<name>Christian Cleberg</name>
<email>hello@cleberg.net</email>
</author>
<published>2026-07-16T04:18:20+00:00</published>
<link rel='alternate' type='text/html' href='http://git.krz.sh/krz/hutch.git/commit/?id=97e8b49fcb56751fa0e80a332484db8252753417'/>
<id>urn:sha1:97e8b49fcb56751fa0e80a332484db8252753417</id>
<content type='text'>
Thread.updated is the root email's insert time. It never advances when a reply
arrives, despite the name and despite the schema describing threads as ordered
"most recently bumped". sr.ht returns updated seven seconds after root.date on
a thread carrying four replies:

    subject: "Re: test post"
    updated: 2026-07-16T03:44:55Z
    replies: 4
    root:    { date: 2026-07-15T22:44:48-05:00 }

The app fed that field into lastActivityAt everywhere, so a reply to an old
thread was invisible: it did not mark the thread unread, did not raise it in the
list, and did not update the timestamp beside it.

This was inert until the unread baseline landed, because an absent view record
returned unread unconditionally and the date was never compared to anything. The
baseline did not break unread; it was the first code to depend on that field
meaning what it says.

Use MailingList.emails instead, which is real reverse-chronological arrival
data, keyed by thread root id. The scan stops once it is older than the cutoff,
so a quiet list costs one page and a busy one costs only what is new. Threads
with nothing in the window keep the root timestamp and stay read, which they
already were. Failure returns empty activity rather than throwing: unread is a
decoration and should not take the thread list down with it.

Uses Email.received rather than Email.date — received is server-side and
non-null, date comes from the sender's header and is neither.
</content>
</entry>
</feed>
