Uh oh!
There was an error while loading. Please reload this page.
feat(live): Ask and Act are separate buttons, and the request says which - #31
Merged
Conversation
One button meaning "hand this over and let the agent decide" left the decision in the wrong place. There are two now, and the request carries an `intent` rather than the instruction being mixed into the comment — the reader's text stays theirs, and the agent is told in a field. `await` still says it in plain words, because that is what an agent acts on: a question gets "Do not change code — they pressed Ask, not Act". An intent that is absent or unrecognised is a question, so a request that does not say what it wants gets an answer rather than an edit, and so does every request written before this. Act is not offered at all on a pull request somebody else wrote — the page asks the server, which derives it from who wrote it. Better than offering a button and then refusing it: a button that is there is a promise. Asking for a change there is still refused, since a request can arrive by other means than the button. Also: editing a comment no longer happens through a three-row letterbox. The box is sized to what is in it, between four rows and twenty-four, past which scrolling inside it is the lesser evil. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
Nothing tested that Act is absent where changes are not allowed — the only rule in the change, held up by a prop being undefined. The decision is now canAskAgent and canActOnCode, with ten cases across the two, including that somebody else's pull request gets Ask and no Act and that nobody listening is not a reason to hide either. The edit box measures its own wrapped height instead of dividing by an assumed 80 characters a line, which was a different number in split and unified view. The module only clamps now. I had reached for arithmetic because it was easy to unit-test, which made the test easy and the result approximate. And the thread says which button was pressed: "change asked for" and "done" rather than "asked" and "answered", now that the two mean different things. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
…count It appeared in the bottom right for a thread that was above. `threadBounds` returns null for a thread that is not rendered — far from the reader, so virtualised away — and null fell through to "below". Where the file sits in the reading order answers it without the DOM, and is used when there is nothing to measure. It now sits over the old side rather than the new: the note should not be on the code being reviewed. Top or bottom still follows the thread. It leaves after ten seconds, with a bar running down so that is not a surprise, and what it leaves behind is a count — a red badge and an arrow at the top of the diff over the gutter, straddling the file header. Going to it takes the oldest first, so following it repeatedly walks through them in the order they arrived. Scrolling a thread into view still clears it, now from the count as well as the note. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
…review Authorship is a proxy for whose work this is, and it stops being one the moment work is handed over: take over a colleague's branch and the pull request still says they opened it, so Act disappeared and a change request was refused on your own work in progress. The agent bringing diffity up knows which it is doing. `--work` says you are working on this branch, `--review` says you are reviewing it — which also covers reviewing a pull request you wrote yourself, where authorship would have said yes. Neither still means derived from authorship, so nothing changes for anyone who says nothing. Not a setting. It lasts as long as that server, so there is nothing to leave switched on, and handing work over means restarting rather than remembering to untick something. diffity-review now launches with --review, since reviewing is the one thing it is for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
… on reload Half the comments added on this branch went. What is left is the handful a reader could not get from the code: a non-obvious invariant, a choice that looks wrong until you know why. The rest was me narrating decisions. Also: threads are an empty array while the query loads, and recording that as "what I have seen" made every existing answer look new on the next poll — so a page rebuilt underneath the reader announced the whole conversation back at them. An empty first look now announces nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
The page said "No agent" while an agent was parked. The status route read `?session=` and the page sends `?ref=`, so it fell back to the shared current-session file — which every worktree using this data directory writes to — and answered about whichever review was opened last. Both live routes resolve the session from the ref, the way /api/info already did, and `agent await` asks the server which session it is serving instead of reading that file. That file was the ambient-session problem flagged earlier today; this is the path where it actually bit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
…wering Between taking a request and answering it the listener is not parked on the claim route, so presence alone reported nobody there — at exactly the moment an agent had the reader's question in hand. Which is the window a reader is most likely to be looking at it. Three states now: waiting, working, none. Working is derived from a request claimed and not yet answered, which is a fact about this review rather than about connections. Also: the test helper that drained the queue claimed without answering, so everything it touched counted as being worked on forever. It answers what it takes now, and the two new cases assert a delta — sessions on one branch share their threads, so an absolute count is about the whole file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
…tore it The restore fired on the first frame after the diff loaded, when the view had not mounted, so the scroll went nowhere — and the guard had already been set, so it never tried again. The reader was left at the top of the diff, which is where the general comments and their reply box are. That is the jump, not the bubble. It waits for the handle now, and gives up after sixty frames rather than spinning. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
The count was a badge hung over the gutter, and every attempt to place it was wrong in a new way: clipped by the container that scrolls, offset by the file list, on the wrong gutter in one view mode. A toolbar element has none of those problems, and a bell with a count is what everyone already knows how to read. Left of the agent indicator. Quiet with nothing unread, a red count when there is, and a list on click — who answered, which file, the first lines — oldest first, so working through them follows the order they arrived. Also: the note's edge now follows the order the reader sees rather than the raw diff order, which is why a note about a file above them arrived in the bottom corner while the walkthrough had reordered the files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
The reader is looking at the page having just asked, and between taking their request and answering it nobody is parked — so it said "No agent" at the moment they were most likely watching. Re-arming first closes that window, and a second request arriving while the answer is written is queued rather than lost. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
Right-aligned against the midpoint in split view, so it covers the right of the old side and never the new code being read. Unified keeps to the left, having no midpoint to sit against. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
The bell stopped counting down once a note expired: the watcher that clears an answer when the reader scrolls to it only ran while a note was showing, so the count quietly changed meaning from "unseen" to "unclicked". It runs whenever either list has anything in it now. The list has a maximum height, since a batch of asks is how this gets used and thirty would have run off the bottom of the window. And the purpose flag is tested from flag to page rather than only at the function that resolves it — the path deciding whether an agent may edit somebody else's branch had been verified by hand and by nothing else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
fiddur
marked this pull request as ready for review
August 25, 2026 07:58
fiddur added a commit
that referenced
this pull request
Aug 25, 2026
A running instance replaces itself only when the binary version differs from the one it registered (`isStale` in `packages/cli/src/index.ts`). So a merge without a bump leaves every instance that is already open serving the old build, silently. #31 and #32 both went in that way — six instances here are still on 0.9.6. Three things: - **`AGENTS.md`** with the rule. It is the first agent-facing doc in the repo. - **`--no-git` on `scripts/release.ts`**, without which the rule cannot be followed: the script commits and tags, and since develop takes squash merges the commit is rewritten on the way in and the tag is left pointing at a commit that never lands. Default behaviour is unchanged. - **v0.9.7**, the rule applied to its own PR. parser 43, git 44, github 21, ui 371, cli 157; the built binary reports 0.9.7. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
fiddur added a commit
that referenced
this pull request
Aug 26, 2026
#36) Everything on the known list, plus the reason two of them were invisible. ### A review taken by an unrelated one `reviewScope` maps every base sha to one scope, because a branch's base moves as the branch is updated. With no branch that reasoning is gone — so a second PR opened in the same detached checkout looked like the same review continuing, and `gatherOpenWork` moved the first one's findings into it. Work may now only move when a branch connects two sessions, or when the ref is identical. Redirecting a stale tab is deliberately **not** guarded: pointing someone at a newer session shows them something they can check, and the legacy-migration tests depend on it. Only taking work is narrowed. ### A detached checkout called its branch HEAD `rev-parse --abbrev-ref HEAD` returns the literal `HEAD`, which matches nothing, stranding any session opened before `gh pr checkout` moved the worktree onto a real branch. Recorded as unknown now, which is what it is. ### The wrong diff decided where a comment could go `gh pr diff --patch` is the mbox commit series, not the PR diff. Measured on our own PRs: | | `--patch` | correct | |---|---|---| | #34 `github-resolution.test.ts` | 25 lines | **89** | | #34 `github-resolution.ts` | 29 | **59** | | #31 `agent.ts` | 15 | **32** | | #31 file count | 49 | 47 | Two of #31's "files" were historical paths from before a rename. And the error message sent you auditing your own line numbers; it names the diff now. ### Text typed into the page was rewritten Shell unescaping lived in `threads.ts`/`tours.ts`, which the browser routes share, so a comment discussing `split('\n')` was stored with a real line break. Moved to the CLI boundary. The CLI still unescapes — verified end to end both ways. ### The two type errors, and why they survived `packages/cli` had **no typecheck script at all**, and the UI's failed on four TS6059 errors from its own tsconfig (`rootDir: src` against an `include` that reaches outside it), so its output had been noise long enough to ignore. Both fixed, `npm test` now runs `typecheck` first, and **the count is zero**. Mutation-checked: reverting each session fix turns the new tests red (1 and 4 of 4 respectively), and putting the unescape back in storage turns 2 of 3 red. parser 43, git 44, github 21, ui 390, cli 179, scripts 4. v0.9.11. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
One button meaning "hand this over and let the agent decide" put the decision in the wrong place.
Two buttons, and a field rather than salted text
AskandActsit beside Cancel and Comment. The request carries anintent, so the instruction ispart of the protocol instead of being mixed into the comment body — the reader's words stay theirs in
the thread, and the agent is told in a field.
It is still said in plain words on the way in, because that is what an agent actually acts on:
An intent that is absent or unrecognised is a question. Least privilege, and it means every
request written before this — and anything malformed arriving at the route — gets an answer rather
than an edit.
Act is not offered where it would be refused
/api/live/statusnow reportsmayChangeCode, derived from who wrote the pull request, and the pagedoes not render the Act button when it is false. Better than offering a button and then refusing it:
a button that is there is a promise.
The refusal still exists underneath, since a request can arrive by means other than the button — an
acton somebody else's pull request is answered rather than acted on, and the agent is told to saythat is why.
Editing a comment
A fixed three-row box meant editing a long finding through a letterbox. It is sized to what is in it
now, between four rows and twenty-four — past that the box would push the diff off screen and
scrolling inside it is the lesser evil.
Verified
parser 43, git 44, github 21, ui 345, cli 148 — 11 new. UI typecheck at its 6 pre-existing errors.
Driven by hand as well as asserted: an
askand anactposted through the API came back toagent awaitwith the right intent and the right directive, in that order.🤖 Generated with Claude Code
https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs