A graph of what is running - #414
Merged
Merged
Conversation
ADR 0025 and the change documents for a graph of what is running, plus the layout it is drawn from. The layout is a pure function of the readiness report and lives in core, not in the view: the terminal and the shell must place changes identically, and a second implementation in the browser would be free to drift invisibly, both pictures continuing to look plausible. A column is a change's depth in the declared blocked_by order. Within a column, changes are ordered by name and not by state: a node that moved because a run started would read as the plan having changed. A collision is not an edge. Two changes that would meet in one spec file have no precedence between them, and a line would assert a sequence the repository does not contain, which a reader would believe because it would look like every other line in the drawing. A cycle of declared blockers is named rather than placed, and so is anything waiting on one. A cycle has no depth, and there is no column one past nowhere; a picture that quietly put one somewhere would be a wrong answer that looks like a right one. ChangeReadiness gains the blockers it declares, kept to those still active. The report carried them only for blocked changes, so the picture would have lost the relations of a running one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Pipeline tab: every active change in the order it declares, what is running right now and whose run it is, and what can be started alongside what. Sequential work is not a separate thing to draw — a chain of changes each blocked by the previous is a graph one lane wide. The picture is derived, coordinates and all, by layoutChanges in core. Nothing in the view is measured: one unit is one rem for both the cards and the SVG, so they line up without either being asked where the other ended up. The alternative held the only state derived from a moment of rendering, needed a browser to check, and failed in a way that looks fine — an edge positioned from stale geometry is a line pointing at nothing. Edge routing is now checked by the same unit tests as the ordering. A declared blocker is drawn as a relation. A collision is not: two changes that would meet in one spec file have no precedence between them, and a line would assert a sequence the repository does not contain, which a reader would believe because it would look like every other line. A collision is stated in words on the card it affects, in the same wording openspec-ui-cli ready uses. A cycle of declared blockers is named rather than placed, and so is anything waiting on one. Below 720px the picture becomes headed lanes. Four columns of cards do not fit a phone in any implementation, and the wording on a card is what the edges illustrate — which is already what the picture offers a screen reader, the SVG being aria-hidden. The bridge operation for the VS Code embed was dropped deliberately: the embed shows one tab, so it would have been an operation nothing could call. Verified: npm run verify unpiped with everything staged — cli 107, core 1092, extension 327, server 83, webui 389. The whole browser suite, 16 of 16, including this tab's axe run at WCAG AA. 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 free
to 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.
A Pipeline tab: every active change in the order it declares, what is running right now and whose run it is, and what can be started alongside what.
Sequential work is not a separate thing to draw — a chain of changes each blocked by the previous is a graph one lane wide, and independent changes are lanes side by side. One drawing covers both.
The three decisions, and why (ADR 0025)
The picture is derived in core, coordinates and all.
layoutChangesreturns each node's position and size and each edge's path, in abstract units. Nothing in the view is measured: one unit is oneremfor both the cards and the SVG, so they line up without either being asked where the other ended up.The alternative was to lay the nodes out with CSS and measure them —
getBoundingClientRectper node, aResizeObserver, a re-run after every layout pass and after fonts settle. That was the only part of this view holding state derived from a moment of rendering, the only part not checkable without a browser, and the only part with a failure mode that looks fine: an edge positioned from stale geometry is a line pointing at nothing. Edge routing is now checked by the same unit tests as the ordering.A
remand not anem, for two reasons that both bite: a custom property holds a token rather than a computed length, so1emwould resolve against the font size of whichever element used it; and the shell fixesbodyat 14px, so anemwould not follow the reader's browser font setting at all.A collision is not an edge.
blocked_byis an order and an arrow means exactly that. Two changes that would meet in one spec file have no precedence between them — either may go first — and a line would assert a sequence the repository does not contain. A reader would believe it, because it would look like every other line in the drawing. So a collision is stated in words on the card it affects, in the same wordingopenspec-ui-cli readyalready uses.No graph library. What one would contribute is the part this puts in core; what it would cost is the part that matters. A card is a
<button>, already focusable and already named, which a canvas or an SVG-only drawing would each need given by hand — and this shell is held to WCAG AA by a browser suite that runs axe on every screen. The SVG carries only edges and isaria-hidden: what a line draws is already on the card it points from.Also
ChangeReadinessgains the blockers it declares (kept to those still active); the report carried them only for blocked changes, so the picture would have lost the relations of a running one.ALLOWED_TABS_VSCODE_EMBED), so it would have been an operation nothing could call.Verification
npm run verifyunpiped with everything staged: typecheck, lint and test all passed — cli 107, core 1092, extension 327, server 83, webui 389; 1998 in all.The whole browser suite, 16 of 16, including this tab's axe run at WCAG AA and a phone-width check that the page body never scrolls sideways. The two harness screenshots regenerated with it: the tab strip they show now carries this tab, so leaving them would have documented a screen that had stopped existing.
One task stays open — 6.5, delegated to
claude-cli: with two changes actually running in their own working directories, check that each card names the right author and the right state. The unit tests drive the view with a written report; only real runs show that the report a live workspace produces is the one the picture draws.🤖 Generated with Claude Code