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>
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)toDecisionRecordinsession.py - Frontend: Add
command_output?: string | nulltoDecisionRecordtype insession.ts - API: No endpoint changes —
PUT /api/v1/sessions/{id}continues to accept full decisions array; now includes optionalcommand_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)toDecisionRecord - The
max_length=10000provides backend validation — requests exceeding this return 422
Step 2: Frontend Type
File: frontend/src/types/session.ts
- Add
command_output?: string | nullto theDecisionRecordtype - 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 existingnotesstate) - Clear
commandOutputwhen node changes (same placenotesis cleared) - When revisiting a step, preload existing
command_outputfrom 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/10background 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
- Placeholder:
- Use a
Terminalicon fromlucide-reactfor the section label
Persistence — in handleContinue():
- Add
command_output: commandOutput.trim() || nullto 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
handleContinueToDescendantorhandleCustomBranchCompleteis called, update the current custom step's decision record withcommand_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_outputfrom a decision dict (strip whitespace, returnNoneif empty) - A helper to resolve the commands associated with a step for context display:
- First look up the tree snapshot action node by
node_id - Fallback to custom step metadata by
node_id - Fallback to no command list (just show the output)
- First look up the tree snapshot action node by
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_performedrendering for each decision, check forcommand_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_outputis 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)
- Update a session with
command_outputin a decision record → verify it round-trips correctly on GET - Submit
command_outputexceeding 10,000 characters → verify 422 response - Submit empty string and whitespace-only
command_output→ verify stored asnull
Export Tests
- Markdown export includes command context and fenced code block for output
- Text export includes output block with preserved line breaks
- HTML export includes escaped
<pre><code>block - PSA export includes compact command context and indented output
- Multi-command action node exports with single shared output block
- Export of session without any
command_outputrenders cleanly (no errors, no empty blocks)
Custom Action Step Tests
- Insert custom action step with commands → capture output → continue → verify output stored in decision
- Custom step output appears in all export formats
Frontend Behavior Tests
- Action node with commands shows the "Paste Output" section (collapsed by default)
- Custom action step with commands shows the "Paste Output" section
- Action node WITHOUT commands does NOT show the "Paste Output" section
- Character count updates live as user types
- Revisiting a step preloads previously captured output
- Session detail page renders output block with monospace formatting
- "Copy to clipboard" includes command output when present
Verification Checklist (Manual)
cd frontend && npm run build— confirm no TypeScript errors- 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
- Start a session and add a custom action step with commands:
- Paste output
- Continue to next step
- Verify output persists
- Complete a session → check SessionDetailPage shows the command output with proper formatting
- Export in all 4 formats → verify output appears correctly formatted in each
- Use "Copy to clipboard" on a step with output → verify output is included
- Run a session on a tree WITHOUT commands on action nodes → verify no output section appears
- Test with existing sessions that have no
command_output→ verify they render and export without errors - Test pasting large output (near 10,000 chars) → verify character count and limit work
- 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