Skip to content
GitHubRSS

src/orchestrator/framed-output.ts — Turbo-style framed blocks

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.

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 hit
export function formatTaskHitLine(node, outcome, colors?): string
// ` ⏺ <time> success miss <id>` — broad-mode executed task
export function formatTaskExecutedLine(node, outcome, colors?): string
// ` ⊘ <blank> skipped <id> • blocked by <id>` — a skip never ran
export 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?,
): string
export 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 task
export 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 body
export function formatPersistentTailBlock(node, outcome, body, dropped?, colors?): string
export 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[]
┌─ @vzn/vx#lint > success
$ oxlint --type-aware --type-check
├─ STDOUT ──────────────────────────────────────────────────
Found 0 warnings and 0 errors.
└─ @vzn/vx#lint ── (327ms) success

The 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.

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 lines
line 71
…
line 100
◼ app#dep — failed (exit 1)
(no output)
… and 2 more failed: app#f6, app#f7

One 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.

One vocabulary across every surface (one-liners, frames, summary, verbose table): success / restored-local / restored-remote / up-to-date / failed / skipped (outcomeWord, events.ts).

StatusHeaderFooter tag
successdim successdim 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: falsegreen up-to-date • <hash>dim up-to-date
failedbold 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
skippedyellow skipped (blocked by <id>), skippedLabel; bare skipped for a fail-fast skipsame

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).