<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>stafford williams</title>
    <description>software by Stafford Williams</description>
    <link>https://staffordwilliams.com/</link>
    <atom:link href="https://staffordwilliams.com/devlog/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 08 Jul 2026 02:35:43 GMT</pubDate>
    <lastBuildDate>Wed, 08 Jul 2026 02:35:43 GMT</lastBuildDate>
    <generator>Astro</generator>
      <item>
        <title>Sub-tasks, moving phases, and a pile of fixes</title>
        <description>Two backlog features led the day. The bigger one was sub-tasks: backlog items can now carry sub-tasks that get attached and checked for completion (`addSubtask`, `attachSubtasks`, `checkSubtasksComplete`), with a `/subtask` command to add them and the review prompt aware of them so an item won&apos;t sail through with unfinished sub-tasks. The second was a `backlog move-phase` command for reordering phases on an item — `movePhase` plus a `reorderPhaseRows` helper to keep the phase ordering consistent after the shuffle, which meant carving the phase-update commands out into their own `registerUpdatePhaseCommand` module.

On the review side I added Review-and-post entries to the review menus, so I can kick off a review that also posts its comments in one action rather than two, backed by a small `reviewModeArgs` mapping. I also disambiguated the review completion gate in `buildReviewPrompt` — the phrasing around when an item is genuinely done versus needing more work was still ambiguous enough to trip a session up.

The rest were fixes across the stack. `backlog add` now requires a git remote (via `ensureRemoteOrigin`) so items can&apos;t be created in a repo with nowhere to push. Sessions whose launch actually failed are no longer auto-dismissed, so I can see what went wrong. macOS notifications stopped hanging their caller — the notify call wasn&apos;t returning cleanly. And a Windows-proxy fix strips stray replay-query escape sequences out of the scrollback so resumed sessions don&apos;t render with junk in them.</description>
        <pubDate>Tue, 07 Jul 2026 00:00:00 GMT</pubDate>
        <link>https://staffordwilliams.com/devlog/assist/v0.353/</link>
        <guid isPermaLink="true">https://staffordwilliams.com/devlog/assist/v0.353/</guid>
        <category>assist</category>
        <category>claude-code</category>
      </item>
      <item>
        <title>A branch command, and grouping sessions by repo</title>
        <description>The main build today was a proper `branch` command. I kept starting work on a stale local base by accident, so `assist branch` now cuts a new branch off the *fresh remote default* — it resolves the default branch, fetches it, and builds a sanitised branch name from a slug (`resolveDefaultBranch`, `buildBranchName`, `validateSlug`). To make it the path of least resistance I added a matching `/branch` slash command and a `cli-hook` deny that intercepts raw `git checkout -b` / `git branch` and points at `assist branch` instead, the same pattern I used earlier for `git commit`. Branch config also shows up in `--help` now so the naming rules are discoverable.

On the web side I grouped session cards by repo. When I&apos;ve got sessions running across several projects the flat list gets noisy, so `groupSessionsByRepo` buckets them and a `SessionGroups`/`SessionGroupSection` pair renders each repo under its own heading. There&apos;s a fair bit of test coverage behind the grouping since the ordering and single-repo edge cases matter for it not feeling jumpy.

Rounding out the day: a new `/update-jira` command for posting a concise session summary onto a ticket, extending the comment policy so the redundant-comment guard also covers YAML files (`extractYamlComments`, `decideCommentGuard`), and a settings-sync fix so that syncing preserves keys that only exist in my user settings rather than clobbering them with the shared set.</description>
        <pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate>
        <link>https://staffordwilliams.com/devlog/assist/v0.350/</link>
        <guid isPermaLink="true">https://staffordwilliams.com/devlog/assist/v0.350/</guid>
        <category>assist</category>
        <category>claude-code</category>
        <category>git</category>
      </item>
      <item>
        <title>Repo summary chips on the backlog</title>
        <description>A single focused feature today: repo summary chips across the top of the backlog. I wanted an at-a-glance count of what&apos;s outstanding per repo without scrolling the whole list, so `loadRepoSummaries` aggregates item counts by repo, `getBacklogSummary` serves them, and a `RepoSummaryChips` component renders them above the `ItemList`, fed by a `useRepoSummaries` hook and `fetchRepoSummaries`. While I was in the request layer I pulled CSS serving out into its own `createCssHandler`, which trimmed a bunch of inline branching out of `handleRequest` and made room for the new endpoint without the handler getting messier than it already was.</description>
        <pubDate>Sun, 05 Jul 2026 00:00:00 GMT</pubDate>
        <link>https://staffordwilliams.com/devlog/assist/v0.346/</link>
        <guid isPermaLink="true">https://staffordwilliams.com/devlog/assist/v0.346/</guid>
        <category>assist</category>
        <category>claude-code</category>
        <category>react</category>
      </item>
      <item>
        <title>Per-phase cost tracking, and keeping session status honest</title>
        <description>The headline was cost visibility per backlog phase. I started recording per-phase token usage and active time, so each phase of an item now carries how many tokens it burned and how long it was actively running. That reshaped a good chunk of the backlog data layer — `loadRelations` and `rowToItem` were pulling everything through one tangled query, so I split the relation lookups into a dedicated `relationQueries` module and added the token/time fields to the item `types`. On the web side it surfaces as a `PhaseUsageLine` under each `PhaseCard` and an `ItemUsageTotal` rolling it up for the whole item, with `formatTokens` and `formatActiveTime` doing the human-readable formatting.

With that data flowing, I used it to seed the session timer. When a run reuses a session, `seedRunningMsFromUsage` and `loadPhaseActiveMs` pull the accumulated active time out of the usage records so the timer picks up where the phase left off rather than resetting to zero — the elapsed time now reflects real work done across resumes, not just the current sitting. Smaller but handy alongside it: a new `assist backlog set-status` command for setting an item&apos;s status directly, which I&apos;d been wanting whenever an item ended up in the wrong state and I just wanted to nudge it without going through a phase transition.

The rest of this release cycle was about making sessions behave the way I expect. The one I&apos;d been circling longest: stopping a session from continuing past a point where it&apos;s asked me something and I&apos;m not there to answer. I first wrote it as a hard rule in `CLAUDE.md` — never continue after &quot;No response after 60s — continued without an answer&quot; — but a prose warning is exactly the kind of thing a session can rationalise its way past. So I replaced it with the real fix: setting `CLAUDE_AFK_TIMEOUT_MS` to max int in `settings.json`, which just stops the AFK timeout from ever firing. If a session asks a question, it waits for me, full stop.

Two more status fixes rounded it out. The review button on a session card was opening the wrong PR, so I pulled the target-PR resolution into a `reviewTargetPr` helper (with tests) and tightened `prStatus` and `createCachedGhJson` so the button links to the PR that actually belongs to the session&apos;s branch. And resuming a waiting session and typing a new prompt should flip the card to running immediately — it was staying waiting until some later signal caught up — so I added a narrow `watchPromptSubmit` watcher hooked into `writeToSession`: if a restored session is waiting and the keystrokes include an Enter, it flips straight to running, without second-guessing status anywhere else.</description>
        <pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate>
        <link>https://staffordwilliams.com/devlog/assist/v0.344/</link>
        <guid isPermaLink="true">https://staffordwilliams.com/devlog/assist/v0.344/</guid>
        <category>assist</category>
        <category>claude-code</category>
      </item>
      <item>
        <title>Review synthesis in the web UI and a tidier transcript workflow</title>
        <description>Continuing the run of PR review work, I made the review synthesis viewable straight from the web UI. A `ViewReviewButton` on the card opens a `ReviewSynthesisDialog` that pulls the synthesis for the current branch — `findSynthesisForBranch` locates it, `getReviewSynthesis` serves it, and a `useReviewSynthesis` hook feeds the dialog. So once a review session has run I can read its summary in place rather than digging for the file.

I also consolidated the transcript commands. The old `transcript-format` and `transcript-summarise` slash commands collapsed into a single `transcripts` workflow that formats and summarises end to end, with the internal `processVttFile` reshuffled into clearer `convert`/`parseVtt` pieces and the allow lists and README updated to match. Small follow-up: transcript move output paths are now labelled so it&apos;s obvious where files landed. On the backlog side, new bugs automatically get a Fix phase added when created, which saves a manual step every time I file one.

The rest was session state correctness, which keeps being the gift that gives. Idle sessions now restore as waiting rather than running, which touched a fair bit of the restore path — `restoreSession`, `restoreInteractiveSession`, `runningSession`, and the backlog `resumeNudge`. Session titles now survive a restore instead of being lost, draft-to-run reuse resets stale state before starting, and review session chips and status resolve correctly through `postAndMaybeSubmit`. Capped off with a tiny CSS fix so markdown lists actually indent in the web UI.</description>
        <pubDate>Wed, 01 Jul 2026 00:00:00 GMT</pubDate>
        <link>https://staffordwilliams.com/devlog/assist/v0.342/</link>
        <guid isPermaLink="true">https://staffordwilliams.com/devlog/assist/v0.342/</guid>
        <category>assist</category>
        <category>claude-code</category>
        <category>react</category>
      </item>
      <item>
        <title>Usage pagination and a lot of card polish</title>
        <description>The usage history view got proper pagination today. It was rendering everything at once, so I taught `listUsagePeaks` to page at the query level and added a `useUsageHistoryPage` hook plus a reusable `PageShell` to drive it, which let `ListPage` and `UsageHistoryView` shed a chunk of one-off layout code. Following on from yesterday&apos;s PR review work, I added an address-comments review mode — a `ReviewDropdownContent` that lets me kick off a session specifically to work through review comments — and swapped the open-PR button over to a proper GitHub icon.

A good slice of the day was session card behaviour, which had accumulated small annoyances. Cards that were still starting up now show a spinner while their chips load, driven by a little `areChipsLoading` helper and a new `CardBody`. Clicking inside a dialog no longer selects the card underneath it — I wrapped the offending controls in a `StopClickPropagation` component — and navigating to `/usage` now correctly deselects whatever card was active via the `useActiveIdReconciler`. Terminal links open without an extra prompt now too.

Two behavioural fixes stood out. Resumed sessions now open in a waiting state rather than pretending to be running, which meant threading the distinction through `SessionManager`, `resumeSession` and the message handlers (and a note in `CLAUDE.md` about it). And I moved the UI-rendered status logging into a `useReportRenderedStatus` hook so what the dashboard actually shows gets echoed to `daemon.log` — much easier to debug status drift when the log tells me what the UI believed, not just what the daemon sent.</description>
        <pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate>
        <link>https://staffordwilliams.com/devlog/assist/v0.338/</link>
        <guid isPermaLink="true">https://staffordwilliams.com/devlog/assist/v0.338/</guid>
        <category>assist</category>
        <category>claude-code</category>
        <category>react</category>
      </item>
      <item>
        <title>Reviewing PRs from the sessions web view</title>
        <description>The headline today was pulling PR review into the sessions web view so I don&apos;t have to bounce out to GitHub to look at what a session produced. A new `ReviewButton`, `ReviewDropdown` and `ReviewPrList` surface the open PRs for a repo, backed by a `prList`/`prStatus` layer and a `createCachedGhJson` helper so the `gh` calls don&apos;t hammer the API. The daemon side grew to match, with PR metadata flowing through `lifecycleHandlers`, `toSessionInfo` and the session `types`. Then came the inevitable trail of follow-ups: an open-PR button right on the session cards, hiding the review dropdown entirely when there are no open PRs, and a fix to get the review card chip and PR meta rendering correctly.

Alongside that I reworked the web layout. `SessionsView` became `AppLayout` with a persistent sidebar that survives navigation instead of being tied to a single view, and `AppRoutes`/`AppSidebar` were reshuffled to suit. The history list got simpler too — I scoped it to the selected repo and deleted the now-redundant `FilterDropdown`, `ProjectFilter` and `uniqueProjects`, which felt good to see go. And because the web bundle and the daemon can drift out of sync, I added a `VersionBadge` that shows the web version whenever it mismatches the running daemon, wiring the version through the WebSocket handshake.

The rest was fixes, mostly around sessions and the backlog signalling. A stale socket now triggers a single session restore instead of looping, `AskUserQuestion` correctly shows a waiting status via the hooks settings, and PTY sessions keep their working directory through an `ASSIST_SESSION` env var. On the backlog side I stopped signal files from leaking into foreign repos when phase results are resolved, and tightened the `cli-hook` deny so it catches raw `git commit` calls buried inside a larger command rather than only at the front.</description>
        <pubDate>Mon, 29 Jun 2026 00:00:00 GMT</pubDate>
        <link>https://staffordwilliams.com/devlog/assist/v0.334/</link>
        <guid isPermaLink="true">https://staffordwilliams.com/devlog/assist/v0.334/</guid>
        <category>assist</category>
        <category>claude-code</category>
        <category>react</category>
      </item>
      <item>
        <title>Refining the comment gate and linking Jira</title>
        <description>Most of today refined yesterday&apos;s comment gate. The first rough edge was that the gate blocked *all* comment edits, including removing them — so I relaxed it to allow trimming, meaning I can still delete or shrink existing comments freely while adding new ones stays gated. The bigger change was hardening the escape hatch: the pin that authorises an allowed comment is now delivered out-of-band (`issuePin`) rather than passed inline, so it can&apos;t be forged in the same edit it&apos;s meant to permit. With the gate carrying the policy properly, I dropped the old block-comments verify step, which had become redundant.

The other feature ties backlog items to Jira. A new `assist backlog associate-jira` command — and a `/associate-jira` slash command in front of it — links a ticket to a backlog item: it validates the key shape, fetches the issue to confirm it actually exists, and stores the key, with `--clear` to remove the association. It&apos;s the connective tissue I&apos;d been missing between my own backlog and the tickets that drive some of the work.

On the daemon side, I added a protocol-version handshake so the WSL and Windows daemons agree on a protocol version up front and a mismatch is caught cleanly at connect time rather than surfacing as confusing downstream failures. Three fixes rounded out the day: a `--once` session re-watches its done signal after a restore so it still auto-dismisses, the daemon no longer replays its whole log buffer to a reconnecting browser (which had been dumping a wall of stale lines on every reconnect), and the web UI now shows unmerged files in the git status.

A couple more fixes landed later in the cycle, both about sessions behaving as I expect. A session would go idle after a tool ran even though the agent was still working, because I only flipped the status to running on `UserPromptSubmit` and `PreToolUse` — adding a `PostToolUse` hook in `ensureHooksSettings` closes that gap so the status follows the agent when a tool completes and it resumes. And the review phase was getting too eager about rewinding: the prompt in `buildReviewPrompt` told the agent to rewind whenever a criterion still failed, which meant it would sometimes throw away already-approved, committed work. I replaced that with explicit precedence rules — approval plus a successful commit means the work is done, diverged-but-approved implementations still complete by default, and rewinding is now a confirm-first exception that must ask me before running `assist backlog rewind`. Never silently.</description>
        <pubDate>Fri, 26 Jun 2026 00:00:00 GMT</pubDate>
        <link>https://staffordwilliams.com/devlog/assist/v0.329/</link>
        <guid isPermaLink="true">https://staffordwilliams.com/devlog/assist/v0.329/</guid>
        <category>assist</category>
        <category>claude-code</category>
        <category>jira</category>
      </item>
      <item>
        <title>Gating comments and unifying daemon logs</title>
        <description>The theme I&apos;m most pleased with today is enforcing the self-documenting code policy at the hook level rather than hoping I remember it. A new comment edit gate is a PreToolUse hook (`decideCommentGuard`) that blocks `Edit`/`Write` from adding code comments, with an escape hatch: when a comment genuinely earns its place I can confirm it through `codeComment` and have it allowed. I also hardened the block-comments verify gate so the check can&apos;t be quietly sidestepped. Alongside it, the maintainability gate got a proper override: a special marker comment exempts a file that legitimately can&apos;t hit the index, and the `edit-hook` protects the marker itself — Edit/Write can&apos;t add, change, or remove one — so the exemption is a deliberate act rather than something a refactor can flip. The complexity output now surfaces which files are running under an override.

The other substantial piece was observability. Daemon logging is now unified into a single `assist.log` stream instead of scattered outputs, daemon session operations are logged as they happen, and I dropped a redundant lifecycle logger that was just adding noise. To go with it, a new `assist daemon drain` command clears all sessions from the local daemon for a clean slate, and a Windows-proxy create refusal now gets logged so I can see why a session didn&apos;t start.

The rest was hardening, much of it on the Windows path now that it carries real traffic. I capped the Windows connect probe at 2s and tightened the create timeout to 5s so a stuck host fails fast, surfaced a failed Windows create to the UI instead of leaving it silent, gated the Windows proxy off when assist is itself running on the Windows host, and stopped an unbounded `w-` growth in Windows session ids. A few general fixes too: a hard cap on session fan-out so a runaway can&apos;t spawn endlessly, the web server now binds before the daemon is ready so the page is reachable during startup, the session-card prompt is clamped to five lines, and the 7-day usage row gets a tinted background to set it apart.</description>
        <pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate>
        <link>https://staffordwilliams.com/devlog/assist/v0.326/</link>
        <guid isPermaLink="true">https://staffordwilliams.com/devlog/assist/v0.326/</guid>
        <category>assist</category>
        <category>claude-code</category>
        <category>windows</category>
      </item>
      <item>
        <title>Restoring the macOS menu bar for the webview</title>
        <description>The macOS webview window had a subtle but irritating problem: because it runs from a bare binary that tao promotes to a foreground app at startup, it had no main menu — and a regular macOS app with no main menu leaves the left side of the system menu bar dead, so the Apple menu and the app menu didn&apos;t respond to clicks. Worse, without an Edit menu the standard Cmd+X/C/V/A shortcuts didn&apos;t work inside the page either.

The fix installs a minimal main menu when `NSApplication` finishes launching (the same `StartCause::Init` moment I already use to set the dock icon, since anything done earlier is discarded by the promotion). It&apos;s just two menus: an app menu with Quit, and an Edit menu wiring Cut/Copy/Paste/Select All to their standard selectors — which both revives the menu bar and restores clipboard shortcuts in the webview. This pulled in the `NSMenu`/`NSMenuItem` features from `objc2-app-kit`.</description>
        <pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate>
        <link>https://staffordwilliams.com/devlog/project-switch/v0.20.1/</link>
        <guid isPermaLink="true">https://staffordwilliams.com/devlog/project-switch/v0.20.1/</guid>
        <category>project-switch</category>
        <category>rust</category>
        <category>macos</category>
      </item>
  </channel>
</rss>
