Repository navigation
UI: unblock from the browser — 'why it's blocked · what you can do' actions, and bulk-retire attempts for closed Issues #243
Description
Problem (v1, operator feedback, 2026-10-26)
"I see many blocked, but I can't unblock them." A blocked or failed Molecule in forgecell ui shows its status and error text but offers no action. Every way out is CLI-only:
amend --accept-protected-failures(by-design test changes, amend --accept-protected-failures: turn a by-design protected-regression failure into reviewed expectedProtectedDeltas + checks-only continuation #231/Protected deltas by test identity across all protected Go checks (incl. the Formula baseline), with engine-streamed -json in the protected view #237);amend --add-scope … --reason …(approving a coder's scope-change request);amend --continuation checks-only(re-running checks after an environment failure, for example low disk);recover --confirm-stopped(interrupted runs);- a fresh intake after a clarifying question.
The UI therefore can't run the loop end to end, which is the v1 goal. On 2026-10-26 the real Lab held 71 blocked Molecules: 29 protected-regression failures, 28 intake clarifications or exhausted evidence passes, 9 invalid analyses and 2 harness errors. All 71 were for already-closed Issues, and none could be acted on or cleared in bulk from the UI.
Fix
- The engine already decides: for each blocked, failed, interrupted or scope-change Molecule, derive a typed next actions list from engine state (no new engine semantics). An action is offered only when the engine call would accept it today, using the same preconditions. Never offer an action that will be refused.
- Accept by-design test failures: when the latest attempt's candidate checks all passed and only protected Go checks failed cleanly. Show the exact failing test list (package, test, declaring file) that
--accept-protected-failureswould declare, then create the revision and show its digest and deltas. The human approves that digest through the existing scope approval. Never approve in the same click. - Approve the requested scope change: when the coding outcome is a scope-change with exact paths. Show each path and the coder's reason, let the operator edit the reason text, create the revision through the
--add-scopepath, and approve the digest separately. - Re-run checks on the same code: when coding completed and the capture is intact, but verification failed for environmental reasons (resources, timeout, harness or provider error) or never started. Use the checks-only path. For the
check-pending+ resource-refusal state, call recover first, exactly as the CLI flow does. - Recover: for interrupted Molecules whose owner is gone (the loop's recovery proposal), with an explicit "I confirm it is stopped" checkbox.
- Start a fresh intake: for clarification-required or exhausted-evidence intakes. Show the open question text (bounded, redacted), link to the Issue so the operator can edit it, and reuse the existing guarded
POST /api/runs. - Retire: always available where the engine allows (Retire stale Molecules: an explicit, reviewed retire action (CLI + UI) that keeps every record and clears the decision lane #236).
- Accept by-design test failures: when the latest attempt's candidate checks all passed and only protected Go checks failed cleanly. Show the exact failing test list (package, test, declaring file) that
- Bulk retire for closed Issues: a Retire attempts for closed Issues action. It lists every non-running Molecule whose Issue is closed and that has no delivery in progress, requires a confirmation that shows the exact count and list, and retires each one through the same engine call with a fixed reason ("Issue closed; superseded attempt"). It reports results per Molecule, with no partial silent failure. The Issue state comes through the same bounded
ghreader and cache as Sync engine + API: read-only GitHub Issue/PR state for every Molecule (all states, batched, cached, fail-closed) + GET /api/issues #242, and refuses when that's unavailable. - UI:
- Panel: a "Why it's blocked · What you can do" panel in the Molecule detail, with the blocking reason (bounded, redacted; UI correctness from the first browser test: delivered lane, actionable refusals, consistent next action, stray null, job 404 #235 style) and the offered actions as buttons.
- Writes: each action is a guarded POST (cookie, tab header, exact Origin, same-origin, 4 KiB JSON) taking the exact parent digest it was shown. The engine refuses stale ones.
- Commit point: an action shows committed only after the ledger read-back, as the gates do.
- Demo refuses all of them.
Added (operator, 2026-10-26): gate actions must look like actions
On #244 the operator couldn't find Approve this plan. It renders as a small underlined text link inside "Gates and decisions", directly above Retire Molecule, which is styled identically. The main action and an irreversible one look the same.
- Primary buttons: every gate's main action (Approve this plan, Preview delivery, Publish draft PR, and each "what you can do" action in this issue) becomes a primary button, using the
.btnsystem from UI polish: composed Start Work controls, segmented lane bar, sharper glass (visual only) #241 (or an equivalent if UI polish: composed Start Work controls, segmented lane bar, sharper glass (visual only) #241 hasn't landed): forest fill, 40 px, placed directly under the plan digest it binds. - Retire: moves to a separate, visually quieter "Danger zone" block at the bottom of the panel, as a secondary danger-styled button (outlined, muted red that passes AA). It needs a two-step confirm ("Retire" → "Confirm retire") and a reason. Never put it adjacent to a primary action.
- Next action: the card names the button ("Click Approve this plan below") instead of "decide under Gates and decisions, or with the forgecell CLI", which also removes the "Decide with the forgecell CLI" copy on the overview's decision card.
- Tests: in the served JS, gate main actions use the primary button class, Retire is in the separate block with the confirm step, and the copy no longer points to the CLI.
Done when
- Tests (ui/molecule/cli, deterministic, fixture Labs):
- each blocked state offers exactly the actions its engine call would accept, and none it would refuse;
- each action creates the same revision the CLI does (digest equality with the CLI path);
- accept-failures and scope-change never approve in the same request;
- re-run checks works from a resource-refused
check-pendingMolecule; - bulk retire lists and retires exactly the closed-Issue, non-running, no-delivery-in-progress Molecules, reports each result, and refuses when Issue state is unavailable;
- the guards on every new route, and demo refusal;
- stale-digest refusal.
- Targeted checks:
go test ./internal/ui/... ./internal/molecule/... ./internal/cli/....
Review checklist (read before coding)
The adversarial review checks these every time. Recent slices failed review on all of them, so build them in from the start:
- Bounds: sizes, counts, time and total work are bounded, and hitting a bound fails safe (incomplete or truncated, never silently accepted).
- Filesystem safety: paths cannot traverse. Use
LstatandO_NOFOLLOW, and check symlinks and ownership. Watch for TOCTOU races on shared dirs (/tmp). Only ever delete Forgecell-owned, marked paths. - Untrusted input: issue text, repository files, model and tool output, and ledgers can be malformed, huge or hostile. Validate them, never interpolate them into HTML or a shell, and never let them trigger retries or approvals.
- No local leaks: no absolute local paths, environment variables or credentials in output, logs, the UI or ledgers.
- Contract changes: a changed adapter invocation bumps the protocol, and approved digests still bind exactly what was shown.
- Tests: deterministic. Use no wall-clock budgets (use readiness markers or seams), write nothing into the user's real temp or home, and cover the failure path as well as the happy path.
Plan checks (for intake)
Leave every check's reviewedAcceptance and independentProvenance empty: those come only from a separate human review, never from analysis. Check category must be one of regression, candidate or setup. Use targeted package tests only as plain go test commands (for example go test ./internal/<pkg>/...); the protected view adds -json itself when protected deltas are declared. The Formula baseline already runs the full suite once, so don't add another full go test ./... check, and don't repeat its go vet or gofmt checks. A plan may hold at most 16 checks in total, counting the 5 Formula baseline checks, so prefer one combined targeted command over one check per package.
Added 2026-10-27: loop status UI (the UI half of #254, which shipped engine and API only in #267)
Owns the loop.js module after #262.
-
Loop pill and panel read
/api/loop, which now reports any loop in this Lab: owner (this UI, CLI, another UI), selector, max-concurrent, start time, in flight (Molecule ids, or "issue #N · planning" during intake) andcanStop. -
Stop loop: shown only when
canStop, behind the confirmation that names the loop. Otherwise show plain text naming the owner, for example "Started from the command line with milestone:v1 · stop it there". -
Start loop:
- refused with the engine's plain reason when another loop holds the Lab marker;
- the selector defaults to
milestone:<active milestone>rather thanoldest, witholdestexplained as "oldest open Issue, any milestone".
-
Tests: a served-JS and handler fixture for each owner case, in-flight intake rendering, and the refusal text.
-
Keep base tests compiling (added 2026-10-27): the protected view runs the base
_test.gofiles against your code. Never change the signature of an existing Go function, method or type that a base test file calls. Add a new one instead. A protected build failure cannot be declared as an expected delta. -
After UI foundation: split app.js/style.css into per-view modules (no behaviour change) so v1 UI slices run in parallel #262: this slice owns its own
web/js/<view>.jsmodule(s) andweb/css/<view>.css. Don't edit other views' modules; shared helpers go incore*.js.
Plan checks for this slice (2026-10-27)
- The operator's visual or browser check is a manual step after delivery, not a plan check. Don't add a check for it.
- Every plan check:
category: regressionorcandidate, withreviewedAcceptanceandindependentProvenanceleft empty. The analysis must never assert independent acceptance. - Use one combined targeted
go testcheck for the packages this slice changes.
Activity
DanielNetzer commentedon Oct 26, 2026
Redesign mapping (2026-10-26): this is redesign slice 5/5. The 'what you can do' actions render as the primary action on the Inbox's blocked cards (#250) and in the Molecule view's decision card (#251). Retire and Stop stay in #251's danger zone with a confirmation. The UX rules listed in #249 apply.
merge main into #243: union setup (#263) and next-action routes in se…
feat(ui): unblock from the browser — why it's blocked · what you can …
DanielNetzer commentedon Oct 27, 2026
Merged via #273.