src/orchestrator/framed-output.ts — Turbo-style framed blocks
Purpose
Section titled “Purpose”Format the per-task framed output block and the two compact one-liners (quiet cache hit, broad-mode executed). Pure functions; the logger calls them at the right moments.
There is no top-of-run header. The run banner — version, the
affected-projects bar, worker pool, cache mode — lives in the
footer (summary.ts’s RunContext), printed once at the end
where the eye lands. See docs/modules/summary.md.
Public surface
Section titled “Public surface”export interface TaskBlockBody { stdout?: string // rendered under `├─ STDOUT ──…` stderr?: string // rendered under `├─ STDERR ──…` droppedStdout?: number // chars a bounded (persistent) capture dropped from the head droppedStderr?: number}
export function formatTaskBlock( node: TaskNode, outcome: TaskOutcome, body: TaskBlockBody, colors?: ColorSupport, forceCommand?: boolean, // `$ cmd` even on a hit: a focused requested task's frame): string
// ` ⇢ <time> success local <id>` — quiet cache hitexport function formatTaskHitLine(node, outcome, colors?): string
// ` ⏺ <time> success miss <id>` — broad-mode executed taskexport function formatTaskExecutedLine(node, outcome, colors?): string// ` ⊘ <blank> skipped <id> • blocked by <id>` — a skip never ranexport function formatTaskSkippedLine(node, colors?, blockedBy?): string// the grid all three share: `<glyph> <time> <status> <cache> <id>`export function formatTaskRow( glyph, ms, status, statusColor, cache, cacheColor, paintedId, colors?,): stringexport const TIME_COL = 7 // the time cell's width// `project#task` halves in identity hues, hashed from `hueSource`export function paintIdParts(hueSource, projectText, taskText, colors, opts?): string
// Focused mode's live frame around a streamed taskexport function formatFrameOpen(node, colors?): string // `┌─ <id> > $ <cmd>`export function formatFrameClose(node, outcome, colors?): string// A held persistent task's output since ready; '' for an empty bodyexport function formatPersistentTailBlock(node, outcome, body, dropped?, colors?): stringexport function formatPersistentList(nodes, colors?): string[] // `▸ <id> running` rows
export interface RecapEntry { node: TaskNode outcome: TaskOutcome tail: RecapTail // failure-recap.ts droppedChars: number // what a persistent task's bounded capture dropped first}
// The run's last block: each failed task's last lines (item 706)export function formatFailureRecap( entries: readonly RecapEntry[], more: readonly string[], // ids of the failures past RECAP_TASKS colors?: ColorSupport, fence?: (lines: string[]) => string[], // wraps each tail's lines (Actions)): string[]Task block shape
Section titled “Task block shape”┌─ @vzn/vx#lint > success
$ oxlint --type-aware --type-check
├─ STDOUT ──────────────────────────────────────────────────
Found 0 warnings and 0 errors.
└─ @vzn/vx#lint ── (327ms) successThe block format is:
- Top line:
┌─ <task-id> > <status header> $ <command>line: executed tasks only (success and failed), dim, between blank lines, with no section label (the owner cut it); cache hits replay stored output and skip it, skips never ran├─ STDOUT ──…/├─ STDERR ──…sections: present only when the stream is non-empty after trim; a blank line above and below the content, and a head-dropped notice when the capture was bounded├─ SANDBOX VIOLATIONS (n)section: when present — unique lines, verbatim, with the header in error red. The buffered renderer and the live frame share one builder, so a focused run shows it too- Bottom line:
└─ <task-id> ── (<duration>) <status tag>
Section headers and corners render dim; ids keep identity coloring.
Content lines are raw — no │ border, no indent — so terminal
wrapping never collides with frame glyphs and copy/paste is clean
(owner feedback, 2026-06). The logger blank-line-delimits blocks on
both sides (the formatter stays pure — no trailing blank inside the
returned string beyond the final newline).
Group tasks (no exec) render empty string — they aren’t real tasks.
Failure recap
Section titled “Failure recap”formatFailureRecap renders the block run() prints last, after the
summary’s own sections (see failure-recap.md):
Failed: 2 tasks — the last lines each one printed
◼ app#fail — failed (exit 3) … 70 earlier linesline 71…line 100
◼ app#dep — failed (exit 1) (no output)
… and 2 more failed: app#f6, app#f7One row per tail: the failed glyph, the id, and failedLabel (a
timeout, a sandbox violation and a never-ready task read as they do on
the frame). The note says what was cut: whole lines above, bytes cut
from the start of the first line shown, and anything a persistent
task’s bounded capture had already dropped. The lines are raw, as a
frame’s are, colour codes included; fence wraps them where the text
could be read as something else. (no output) stands for a task that
printed nothing. The last line names, by id, the failures past the
tail limit.
Outcome vocabulary + colors
Section titled “Outcome vocabulary + colors”One vocabulary across every surface (one-liners, frames, summary,
verbose table): success / restored-local / restored-remote /
up-to-date / failed / skipped (outcomeWord, events.ts).
| Status | Header | Footer tag |
|---|---|---|
success | dim success | dim success |
cache-hit (restored) | green restored-local • <hash> | dim restored-local |
cache-hit-remote (restored) | cyan restored-remote • <hash> | dim restored-remote |
either hit with restored: false | green up-to-date • <hash> | dim up-to-date |
failed | bold red failed (exit N), failedLabel (+ , 128 + SIGKILL above 128; a timeout reads failed (timed out, exit 143); a persistent task that never became ready reads never ready: …; a sandboxed task’s violations are counted after) | same |
skipped | yellow skipped (blocked by <id>), skippedLabel; bare skipped for a fail-fast skip | same |
Duration formats: <1s → Nms, ≥1s → N.NNs.
tests/failure-recap.test.ts renders the recap through the logger.
tests/framed-output.test.ts:
- Block shape per status (cache-hit, success, failed, skipped, remote, up-to-date, sandbox violations).
- Group task elision.
- Color on/off (assertions strip ANSI when colors off).