Skip to content

Build a delivered plan's later changes on demand without LIVE_BUILD - #510

Merged
MaggieAppleton merged 18 commits into
mainfrom
design-build-capsule-manual
Oct 12, 2026
Merged

MaggieAppleton merged 18 commits into
mainfrom
design-build-capsule-manual

Conversation

@MaggieAppleton

@MaggieAppleton MaggieAppleton commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Note: This branch now also carries the 13 commits of #454 (prototype spikes). #454 was merged into this branch while it was #454's base, so landing this PR lands #454's content on main too.

Slice 2 of the approved build-plan jig. Stacks on #508.

What changes

  • Manual mode records the built source. Without LIVE_BUILD, a browser build that delivers (verification passed) now records the source it built (plan.live). The flag-off lifecycle is unchanged: verification still ends the run, and nothing auto-triggers. The live record now restores for a verified manual run as well as a live one.
  • N changes to build + Build. The capsule counts the top-level blocks changed since that source and offers a small outline Build. POST /api/channels/:id/implementation/rebuild checks the origin and write access, then reuses queueRebuild to queue one rebuild of the edits onto the existing pull requests on the presser’s own connected agent. It persists the rebuild before answering and before announcing. A collaborator’s tooltip says “Edits since @Maggie built it. Building them runs on your agent.”
  • Retry on a failed rebuild uses the same route, in either mode. Under LIVE_BUILD the capsule still shows a clock and no Build.
  • Docs: implementation-lifecycle.md.

Tests

  • Route test: a manual build delivers, records its source (0 changes), counts an edit (1 change), refuses a cross-origin Build, queues one rebuild for the presser, and refuses a second while it runs.
  • Unit: Build appears only in manual mode for someone who can act, Retry on a failed rebuild, and the collaborator tooltip.
  • E2E: Build plan → Built → typing an edit → “1 change to build” → the capsule’s Build queues a rebuild (the tab stays inert).
  • apps/server unit tests and implementation-launcher pass locally.

Screenshots

Desktop and phone: 3 changes to build with Build, Build failed with Retry, and a collaborator with 2 changes.

Review fixes

  1. A pressed Build (and Retry on a failed rebuild) is refused by queueRebuild and the route while decisions or accepted comments are open. The capsule’s action stays visible but inert, with the same reason as Build plan in its tooltip and accessible description.
  2. The press is the approval. This is now stated at reportRebuild, in docs/implementation-lifecycle.md and in AGENTS.md “Implementation graphs”, replacing the LIVE_BUILD-only wording.
  3. restoreLive accepts a live run, or a manual run whose outcome is implemented or delivered. A run returned for changes is rejected; the new test covers this.
  4. A refused Build is visible.
    • Desktop: the reason shows inline beside the capsule, as an alert.
    • Phone: Build opens with the reason in its headline.
    • The reason clears once the changes are building or built.
  5. On a phone the action reads Build changes, both as its label and as its accessible name.
  6. “Building them runs on your agent.” is shown only to people who can build.
  7. New server tests.
    • A manual build goes queued → read_rebuild → report_rebuild, then reads as up to date (changes: 0).
    • A pressed Build is refused while a decision is open.
    • A delivered manual build’s live record restores after reopen.

Review fixes (restack, 2026-10-12)

  • Busy agent. A pressed Build skips connections that are running a prototype or another document's build, as POST /implementation does (options.busy and the connector's busy check). When none is free it answers agent-busy, shown as "Your local agent is busy with another document".
  • Refusal copy. Rebuild refusals go through the shared copy mapper.
  • DELETE /implementation/request. It refuses archived documents.
  • e2e. The completing fixture agent now reads and reports the rebuild, so the test waits for Built and a recorded commit; |Build failed is no longer accepted. It finds the capsule's Build by role, not by class.
  • Route tests. Pull-only callers get 403, archived documents 403, and children 404. A collaborator's press runs on their own agent, a busy agent is refused, and a manual rebuild reopens intact.
  • Capsule action. The Retry and Build action uses aria-disabled and aria-busy instead of disabled.

🤖 Generated with Claude Code

desktop-08-stale-manual

desktop-11-failed

desktop-14-collaborator-stale-manual

phone-08-stale-manual

phone-11-failed

phone-14-collaborator-stale-manual

@MaggieAppleton

Copy link
Copy Markdown
Collaborator Author

Review: Changes needed

Diffed against design-build-capsule (#508). I ran the branch on the fake server (8825) and stubbed the snapshot to check the manual states at 1440×900 and 390×844, as the builder and as a collaborator. The capsule shows “3 changes to build” with Build, “Build failed” with Retry, and “Edits since @Maggie built it. Building them runs on your agent.” for a collaborator, as in the jig.

What holds up

  • Route authorization. It follows the pattern of the existing POST. The origin check comes first. Then browser() runs: the session (admission), the channel (no child documents), and a fresh repositoryAccess on the caller’s token with a repository-id match. After that it requires push or admin and an unarchived document. So admission alone is not enough.
  • Persistence before publication. queueRebuild persists (persistExclusive) before connections.assign, announceImplementation and the HTTP answer, and it rolls back on failure.
  • Double-click. The client guard (rebuilding, disabled) and the server’s buildActive check under exclusive refuse a second rebuild with a 409. The route test covers this.
  • Clicker’s agent. by = session.user.id picks the owner, so only the presser’s own connections are eligible, and the build is recorded under the presser.
  • Flag-off lifecycle. It is unchanged apart from the new plan.live record: verification still ends the run, and LiveSyncCoordinator stays gated on config.liveBuild, so nothing auto-triggers.

Findings

  1. Build can run with open decisions or accepted comments (routes.ts, rebuild route; builds.ts:queueRebuild). Nothing calls implementationReadiness. I reproduced this in the UI: a built document with 2 unanswered decisions still shows “3 changes to build” with an active Build. Build plan refuses this same state, and it is the stated rule (“Answer N open decisions first”). Today, pressing Build sends half-decided prose to the agent. Fix: in the rebuild route, refuse with implementationReadiness(plan, plan.revision), at least when by is set. In buildLook, return build: false with the same reason as the tooltip when phase.blocked or unanswered > 0, so the button is inert with its reason, like Build plan.
  2. Flag-off deployments now get the rebuild path that skips graph approval (builds.ts:reportRebuild). The rebuild appends an approved, completed graph version from the coding agent’s report alone. The code comment calls this “LIVE_BUILD prototype … deliberately bypasses the human-only graph approval.” This PR makes it reachable without LIVE_BUILD. A human press is arguably the approval, but AGENTS.md says approval has no production route. Update that comment, and update docs/implementation-lifecycle.md and AGENTS.md (“Implementation graphs”) to say a user-pressed Build is the approval in flag-off mode. Or gate the manual path behind its own flag.
  3. Restore is looser than it needs to be (builds.ts:restoreLive). Dropping && item.live means any archived run with that id passes, including one that ended in revision_requested or was blocked. Only a delivered or implemented run should, so check that rather than accepting any history entry. For example, keep item.live, or require the run’s outcome to be implemented/delivered via historyFor. The sidecar is server-written, so this is defence in depth rather than an exploit, but the old check was doing exactly that job.
  4. A refused Build gives no visible feedback (build-plan-button.tsx). rebuildError appears only as the capsule’s tooltip and aria-description. On desktop you see it only if you hover again. On the phone the capsule is glyph-only and tooltips don’t show, so “Start your local agent, then build” is never seen. Show it, for example as a toast, or in Build’s headline, where the phone already carries the words. It also stays set after a later successful rebuild until the next press. Clear it on the next snapshot change.
  5. The phone shows two adjacent “Build” controls. The top bar has the outline Build action next to a bare orange dot, and directly below it is the Build destination tab with the same dot. One acts and the other navigates, but they read as the same control. Their accessible names also collide (getByRole("button", { name: "Build" }) matches both outside the nav group). Use “Build changes” on compact, or always.
  6. The collaborator copy says “Building them runs on your agent.” even to people who can’t act. tip doesn’t depend on act. Keep the clause only when look.build is true.
  7. The manual rebuild is only tested to “queued”. The route test and the e2e stop once the rebuild is queued. Add one route test that runs read_rebuild → report_rebuild in flag-off mode and asserts the base advances (changes: 0). Nothing currently proves that the rest of the rebuild path works without LIVE_BUILD.

Checks

After the hash renewal commit (2a8ea49c, which changes only dynamic-web.json), validation and container pass on the rebased head 544f2096. That rebase changes only the hash file relative to the head I reviewed. e2e was still running when I posted this. Locally on 0bbd0823, the full bun test gives 4676 pass and 0 fail, and types, check-design-record and design:check pass. The merge-conflict notes for #457/#460/#495 are on #508 and apply here too: #460 also edits implementation-lifecycle.md.

@MaggieAppleton
MaggieAppleton force-pushed the design-build-capsule-manual branch from 544f209 to e057999 Compare October 12, 2026 10:03
@MaggieAppleton
MaggieAppleton force-pushed the design-build-capsule-manual branch from e057999 to 2c5639e Compare October 12, 2026 10:31
@MaggieAppleton
MaggieAppleton force-pushed the design-build-capsule branch 2 times, most recently from 3ccfcd5 to 3aa5751 Compare October 12, 2026 11:55
@MaggieAppleton
MaggieAppleton force-pushed the design-build-capsule-manual branch from 2c5639e to 7e3824a Compare October 12, 2026 11:55
@MaggieAppleton
MaggieAppleton force-pushed the design-build-capsule-manual branch from 7e3824a to 3778af8 Compare October 12, 2026 12:34
@MaggieAppleton
MaggieAppleton force-pushed the design-build-capsule-manual branch from 150759f to c05084d Compare October 12, 2026 13:09
@MaggieAppleton
MaggieAppleton force-pushed the design-build-capsule-manual branch from c05084d to 7651762 Compare October 12, 2026 13:46
@MaggieAppleton
MaggieAppleton force-pushed the design-build-capsule branch 2 times, most recently from 60a8677 to d58b3dc Compare October 12, 2026 16:41
Base automatically changed from design-build-capsule to main October 12, 2026 17:00
MaggieAppleton and others added 7 commits October 12, 2026 18:01
A browser build that delivers now records its built source in manual mode, so
edits read as "N changes to build". The capsule's Build queues one rebuild of
those edits onto the existing pull requests on the presser's own agent, reusing
queueRebuild without LIVE_BUILD's automatic trigger. Retry on a failed rebuild
uses the same route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A person's Build now waits, like Build plan, for open decisions and accepted
comments: the server refuses it and the capsule's Build is inert with the
reason. The press is the approval for the rebuild's tasks, now documented at
reportRebuild, in the lifecycle docs and in AGENTS.md. A manual live record
restores only for a delivered run. A refused Build shows inline (on a phone,
Build opens with it) and clears once the changes build. On a phone the action
reads "Build changes", and only people who can build are told it runs on their
agent. A server test now runs read_rebuild and report_rebuild without
LIVE_BUILD.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Build does

A pressed Build skips connections running a prototype or another document's
build, as POST /implementation does, and answers agent-busy when none is free
instead of no-workspace. Rebuild refusals read through the shared copy
mapper. Cancelling a waiting Build plan refuses an archived document. Route
tests cover pull-only callers, archived documents, children, a collaborator's
press on their own agent, a busy agent, and reopening after a manual rebuild.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The completing fixture agent now reads and reports a rebuild, so the e2e test
waits for Built and a recorded commit instead of accepting Build failed. It
finds the capsule's Build by role rather than by class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Whitespace only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…build

Behind LIVE_SPIKES=on (also on with LIVE_BUILD=on), a SpikeScout scans a
document 20 seconds after a person's edit settles, only while it has no
live build. It judges at most five unseen prose blocks per scan with one
Jev noul each (>= 0.6; explicit-uncertainty matching when Jev is absent
or fails) and keeps at most three spikes active. A hit becomes an
investigation marked as a spike, attributed to the last editor and
authorized on that person's live repository connection; with no
connection nothing is created.

A "Prototyping…" Callout is placed directly under the passage after the
record persists, and is rewritten in place when the spike completes or
stops. Removing the callout dismisses the spike, and a passage whose
digest has a spike never re-triggers. Spike runs get run-scoped
upload_investigation_image and submit_spike_result tools; the server
builds the report from the headline, findings, recommendation and
uploaded images. Spikes stay out of the investigation list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A claimed spike checks out chopin/spike-<id> in the disposable worktree,
gets a prompt to build the smallest prototype, upload screenshots and
report with submit_spike_result, and relays the server's run-scoped tool
list through the stdio bridge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaggieAppleton and others added 11 commits October 12, 2026 18:01
Scope image uploads to spike runs with a three-image cap and a per-run
citation check, and raise the connector body limit only for that tool.
Make LIVE_SPIKES independent of LIVE_BUILD. Persist a placing state
before a callout is published and recover in place. Stage callouts with
copied, rebased decision and comment anchors. Keep human edits inside a
callout by placing new state in a sibling. Leave passages unjudged when
Jev fails, keep approved over-capacity passages for later scans, and
dismiss deleted callouts after a living build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Spike runs get a connector-local upload_image_file that reads a PNG, JPEG or
WebP from the run worktree and forwards it, so agents no longer emit base64
and the prompt asks for 1280x800 Playwright screenshots. Callouts read Queued
until their run is claimed, and runs dispatch in creation order. A scan that
leaves passages unjudged follows up in five seconds, passages already above
a spike callout are skipped, and deleting a callout stops its spike on the
edit rather than the next scan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y frees

Copilot CLI in ACP mode rejects client-provided stdio MCP servers, so every
spike failed without a result. A spike run whose agent accepts HTTP servers now
gets a per-run streamable-HTTP bridge on 127.0.0.1, guarded by a random bearer
token and closed with the run, that relays the run's Chopin tools and adds
upload_image_file. Agents without HTTP support keep the stdio bridge.

A scan that stopped at the active-spike cap never judged the waiting passages
until another edit. It now resumes when a spike record changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d lock

Concurrent upload_investigation_image calls in one run now share one per-run
set and reserve their slot before awaiting storage, so neither overwrites the
other and the three-image cap holds. A spike placement deferred while a first
build locks editing is retried when the lock releases and when the room opens
unlocked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A crashed connector left its lock behind and blocked every later start until
someone removed the file. A lock naming a process that no longer exists is now
stale and replaced; a live or unreadable lock still refuses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A queued first build already holds the editing lock, so a spike that finished
while the build waited had its callout deferred until after the build. The
spike refresh may now update its callout under a build that is only queued,
rebinding the build and its approved graph to the callout-only revision in the
same commit, and the build claim waits (bounded) for those results to land.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a build

A spike needs the editor's opt-in at pairing and judges only passages that
editor changed in their own edits, so nobody else's text runs on their laptop.
Without Jev nothing is spiked unless LIVE_SPIKES_REGEX_JUDGE=on. Spikes are
capped per document and per editor each hour and interrupted 20 minutes after
their claim. A spike run cannot submit an investigation or experiment result.

A finding no longer lands under a queued first build by moving its approved
graph to a new revision: any build lock defers the callout until the build
ends. The connector runs a spike under Git configuration that refuses every
push and removes its worktree and branch afterwards. Callout deletion is
detected anywhere in the tree, a placement that throws gives its slot back,
image uploads check size before reading, and a file named ..shot.png is no
longer mistaken for one outside the worktree. Briefs and judge prompts frame
document text as untrusted data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaggieAppleton
MaggieAppleton force-pushed the design-build-capsule-manual branch from 36eeb69 to 8cec41e Compare October 12, 2026 17:01
@MaggieAppleton
MaggieAppleton merged commit 4f12010 into main Oct 12, 2026
3 checks passed
@MaggieAppleton
MaggieAppleton deleted the design-build-capsule-manual branch October 12, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant