Skip to content

feat(run): minimalist narration-style output for roar run#66

Merged
TrevorBasinger merged 14 commits intomainfrom
cg/run-ux
Apr 21, 2026
Merged

feat(run): minimalist narration-style output for roar run#66
TrevorBasinger merged 14 commits intomainfrom
cg/run-ux

Conversation

@christophergeyer
Copy link
Copy Markdown
Member

Summary

Replaces the boxed "ROAR Run Complete" summary with a compact, narration-style output where every status line is voiced by 🦖 and lineage details follow in a ·-prefixed block.

Before:

============================================================
ROAR Run Complete
============================================================
Command: python train.py
Duration: 0.9s
Exit code: 0

Read files:
  input.txt
    size: 36B  blake3: 686e9ad52674...

Written files:
  output.json
    size: 151B  blake3: 38dab41bcc89...

Job: 631f4d81

Next:
  roar show --job 631f4d81
  roar dag

After:

🦖 tracing · tracer:preload proxy:off sync:off
🦖 trace done · 0.9s · exit 0
🦖 hashed 3 artifacts · 204.9 MB/s
🦖 lineage captured:
·  i/o  2 inputs · 1 output
·  job  f3fba717
·  git  main @ 10c570b · clean
·  env  9 pip · 10 dpkg · 3 vars
·  dag  4 jobs · 1 artifact · depth 2
·
·  $ roar show --job f3fba717    # details
🦖 done · trace 0.9s + post 0.6s

Key design decisions

  • 🦖 is the consistent narrator — one emoji, all status lines. No 🫆/🧬 (tmux compat issues with newer emoji).
  • Tracer resolved upfront — reads config tracer.default, shows actual backend (preload/ebpf/ptrace) not "auto".
  • Separate trace vs post timing — makes roar's overhead visible.
  • Detail block uses 3-char labels (i/o, job, git, env, dag) for scanability.
  • Three output modes: rich (TTY+emoji), plain (TTY, no emoji → roar: prefix), pipe (no TTY → single done line to stderr).
  • Color tokens centralized in terminal.py: status_green (refactor: Restructure to support multiple backends and integrations #35), warn_amber (#172), command_blue (#74), dim, bold. No raw ANSI outside the style module.
  • Hashes carry no hue — weight only: bold (current job), dim (context).
  • Hashing spinner transient, skipped for < 5 artifacts.
  • Singular/plural throughout (1 artifact, 1 job, 1 var).

Files changed (9)

File Change
roar/presenters/run_report.py Full rewrite — narration-style presenter
roar/presenters/terminal.py New — centralized TTY/color/emoji detection, ANSI helpers
roar/presenters/spinner.py Clock emoji frames, counter API, prefix support
roar/core/models/run.py RunResult/TracerResult gain UX metadata fields
roar/execution/runtime/coordinator.py Lifecycle events, git/DAG/env collection, parent job lookup
roar/execution/runtime/tracer.py backend field on TracerResult
roar/application/run/execution.py Wire new presenter
tests/unit/test_run_report.py 16 tests: narration output, modes, colors
tests/unit/test_terminal_caps.py New — 9 tests for terminal detection

Test plan

  • 661 unit tests pass (25 new + all existing)
  • Lint clean (ruff check + ruff format)
  • Manual: TTY output matches target format
  • Manual: pipe mode emits single done line to stderr
  • Manual: roar tracer ptraceroar run shows tracer:ptrace
  • Manual: dirty git → amber; clean → green
  • Rebased on latest main, no conflicts

🤖 Generated with Claude Code

chrisgeyertreqs and others added 14 commits April 17, 2026 19:44
… summary

Replaces the old boxed "ROAR Run Complete" summary with a streamed
lifecycle of brief, prefixed lines followed by a three-column inputs /
job / outputs block.

Lifecycle (TTY, emoji-capable):
  🦖 tracing with preload (proxy off)
  [user command output streams here]
  🦖 trace done · 0.9s · exit 0
  🦖 hashing (3 artifacts) 🕐
  🦖 lineage captured

  · Inputs (1)       Job  4bce5669        Outputs (2)
  · input.txt    →   9 pip pkgs       →   input.txt
  ·                  10 dpkg pkgs         output.json
  ·                  2 env vars
  ·
  · roar show --job 4bce5669    roar dag

  🦖 done · 1.4s (trace 0.9s + post 0.5s)

Design points:
* 🦖 prefix on lifecycle lines (roar: fallback without emoji) — reading
  down the left margin shows exactly what roar did.
* Middle-dot · prefix on summary lines (quieter than 🦖 on every line)
  keeps the block visually grouped.
* Arrow between columns only on the first data row — flow direction
  shown once, not on every row.
* Trace duration and post-processing duration reported separately so
  roar's overhead is visible.
* All lifecycle output goes to stderr; user command stdout stays clean
  for piping.
* Three modes:
  - Rich (TTY + emoji): full output with color, spinner, emoji
  - Plain (TTY, no emoji): same but with `roar:` prefix and braille spinner
  - Pipe (no TTY): single `roar: done · ... (exit 0)` line only

Respects NO_COLOR env var.

Plumbs new data through RunResult: backend, post_duration, proxy_active,
pip_count, dpkg_count, env_count. TracerResult gains `backend`.

Supplemental changes:
* roar/presenters/terminal.py (new): centralized TTY/color/emoji/width
  detection, replacing ad-hoc checks scattered across presenters.
* roar/presenters/spinner.py: clock-emoji frame set + optional total
  count, with braille fallback. (Live counter advance plumbed but not
  yet wired end-to-end — current label shows the static total honestly.)

Test plan: 16 new unit tests cover all three modes, interrupted runs
(suggest `roar pop`), summary column layout, truncation, and the legacy
one-shot show_report path. Full suite 646 passed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds thin vertical rules (│, | without UTF-8) between the three columns
so the Inputs/Job/Outputs grouping is unmistakable. Previously the
columns relied on whitespace alone, which made subsequent rows look
disconnected from their headers.

Also splits the "next steps" line into two: `roar show --job <id>`
on one line, `roar dag` (or `roar pop` for interrupted runs) on the
next. Easier to pick out and copy.

The vertical rules are dim, so they anchor visually without competing
with the content. The first-row arrow (→ or >) sits just past the rule
on its way into the destination column. Gutter width grew from 4 to 5
chars to accommodate the rule.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major iteration on the run output design per user feedback:

Lifecycle changes:
- trace_starting shows tracer/proxy/sync params in compact format
- New "hashed N artifacts · XX.XMB/s" throughput line after hashing
- "lineage captured:" with colon (introduces the block below)
- "done" omits total time, shows only "(trace Xs + post Ys)"

Summary block redesign:
- Removed vertical rule column separators; use color instead (job
  UID in yellow/bold to distinguish from input/output columns)
- Added [Parent] sub-column to inputs showing producing job UID
  (currently shows "--" placeholder — parent lookup not yet plumbed)
- Added "git:" line showing branch @ short-commit + clean/dirty
- Added "env:" compact summary (N pip • N dpkg • N vars)
- Added "Inspect:" section with blue commands + dim "#" comments
- roar show and roar dag on separate lines under "Inspect:" label

Data plumbing:
- RunResult gains: git_branch, git_short_commit, git_clean,
  total_hash_bytes, hash_duration
- Coordinator collects git info via subprocess (best-effort)
- Coordinator computes hash throughput from output sizes + timing

Stubs/follow-ups:
- Parent job UID lookup (needs DB query per input artifact)
- DAG summary line (needs session-level aggregate query)
- Live m/n hashing counter (needs hook into record_job)
- sync: always "off" (future capability)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds two missing pieces to the run summary block:

1. Short artifact hashes — each input and output now shows its primary
   hash digest (blake3:a1b2c3d4…) on a dim sub-line beneath the path.

2. DAG summary line — "DAG: N jobs • M artifacts • depth D" computed
   from the session's dependency graph via DagDataBuilder. Depth is
   the longest root-to-leaf chain. Best-effort; never fails the run.

Plumbs dag_jobs, dag_artifacts, dag_depth through RunResult.
Coordinator computes them after job recording using an additional
read-only DB context.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary block changes per review:
- Hashes on same line as filename (8-char digest, no algorithm prefix)
- No brackets on Parent column
- Color-coded columns: inputs=cyan, job=yellow, outputs=green,
  parent=dim gray. Headers use same color as their column but dim.
  Hashes and job UIDs are dim, not bright.
- Parent job UID now populated from DB: each input artifact's
  producing job is looked up via artifacts.get_jobs(). Shows "--"
  when no producing job is found.
- Filenames always shown (truncated with … when needed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the layout from roar_run_output_redesign.md:

- Five distinct sections (Inputs, Job, Outputs, DAG, Inspect) with
  bold green headers and indented data rows, replacing the three-column
  table.
- Emoji progression: 🦖 → 🦖 → 🫆 → 🧬 for the lifecycle phases.
- exit code appears before duration in the trace-done line.
- Color tokens: status_green (256-color #35), command_blue (#74),
  dim, bold. No raw ANSI codes outside terminal.py.
- Hashes carry no hue — weight only: bold (current job), regular
  (artifact digests), dim (source-job hashes).
- "Source Job" replaces "Parent" in Inputs section.
- Job section is key-value: id (bold), git, env.
- Singular/plural: "1 artifact", "1 job", "1 var", etc.
- 2-space section indent, 4-space row indent.
- Column headers on section-header line, dim, aligned with data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The trace_starting line shows the requested mode (often "auto").
The trace_ended line now appends the actual backend that was used
in dim brackets: `🦖 trace done [preload]  exit 0 · 0.9s`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Status-line values (tracer:auto, MB/s, etc.) now start at the same
  column as the Hash column in Inputs/Outputs sections.
- Path column is variable-width: sized to the longest filename in each
  section (min 14, max 30, default 20). Short filenames no longer waste
  space; long ones get room.
- Section column headers (Hash, Source Job) aligned with data below.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All three zones now start at the same column (_VALUE_COL = 24):
- Status-line values (tracer:auto, MB/s, timing)
- Section column headers (Hash, Source Job)
- Data-row hashes

Previously the section headers used a path_w-relative offset that
drifted from the status-line padding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use 🦖 for all status lines. Drops 🫆 (Emoji 15.1, 2024 — broken
  in tmux and older terminals) and 🧬. Consistent branding, no compat
  issues.
- Account for emoji being 2 display cells in padding calculation so
  status values align correctly regardless of emoji/non-emoji mode.
- Align # comments in Inspect section at the same column by padding
  commands to uniform width.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the section-based layout with a compact, git-like narration
where every line is voiced by 🦖 or prefixed with · (detail block).

Format:
  🦖 tracing · tracer:preload proxy:off sync:off
  🦖 trace done [preload] · 0.9s · exit 0
  🦖 hashed 5 artifacts · 204.9 MB/s
  🦖 lineage captured:
  ·  i/o  2 inputs ← 1 prior job · 3 outputs
  ·  job  32156d79
  ·  git  master @ efc9a23 · clean
  ·  env  9 pip · 10 dpkg · 2 vars
  ·  dag  1 job · 3 artifacts · depth 1
  ·
  ·  $ roar show --job 32156d79    # details
  🦖 done · trace 0.9s + post 0.6s

Key changes from v7:
- No section headers, column headers, or per-artifact rows
- Summary is counts only: i/o (with prior-job count), job (bold),
  git, env, dag — each on a · detail line with 3-char label
- Hashing spinner is transient (skipped for < 5 artifacts)
- Suggested command uses $ prefix ("paste this")
- pip/dpkg stay uncountable ("9 pip" not "9 pips")
- warn_amber (256 #172) for dirty git and non-zero exit
- · (middle dot U+00B7) as sole separator everywhere

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Resolve the actual tracer backend (preload/ebpf/ptrace) before
  execution by calling _get_tracer_candidates() early. The
  trace_starting line now shows the real backend instead of "auto".
- Remove [backend] suffix from trace_done line — redundant now that
  trace_starting already shows it.
- Remove "← N prior jobs" from the i/o detail line. Just show input
  and output counts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The early backend resolution was hardcoding "auto" when no CLI --tracer
flag was given, ignoring the config's tracer.default setting. Now
mirrors execute()'s resolution: config default → CLI override → auto
fallback. `roar tracer ptrace` followed by `roar run` now correctly
shows tracer:ptrace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove unused symbols left over from the v5→v8 iteration:
- _HASH_W, _pad, _visible_len, format_size (run_report.py)
- Unused `re` and `os` imports
- Duplicate total_hash_bytes / hash_duration computation in
  coordinator (computed once now, used for both the hashed line
  and RunResult)

No behavior change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@TrevorBasinger TrevorBasinger merged commit 41d0aac into main Apr 21, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants