Files
resolutionflow/docs/plans/archive/2026-02-18-canvas-ux-fixes-design.md
chihlasm 932927b9df chore: archive old plan docs + add survey foundation files
Move completed plan docs to docs/plans/archive/. Add survey migration 046
and reference HTML/plan files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 02:03:38 -05:00

11 KiB
Raw Permalink Blame History

Canvas UX Fixes — Design Document

Date: 2026-02-18 Branch: feature/tree-editor-canvas Status: Approved, pending implementation


Context

The new TreeCanvas editor (Phase 14) was tested and three UX problems were identified:

  1. Scroll: Expanded card forms have no height limit — long forms are cut off and unreachable
  2. Busy forms: Inline hint text (<p className="text-xs">) inside NodeForm components creates visual clutter
  3. Answer stubs: When building a decision node, users must immediately pick a child node type — there's no way to sketch out answer options first and decide types later

All three fixes apply exclusively to the canvas editor. No session, navigation, backend session-saving, or procedural flow code is affected.


Fix 1: Card Scroll

Problem

TreeCanvasNode.tsx renders the expanded editing area as an unbounded <div>. On decision nodes with many options, or on any node when the browser viewport is short, the card overflows off-screen. There is no scrollbar — content is unreachable. Tab cycling doesn't scroll the canvas to bring hidden fields into view.

Design

Apply max-h-[70vh] overflow-y-auto to the expanded editing <div> inside TreeCanvasNode.tsx.

Make the save/cancel header row sticky (sticky top-0 z-10 bg-card) so the action buttons are always visible when the user scrolls the form content.

Files changed:

  • frontend/src/components/tree-editor/TreeCanvasNode.tsx
    • Add max-h-[70vh] overflow-y-auto to the expanded area <div> (currently border-t border-border px-3 pb-3 pt-3)
    • Add sticky top-0 z-10 bg-card to the card header <div> containing the save/cancel row when in expanded state

No other files affected.


Fix 2: Info Tooltips

Problem

NodeFormDecision.tsx, NodeFormAction.tsx, and NodeFormResolution.tsx each contain <p className="mb-1 text-xs text-muted-foreground"> hint paragraphs below field labels. These add vertical height and visual noise inside a card that's already compact.

Examples of the current hint text:

  • "Supports markdown: bold, italic, - lists, 1. numbered lists, `code`"
  • "PowerShell or CLI commands to execute"
  • "Step-by-step instructions for resolving the issue"

Design

Replace each hint <p> with a small icon placed inline next to the field label. The icon shows a tooltip on hover containing the same text.

Tooltip implementation:

Use title="" on the icon element for a native browser tooltip. No third-party tooltip library needed — keeps the implementation minimal and consistent with the existing codebase pattern (the validation badge already uses title={nodeErrors.map(...).join('\n')}).

// Before
<label className="block text-sm font-medium text-foreground">
  Description
</label>
<p className="mb-1 text-xs text-muted-foreground">
  Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`
</p>

// After
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
  Description
  <span
    className="inline-flex items-center justify-center h-3.5 w-3.5 rounded-full border border-muted-foreground/40 text-[9px] text-muted-foreground cursor-help"
    title="Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`"
  >
    i
  </span>
</label>

Files changed:

  • frontend/src/components/tree-editor/NodeFormDecision.tsx — remove help_text hint <p>, replace with on Help Text label
  • frontend/src/components/tree-editor/NodeFormAction.tsx — remove description markdown hint <p> and commands hint <p>, add on those labels
  • frontend/src/components/tree-editor/NodeFormResolution.tsx — remove description markdown hint <p> and steps hint <p>, add on those labels

Fix 3: Answer Stubs (New answer Node Type)

Problem

Decision nodes require the user to pick child node types at the same time they're creating the decision. This is backwards — you naturally know the answer options before you know what each one should do. The NodePicker in NodeFormDecision forces a concrete type selection (decision / action / solution) or leaves the option disconnected (next_node_id: null).

Users want to type answer labels first, see those answers appear as placeholder cards in the canvas, and then click each placeholder to assign a type and fill in details.

Design

Introduce 'answer' as a new internal NodeType that represents a typed-but-unresolved branch placeholder. Answer nodes are:

  • Created when a user types an answer label in the decision node form
  • Shown in the canvas as a dashed-border stub card with the answer label
  • Clickable to open a type picker (decision / action / solution) and convert to a real node
  • Not publishable — blocked by backend and frontend validation on publish

Answer nodes persist to draft saves so users don't lose their sketch when they navigate away.

Data Model

frontend/src/types/tree.ts

// Before
export type NodeType = 'decision' | 'action' | 'solution'

// After
export type NodeType = 'decision' | 'action' | 'solution' | 'answer'

TreeStructure interface: no new fields needed. Answer nodes use:

  • id: auto-generated UUID (same as other nodes)
  • type: 'answer'
  • title: the answer label text (e.g. "Server", "Desktop")
  • No other fields required

NodeFormDecision Redesign

Replace the current options UI (NodePicker per option → picks existing or creates new) with a two-zone layout:

Zone 1 — Answer Labels A simple list of text inputs, one per answer option. Each input edits options[i].label. Add/remove/reorder via DynamicArrayField (already available).

No next_node_id selection here. When the user saves, for each option that has a label but no next_node_id, a new answer-type stub node is created and linked automatically.

Options (answer labels):
[  Server              ] [×]
[  Desktop             ] [×]
[ + Add Answer ]

Zone 2 — (removed) NodePicker per option The per-option NodePicker is removed entirely. The canvas becomes the way to traverse to a child and set its type.

TreeCanvas Changes

Rendering answer nodes:

When a node has type === 'answer', render an AnswerStubCard instead of a full TreeCanvasNode. The stub card:

  • Dashed border: border-2 border-dashed border-border
  • Colored left accent: none (neutral/muted)
  • Shows the answer label (node.title) centered
  • Shows a "+ Choose Type" label below the title
  • On click: opens an inline type picker (3 buttons: Decision / Action / Solution)
  • On type selection: calls updateNode(node.id, { type: selectedType }) and immediately expands the node for editing

New component: frontend/src/components/tree-editor/AnswerStubCard.tsx

Props:

interface AnswerStubCardProps {
  node: TreeStructure         // type === 'answer'
  fromOption?: string         // the answer label (same as node.title)
  onSelectType: (nodeId: string, type: 'decision' | 'action' | 'solution') => void
}

Stub Creation Logic (TreeCanvas)

When a decision node is saved (onSave), the canvas compares options before/after:

For each option in the saved node:

  • If option.next_node_id is null/undefined → create a new answer stub node with title = option.label and link option.next_node_id to its ID.
  • If option.next_node_id already points to a node → leave it.

This creation logic lives in TreeCanvas.tsx's handleNodeSave() function, which already handles pending link resolution.

Backend Validation

backend/app/core/tree_validation.py

_validate_node() currently rejects unknown node types:

if node_type not in ('decision', 'action', 'solution'):
    errors.append(...)

Changes:

  1. Allow 'answer' type through without structural validation (answer nodes have no required fields beyond id and type).
  2. Add a publish-time check in can_publish_tree() (or in validate_tree_structure() before publish): if any node has type == 'answer', reject with a clear message: "Answer placeholders must be resolved to a node type before publishing."
  3. The draft save endpoint (PUT /trees/:id) does not call can_publish_tree(), so draft saves continue to work with answer nodes present.

Frontend Publish Guard

In TreeEditorPage.tsx, before calling the publish API, add a check:

const hasAnswerNodes = findAllAnswerNodes(tree.tree_structure).length > 0
if (hasAnswerNodes) {
  // Show toast or inline error: "Resolve all answer placeholders before publishing."
  return
}

findAllAnswerNodes is a simple recursive traversal (can be a small utility function in TreeCanvas.tsx or a new file lib/treeUtils.ts).

Visual Design (AnswerStubCard)

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
  Server
  [ ? Decision ] [ ⚡ Action ] [ ✓ Solution ]
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
  • Card: min-w-[180px] max-w-[280px] rounded-xl border-2 border-dashed border-border bg-card/50
  • Title: text-sm font-heading font-medium text-foreground text-center py-2 px-3
  • Type picker row (default state — not yet clicked): text-xs text-muted-foreground text-center pb-2 cursor-pointer hover:text-foreground
    • Clicking the card reveals three compact buttons for type selection
  • Type picker (expanded): three small buttons side-by-side in the card footer

Files Changed Summary

File Change
frontend/src/components/tree-editor/TreeCanvasNode.tsx Fix 1: max-h + overflow-y + sticky header
frontend/src/components/tree-editor/NodeFormDecision.tsx Fix 2: ⓘ tooltip on help_text; Fix 3: replace NodePicker with answer label list
frontend/src/components/tree-editor/NodeFormAction.tsx Fix 2: ⓘ tooltips on description + commands fields
frontend/src/components/tree-editor/NodeFormResolution.tsx Fix 2: ⓘ tooltips on description + steps fields
frontend/src/components/tree-editor/TreeCanvas.tsx Fix 3: stub creation in handleNodeSave; AnswerStubCard rendering
frontend/src/components/tree-editor/AnswerStubCard.tsx Fix 3: NEW — dashed stub card with inline type picker
frontend/src/types/tree.ts Fix 3: add 'answer' to NodeType union
backend/app/core/tree_validation.py Fix 3: allow 'answer' in draft; block on publish
frontend/src/pages/TreeEditorPage.tsx Fix 3: frontend publish guard

Non-Goals

  • No changes to session navigation, procedural flows, or maintenance flows
  • No changes to the Code mode editor
  • No changes to treeEditorStore.ts store actions (addNode, updateNode, deleteNode are used as-is)
  • No third-party tooltip library
  • No new backend endpoints

Verification

  1. Open a troubleshooting tree in the canvas editor
  2. Click a decision node → card expands, form is scrollable with sticky save/cancel header
  3. Field labels show icons; hovering reveals the hint text
  4. Type answer labels in the Options section; click ✓ to save
  5. Answer stub cards appear as dashed cards below the decision node
  6. Click a stub card → type picker appears; select "Decision" → card converts and expands for editing
  7. Draft save works with answer nodes present (no backend error)
  8. Attempt to publish with unresolved answer nodes → blocked with a clear error message
  9. npm run build passes with no TypeScript errors