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

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