feat(tui): add workflow Navigator and call inspector - #152
Conversation
📝 WalkthroughWalkthroughThe PR adds workflow phase, call, activity, usage, and inspection data. It replaces the single-screen workflow TUI with stateful navigation, call inspectors, refresh reconciliation, responsive rendering, and documented CLI behavior. ChangesWorkflow Navigator
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant workflow-tui.ts
participant workflow-view.ts
User->>workflow-tui.ts: Select workflow, phase, or call
workflow-tui.ts->>workflow-view.ts: Read workflow and call inspection data
workflow-view.ts-->>workflow-tui.ts: Return phases, calls, usage, and metadata
workflow-tui.ts-->>User: Render navigator or inspector screen
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Greptile SummaryThe PR expands
Confidence Score: 1/5This PR should not merge until navigation preserves run identity, exposes unphased calls, and renders provider content as inert terminal text. Periodic refresh can silently retarget the inspector to another run, valid unphased calls become inaccessible when phases exist, and raw model output reaches a terminal-control sink. Files Needing Attention: src/workflow-tui.ts
|
| Filename | Overview |
|---|---|
| src/workflow-tui.ts | Introduces the navigator and inspector, but loses unphased calls, reconciles refreshes by unstable indices, and renders untrusted terminal content without escaping. |
| src/workflow-view.ts | Adds declared-phase status, call inspection fields, and replay-excluding token totals; its separate unphased call collection is not represented by the navigator. |
| src/workflow-tui.test.ts | Covers primary navigation and narrow rendering but omits mixed phased/unphased runs, refresh reordering, and control-character rendering. |
| scripts/workflow-tui-fixture.ts | Enriches TUI fixtures with declared phases, provider sessions, activity, and partial/final usage snapshots. |
| docs/dynamic-workflows.md | Documents the navigator controls, inspector data, and best-effort token semantics. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Store[(Workflow Store)] --> View[Workflow Project View]
View --> List[Workflow List]
List --> Navigator[Phase and Call Navigator]
Navigator --> Inspector[Call Inspector]
Inspector --> Activity[Activity]
Inspector --> Prompt[Prompt]
Inspector --> Result[Result]
Inspector --> Files[Files]
Inspector --> Metadata[Metadata]
Timer[750 ms Refresh] --> View
Reviews (1): Last reviewed commit: "test(tui): seed navigator observability ..." | Re-trigger Greptile
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/workflow-tui.ts (1)
71-90: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winHandle failures inside
renderso the terminal is restored.
rendercallsload()andprocess.stdout.writewithout error handling.renderruns fromsetInterval, from thekeypresshandler, and from theresizehandler. Ifload()throws, for example on a transient store read error, the rejection escapes as an uncaught exception. The process then exits while raw mode, the hidden cursor, and the alternate screen buffer are still active, becausefinish()never runs. The user is left with a broken terminal.Catch the error inside
renderand either show it in the frame or run the cleanup path.🛠️ Proposed fix: render a load error instead of throwing
let closed = false; let rendering = false; + let lastError: string | undefined; const render = (): void => { if (rendering || closed) return; rendering = true; try { const previousProject = project; - project = load(state.screen !== "workflows"); - state = reconcileWorkflowTuiState(previousProject, project, state); + try { + project = load(state.screen !== "workflows"); + state = reconcileWorkflowTuiState(previousProject, project, state); + lastError = undefined; + } catch (error) { + lastError = error instanceof Error ? error.message : String(error); + } process.stdout.write( `\u001b[H\u001b[2J${renderWorkflowTui( project, state, process.stdout.columns || 100, process.stdout.rows || 40, { ansi: true, activity: activityForState() }, - )}`, + )}${lastError ? `\nRefresh failed: ${lastError}` : ""}`, ); } finally { rendering = false; } };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/workflow-tui.ts` around lines 71 - 90, Update the render function to catch failures from load, reconcileWorkflowTuiState, rendering, or process.stdout.write, ensuring they do not escape interval or event-handler callbacks; render an appropriate error frame or invoke the existing finish cleanup path, while always resetting rendering and restoring the terminal on failure.
🧹 Nitpick comments (2)
src/workflow-tui.ts (1)
209-210: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winSanitize only the strings that reach the frame.
sanitizeTerminalValue(project)deep-copies and regex-scans the whole project view on every frame. The view holds up to 50 runs, and each call carriesprompt,responseText,structuredJson, andreturnValueJson. The refresh interval is 750 ms, so large prompts and results are rescanned about 1.3 times per second while only a few lines are displayed.Apply
sanitizeTerminalValueinsidetruncate, or to each string as it is composed into a line. The security property stays the same and the cost scales with the visible output.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/workflow-tui.ts` around lines 209 - 210, Update the frame-rendering flow around project and activity formatting so sanitizeTerminalValue is applied only to strings emitted into visible lines, such as inside truncate or during line composition, rather than to the entire project view before rendering. Preserve sanitization for every displayed string while avoiding repeated scans of full run data on each refresh.src/workflow-tui.test.ts (1)
42-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd fixture coverage for unavailable tokens and replayed calls.
Every call in the fixture is non-cached, and the only call with a
usagefield is the running one. Two documented behaviors are therefore untested:
docs/dynamic-workflows.mdLine 77 promises that a provider that cannot report tokens stays visibly unavailable. No call omitsusage, so the—path incallRowsand theunavailablepath ininspectorBodyare never rendered in a test.docs/dynamic-workflows.mdLine 78 describes replayed calls. No call setsfromCache: true, socallElapsedLabelnever returnsreplayed.Add one call without
usageand one withfromCache: true, then assert the rendered markers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/workflow-tui.test.ts` around lines 42 - 75, Extend the workflow TUI fixture in the relevant test setup with one call that omits usage and one call marked fromCache: true, while preserving the existing calls. Update the assertions covering callRows and inspectorBody to verify the unavailable token markers, and assert that callElapsedLabel renders the replayed marker for the cached call.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/dynamic-workflows.md`:
- Around line 67-72: Update the workflow TUI documentation to describe both
entry points: `workflow tui` opens the workflow list, while `devspace workflow
tui [run-id]` opens the specified run directly. Clarify that phases and calls
use a two-pane layout only at terminal widths of at least 80 columns; narrower
terminals show one pane at a time, with `Tab` switching panes. Explain that the
synthetic `Other` phase groups calls without a declared phase.
In `@src/workflow-tui.test.ts`:
- Around line 122-123: Update timeLabel to use an explicit locale and timeZone
so activity timestamps are deterministic across environments, then extend the
rendered activity assertion in the workflow TUI test to verify the leading
timestamp format alongside the existing activity text.
- Line 162: Make the assertion around resolveWorkflowTuiWorkspaceRoot
deterministic by isolating it from ambient environment and repository context:
create a temporary directory outside the project, invoke the resolver with that
directory as the working directory, and temporarily ensure
DEVSPACE_WORKSPACE_ROOT is unset while restoring the environment afterward. Use
the existing resolver symbols and required filesystem, OS, and path utilities
rather than asserting against process.cwd().
In `@src/workflow-tui.ts`:
- Around line 246-247: Update renderNavigator at src/workflow-tui.ts#L246-L247
and renderCallInspector at src/workflow-tui.ts#L282-L284 so their
unavailable-resource returns include rule(width) followed by the muted “Esc back
· q quit” footer, matching the normal render paths while preserving each
existing message.
- Around line 197-199: Update the down-scroll handling in the reducer around the
up/down key branch to cap state.scroll at the inspector body length, using the
available state/call/activity context or passing the body length into the
reducer. Preserve the existing lower bound for upward scrolling and ensure
scrolling past the final inspector line never produces an empty view.
- Around line 461-463: Update fitRows to preserve the trailing footer lines when
lines exceeds the available rows, reserving space for the footer while retaining
the existing output limit and minimum-row behavior. Ensure the call inspector’s
appended key-hint footer remains visible during overflow, while non-overflowing
content is unchanged.
- Around line 107-110: Update the onKeypress handler to safely handle an
undefined key argument before accessing key.ctrl or key.name; return without
processing when key is absent, while preserving the existing finish, state
reduction, and render behavior for valid key events.
---
Outside diff comments:
In `@src/workflow-tui.ts`:
- Around line 71-90: Update the render function to catch failures from load,
reconcileWorkflowTuiState, rendering, or process.stdout.write, ensuring they do
not escape interval or event-handler callbacks; render an appropriate error
frame or invoke the existing finish cleanup path, while always resetting
rendering and restoring the terminal on failure.
---
Nitpick comments:
In `@src/workflow-tui.test.ts`:
- Around line 42-75: Extend the workflow TUI fixture in the relevant test setup
with one call that omits usage and one call marked fromCache: true, while
preserving the existing calls. Update the assertions covering callRows and
inspectorBody to verify the unavailable token markers, and assert that
callElapsedLabel renders the replayed marker for the cached call.
In `@src/workflow-tui.ts`:
- Around line 209-210: Update the frame-rendering flow around project and
activity formatting so sanitizeTerminalValue is applied only to strings emitted
into visible lines, such as inside truncate or during line composition, rather
than to the entire project view before rendering. Preserve sanitization for
every displayed string while avoiding repeated scans of full run data on each
refresh.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f5c60d9b-813e-4431-98eb-34901dafaa61
📒 Files selected for processing (6)
docs/dynamic-workflows.mdscripts/workflow-tui-fixture.tssrc/workflow-tui.test.tssrc/workflow-tui.tssrc/workflow-view.test.tssrc/workflow-view.ts
| `workflow tui` opens a project-scoped, read-only Navigator. The first screen | ||
| lists workflows. Opening a run shows its declared phases beside the agent calls | ||
| in the selected phase; opening a call exposes normalized activity, prompt, | ||
| result, worktree details, and provider metadata. Use arrow keys (or `j`/`k`) to | ||
| navigate, `Tab` to switch panes or inspector sections, `Enter` to open, `Esc` | ||
| to go back, and `q` to quit. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Three Navigator behaviors are missing or contradicted here.
- Line 67-68 states that the first screen lists workflows. Line 54 documents
devspace workflow tui [run-id]. When the user suppliesrun-id,createWorkflowTuiStatereturns theworkflowscreen directly and skips the list. State the two entry points. - Line 68-69 describes phases shown beside the calls.
renderNavigatoruses that two-pane layout only when the terminal is at least 80 columns wide. Below 80 columns it shows one pane at a time andTabswitches between them. Document the narrow layout. navigatorPhasesappends a synthetic phase namedOtherthat holds every call without a declared phase. The PR objectives describe this grouping, but this section does not. A user who seesOtherbeside the declared phases has no explanation for it.
📝 Proposed wording
-`workflow tui` opens a project-scoped, read-only Navigator. The first screen
-lists workflows. Opening a run shows its declared phases beside the agent calls
-in the selected phase; opening a call exposes normalized activity, prompt,
-result, worktree details, and provider metadata. Use arrow keys (or `j`/`k`) to
-navigate, `Tab` to switch panes or inspector sections, `Enter` to open, `Esc`
-to go back, and `q` to quit.
+`workflow tui` opens a project-scoped, read-only Navigator. Without a run id it
+starts on the workflow list. With a run id it opens that run directly, and the
+run must belong to the current project. Opening a run shows its declared phases
+beside the agent calls in the selected phase. Calls that declare no phase are
+grouped under a synthetic `Other` phase. Opening a call exposes normalized
+activity, prompt, result, worktree details, and provider metadata. Use arrow
+keys (or `j`/`k`) to navigate, `Tab` to switch panes or inspector sections,
+`Enter` to open, `Esc` to go back, and `q` to quit.
+
+Terminals narrower than 80 columns show one pane at a time. `Tab` then switches
+between the phase pane and the agent pane.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| `workflow tui` opens a project-scoped, read-only Navigator. The first screen | |
| lists workflows. Opening a run shows its declared phases beside the agent calls | |
| in the selected phase; opening a call exposes normalized activity, prompt, | |
| result, worktree details, and provider metadata. Use arrow keys (or `j`/`k`) to | |
| navigate, `Tab` to switch panes or inspector sections, `Enter` to open, `Esc` | |
| to go back, and `q` to quit. | |
| `workflow tui` opens a project-scoped, read-only Navigator. Without a run id it | |
| starts on the workflow list. With a run id it opens that run directly, and the | |
| run must belong to the current project. Opening a run shows its declared phases | |
| beside the agent calls in the selected phase. Calls that declare no phase are | |
| grouped under a synthetic `Other` phase. Opening a call exposes normalized | |
| activity, prompt, result, worktree details, and provider metadata. Use arrow | |
| keys (or `j`/`k`) to navigate, `Tab` to switch panes or inspector sections, | |
| `Enter` to open, `Esc` to go back, and `q` to quit. | |
| Terminals narrower than 80 columns show one pane at a time. `Tab` then switches | |
| between the phase pane and the agent pane. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/dynamic-workflows.md` around lines 67 - 72, Update the workflow TUI
documentation to describe both entry points: `workflow tui` opens the workflow
list, while `devspace workflow tui [run-id]` opens the specified run directly.
Clarify that phases and calls use a two-pane layout only at terminal widths of
at least 80 columns; narrower terminals show one pane at a time, with `Tab`
switching panes. Explain that the synthetic `Other` phase groups calls without a
declared phase.
| assert.match(rendered, /Workflow › Implementation › Patch auth/); | ||
| assert.match(rendered, /tool\s+bash · npm test/); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Assert the activity timestamp, and pin the locale in timeLabel.
Line 123 asserts only the kind, label, and detail of the activity row. It does not assert the leading timestamp. timeLabel in src/workflow-tui.ts Line 450 calls toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }). It passes no locale and no timeZone, so the rendered value depends on the host ICU locale and the TZ environment variable. Two developers running the same fixture see different activity rows.
Pin the format in timeLabel, then assert it here. The root cause is in src/workflow-tui.ts Line 449-451.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/workflow-tui.test.ts` around lines 122 - 123, Update timeLabel to use an
explicit locale and timeZone so activity timestamps are deterministic across
environments, then extend the rendered activity assertion in the workflow TUI
test to verify the leading timestamp format alongside the existing activity
text.
| assert.match(narrow, /PHASES/); | ||
| assert.doesNotMatch(narrow, /AGENTS · Implementation/); | ||
|
|
||
| assert.equal(resolveWorkflowTuiWorkspaceRoot(process.cwd()), process.cwd()); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
This assertion depends on the ambient environment and can fail in CI.
resolveWorkflowTuiWorkspaceRoot delegates to resolveCliWorkspaceContext(process.env, cwd). That function returns resolve(DEVSPACE_WORKSPACE_ROOT) when the variable is set, and otherwise findDevspaceProjectRoot(cwd, env, gitRoot) ?? gitRoot ?? resolve(cwd). The process.cwd() fallback is the last of four branches.
Two environments break the assertion:
- A developer or CI job that exports
DEVSPACE_WORKSPACE_ROOTgets the injected root. - A test runner invoked from a subdirectory of the repository gets the Git root.
Isolate the environment, or assert the delegation instead of the value.
🛠️ Proposed fix: isolate the environment for the assertion
-assert.equal(resolveWorkflowTuiWorkspaceRoot(process.cwd()), process.cwd());
+const previousRoot = process.env.DEVSPACE_WORKSPACE_ROOT;
+const isolated = mkdtempSync(join(tmpdir(), "devspace-tui-"));
+process.env.DEVSPACE_WORKSPACE_ROOT = isolated;
+try {
+ assert.equal(resolveWorkflowTuiWorkspaceRoot(isolated), resolve(isolated));
+} finally {
+ if (previousRoot === undefined) delete process.env.DEVSPACE_WORKSPACE_ROOT;
+ else process.env.DEVSPACE_WORKSPACE_ROOT = previousRoot;
+}Add the supporting imports:
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| assert.equal(resolveWorkflowTuiWorkspaceRoot(process.cwd()), process.cwd()); | |
| const previousRoot = process.env.DEVSPACE_WORKSPACE_ROOT; | |
| const isolated = mkdtempSync(join(tmpdir(), "devspace-tui-")); | |
| process.env.DEVSPACE_WORKSPACE_ROOT = isolated; | |
| try { | |
| assert.equal(resolveWorkflowTuiWorkspaceRoot(isolated), resolve(isolated)); | |
| } finally { | |
| if (previousRoot === undefined) delete process.env.DEVSPACE_WORKSPACE_ROOT; | |
| else process.env.DEVSPACE_WORKSPACE_ROOT = previousRoot; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/workflow-tui.test.ts` at line 162, Make the assertion around
resolveWorkflowTuiWorkspaceRoot deterministic by isolating it from ambient
environment and repository context: create a temporary directory outside the
project, invoke the resolver with that directory as the working directory, and
temporarily ensure DEVSPACE_WORKSPACE_ROOT is unset while restoring the
environment afterward. Use the existing resolver symbols and required
filesystem, OS, and path utilities rather than asserting against process.cwd().
| const onKeypress = (_input: string, key: { name?: string; ctrl?: boolean }): void => { | ||
| if ((key.ctrl && key.name === "c") || key.name === "q") return finish(); | ||
| state = reduceWorkflowTuiState(project, state, key.name ?? ""); | ||
| render(); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Guard against an undefined key argument.
readline emits keypress(str, key) and key can be undefined for some input sequences. key.ctrl then throws a TypeError inside the listener. The process exits with raw mode and the alternate screen buffer still active, because finish() never runs.
🛡️ Proposed guard
- const onKeypress = (_input: string, key: { name?: string; ctrl?: boolean }): void => {
- if ((key.ctrl && key.name === "c") || key.name === "q") return finish();
- state = reduceWorkflowTuiState(project, state, key.name ?? "");
+ const onKeypress = (_input: string, key?: { name?: string; ctrl?: boolean }): void => {
+ if ((key?.ctrl && key.name === "c") || key?.name === "q") return finish();
+ state = reduceWorkflowTuiState(project, state, key?.name ?? "");
render();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const onKeypress = (_input: string, key: { name?: string; ctrl?: boolean }): void => { | |
| if ((key.ctrl && key.name === "c") || key.name === "q") return finish(); | |
| state = reduceWorkflowTuiState(project, state, key.name ?? ""); | |
| render(); | |
| const onKeypress = (_input: string, key?: { name?: string; ctrl?: boolean }): void => { | |
| if ((key?.ctrl && key.name === "c") || key?.name === "q") return finish(); | |
| state = reduceWorkflowTuiState(project, state, key?.name ?? ""); | |
| render(); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/workflow-tui.ts` around lines 107 - 110, Update the onKeypress handler to
safely handle an undefined key argument before accessing key.ctrl or key.name;
return without processing when key is absent, while preserving the existing
finish, state reduction, and render behavior for valid key events.
| if (key === "up" || key === "k") return { ...state, scroll: Math.max(0, state.scroll - 1) }; | ||
| if (key === "down" || key === "j") return { ...state, scroll: state.scroll + 1 }; | ||
| return state; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Clamp the inspector scroll to the body length.
Line 198 increments scroll without an upper bound. renderCallInspector calls inspectorBody(...).slice(state.scroll), so once scroll passes the body length the inspector shows an empty body. The user must then press up the same number of times to see content again. The reducer has no access to the rendered body height, so pass the body length or clamp against inspectorBody(state.tab, call, activity).length.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/workflow-tui.ts` around lines 197 - 199, Update the down-scroll handling
in the reducer around the up/down key branch to cap state.scroll at the
inspector body length, using the available state/call/activity context or
passing the body length into the reducer. Preserve the existing lower bound for
upward scrolling and ensure scrolling past the final inspector line never
produces an empty view.
| const run = project.runs[state.runIndex]; | ||
| if (!run) return ["Workflow is no longer available."]; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
The unavailable-resource frames omit the key-hint footer. Both renderers return early with a single message line and skip the rule(width) plus footer that their normal paths append. Escape is no longer a global quit key, so these frames show the user no way to go back or to quit. q still works, but nothing on screen says so. A run or a call can disappear between two 750 ms refreshes, so these frames are reachable in normal use.
src/workflow-tui.ts#L246-L247: inrenderNavigator, appendrule(width)and the mutedEsc back · q quitfooter to the"Workflow is no longer available."return.src/workflow-tui.ts#L282-L284: inrenderCallInspector, appendrule(width)and the mutedEsc back · q quitfooter to the"Agent call is no longer available."return.
📍 Affects 1 file
src/workflow-tui.ts#L246-L247(this comment)src/workflow-tui.ts#L282-L284
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/workflow-tui.ts` around lines 246 - 247, Update renderNavigator at
src/workflow-tui.ts#L246-L247 and renderCallInspector at
src/workflow-tui.ts#L282-L284 so their unavailable-resource returns include
rule(width) followed by the muted “Esc back · q quit” footer, matching the
normal render paths while preserving each existing message.
| function fitRows(lines: string[], rows: number): string[] { | ||
| return rows > 0 && lines.length > rows ? lines.slice(0, Math.max(1, rows)) : lines; | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
fitRows drops the footer when content overflows the terminal.
fitRows keeps the first rows lines. Every renderer appends the key-hint footer last. If the call inspector body is longer than the terminal height, the footer is cut, so Tab next section · ↑/↓ scroll · Esc back · q quit disappears exactly when the user needs to scroll. Reserve the trailing footer lines instead of slicing from the top.
🛠️ Proposed fix: keep the trailing footer lines
-function fitRows(lines: string[], rows: number): string[] {
- return rows > 0 && lines.length > rows ? lines.slice(0, Math.max(1, rows)) : lines;
+function fitRows(lines: string[], rows: number, reserved = 2): string[] {
+ if (rows <= 0 || lines.length <= rows) return lines;
+ const keep = Math.max(1, rows);
+ if (keep <= reserved) return lines.slice(-keep);
+ return [...lines.slice(0, keep - reserved), ...lines.slice(-reserved)];
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function fitRows(lines: string[], rows: number): string[] { | |
| return rows > 0 && lines.length > rows ? lines.slice(0, Math.max(1, rows)) : lines; | |
| } | |
| function fitRows(lines: string[], rows: number, reserved = 2): string[] { | |
| if (rows <= 0 || lines.length <= rows) return lines; | |
| const keep = Math.max(1, rows); | |
| if (keep <= reserved) return lines.slice(-keep); | |
| return [...lines.slice(0, keep - reserved), ...lines.slice(-reserved)]; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/workflow-tui.ts` around lines 461 - 463, Update fitRows to preserve the
trailing footer lines when lines exceeds the available rows, reserving space for
the footer while retaining the existing output limit and minimum-row behavior.
Ensure the call inspector’s appended key-hint footer remains visible during
overflow, while non-overflowing content is unchanged.
This layer replaces the flat workflow monitor with a project-scoped, read-only Navigator. Users can move from workflow list to declared phases, unphased calls grouped under Other, and agent calls, then inspect activity, prompt, result, worktree details, session metadata, elapsed time, and best-effort token usage. Refresh preserves selection by run id, and all persisted model/provider strings are neutralized before terminal rendering. Narrow terminals use a single-pane layout; wide terminals show phases and agents side by side.\n\nIt also adds a large fixture gallery covering active, replayed, failed, completed, and cancelled workflows, and documents the interaction and token semantics.\n\nDepends on #151.
Summary by CodeRabbit
New Features
Documentation