CI surfaced react-hooks/set-state-in-effect on the synchronous setState(computeState(token)) inside the useEffect body. The earlier shape mirrored token -> state via an effect, which is exactly the "you might not need an effect" pattern React 19's eslint rule now flags. Switch to derived state: compute during render, use a useReducer tick to force re-render on the 30s cadence (so relative timestamps stay current even when token props don't change). Same observable behavior, no cascading renders. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
14 KiB
Editor-Embedded Flow Assist - Design Document
Date: 2026-03-06 Status: Approved Replaces: Standalone AI Chat Builder (
/ai/chat)
Overview
Replace the standalone /ai/chat page with a context-aware AI side panel embedded directly in each editor (Troubleshooting + Procedural). The panel knows which node/step is focused, supports targeted and open-ended actions, and applies changes via a tiered suggestion system. Knowledge integration and variable inference are phased features built on the same panel architecture.
Key Principles:
- Context-aware: panel knows the full tree/step structure + focal node
- Targeted actions auto-apply; open-ended suggestions require acceptance
- Output-based thresholds determine suggestion UX
- Model routing is config-driven, not hardcoded
- Chat history persists per-flow, per-user
Panel Layout & Behavior
Dimensions & Styling
- Width: 320px fixed, right side
- Styling: Glassmorphism (
.glass-card-staticbg, backdrop blur,border-l border-border) - Z-index: Same layer as node editor panel (not overlay)
Single-Panel Rule
- Tree editor: AI panel occupies the right panel slot, closing the node editor panel. When AI panel closes, if a node was previously being edited, the node editor panel reopens for that node.
- Procedural editor: AI panel slides in from right, narrowing the step list (step list takes
flex-1). No existing panel to replace.
Top Section: Context Summary
- Node/step selected: Read-only summary showing type, title, question/description of the focused item.
- No selection: Flow summary showing name, node/step count, flow type.
- Switching selection updates the summary live.
Tabs
- Chat — conversation + inline suggestions
- Suggestions — audit trail of all AI-applied changes to this flow (accepted, dismissed, pending)
Visibility
- Hidden by default
- Auto-opens on: AI-assisted flow creation, right-click AI action, toolbar toggle
- Auto-contextual: opens with focal node already set when triggered via context menu
Entry Points
1. Create Flow Dropdown (AI-Assisted)
- "Blank" or "AI-assisted" option per flow type (Troubleshooting, Project, Maintenance)
- AI-assisted shows a simple prompt dialog modal:
- Text area: "Describe the flow you want to build"
- Flow type already known from dropdown selection
- Loading state during generation
- On failure: error message + retry button (stays in dialog)
- On success: creates tree via API, navigates to editor with AI panel auto-opened and generation chat history loaded
- No multi-phase interview, no preview — just prompt and go
2. Right-Click Context Menu
- New
<ContextMenu>component (no existing context menus in either editor) - Positioned absolutely at right-click point
- Closes on click-away, Escape, or action selection
- Tree editor items: Generate branch, Add decision/action/solution, Explain node, Find known fixes, Delete
- Procedural editor items: Generate steps after, Add verification step, Expand step, Generate section, Delete
- Selecting an AI action sets the focal node/step and opens the AI panel
3. Toolbar Toggle
- "AI Assist" button in editor toolbar to manually open/close the panel
4. Existing Flows
- AI panel works on any flow — new or existing, AI-created or manually built
- No restriction to AI-created flows
Suggestion & Apply System
Ghost Node/Step Mechanics
Ghost nodes/steps are added to treeStructure/steps array with a _suggestion: true flag:
- Canvas/step list renders them normally (auto-layout works) but with dashed borders + reduced opacity
- Zundo temporal store paused while suggestions are pending
- On accept: remove
_suggestionflag, unpause zundo (creates one clean undo point) - On dismiss: remove ghost nodes from structure, unpause zundo (no undo point created)
- Ghost nodes participate in auto-layout and connection drawing but are visually distinct
Addition vs Modification
| Change Type | Visual Treatment |
|---|---|
| New nodes/steps | Ghost nodes: dashed borders, reduced opacity |
| Modified existing nodes | Subtle highlight + badge showing what changed |
| Modified selected node | Before/after shown in chat message with Apply button (not inline ghost) |
Output-Based Threshold
| Output Size | Behavior |
|---|---|
| 1 node/step | Auto-apply + toast notification with undo link |
| 2-4 nodes/steps | Individual ghost suggestions + "Accept All" shortcut button |
| 5+ nodes/steps | Ghost suggestions grouped by branch (tree) or section (procedural) with "Accept Branch"/"Accept Section" and "Accept All" controls + summary card in panel |
All changes (accepted or dismissed) logged in the Suggestions tab as an audit trail.
Backend Action Types
Each message to the AI includes an action_type that determines prompt construction, response schema, and model routing:
| Action Type | Description | Model Tier | Response Format |
|---|---|---|---|
generate_full |
Initial skeleton from prompt dialog | standard | Full tree structure or step array |
generate_branch |
Generate children for a specific node | standard | Subtree delta (node + children) |
modify_node |
Update a specific node's content | fast | Single node delta (before/after) |
add_steps |
Add steps after a specific step | standard | Step array delta |
quick_action |
Single-node operations (explain, expand) | fast | Single node delta or text response |
open_chat |
General conversation about the flow | standard | Text + optional delta |
variable_inference |
Detect implicit variables in step content | fast | Variable suggestions |
Prompt Construction
Each action type gets a tailored system prompt:
- Full tree context always included (so AI understands the complete flow)
- Focal node highlighted when present (the specific node/step being acted on)
- Action instruction describes what the AI should return
- Response schema constrains output format (full tree, subtree delta, single node, text)
Delta Response Format
For partial updates, the AI returns a delta object:
{
"action": "add" | "modify" | "delete",
"target_node_id": "node-to-modify-or-insert-after",
"nodes": [{ /* node objects */ }],
"explanation": "What was changed and why"
}
The frontend applies the delta to the tree structure and renders ghost nodes as appropriate.
Model Routing (Config-Driven)
Configuration
# backend/app/core/config.py
AI_MODEL_TIERS = {
"fast": "claude-haiku-4-5-20251001",
"standard": "claude-sonnet-4-6-20250514",
}
ACTION_MODEL_MAP = {
"generate_full": "standard",
"generate_branch": "standard",
"modify_node": "fast",
"add_steps": "standard",
"quick_action": "fast",
"open_chat": "standard",
"variable_inference": "fast",
}
Routing Logic
- Message endpoint receives
action_typeparameter - Look up tier from
ACTION_MODEL_MAP - Resolve model name from
AI_MODEL_TIERS - Pass to Anthropic API call
Both tiers can map to the same model initially. Changing model assignment is a config change, not a code change.
Knowledge Integration (Phased)
Phase 1 (Initial Release)
- Uses existing Microsoft Learn MCP server
- AI can cite KB articles, known issues, and official fix procedures in chat responses
- Citations rendered inline as collapsible cards with source URL and title
- AI response marker:
[KNOWLEDGE]{"title": "...", "url": "...", "excerpt": "..."}[/KNOWLEDGE]
Phase 2 (Future)
- Additional vendor documentation sources
- Community knowledge bases
- Proactive suggestions ("Microsoft released KB5034441 addressing this scenario")
Chat Persistence
Session Model
ai_chat_sessionmodel extended with:tree_idFK (which flow this session belongs to)archived_attimestamp (null = active)
- Per-flow, per-user sessions: multiple engineers on the same flow get separate chat histories
- Session loads on panel open if one exists for this flow + user
Suggestions Audit Trail
New ai_suggestion table:
| Column | Type | Description |
|---|---|---|
id |
UUID | Primary key |
tree_id |
UUID FK | Which flow |
user_id |
UUID FK | Who triggered |
session_id |
UUID FK | Which chat session |
action_type |
String | Action that generated this suggestion |
target_node_id |
String | Node/step acted on (nullable) |
changes_json |
JSONB | Before/after snapshot |
status |
Enum | pending, accepted, dismissed |
created_at |
DateTime(tz) | When suggested |
resolved_at |
DateTime(tz) | When accepted/dismissed (nullable) |
Auto-Archive
- APScheduler task runs daily
- Archives sessions with no activity for 30 days (
archived_at = now()) - Archived sessions viewable in Suggestions tab but not resumable for chat
Troubleshooting Editor Integration
Panel Context
- Full tree structure included in AI context
- Focal node (when selected/right-clicked) highlighted in context
- Node summary at panel top shows: type icon, node ID, question/title, option count
Context Menu Actions
| Action | Description | Model Tier |
|---|---|---|
| Generate branch | Create child nodes from this decision | standard |
| Add decision node | Add a decision child | fast |
| Add action node | Add an action child | fast |
| Add solution node | Add a solution child | fast |
| Explain node | AI explains what this node does | fast |
| Find known fixes | Search knowledge sources for this scenario | standard |
Ghost Node Rendering
- Dashed
border-dashed border-primary/40borders opacity-60on the node card- Connection lines drawn with dashed stroke
- Accept/dismiss buttons overlaid on each ghost node
- "Accept All" button in the panel when 2+ ghost nodes
Procedural Editor Integration
Panel Context
- Full step list included in AI context
- Focal step (when selected/right-clicked) highlighted in context
- Step summary at panel top shows: step number, type badge, title, content type
Context Menu Actions
| Action | Description | Model Tier |
|---|---|---|
| Generate steps after | Add steps following this one | standard |
| Add verification step | Insert a verification step | fast |
| Expand step | Break this step into substeps | standard |
| Generate section | Create a section header + steps | standard |
Ghost Step Rendering
- Dashed left border (
border-l-2 border-dashed border-primary/40) opacity-60background- Accept/dismiss buttons on each ghost step
- Grouped by section when 5+ suggestions
Intake Variable Detection (Three Tiers)
| Tier | Trigger | Timing | Model Tier |
|---|---|---|---|
| Explicit | [VAR:name] syntax in step content |
Immediate on content save | None (regex match) |
| Inference | Natural language suggests variable ("check the customer's server") | Debounced on step save/blur | fast |
| Cross-step | Same implicit variable in 2+ steps | On panel open + when steps modified | fast |
Behavior:
- Explicit: immediate inline suggestion card in panel ("Add
server_nameto intake form?") - Inference: non-blocking suggestion in panel, lower confidence indicator
- Cross-step: promoted suggestion with gap flag ("Variable
server_nameused in steps 3, 7, 12 but not captured in intake form") - Results cached per-session until step content changes
What Gets Removed
| Item | Location |
|---|---|
AIChatBuilderPage.tsx |
frontend/src/pages/ |
aiChatStore.ts |
frontend/src/store/ |
ai-chat/ component directory |
frontend/src/components/ |
AIFlowBuilderModal |
frontend/src/components/ |
/ai/chat route |
frontend/src/router.tsx |
| Flow type selection routing | URL params ?type=... |
What Gets Repurposed
| Item | Changes |
|---|---|
ai_chat_service.py |
Action-type dispatch, partial generation prompts, model routing, focal node context |
ai_tree_validator.py |
Validates AI-generated fragments (subtree, step batch) in addition to full trees |
ai_chat_session model |
Extended with tree_id FK, archived_at timestamp |
| AI chat endpoints | Tree-scoped sessions, action_type parameter, model tier routing |
What Gets Built (New)
| Item | Description |
|---|---|
EditorAIPanel component |
Shared panel with Chat + Suggestions tabs, node summary, input |
ContextMenu component |
Shared right-click menu for nodes and steps |
useEditorAI hook |
Panel state, focal node, suggestion management, ghost node lifecycle |
| Prompt dialog modal | Simple "describe your flow" modal for AI-assisted create |
ai_suggestion DB model |
Audit trail table + Alembic migration |
| Ghost node CSS | Dashed borders, reduced opacity, accept/dismiss overlays |
| Model tier config | AI_MODEL_TIERS + ACTION_MODEL_MAP in config.py |
| APScheduler archive task | Daily job to archive stale sessions |
What Gets Modified
| Item | Changes |
|---|---|
TreeEditorPage |
Right panel slot for AI, context menu handler, ghost node support |
TreeCanvas / TreeCanvasNode |
Ghost node rendering (dashed borders, overlays) |
ProceduralEditorPage |
Flex layout for AI panel, context menu on steps |
StepList / StepEditor |
Ghost step rendering |
treeEditorStore |
Ghost node state slice, zundo pause/resume, orphan bug fix |
proceduralEditorStore |
Ghost step state slice |
ai_chat_service.py |
Action-type dispatch, delta response format, model routing |
ai_chat_session model |
tree_id FK, archived_at |
config.py |
Model tier configuration |
CreateFlowDropdown |
AI-assisted option + prompt dialog trigger |
router.tsx |
Remove /ai/chat route |
Bug Fix (Included)
File: frontend/src/store/treeEditorStore.ts line 858
Current code:
if (id !== 'root' && !referencedIds.has(id)) {
Fixed code:
if (id !== state.treeStructure?.id && !referencedIds.has(id)) {
Root cause: Orphan check hardcodes 'root' as the expected root node ID. AI-generated trees use descriptive IDs (e.g., "verify-account-exists"). Since the root is never referenced by any other node's next_node_id, it gets flagged as orphaned. This is a false positive.