Files
resolutionflow/docs/archive/2026-02-12-issue-57-command-output-capture.md
chihlasm 350c977eda feat: add procedural flows with intake forms, navigation, and seed templates
Adds a new "procedural" tree type for linear step-by-step project workflows
(domain controller setup, M365 onboarding, VPN config, etc). Includes intake
form builder, two-panel step navigation, variable resolution, procedural
exports, 3 seed templates, and UI rename from "Trees" to "Flows".

Also archives 19 implemented plan docs and creates deferred features backlog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 04:13:52 -05:00

10 KiB

Issue #57: Command Output Capture — Implementation Plan

Overview

Engineers run commands during troubleshooting sessions but the output is lost — exports say "ran this command" but not what it returned. This feature adds a "Paste Output" textarea on action nodes and custom action steps so command output is captured in session data and included in all exports and session review.

Scope: Built-in action nodes AND custom action steps. Migration: None required — decisions is already a JSONB array with flexible dict entries.


Public Interfaces / Type Changes

  • Backend: Add command_output: Optional[str] = Field(None, max_length=10000) to DecisionRecord in session.py
  • Frontend: Add command_output?: string | null to DecisionRecord type in session.ts
  • API: No endpoint changes — PUT /api/v1/sessions/{id} continues to accept full decisions array; now includes optional command_output
  • Validation: Backend enforces 10,000 character hard limit (returns 422 on overflow); frontend shows live character count

Files to Modify

Backend (3 files)

File Change
backend/app/schemas/session.py Add command_output field to DecisionRecord
backend/app/services/export_service.py Render command_output in all 4 export formats with command context
backend/app/core/session_to_tree.py Include command output when converting session decisions to forked tree nodes

Frontend (3 files)

File Change
frontend/src/types/session.ts Add command_output to DecisionRecord type
frontend/src/pages/TreeNavigationPage.tsx Add capture UI for both built-in action nodes and custom action steps
frontend/src/pages/SessionDetailPage.tsx Render command_output in decision review and clipboard copy

Implementation Steps

Step 1: Backend Schema

File: backend/app/schemas/session.py

  • Add command_output: Optional[str] = Field(None, max_length=10000) to DecisionRecord
  • The max_length=10000 provides backend validation — requests exceeding this return 422

Step 2: Frontend Type

File: frontend/src/types/session.ts

  • Add command_output?: string | null to the DecisionRecord type
  • This ensures the field is not dropped by TypeScript typing during round-trips

Step 3: TreeNavigationPage — Capture UI for Built-in Action Nodes

File: frontend/src/pages/TreeNavigationPage.tsx

State:

  • Add const [commandOutput, setCommandOutput] = useState('') (same pattern as existing notes state)
  • Clear commandOutput when node changes (same place notes is cleared)
  • When revisiting a step, preload existing command_output from the decision record if present

UI — on action nodes with currentNode.commands?.length > 0:

  • Render a collapsible "Paste Output (Optional)" section below the commands display
  • Inside: a textarea with:
    • Placeholder: "Paste command output here..."
    • Monospace font (font-mono), consistent with command code block styling
    • bg-white/10 background styling to match existing design
    • Live character count display: "{count} / 10,000" shown below the textarea
    • Max length enforced on the frontend at 10,000 characters
  • Use a Terminal icon from lucide-react for the section label

Persistence — in handleContinue():

  • Add command_output: commandOutput.trim() || null to the decision record pushed to the session
  • Empty or whitespace-only input is normalized to null (treated as not provided)

Step 4: TreeNavigationPage — Capture UI for Custom Action Steps

File: frontend/src/pages/TreeNavigationPage.tsx

Custom action steps create their decision record at insertion time, which is different from built-in action nodes. The output capture UI and behavior should be the same as Step 3, but persistence requires updating the existing decision rather than creating a new one.

UI:

  • Same collapsible "Paste Output (Optional)" section as built-in action nodes
  • Available when a custom action step has commands defined

Persistence:

  • Before handleContinueToDescendant or handleCustomBranchComplete is called, update the current custom step's decision record with command_output: commandOutput.trim() || null
  • Persist the updated decisions array to the backend before navigation/completion transitions
  • This is a wrapper flow around the existing custom step logic — not a replacement of it

Step 5: Export Service — All 4 Formats

File: backend/app/services/export_service.py

Helpers to add:

  • A helper to safely extract and normalize command_output from a decision dict (strip whitespace, return None if empty)
  • A helper to resolve the commands associated with a step for context display:
    1. First look up the tree snapshot action node by node_id
    2. Fallback to custom step metadata by node_id
    3. Fallback to no command list (just show the output)

Export rendering per format (all guarded by if command_output := decision.get("command_output")):

Markdown (_generate_markdown_export):

**Commands Run:** `ping 8.8.8.8`, `tracert 8.8.8.8`
**Output:**

{output}

Text (_generate_text_export):

  Commands Run: ping 8.8.8.8, tracert 8.8.8.8
  Output:
    {output with each line indented}

HTML (_generate_html_export):

<p><strong>Commands Run:</strong> <code>ping 8.8.8.8</code>, <code>tracert 8.8.8.8</code></p>
<pre><code>{html.escape(output)}</code></pre>

PSA (_generate_psa_export):

Commands: ping 8.8.8.8, tracert 8.8.8.8
Output:
  {output with each line indented}

Step 6: SessionDetailPage — Review Display

File: frontend/src/pages/SessionDetailPage.tsx

Decision review:

  • After the action_performed rendering for each decision, check for command_output
  • If present, render in a <pre> block with monospace styling and preserved whitespace
  • Label: "Command Output" with consistent styling

Clipboard copy (copyTicketNotes):

  • After the action performed line, add:
    Output:
    {decision.command_output}
    
  • Only include if command_output is present and non-empty

Step 7: Session-to-Tree Conversion

File: backend/app/core/session_to_tree.py

  • When building node descriptions from decisions, check for command_output
  • If present, append the output text to the node description so forked trees retain the captured output
  • Format: include a "Command Output:" label followed by the output text

Edge Cases & Failure Modes

Scenario Behavior
Existing sessions without command_output Render normally with no errors — field is optional
Output exceeds 10,000 characters Frontend prevents input beyond limit; backend returns 422 if somehow exceeded
Empty or whitespace-only input Normalized to null — treated as not provided
Multiline output, JSON, special characters Preserved as-is; HTML export escapes all content
Steps without commands Output can still be stored; export shows output even without command context
Multi-command action nodes One shared output field per step (not per command)
Revisiting a completed step Preloads the previously captured output

Test Cases

Backend API Tests (test_sessions.py)

  1. Update a session with command_output in a decision record → verify it round-trips correctly on GET
  2. Submit command_output exceeding 10,000 characters → verify 422 response
  3. Submit empty string and whitespace-only command_output → verify stored as null

Export Tests

  1. Markdown export includes command context and fenced code block for output
  2. Text export includes output block with preserved line breaks
  3. HTML export includes escaped <pre><code> block
  4. PSA export includes compact command context and indented output
  5. Multi-command action node exports with single shared output block
  6. Export of session without any command_output renders cleanly (no errors, no empty blocks)

Custom Action Step Tests

  1. Insert custom action step with commands → capture output → continue → verify output stored in decision
  2. Custom step output appears in all export formats

Frontend Behavior Tests

  1. Action node with commands shows the "Paste Output" section (collapsed by default)
  2. Custom action step with commands shows the "Paste Output" section
  3. Action node WITHOUT commands does NOT show the "Paste Output" section
  4. Character count updates live as user types
  5. Revisiting a step preloads previously captured output
  6. Session detail page renders output block with monospace formatting
  7. "Copy to clipboard" includes command output when present

Verification Checklist (Manual)

  1. cd frontend && npm run build — confirm no TypeScript errors
  2. Start a session on a tree with action nodes that have commands:
    • Paste output into the textarea
    • Click Continue
    • Verify output persists in the session data
  3. Start a session and add a custom action step with commands:
    • Paste output
    • Continue to next step
    • Verify output persists
  4. Complete a session → check SessionDetailPage shows the command output with proper formatting
  5. Export in all 4 formats → verify output appears correctly formatted in each
  6. Use "Copy to clipboard" on a step with output → verify output is included
  7. Run a session on a tree WITHOUT commands on action nodes → verify no output section appears
  8. Test with existing sessions that have no command_output → verify they render and export without errors
  9. Test pasting large output (near 10,000 chars) → verify character count and limit work
  10. Test pasting multiline output with special characters → verify preservation in review and exports

Assumptions

  • One shared output field per step, not per individual command
  • Maximum stored output is 10,000 characters
  • v1 does not include syntax highlighting or image paste
  • No feature flag gating — ships directly
  • Collapsed-by-default UI keeps the interface clean for steps where output isn't needed