Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
84de9cb
docs: update phase 3 plan with implementation log
prosdev Mar 18, 2026
cf7db3b
feat: skip run dialog for zero-input graphs, pydantic model support
prosdev Mar 18, 2026
c5fde4d
feat: change LLM default output_key to llm_response
prosdev Mar 18, 2026
8557d2c
feat(canvas): add input_map and output_key config to LLM nodes
prosdev Mar 18, 2026
01d0bac
feat: fetch real model lists from LLM providers
prosdev Mar 18, 2026
2f76532
feat(canvas): add state panel as left sidebar
prosdev Mar 18, 2026
e072bfa
docs: add Phase 4 plan files
prosdev Mar 18, 2026
9c37316
fix(canvas): review fixes — timer ref, preset hoist, timeout type
prosdev Mar 18, 2026
60e975e
fix(execution): skip empty expressions in resolve_input_map
prosdev Mar 18, 2026
d5ea48a
feat(canvas): add user_input to DEFAULT_STATE
prosdev Mar 18, 2026
acc94d6
fix(canvas): move State button to top-level toolbar, add cursor
prosdev Mar 18, 2026
4eaad2c
feat(canvas): replace native select with Radix Select component
prosdev Mar 19, 2026
d79a217
fix: address code review findings from PR #14
prosdev Mar 19, 2026
4265d7d
feat(canvas): improve AddFieldForm UX and switch Tooltip to Radix
prosdev Mar 19, 2026
3321047
feat(canvas): add suggestion chips for tool output fields in LLM config
prosdev Mar 19, 2026
e00b98a
fix(canvas): reset run state when navigating between graphs
prosdev Mar 20, 2026
dc30e64
feat(canvas): smart output_key naming — tool-derived defaults + dedup
prosdev Mar 20, 2026
1811842
feat(canvas): show source node name in preset labels
prosdev Mar 20, 2026
1d91233
refactor(canvas): show source hint as secondary text, not in label
prosdev Mar 20, 2026
89857a2
fix(canvas): cascade output_key renames to downstream node mappings
prosdev Mar 20, 2026
e0deee2
fix(canvas): create unique input fields when user_input already claimed
prosdev Mar 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion .claude/agents/plan-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,51 @@ For each gap:

---

### Pass 3: Risk Mitigation Review

You are a senior reliability engineer. Your job is to identify what could go wrong in production and ensure the plan has proactive mitigations.

**Review Criteria:**

#### 1. Failure Mode Analysis
For each significant code path:
- What happens if this fails at runtime?
- Is the failure visible or silent?
- Does the failure cascade to other components?

#### 2. Behavioral Regressions
For each existing behavior being modified:
- What currently works that could break?
- Is there a test that would catch this regression?
- If not, flag it as a test gap (cross-reference with Pass 2)

#### 3. Edge Case Scenarios
Concrete scenarios the plan should address:
- Empty / null / unexpected inputs
- Timing / ordering assumptions
- State that persists across sessions (orphaned fields, stale data)

#### 4. Rollback Strategy
- Can the change be reverted cleanly?
- Are there schema migrations, API changes, or persisted data changes that make rollback non-trivial?
- If rollback is risky, is there an incremental deployment strategy?

#### 5. Blast Radius Assessment
- Frontend-only vs cross-layer changes
- Number of files / components affected
- Does this touch shared utilities used by other features?

**Findings Format:**
```
- **Risk**: [Description of what could go wrong]
- **Likelihood**: High / Medium / Low
- **Impact**: High / Medium / Low
- **Mitigation**: [Specific action to take]
- **Detection**: [How would we know this happened?]
```

---

## Output Format

```markdown
Expand Down Expand Up @@ -147,6 +192,27 @@ For each gap:
|-------------------|-----------------------|
| ... | ... |

---

### Pass 3: Risk Mitigation Review

#### Failure Mode Analysis
| Code Path | Failure Mode | Severity | Mitigation |
|-----------|-------------|----------|------------|
| ... | ... | ... | ... |

#### Rollback Assessment
- **Rollback complexity**: Trivial / Moderate / Complex
- **Reason**: ...
- **Recommended strategy**: ...

#### Risk Register
| # | Risk | Likelihood | Impact | Mitigation | Detection |
|---|------|-----------|--------|------------|-----------|
| 1 | ... | ... | ... | ... | ... |

---

### Verdict
APPROVE / REVISE (with specific items to address)
```
Expand All @@ -157,4 +223,5 @@ APPROVE / REVISE (with specific items to address)
2. Read referenced source files (schema.ts, existing code, skills)
3. Run Pass 1 (Engineering Review)
4. Run Pass 2 (SDET Review)
5. Combine into final output with verdict
5. Run Pass 3 (Risk Mitigation)
6. Combine into final output with verdict (all 3 passes must approve)
4 changes: 2 additions & 2 deletions .claude/gw-plans/canvas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ React 19 + React Flow frontend phases. Depends on execution phases 3-4 for API s
|-------|------|--------|
| 1 | [Canvas core](phase-1-canvas-core/overview.md) -- Home view, Start/LLM/End nodes, edge wiring, config panel, save/load | Complete |
| 2 | [SSE run panel](phase-2-sse-run-panel/overview.md) -- SSE streaming, run panel, node highlighting, reconnection, resume | Complete |
| 3 | [Full node set](phase-3-full-node-set/overview.md) -- Tool/Condition/HumanInput nodes, settings page | Planned |
| 4 | Validation, run input modal, state panel | Not started |
| 3 | [Full node set](phase-3-full-node-set/overview.md) -- Tool/Condition/HumanInput nodes, settings page, run input dialog, graph traversal, auto-map | Complete |
| 4 | [State panel + LLM wiring](phase-4-state-panel/overview.md) -- LLM output_key clean break, LLM input_map/output_key config, state panel, model fetching | Not started |
| 5 | Error handling, run history, debug panel, JSON schema panel | Not started |
| 6 | Python export, JSON read/write, dark mode, polish | Not started |

Expand Down
64 changes: 49 additions & 15 deletions .claude/gw-plans/canvas/phase-3-full-node-set/overview.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,35 @@
# Canvas Phase 3 — Full Node Set + Settings Page

**Status: Complete** (merged to main 2026-03-18)

## Goal

Add Tool, Condition, and HumanInput node components to the canvas so users can
build complete graphs visually. Add a settings page showing provider status.
After this phase, every node type in GraphSchema is drawable and configurable.

## What already exists
## What was delivered

Phase 3 expanded significantly beyond the original scope during implementation.
In addition to the 6 planned parts, the following were pulled forward from
Phase 4 and delivered:

### Pulled forward from Phase 4

| Feature | Files | Why |
|---------|-------|-----|
| **Run input dialog** — schema-driven form/JSON modal | `RunInputDialog.tsx`, `RunFormFields.tsx`, `runInputUtils.ts` | Tool nodes are unusable without a way to provide initial state |
| **Consumed-fields logic** — only show fields that nodes actually read | `runInputUtils.ts` (`getConsumedInputFields`) | Without this, dialog shows every state field including internal ones |
| **Field hints + presets** — tool-aware descriptions, type-filtered dropdowns | `runInputUtils.ts` (`buildFieldHints`), `presetUtils.ts` | Core UX for mapping params to state fields |
| **Graph traversal utilities** — upstream BFS, relevant fields, terminal detection | `graphTraversal.ts` | Context-aware dropdown filtering in ToolNodeConfig |
| **Two-tier ToolNodeConfig** — collapsed summary / expanded card views | `ToolNodeConfig.tsx` | Auto-mapped params need a compact view; editing needs detail |
| **Auto-map** — automatic param→state field matching with field creation | `presetUtils.ts` (`autoMapParams`) | Eliminates manual wiring for simple graphs |
| **Reducer-aware classification** — append/merge fields stay as user inputs | `runInputUtils.ts` (`classifyFields`) | LLM default `output_key: "messages"` was hiding the messages input |
| **Combobox UI** — cmdk-based searchable dropdown | `Combobox.tsx`, `Command.tsx`, `Popover.tsx` | Run dialog field selection |
| **Skip-dialog for zero-input graphs** — run immediately when no inputs needed | `CanvasHeader.tsx` (uncommitted) | UX: `Start → Tool → End` shouldn't prompt for input |
| **Pydantic model_dump in state_utils** — LangChain message object support | `state_utils.py` (uncommitted) | `messages[-1].content` expressions failed on Pydantic objects |

### Originally planned (all complete)

| Layer | Status |
|-------|--------|
Expand All @@ -16,8 +39,8 @@ After this phase, every node type in GraphSchema is drawable and configurable.
| `GET /settings/providers` endpoint | Returns configured status per provider |
| BaseNodeShell, StartNode, LLMNode, EndNode components | Complete |
| Config panel pattern (NodeConfigPanel, LLMNodeConfig) | Complete |
| NODE_DEFAULTS, TOOLBAR_ITEMS, nodeTypes | Only start/llm/end registered |
| Client-side validation (validateGraph) | Only start/end/llm rules |
| NODE_DEFAULTS, TOOLBAR_ITEMS, nodeTypes | All 6 types registered |
| Client-side validation (validateGraph) | All node types covered |

## Architecture

Expand Down Expand Up @@ -112,25 +135,36 @@ Model fetching is Phase 4+ (endpoint currently returns empty models arrays).

## Parts

| Part | Summary | Depends on |
|------|---------|------------|
| 3.1 | [Node defaults + toolbar](phase-3.1-node-defaults-toolbar.md) -- Register 3 new node types | -- |
| 3.2 | [ToolNode config](phase-3.2-tool-node-config.md) -- Tool select, input_map editor, output_key | 3.1 |
| 3.3 | [ConditionNode config + edge wiring](phase-3.3-condition-node-config.md) -- 6 condition types, branch edges | 3.1 |
| 3.4 | [HumanInputNode config](phase-3.4-human-input-node-config.md) -- Prompt, input_key, timeout | 3.1 |
| 3.5 | [Validation rules](phase-3.5-validation.md) -- Client-side rules for new nodes | 3.2, 3.3, 3.4 |
| 3.6 | [Settings page](phase-3.6-settings-page.md) -- Provider status + tool list | 3.2 (settingsSlice) |
| Part | Summary | Status |
|------|---------|--------|
| 3.1 | [Node defaults + toolbar](phase-3.1-node-defaults-toolbar.md) -- Register 3 new node types | Complete |
| 3.2 | [ToolNode config](phase-3.2-tool-node-config.md) -- Tool select, input_map editor, output_key | Complete (expanded: two-tier UI, auto-map, presets, graph traversal) |
| 3.3 | [ConditionNode config + edge wiring](phase-3.3-condition-node-config.md) -- 6 condition types, branch edges | Complete |
| 3.4 | [HumanInputNode config](phase-3.4-human-input-node-config.md) -- Prompt, input_key, timeout | Complete |
| 3.5 | [Validation rules](phase-3.5-validation.md) -- Client-side rules for new nodes | Complete |
| 3.6 | [Settings page](phase-3.6-settings-page.md) -- Provider status + tool list | Complete |

## Remaining for Phase 4

## Out of scope (Phase 4+)
Items originally scoped as Phase 4 that were NOT pulled forward:

- **State panel** (Phase 4) -- full state field management, input_map visual wiring
- **State panel** -- full state field management UI (add/remove/reorder fields, configure reducers)
- **LLM input_map wiring** -- LLM nodes currently show "State wiring coming soon"; need input_map + output_key config like Tool nodes have
- **Model fetching** from providers -- settings/providers currently returns empty models[]
- **Custom edge components** -- edges use default React Flow rendering with labels
- **Condition branch auto-creation** -- users create edges manually
- **Run input modal** (Phase 4) -- schema-driven form for providing run input
- **Debug panel** (Phase 5) -- per-node state inspection during runs
- **LLM router condition wizard** -- users fill in the form fields directly

### Known architectural debt (from Phase 3)

- **LLM default `output_key: "messages"`** — LLM nodes should write to a
dedicated field (e.g. `llm_response`) and the execution layer should append
to `messages`. Currently `classifyFields` works around this with reducer-aware
filtering. See TODOs in `nodeDefaults.ts` and `runInputUtils.ts`.
- **Tool result not auto-wired to LLM** — In `Start → Tool → LLM → End`, the
LLM's empty `input_map` means `tool_result` never reaches the LLM. Requires
the State panel + LLM input_map config to fix properly.

## Architecture constraints

- Components read store only -- no `fetch()`, no API imports
Expand Down
169 changes: 169 additions & 0 deletions .claude/gw-plans/canvas/phase-4-state-panel/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Canvas Phase 4 -- State Panel + LLM Wiring

**Status: Not started**

## Goal

Give users full control over graph state and LLM node wiring. After this phase:

1. LLM nodes write to `llm_response` (not `messages`), fixing the reducer workaround
2. LLM nodes have the same input_map/output_key config as Tool nodes
3. A left-sidebar State Panel shows all state fields with add/remove/type/reducer editing
4. Model dropdowns show real models fetched from providers

## Architecture

```
CanvasRoute
+-------------------------------------------------------------+
| CanvasHeader (h-12) |
+-------------------------------------------------------------+
| main (relative h-[calc(100vh-3rem)]) |
| |
| +--StatePanel--+ +---GraphCanvas---+ +--NodeConfig--+ |
| | (Sheet left) | | | | (Sheet right)| |
| | side="left" | | ReactFlow | | side="right" | |
| | | | | | | |
| | [messages] | | FloatingTbar | | LLM Config | |
| | [user_input] | | | | input_map | |
| | [llm_resp.] | | | | output_key | |
| | + Add field | | | | | |
| +--------------+ +-----------------+ +--------------+ |
| |
| +---RunPanel (Sheet bottom)---+ |
| | ... | |
| +-----------------------------+ |
+-------------------------------------------------------------+

State Panel and NodeConfig are both visible simultaneously.
They use the existing Sheet component (side="left" / side="right").
```

### Data flow: LLM output_key clean break

```
BEFORE (Phase 3):
LLM node: output_key = "messages"
_make_llm_node() returns { "messages": response.content }
LangGraph add_messages reducer appends it
classifyFields: "messages" has append reducer -> stays in inputFields (workaround)

AFTER (Phase 4):
LLM node: output_key = "llm_response"
_make_llm_node() returns {
"llm_response": response.content,
"messages": [AIMessage(content=response.content)] <-- NEW
}
LangGraph add_messages reducer appends the AIMessage to history
classifyFields: output_key "llm_response" is replace -> excluded
"messages" never in outputKeys -> always in inputFields
Reducer check KEPT for merge/append fields (legitimate feature)
DEFAULT_STATE includes: llm_response (type: "string", reducer: "replace")
```

### Data flow: LLM input_map config

```
User selects LLM node -> NodeConfigPanel -> LLMNodeConfig
|
+-- Provider/Model (existing)
+-- System Prompt (existing)
+-- Temperature (existing)
|
+-- Input Mappings (NEW)
| collapsed: param <- source
| expanded: card editor
| presets from getRelevantFields()
|
+-- Output Key (NEW, hidden if terminal)
| default: "llm_response"

LLM auto-map differs from Tool:
- No parameter registry (LLM has no typed params)
- LLM has implicit inputs: "messages" (conversation) or explicit input_map
- If input_map empty: uses messages from state (conversational pattern)
- If input_map present: formats inputs as HumanMessage content
- Users add rows manually (no schema-driven auto-fill)
```

### Data flow: Model fetching

```
GET /v1/settings/providers
|
v
Execution layer: for each configured provider, call provider API
|
openai: client.models.list() -> filter chat models
gemini: genai.list_models() -> filter generateContent
anthropic: hardcoded list (no list API)
|
v
Response: { openai: { configured: true, models: ["gpt-4o", ...] }, ... }
|
v
settingsSlice.loadProviders() -> stores in state
|
v
LLMNodeConfig reads settingsSlice.providers -> populates model dropdown
Falls back to hardcoded MODEL_OPTIONS if fetch fails or empty
```

## Scope decisions

| Decision | Choice | Rationale |
|----------|--------|-----------|
| LLM output_key default | `"llm_response"` everywhere | Solo dev, can wipe graphs. Eliminates reducer workaround |
| State panel location | Left sidebar Sheet | Simultaneous view with right-side NodeConfig |
| State panel discoverability | 3 entry points: toolbar, "Manage fields →" link in configs, "+ New field..." in dropdown | Single toolbar icon is too hidden; link from node config creates a direct path |
| Terminology | "This node reads from" not "Input Mappings"; "When updated" not "Reducer" | User-facing language, not execution-layer jargon |
| Config panel sections | Collapsible "Model Settings" group in LLM config | Reduces scrolling; defaults collapsed after initial provider/model setup |
| Delete safety | Undo toast (5s) after field deletion + amber badge on affected nodes | Faster and more forgiving than confirmation-only; surfaces breakage immediately |
| Cross-panel linking | Clickable node names in state panel usage lines | Direct path from state field to node config without manual searching |
| Model fetching | Include in this phase | Low effort, high value. Endpoint exists, just returns empty |
| LLM auto-map | Manual rows only | LLM has no parameter registry. User adds rows as needed |
| Field reordering in state panel | Not in scope | Nice-to-have, adds drag complexity. Defer to Phase 5+ |

## Parts

| Part | Summary | Dependencies | Est. files |
|------|---------|-------------|------------|
| 4.1 | [LLM output_key clean break](phase-4.1-llm-output-key.md) | None | 5 |
| 4.2 | [LLM input_map + output_key config](phase-4.2-llm-config-wiring.md) | 4.1 | 3 |
| 4.3 | [State panel](phase-4.3-state-panel.md) | 4.1 | 5 |
| 4.4 | [Model fetching](phase-4.4-model-fetching.md) | None (parallel with 4.2/4.3) | 4 |

Parts 4.2 and 4.3 depend on 4.1. Part 4.4 is independent.
After 4.1, parts 4.2/4.3/4.4 can proceed in parallel.

## Out of scope

- Field reordering in state panel (drag-and-drop)
- Custom edge components (React Flow edge rendering)
- Debug panel (Phase 5)
- LLM router condition wizard improvements
- Python export
- System prompt template variables (e.g. `{{field_name}}` interpolation)
- State field validation rules (min/max for numbers, regex for strings)
- Undo/redo for state field changes

## Architecture constraints

- Components read store only -- no fetch(), no API imports (enforced by tsconfig)
- @api layer handles all HTTP calls
- Settings data cached in settingsSlice (fetch-once pattern)
- Schema changes are breaking changes -- but `llm_response` default is NOT a schema
change (it's a config default change). The schema interface stays the same.
- Execution layer changes must not break existing Tool node behavior
- State panel must work with zero state fields (empty graph)
- Sheet component already supports side="left" -- no UI library changes needed

## Decisions & risks

| Decision | Risk | Mitigation |
|----------|------|------------|
| Change LLM default to `llm_response` | Existing saved graphs have `output_key: "messages"` | Solo dev, wipe graphs. No migration needed. |
| Execution appends AIMessage to messages | Double-write if user manually sets output_key to "messages" | Execution only appends to messages when output_key != "messages". If user sets "messages", old behavior preserved. |
| Left sidebar Sheet | May overlap with floating toolbar on small screens | Toolbar is positioned at left-4 (16px). Sheet is w-80 (320px). When sheet is open, toolbar stays underneath -- acceptable since user is focused on state panel. |
| Real model fetching | Provider APIs may be slow/fail | Use fallback hardcoded list. Fetch is async, non-blocking. Show "Loading..." during fetch. |
| State panel field deletion warning | User might ignore warning and break nodes | Warning is informational. Node configs with dangling references show validation errors on Run. |
Loading
Loading