Skip to content

feat(tui): add workflow Navigator and call inspector - #152

Open
Waishnav wants to merge 4 commits into
codex/workflow-observabilityfrom
codex/workflow-navigator
Open

feat(tui): add workflow Navigator and call inspector#152
Waishnav wants to merge 4 commits into
codex/workflow-observabilityfrom
codex/workflow-navigator

Conversation

@Waishnav

@Waishnav Waishnav commented Aug 8, 2026

Copy link
Copy Markdown
Owner

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

    • Added a multi-screen workflow navigator for browsing runs, phases, and calls.
    • Added call inspection tabs for activity, prompts, results, files, and metadata.
    • Added workflow phase statuses, details, token totals, durations, and provider session information.
    • Added responsive layouts, keyboard navigation, refresh-state preservation, and safe terminal output handling.
  • Documentation

    • Documented the project-scoped, read-only workflow navigator command and its navigation behavior.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

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

Changes

Workflow Navigator

Layer / File(s) Summary
Workflow view data and phase status
src/workflow-view.ts, src/workflow-view.test.ts
Workflow views expose call inspection fields, phase details and statuses, non-cached token totals, and ordered phases.
Workflow phase and activity fixture data
scripts/workflow-tui-fixture.ts
The fixture creates phases, provider sessions, activity events, and deterministic partial and final token usage.
TUI state navigation and inspection
src/workflow-tui.ts
The TUI manages workflow, phase/call, and call-inspector screens with keyboard navigation, refresh reconciliation, responsive rendering, activity display, and terminal sanitization.
TUI behavior validation and command documentation
src/workflow-tui.test.ts, docs/dynamic-workflows.md
Tests cover navigation, reconciliation, unphased calls, sanitization, narrow layouts, and workspace resolution. Documentation describes the command and Navigator behavior.

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
Loading

Possibly related PRs

  • Waishnav/devspace#83: Provides workflow orchestration data and provider event/session APIs consumed by the TUI.
  • Waishnav/devspace#96: Introduces workflow phases, calls, activity, usage, and metadata used by the view models.
  • Waishnav/devspace#111: Supplies the earlier workflow TUI implementation extended by this stateful navigation model.

Poem

A rabbit hops through phases bright,
Reviews each call by lantern light.
Tokens count and sessions gleam,
Tabs reveal the workflow stream.
“Hop, inspect, and safely roam!”
The Navigator guides them home.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change by naming the new workflow Navigator and call inspector.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/workflow-navigator

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@Waishnav Waishnav changed the title codex/workflow navigator feat(tui): add workflow Navigator and call inspector Aug 8, 2026
@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown

Greptile Summary

The PR expands workflow tui into a multi-screen workflow navigator with phase, agent-call, activity, result, file, and metadata views, and enriches workflow views with declared-phase status and token usage.

  • Adds keyboard-driven workflow, phase, call, and inspector navigation.
  • Adds persisted provider activity and usage data to the large TUI fixture.
  • Extends workflow projections and tests with phase status, prompts, results, provider sessions, and token totals.
  • Documents the new navigator and token-accounting behavior.

Confidence Score: 1/5

This 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

Security Review

The new inspector renders raw provider and workflow content in the terminal. A response containing terminal control sequences can manipulate terminal state when its inspector section is opened. How this was verified: The provider response was traced through persistence and view projection to direct stdout rendering with no control-character escaping.

Important Files Changed

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
Loading

Reviews (1): Last reviewed commit: "test(tui): seed navigator observability ..." | Re-trigger Greptile

Comment thread src/workflow-tui.ts Outdated
Comment thread src/workflow-tui.ts Outdated
Comment thread src/workflow-tui.ts
@Waishnav

Waishnav commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Handle failures inside render so the terminal is restored.

render calls load() and process.stdout.write without error handling. render runs from setInterval, from the keypress handler, and from the resize handler. If load() 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, because finish() never runs. The user is left with a broken terminal.

Catch the error inside render and 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 win

Sanitize 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 carries prompt, responseText, structuredJson, and returnValueJson. 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 sanitizeTerminalValue inside truncate, 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 win

Add fixture coverage for unavailable tokens and replayed calls.

Every call in the fixture is non-cached, and the only call with a usage field is the running one. Two documented behaviors are therefore untested:

  • docs/dynamic-workflows.md Line 77 promises that a provider that cannot report tokens stays visibly unavailable. No call omits usage, so the path in callRows and the unavailable path in inspectorBody are never rendered in a test.
  • docs/dynamic-workflows.md Line 78 describes replayed calls. No call sets fromCache: true, so callElapsedLabel never returns replayed.

Add one call without usage and one with fromCache: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 95becf7 and 3aadb3c.

📒 Files selected for processing (6)
  • docs/dynamic-workflows.md
  • scripts/workflow-tui-fixture.ts
  • src/workflow-tui.test.ts
  • src/workflow-tui.ts
  • src/workflow-view.test.ts
  • src/workflow-view.ts

Comment thread docs/dynamic-workflows.md
Comment on lines +67 to +72
`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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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 supplies run-id, createWorkflowTuiState returns the workflow screen directly and skips the list. State the two entry points.
  • Line 68-69 describes phases shown beside the calls. renderNavigator uses that two-pane layout only when the terminal is at least 80 columns wide. Below 80 columns it shows one pane at a time and Tab switches between them. Document the narrow layout.
  • navigatorPhases appends a synthetic phase named Other that holds every call without a declared phase. The PR objectives describe this grouping, but this section does not. A user who sees Other beside 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.

Suggested change
`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.

Comment thread src/workflow-tui.test.ts
Comment on lines +122 to +123
assert.match(rendered, /Workflow › Implementation › Patch auth/);
assert.match(rendered, /tool\s+bash · npm test/);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/workflow-tui.test.ts
assert.match(narrow, /PHASES/);
assert.doesNotMatch(narrow, /AGENTS · Implementation/);

assert.equal(resolveWorkflowTuiWorkspaceRoot(process.cwd()), process.cwd());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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_ROOT gets 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.

Suggested change
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().

Comment thread src/workflow-tui.ts
Comment on lines +107 to +110
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();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Suggested change
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.

Comment thread src/workflow-tui.ts
Comment on lines +197 to +199
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/workflow-tui.ts
Comment on lines +246 to +247
const run = project.runs[state.runIndex];
if (!run) return ["Workflow is no longer available."];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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: in renderNavigator, append rule(width) and the muted Esc back · q quit footer to the "Workflow is no longer available." return.
  • src/workflow-tui.ts#L282-L284: in renderCallInspector, append rule(width) and the muted Esc back · q quit footer 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.

Comment thread src/workflow-tui.ts
Comment on lines +461 to +463
function fitRows(lines: string[], rows: number): string[] {
return rows > 0 && lines.length > rows ? lines.slice(0, Math.max(1, rows)) : lines;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Suggested change
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.

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.

1 participant