Files
resolutionflow/docs/plans/2026-02-18-canvas-ux-fixes-design.md
chihlasm 94de29b5f2 feat: canvas UX fixes — scroll, fullscreen, InfoTip tooltips, answer stub system (#80)
* feat: Add TreeCanvasNode inline editor card component

Replaces modal-based node editing with inline expand/collapse cards.
Each card shows node type, title, and options in compact mode, then
renders the full edit form inline on expand — no modal required.

Local draft state with save/cancel prevents premature store writes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: Add TreeCanvas layout with visual branching and orchestration

Replaces NodeList + TreePreviewPanel with a single full-width canvas.
Decision nodes branch horizontally; action/solution nodes flow vertically.
Inline type picker adds nodes without modal interruption. Handles pending
link resolution, inbound reference cleanup on delete, and selection sync.

CSS dot-grid background + connector lines for structure clarity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: Update forms for inline safety, add MetadataSidePanel, update layout

- NodeFormDecision: option reorder via onUpdate (no premature store writes)
- NodePicker: add allowCreate prop (default true) to hide Create New options
  during inline canvas editing, preventing side-effect node creation
- MetadataSidePanel: 320px right slide-in overlay wrapping TreeMetadataForm,
  closes on backdrop click, close button, and Escape key
- TreeEditorLayout: Flow mode now renders full-width TreeCanvas + MetadataSidePanel
  overlay; Code mode unchanged (Monaco + preview split)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: Wire toolbar metadata toggle and integrate canvas layout

- Add isMetadataOpen state in TreeEditorPage
- Add Metadata toolbar button (visible in Flow mode only)
- Auto-close metadata panel when switching to Code mode
- Pass isMetadataOpen/onCloseMetadata props through TreeEditorLayout
- Update Flow mode toggle tooltip to reflect new canvas editing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add canvas UX fixes design doc (scroll, tooltips, answer stubs)

Captures approved design for three post-implementation UX improvements
to the tree canvas editor: card scroll fix, info tooltip replacement for
hint text, and the new 'answer' node type for sketching decision branches
before assigning types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add implementation plan for canvas UX fixes

12-task plan covering scroll fix, info tooltips, and answer stub
node type. Each task has exact file paths, code, and build
verification steps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: make canvas card expanded area scrollable with sticky header

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add fullscreen toggle to Modal, enable in NodeEditorModal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add reusable InfoTip component for field-level help

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: replace hint paragraphs with InfoTip tooltips in NodeFormDecision

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: replace hint paragraphs with InfoTip tooltips in NodeFormAction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: replace hint paragraphs with InfoTip tooltips in NodeFormResolution

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add 'answer' to NodeType union for branch placeholder stubs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add AnswerStubCard component for unresolved branch placeholders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: guard NODE_TYPE_CONFIG lookup against 'answer' type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: redesign NodeFormDecision to label-only options, remove NodePicker

Users now type answer labels only. Stub nodes are created automatically
by TreeCanvas when the decision node is saved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: auto-create answer stubs on decision save, render AnswerStubCard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add answer type to all Record<NodeType> icon and color maps

Fixes NodeList, ContinuationModal, NodePicker, and TreePreviewNode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: allow 'answer' type in tree drafts, block on publish

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: block publish if unresolved answer stub nodes exist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: serialize 'answer' stub nodes in markdown output

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add defensive guard for answer nodes in session navigation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add delete button with confirmation to AnswerStubCard

Adds an inline delete flow to answer stub placeholder cards:
- Trash icon button (top-right, subtle) visible in idle state
- Click reveals "Delete this stub?" confirmation with Delete/Cancel
- Confirmed delete calls onDelete(nodeId) wired to handleDelete in TreeCanvas

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: prevent category Cancel overflow and add Tab/Enter to create options

- TreeMetadataForm: add min-w-0 + shrink-0 to keep Cancel button in-panel
- NodeFormDecision: Tab or Enter on the last non-empty option input adds a
  new option and auto-focuses it; empty last input lets Tab pass through normally

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: re-sync draft from store when canvas card is opened

When a decision node is saved with new options, stub next_node_id values
are written back to the store. But the local draft was initialized once at
mount and never refreshed, so reopening the card gave a stale draft with
empty next_node_ids — causing duplicate stubs on every subsequent save.

Fix: reset draft from the live node whenever isExpanded transitions to true.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix+feat: blank options, stub card dismiss, collapsible subtrees

- TreeCanvas: strip blank-label options on save so they don't generate
  stubs; also filter them from the unlinked-option add-button list
- AnswerStubCard: collapse type-picker when clicking outside the card
- TreeCanvasNode: add subtree collapse toggle button (ChevronsDownUp icon)
  visible in compact mode when the node has children
- TreeCanvas: track collapsedNodeIds; hide subtree behind a clickable
  "N nodes hidden" pill when collapsed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: stop connector fork line from overlapping child cards

Replace the two-element approach (separate fork line + child lanes div with
mismatched maxWidth values) with a single relative-positioned container.
The fork line is absolutely positioned and its left/right are calculated from
the number of children so it spans exactly from the center of the first lane
to the center of the last lane.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: replace Show Drafts checkbox with Drafts tab in Flow Library

- Remove the out-of-place checkbox; add 'Drafts' as a tab alongside
  All | Troubleshooting | Projects | Maintenance
- Drafts tab sets showDrafts=true + typeFilter='all' so the API filter
  still works correctly via include_drafts
- Move SortDropdown to the right side next to ViewToggle, so both
  secondary controls are grouped together

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 12:52:08 -05:00

259 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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')}`).
```tsx
// 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`**
```typescript
// 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:
```typescript
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:
```python
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:
```typescript
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