feat(dashboard): batch the gates into one queue (DRC-4018) - #125
Merged
kentwelcome merged 3 commits intoAug 18, 2026
Conversation
Attention has a fixed cost per switch, so three separate pings cost more than one list of three. The blocked sessions were already a list on screen. What they were not was a queue: the band rendered them in session id order, which is an arbitrary order to be stopped in, and gave you nothing to act with — you read a session id off the screen by eye and went hunting for its terminal. Four changes, and none of them is new data. Order. Needs-input rows are published longest-blocked first. The gate that has held you up longest is the one still costing something and the one you are least likely to have remembered; the one that just opened is the one you already know about. It ranks on `blocked_since`, a fixed timestamp rather than an elapsed time, so the queue does not reshuffle under you while every row in it waits longer. Calm's `attention` ordering makes the same call for the same rows — its needs group was newest-first, which is right for a row you are watching and wrong for one waiting on you. `recent` is untouched and still genuinely newest-first for every state. Position. Rows are numbered from the top and the band head says how many are waiting, so the list reads as a queue and you can watch it shrink. A handle. Each row carries `copy id`, the control calm's rows already had, routed through the same action channel so there is one copy implementation rather than two that report success differently. A pass. `j`/`k` step the queue, Enter takes the row you are on, and `g` reaches the queue from either mode. Nothing marks a gate handled: Cargento does not write to a session, and a mark it did not measure would hide a gate that is still open. You answer in the terminal, the server stops calling the session blocked, the row leaves on the next refresh and the cursor inherits the new head. The queue clears itself out of measurements. One thing that is not interface. DRC-4015 found the per-row text reaches disk only sometimes. A row without it used to render an empty div — the same blank a row with nothing to add would leave — so the one row you cannot triage was also the one row that looked ordinary. It now says so, in both views. Scope note: D7 (DRC-4030) stays separate. This orders the gate queue; ranking working cards and the idle list against each other is a different question. Signed-off-by: Kent Huang <kent@infuseai.io>
Two of these were bugs, and both were bugs of the same kind: a binding that looked right in calm mode and was wrong in the view it was copied into. The regular view took Space and the arrow keys. Calm can bind those because its ledger scrolls inside its own frame under its own cursor; the regular view is an ordinary long page, so preventDefault there removed paging and line-scrolling — and removed them only while something was blocked, since the branch returns early on an empty queue. Scroll keys that work or not depending on the payload are worse than no bindings. `j`/`k` and Enter only, now. `g` into calm narrowed to the blocked rows without setting the ordering. From `recent` that renders the queue exactly backwards with the cursor parked on the last row; from `fastest` it files the gates under "not working now". SKILL.md asserted both modes show the same queue in the same order, which was true only under `attention`. It sets the ordering too, so the doc is now true as written. Also from the review: The band's `copy id` dropped keyboard focus on the render it triggered. Calm solves this with calmFocusKey/calmRestoreFocus; the regular path called neither, so activating the button from the keyboard sent focus to <body> and the next Tab restarted at the top of the document. Calm's `attention` ranked the queue on `waitSec`, the rendered elapsed time, which floors at zero. Two rows carrying implausibly future stamps would tie there while the server still separated them — the one way the two views could name a different gate at the head. It ranks on the raw timestamp now, as aggregate.py does. `gateCursorKey` outlived an emptied queue, so a session that blocked, was answered, and blocked again later inherited a cursor that belonged on whichever gate had waited longest. test_the_order_does_not_move_as_the_wait_lengthens could not fail: sorted() over one list with one key is deterministic by construction, and flipping the key's sign left it green. Replaced with an assertion on the key itself. Two gaps closed: no test clicked the band's copy button, so the data-calm reuse — the riskiest part — was only ever exercised through the keyboard, which does not go through the global listener; and nothing covered the focus hand-off. Both new tests were mutation-checked against the un-fixed code. Docs: the calm-mode controls paragraph had ended up inside the gate-queue section though it owns sorting, filtering and the `u` key, so it moves back under Display modes. The queue's key list said `⏎` copies, which is regular mode only — in calm it expands. The "nothing marks a gate handled" argument was being restated in five places; it moves to docs/design-needs-input.md as N-8, which already owns the needs-input reasoning, and the comments become pointers. Comment trimming throughout, on the same standard: the paragraph in main.js restated the three lines under it, and row_order carried an 18-line docstring over a 4-line function while leaving the one branch a reader would stop at — the last_activity fallback — unexplained. Signed-off-by: Kent Huang <kent@infuseai.io>
The module ownership table in the architecture doc did not say where the queue lives. It is split three ways on purpose: the queue itself is shared page state because both views read it, the band's cursor belongs to the regular view, and the listeners that route both views' controls have always been in calm.js and are the page's rather than calm's. A reader checking the inward-only rule against that table would have read the last one as drift. The N-8 entry added in the previous commit carried two em dashes, which the voice standard bans in docs/design-*.md. Nothing in CI checks that. The sync marker was stamped at 8f859f8 and four PRs have merged since: #121 through #124. Their docs are current, so this is the orphaned-marker failure the skill warns about rather than a real backlog, and the new marker is the merge base so the next squash cannot orphan it again. Signed-off-by: Kent Huang <kent@infuseai.io>
Contributor
CoverageThreshold: |
There was a problem hiding this comment.
Pull request overview
This PR implements DRC-4018 by turning “needs input” sessions into an explicit gate queue across both dashboard display modes, reducing attention cost and making blocked sessions actionable without introducing new data sources.
Changes:
- Server now publishes blocked sessions in a meaningful queue order (longest-blocked first) via a shared row sort key.
- Regular mode gains a numbered gate queue with a keyboard cursor (
j/k) and a per-rowcopy idhandle routed through the existing action channel. - Calm mode aligns its
attentionordering and addsgto jump into the gate queue consistently from either mode, including “not readable” messaging when detail is absent.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| docs/design-runtime-architecture.md | Updates module ownership notes to reflect gate queue responsibilities split across spark.js, regular.js, and global listeners. |
| docs/design-needs-input.md | Adds N-8 design rationale: queue is payload-driven, cursor is key-based, and no “handled” state is stored locally. |
| COMPATIBILITY.md | Updates docs sync marker. |
| cargento/skills/cargento/tests/test_page.py | Updates asset oracles and adds comprehensive regular-mode gate queue behavior tests (ordering, cursor, copy, focus retention). |
| cargento/skills/cargento/tests/test_page_calm.py | Adds calm-mode regression tests for attention ordering, unreadable detail labeling, and g jump behavior. |
| cargento/skills/cargento/tests/test_observation.py | Adds RowOrderTest covering the new aggregate.row_order semantics. |
| cargento/skills/cargento/tests/test_lifecycle.py | Updates expected assembled page size. |
| cargento/skills/cargento/SKILL.md | Documents the gate queue behavior, ordering, affordances, and keybindings in both modes. |
| cargento/skills/cargento/cargento_runtime/web/styles.css | Styles the gate queue as a numbered, cursor-highlightable list and adds “unreadable” styling for missing detail in both views. |
| cargento/skills/cargento/cargento_runtime/web/spark.js | Introduces gateQueue(d) and shared cursor state for regular-mode queue navigation. |
| cargento/skills/cargento/cargento_runtime/web/regular.js | Implements regular-mode gate queue cursor logic, numbering, copy button rendering, and unreadable-detail messaging. |
| cargento/skills/cargento/cargento_runtime/web/main.js | Switches needs-input derivation to gateQueue, adds band header count/keys, and restores focus/cursor after renders. |
| cargento/skills/cargento/cargento_runtime/web/calm.js | Aligns attention ordering for blocked rows, adds “not readable” doing-cell behavior, and implements g queue jump across modes. |
| cargento/skills/cargento/cargento_runtime/aggregate.py | Adds row_order() and applies it to publish sessions in queue order (state rank + blocked_since/last_activity + sid). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
kentwelcome
deleted the
feature/drc-4018-b7-batch-the-gates-into-one-queue
branch
August 18, 2026 00:34
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.
Implements DRC-4018 — B7 · Batch the gates into one queue. Unblocked by DRC-4015 (#124).
Attention has a fixed cost per switch, so three separate pings cost more than one list of three. The blocked sessions were already a list on screen. What they were not was a queue: the band rendered them in session id order — an arbitrary order to be stopped in — and gave you nothing to act with, so taking one meant reading a session id off the screen by eye and going hunting for its terminal.
No new data. All of this is interface over what the payload already carried, which is what the board note predicted.
What changed
Order. Needs-input rows are published longest-blocked first. The gate that has held you up longest is the one still costing something and the one you are least likely to have remembered; the one that just opened is the one you already know about. It ranks on
blocked_since, a fixed timestamp rather than an elapsed wait, so the queue does not reshuffle under you while every row in it waits longer. Calm'sattentionordering makes the same call for the same rows — its needs group was newest-first, which is right for a row you are watching and wrong for one waiting on you.recentis untouched.Position. Rows numbered from the top,
N waitingin the band head. The list reads as a queue and you can watch it shrink.A handle.
copy idper row, routed through the page's existing action channel so there is one clipboard implementation rather than two that report success differently.A pass.
j/kstep,⏎takes the row you are on,greaches the queue from either display mode. Nothing marks a gate handled — Cargento does not write to a session, so a mark would record only that you clicked something, and a wrong one would hide a gate that is still open, which is the exact failure this band exists to prevent. You answer in the terminal, the server stops calling the session blocked, the row leaves on the next refresh and the cursor inherits the new head. The queue clears itself out of measurements.One thing that is not interface. DRC-4015 found the per-row text reaches disk only sometimes. A row without it rendered an empty div — the same blank a row with nothing to add would leave — so the one row you cannot triage was also the one row that looked ordinary. It now says so, in both views.
Demo
The page in these shots is the shipped one — served through this branch's own
page.load_page(), with the row order coming from the realaggregate.row_order(). The gate rows are a fixture on/api/data: the queue only shows its shape with several sessions held at a gate at once, and a live store rarely has four. Four gates at 47m / 19m / 6m / 1m, deliberately built newest-first in the payload so the sort has visible work to do.The queue.
4 waitingin the band head, rows numbered from the top, longest-blocked first, acopy idhandle on each. Row 3 is the case DRC-4015 left behind: its detail never reached disk, so the row says so rather than leaving the blank that a row with nothing to add would leave.jsteps the cursor. Two presses, row 1 to row 3.⏎takes the row. The label readscopiedonly for a clipboard write the browser actually accepted — a refused write reportsblockedinstead.Calm mode, same queue, same order, and
not readableon the same row.The
gfix, shown in the state that broke it. Underrecentthe gates scatter through the board and the 47m one falls to the bottom. This is where the pre-fixgnarrowed without setting the ordering, rendering the queue backwards with the cursor on the last row.Pressing
gfrom there forces the ordering back toattention, narrows to the four gates, and lands the cursor on the head.Scope
D7 (DRC-4030) stays separate, by decision. This orders the gate queue; ranking working cards and the idle list against each other is a different question with different answers. B7 shrinks D7's remaining scope but does not close it.
Review
A subagent review found two real bugs, both the same kind — a binding that was right in calm mode and wrong in the view it was copied into:
j/kand⏎only, now.ginto calm narrowed to the blocked rows without setting the ordering, so fromrecentit rendered the queue backwards with the cursor on the last row.Also fixed from that review: the band's
copy iddropped keyboard focus on the render it triggered; calm ranked on the clampedwaitSecrather than the raw timestamp, the one way the two views could name a different head gate;gateCursorKeyoutlived an emptied queue; and one test could not fail (flipping the sort key's sign left it green). Both new regression tests were mutation-checked against the un-fixed code.Verification
Full canonical pre-PR suite, locally:
ruff check,ruff format --check,mypy --strict,scripts/lint_embedded.py,scripts/validate_plugins.py,claude plugin validate ./cargento --strictfail_underof 73/sync-docsrun; its commit is on this branchAlso driven in a real browser: the ordering in both views, the ordinals, the cursor and its clamping,
gfrom either mode, andcopy idreportingcopiedon a real click.🤖 Generated with Claude Code