2529 lines
80 KiB
Markdown
2529 lines
80 KiB
Markdown
# Visual Navigation Editor for navigation.json
|
||
|
||
## Concept Overview
|
||
A drag-and-drop visual interface for creating and editing the navigation flow, similar to Talend's workflow editor. This would replace manual JSON editing with an intuitive node-based editor.
|
||
|
||
## Similar Systems & Industry Parallels
|
||
|
||
This visual navigation editor concept follows proven patterns from industrial automation and visual programming tools:
|
||
|
||
### **Node-RED** (Industrial IoT & Automation)
|
||
- Flow-based programming for IoT devices and industrial automation
|
||
- Drag-and-drop nodes with input/output connections
|
||
- **Sub-flows**: Reusable flow components that can be nested and shared
|
||
- Used in manufacturing, building automation, industrial control systems
|
||
- **Key Feature**: Double-click sub-flow to drill down and edit internal logic
|
||
|
||
### **LabVIEW** (National Instruments - Industrial Control)
|
||
- Visual programming for measurement and control systems
|
||
- **SubVIs**: Reusable virtual instruments (similar to functions/methods)
|
||
- Used in aerospace, automotive testing, manufacturing quality control
|
||
- Hierarchical design: Complex systems broken into manageable sub-components
|
||
- **Key Feature**: Icon view shows high-level, double-click to edit internal diagram
|
||
|
||
### **PLC Ladder Logic** (Industrial Machine Control)
|
||
- Visual programming for Programmable Logic Controllers
|
||
- **Function Blocks**: Reusable logic blocks for common operations
|
||
- Used in factory automation, assembly lines, industrial machinery
|
||
- Modular design allows sharing common patterns across machines
|
||
|
||
### **Unreal Engine Blueprints** (Game Development)
|
||
- Visual scripting for game logic
|
||
- **Blueprint Functions/Macros**: Reusable logic components
|
||
- **Collapse to Function**: Select nodes, create reusable component
|
||
- Used by game developers worldwide for complex interactive systems
|
||
|
||
### **Power Automate / Microsoft Flow** (Business Process Automation)
|
||
- No-code workflow automation
|
||
- Visual flow designer with connectors and conditions
|
||
- Used for business process automation in enterprises
|
||
|
||
### **Why This Pattern Works**
|
||
- **Visual Clarity**: See the entire flow at a glance
|
||
- **Complexity Management**: Nest/collapse to manage large systems
|
||
- **Reusability**: Build once, use everywhere
|
||
- **Collaboration**: Team members can understand and contribute
|
||
- **Lower Barrier**: Non-programmers can build complex logic
|
||
|
||
Your navigation editor would bring these industrial-strength patterns to product configuration workflows!
|
||
|
||
---
|
||
|
||
## 🏗️ Foundational Architecture: Component-Based Rendering System
|
||
|
||
### Overview
|
||
|
||
The navigation system will use a **component-based architecture** where each question node contains an ordered array of components that render sequentially from top to bottom. This replaces the current type-based system (`inputType: "button"` vs `inputType: "form"`) with a more flexible, composable approach.
|
||
|
||
### Key Principle
|
||
|
||
**Components set variables, navigation controls flow.**
|
||
|
||
- **Toggle/Field components**: Set variables in the application state
|
||
- **Navigation components**: Control which page appears next
|
||
- **Notes/Separator components**: Provide information and visual structure
|
||
|
||
Components can be combined in any order, allowing for maximum flexibility without mixing concerns (e.g., navigation buttons remain separate from data input controls).
|
||
|
||
### Current System vs. Component-Based System
|
||
|
||
#### Current System (Limited)
|
||
```json
|
||
{
|
||
"q-door-type": {
|
||
"type": "question",
|
||
"inputType": "button", // ← Only navigation OR only form
|
||
"title": "What type of door?",
|
||
"answers": [...]
|
||
},
|
||
"q-dimensions": {
|
||
"type": "question",
|
||
"inputType": "form", // ← Can't mix with navigation
|
||
"measurementType": {...},
|
||
"fields": [...]
|
||
}
|
||
}
|
||
```
|
||
|
||
**Limitations:**
|
||
- `inputType: "button"` = Only navigation buttons
|
||
- `inputType: "form"` = Only input fields (no navigation buttons)
|
||
- Can't have multiple toggle groups
|
||
- Navigation always at bottom
|
||
- Hard to add new input types
|
||
|
||
#### Component-Based System (Flexible)
|
||
```json
|
||
{
|
||
"q-configure-door": {
|
||
"type": "question",
|
||
"title": "Configure Your Door",
|
||
"subtitle": "Set your preferences and continue",
|
||
"components": [
|
||
{
|
||
"type": "toggle",
|
||
"variable": "includeHardware",
|
||
"label": "Include Hardware?",
|
||
"options": [
|
||
{ "value": "yes", "label": "Yes", "default": true },
|
||
{ "value": "no", "label": "No" }
|
||
]
|
||
},
|
||
{
|
||
"type": "fields",
|
||
"label": "Installation Details",
|
||
"fields": [
|
||
{ "name": "installDate", "label": "Preferred Installation Date", "type": "date" },
|
||
{ "name": "notes", "label": "Special Instructions", "type": "text", "optional": true }
|
||
]
|
||
},
|
||
{
|
||
"type": "toggle",
|
||
"variable": "measurementType",
|
||
"label": "Measurement Type",
|
||
"options": [
|
||
{ "value": "opening-size", "label": "Opening Size", "icon": "📏", "default": true },
|
||
{ "value": "tip-to-tip", "label": "Tip-to-tip", "icon": "📏" }
|
||
]
|
||
},
|
||
{
|
||
"type": "fields",
|
||
"fields": [
|
||
{ "name": "width", "label": "Width (inches)", "type": "number", "required": true },
|
||
{ "name": "height", "label": "Height (inches)", "type": "number", "required": true }
|
||
]
|
||
},
|
||
{
|
||
"type": "navigation",
|
||
"answers": [
|
||
{ "caption": "Continue", "next": "results", "primary": true },
|
||
{ "caption": "Skip Optional Settings", "next": "q-accessories" }
|
||
]
|
||
},
|
||
{
|
||
"type": "notes",
|
||
"content": "<p><strong>Tip:</strong> For accurate measurements, refer to our <a href='#'>measuring guide</a>.</p>"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**Advantages:**
|
||
- ✅ Mix any components in any order
|
||
- ✅ Multiple toggle groups in same question
|
||
- ✅ Multiple field groups in same question
|
||
- ✅ Navigation can be anywhere (top, middle, bottom)
|
||
- ✅ Easy to add new component types
|
||
- ✅ Clean separation of concerns
|
||
- ✅ Editor-friendly (drag components to reorder)
|
||
|
||
### Component Type Reference
|
||
|
||
#### 1. **Toggle Component**
|
||
Sets a variable by presenting mutually exclusive options (radio button style).
|
||
|
||
```json
|
||
{
|
||
"type": "toggle",
|
||
"variable": "rushOrder",
|
||
"label": "Rush Order?",
|
||
"options": [
|
||
{ "value": "yes", "label": "Rush (3-5 days)", "icon": "⚡", "default": false },
|
||
{ "value": "no", "label": "Standard (2-3 weeks)", "icon": "📦", "default": true }
|
||
]
|
||
}
|
||
```
|
||
|
||
**Properties:**
|
||
- `variable` (string, required): Variable name to store selection
|
||
- `label` (string, optional): Section heading
|
||
- `options` (array, required): Available choices
|
||
- `value`: Stored value
|
||
- `label`: Display text
|
||
- `icon`: Optional emoji/icon
|
||
- `default`: Whether this is the default selection
|
||
- `disabled`: Whether this option is disabled
|
||
|
||
#### 2. **Fields Component**
|
||
Group of input fields for collecting user data.
|
||
|
||
```json
|
||
{
|
||
"type": "fields",
|
||
"label": "Product Dimensions",
|
||
"fields": [
|
||
{
|
||
"name": "width",
|
||
"label": "Width (inches)",
|
||
"type": "number",
|
||
"required": true,
|
||
"min": 12,
|
||
"max": 120,
|
||
"placeholder": "26"
|
||
},
|
||
{
|
||
"name": "height",
|
||
"label": "Height (inches)",
|
||
"type": "number",
|
||
"required": true,
|
||
"placeholder": "81"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Properties:**
|
||
- `label` (string, optional): Group heading
|
||
- `fields` (array, required): Input fields
|
||
- `name`: Variable name
|
||
- `label`: Field label
|
||
- `type`: Input type (text, number, date, email, tel, textarea)
|
||
- `required`: Whether field is required
|
||
- `placeholder`: Placeholder text
|
||
- `min`, `max`: Constraints for number/date fields
|
||
- `pattern`: Regex validation pattern
|
||
|
||
#### 3. **Navigation Component**
|
||
Navigation buttons to control flow to next question/page.
|
||
|
||
```json
|
||
{
|
||
"type": "navigation",
|
||
"answers": [
|
||
{
|
||
"caption": "Continue",
|
||
"next": "results",
|
||
"primary": true,
|
||
"filter": { "configComplete": true }
|
||
},
|
||
{
|
||
"caption": "Add Accessories",
|
||
"next": "q-accessories"
|
||
},
|
||
{
|
||
"caption": "Start Over",
|
||
"next": "start",
|
||
"style": "secondary"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Properties:**
|
||
- `answers` (array, required): Navigation buttons
|
||
- `caption`: Button text (can include HTML)
|
||
- `image`: Optional icon/emoji
|
||
- `next`: Target question ID
|
||
- `primary`: Style as primary action button
|
||
- `style`: Button style (primary, secondary, danger)
|
||
- `filter`: Filter object to apply on click
|
||
- `dimensions`: Dimension object to set on click
|
||
|
||
#### 4. **Notes Component**
|
||
Static informational content (tips, instructions, links).
|
||
|
||
```json
|
||
{
|
||
"type": "notes",
|
||
"content": "<p><strong>How to Measure:</strong></p><ul><li>Measure the rough opening</li><li>Round down to nearest inch</li></ul>",
|
||
"style": "info"
|
||
}
|
||
```
|
||
|
||
**Properties:**
|
||
- `content` (string, required): HTML content
|
||
- `style` (string, optional): Visual style (info, warning, tip, default)
|
||
|
||
#### 5. **Separator Component** (Optional)
|
||
Visual divider between sections.
|
||
|
||
```json
|
||
{
|
||
"type": "separator",
|
||
"style": "line"
|
||
}
|
||
```
|
||
|
||
**Properties:**
|
||
- `style`: Separator style (line, space, none)
|
||
|
||
### Rendering Order Example
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ Configure Your Door │ ← Title/Subtitle
|
||
├─────────────────────────────────────────┤
|
||
│ Include Hardware? │
|
||
│ ● Yes ○ No │ ← Toggle Component #1
|
||
├─────────────────────────────────────────┤
|
||
│ Installation Details │
|
||
│ [Preferred Installation Date: ____] │
|
||
│ [Special Instructions: _________] │ ← Fields Component #1
|
||
├─────────────────────────────────────────┤
|
||
│ Measurement Type │
|
||
│ ● Opening Size ○ Tip-to-tip │ ← Toggle Component #2
|
||
├─────────────────────────────────────────┤
|
||
│ Width (inches): [____] │
|
||
│ Height (inches): [____] │ ← Fields Component #2
|
||
├─────────────────────────────────────────┤
|
||
│ [Continue] [Add Accessories] │ ← Navigation Component
|
||
├─────────────────────────────────────────┤
|
||
│ 💡 Tip: For accurate measurements... │ ← Notes Component
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
### Rendering Logic Implementation
|
||
|
||
```javascript
|
||
function renderQuestion(questionData) {
|
||
// Render title/subtitle
|
||
renderHeader(questionData.title, questionData.subtitle);
|
||
|
||
// Iterate through components array
|
||
questionData.components.forEach((component, index) => {
|
||
const container = document.createElement('div');
|
||
container.className = `component component-${component.type}`;
|
||
container.dataset.componentIndex = index;
|
||
|
||
switch(component.type) {
|
||
case 'toggle':
|
||
renderToggleComponent(container, component);
|
||
break;
|
||
case 'fields':
|
||
renderFieldsComponent(container, component);
|
||
break;
|
||
case 'navigation':
|
||
renderNavigationComponent(container, component);
|
||
break;
|
||
case 'notes':
|
||
renderNotesComponent(container, component);
|
||
break;
|
||
case 'separator':
|
||
renderSeparatorComponent(container, component);
|
||
break;
|
||
default:
|
||
console.warn(`Unknown component type: ${component.type}`);
|
||
}
|
||
|
||
document.getElementById('question-container').appendChild(container);
|
||
});
|
||
}
|
||
|
||
function handleFormSubmit(questionData) {
|
||
const variables = {};
|
||
|
||
// Collect all variable values from toggle and field components
|
||
questionData.components.forEach(component => {
|
||
if (component.type === 'toggle') {
|
||
const selected = document.querySelector(`input[name="${component.variable}"]:checked`);
|
||
variables[component.variable] = selected ? selected.value : null;
|
||
}
|
||
if (component.type === 'fields') {
|
||
component.fields.forEach(field => {
|
||
const input = document.querySelector(`[name="${field.name}"]`);
|
||
variables[field.name] = input ? input.value : null;
|
||
});
|
||
}
|
||
});
|
||
|
||
// Validate all required fields
|
||
if (!validateComponents(questionData.components)) {
|
||
return false;
|
||
}
|
||
|
||
// Save variables to userAnswers
|
||
Object.assign(userAnswers, variables);
|
||
|
||
// Navigation is handled by navigation component button clicks
|
||
return true;
|
||
}
|
||
```
|
||
|
||
### Migration Path from Current System
|
||
|
||
To maintain backwards compatibility during transition:
|
||
|
||
1. **Phase 1: Dual Support** - Support both old and new formats
|
||
2. **Phase 2: Auto-conversion** - Auto-convert old format to components on load
|
||
3. **Phase 3: Deprecation** - Remove old format support
|
||
|
||
```javascript
|
||
function normalizeQuestion(questionData) {
|
||
// If already using components, return as-is
|
||
if (questionData.components) {
|
||
return questionData;
|
||
}
|
||
|
||
// Auto-convert old format to components
|
||
const components = [];
|
||
|
||
if (questionData.inputType === 'button') {
|
||
// Convert to navigation component
|
||
components.push({
|
||
type: 'navigation',
|
||
answers: questionData.answers
|
||
});
|
||
}
|
||
|
||
if (questionData.inputType === 'form') {
|
||
// Convert measurementType to toggle component
|
||
if (questionData.measurementType) {
|
||
components.push({
|
||
type: 'toggle',
|
||
variable: 'measurementType',
|
||
options: questionData.measurementType.options
|
||
});
|
||
}
|
||
|
||
// Convert fields to fields component
|
||
if (questionData.fields) {
|
||
components.push({
|
||
type: 'fields',
|
||
fields: questionData.fields
|
||
});
|
||
}
|
||
|
||
// Add navigation if next exists
|
||
if (questionData.next) {
|
||
components.push({
|
||
type: 'navigation',
|
||
answers: [
|
||
{ caption: 'Continue', next: questionData.next }
|
||
]
|
||
});
|
||
}
|
||
}
|
||
|
||
// Add notes component if notes exist
|
||
if (questionData.notes) {
|
||
components.push({
|
||
type: 'notes',
|
||
content: questionData.notes
|
||
});
|
||
}
|
||
|
||
return {
|
||
...questionData,
|
||
components
|
||
};
|
||
}
|
||
```
|
||
|
||
### Benefits for Visual Editor
|
||
|
||
This component-based architecture makes the visual editor **dramatically simpler** to implement:
|
||
|
||
#### **Component Palette**
|
||
Drag components from palette onto canvas:
|
||
- 🎚️ Toggle
|
||
- 📝 Fields
|
||
- 🔗 Navigation
|
||
- 📄 Notes
|
||
- ➖ Separator
|
||
|
||
#### **Component List View**
|
||
Within each tile, show reorderable list of components:
|
||
```
|
||
📋 Configure Door
|
||
🎚️ Toggle: Include Hardware?
|
||
📝 Fields: Installation Details (2 fields)
|
||
🎚️ Toggle: Measurement Type
|
||
📝 Fields: Dimensions (2 fields)
|
||
🔗 Navigation: 2 buttons
|
||
📄 Notes: Measuring tip
|
||
[+ Add Component]
|
||
```
|
||
|
||
#### **Drag to Reorder**
|
||
Users can drag components up/down within the list to change render order.
|
||
|
||
#### **Click to Configure**
|
||
Clicking a component opens the config panel with type-specific options.
|
||
|
||
#### **Editor UI Mockup**
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ Component Palette │
|
||
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
|
||
│ │🎚️ │ │📝 │ │🔗 │ │📄 │ │➖ │ │
|
||
│ └────┘ └────┘ └────┘ └────┘ └────┘ │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ Selected Tile: q-configure-door │
|
||
│ │
|
||
│ ┌─────────────────────────────────────────────┐ │
|
||
│ │ 🎚️ Toggle: Include Hardware? [↑][↓] │ │
|
||
│ │ 📝 Fields: Installation Details [↑][↓] │ │
|
||
│ │ 🎚️ Toggle: Measurement Type [↑][↓] │ │
|
||
│ │ 📝 Fields: Dimensions [↑][↓] │ ┌────────┐ │
|
||
│ │ 🔗 Navigation: 2 buttons [↑][↓] │ │ Config │ │
|
||
│ │ 📄 Notes: Measuring tip [↑][↓] │ │ Panel │ │
|
||
│ │ │ │ │ │
|
||
│ │ [+ Add Component ▼] │ │ │ │
|
||
│ └─────────────────────────────────────────────┘ └────────┘ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Implementation Considerations
|
||
|
||
**Validation:**
|
||
- Ensure at least one navigation component exists in reachable nodes
|
||
- Validate variable naming (no conflicts)
|
||
- Ensure required fields are marked
|
||
|
||
**State Management:**
|
||
- All variables collected before navigation
|
||
- Variables persist across question navigation
|
||
- Can reference previous answers in conditional logic
|
||
|
||
**Extensibility:**
|
||
- Easy to add new component types (checkbox group, radio group, slider, datepicker, file upload, etc.)
|
||
- Plugin architecture for custom components
|
||
- Community-contributed component library
|
||
|
||
**Editor Features:**
|
||
- Duplicate component
|
||
- Copy/paste components between tiles
|
||
- Component templates/presets
|
||
- Import component from library
|
||
|
||
---
|
||
|
||
## UI Layout
|
||
|
||
### Two-Panel Design
|
||
|
||
#### Panel 1: Canvas/Drop Area (Left/Main Panel)
|
||
- **Drag-and-drop workspace** where navigation nodes (tiles) can be moved around
|
||
- **Tiles/Nodes** represent each question/step in the navigation
|
||
- **Connection arrows** show the flow between steps
|
||
- **Colored circles** (connection points) on tiles indicate multiple possible outputs
|
||
- Number of circles determined by configuration (buttons, alternatives, etc.)
|
||
- Circles appear when tile is selected
|
||
- Different colors for different types of connections
|
||
- **Grid/snap system** for clean alignment
|
||
- **Zoom and pan** controls for large navigation flows
|
||
- **Minimap** for overview of complex flows
|
||
|
||
#### Panel 2: Configuration Panel (Right/Sidebar)
|
||
- **Tile Properties** editor (activated when a tile is selected)
|
||
- **Dynamic form fields** based on question type:
|
||
- Question ID, title, subtitle
|
||
- Question type selector (buttons, dimensions, multi-select, etc.)
|
||
- Type-specific options (button labels, icons, validation, etc.)
|
||
- **Output/Connection Groups** section:
|
||
- Group buttons together that share the same output path
|
||
- Add/remove connection groups
|
||
- Each group maps to a colored circle on the tile
|
||
- Default output selection (for ungrouped actions)
|
||
- **Default Values** section:
|
||
- List of variable assignments
|
||
- Each row: `[Variable Dropdown] : [Value Field] [Remove Button]`
|
||
- `[Add Another]` button to create new default assignments
|
||
- **Validation** section for input requirements
|
||
- **Conditional Logic** editor for complex routing
|
||
|
||
## Features
|
||
|
||
### Node/Tile Management
|
||
- **Visual representation** of each navigation step
|
||
- Title displayed on tile
|
||
- Icon/badge showing question type
|
||
- Color coding by category
|
||
- **Multiple outputs** via colored circles:
|
||
- Small circles appear on tile edges when selected
|
||
- Each circle represents a possible path forward
|
||
- Example: Door size question has 2 circles (predefined sizes, custom dimensions)
|
||
- **Drag and drop** positioning
|
||
- **Auto-layout** option to organize nodes cleanly
|
||
- **Duplicate node** feature for similar questions
|
||
- **Delete with confirmation** and automatic connection cleanup
|
||
|
||
### Connection System
|
||
|
||
#### Option A: Click-and-Drag (Visual)
|
||
- Click a circle on source tile
|
||
- Drag to create arrow/line
|
||
- Drop on target tile to connect
|
||
- Arrow shows direction of flow
|
||
- Hover shows connection metadata (condition, button labels)
|
||
|
||
#### Option B: Dropdown Selection (Form-based)
|
||
- Select a connection group in config panel
|
||
- Dropdown shows all available destination tiles
|
||
- Select target from list
|
||
- Visual arrow updates automatically
|
||
|
||
#### Option C: Hybrid Approach (Recommended)
|
||
- Both methods available
|
||
- Click-drag for quick visual connections
|
||
- Dropdown for precise selection or when tiles are far apart
|
||
- Arrows can be clicked to edit connection details
|
||
|
||
### Button/Output Grouping
|
||
Buttons within a tile can be grouped to share the same output:
|
||
|
||
**Example 1: Door Size Question**
|
||
- Group 1: "All predefined size buttons" → Circle 1 (blue) → Next standard question
|
||
- Group 2: "Custom dimensions button" → Circle 2 (orange) → Alternative dimension entry
|
||
- Default: Any ungrouped actions → Circle 3 (gray) → Fallback route
|
||
|
||
**Example 2: Simple Yes/No Question**
|
||
- Group 1: "Yes button" → Circle 1 → Path A
|
||
- Group 2: "No button" → Circle 2 → Path B
|
||
|
||
**Configuration UI:**
|
||
```
|
||
Output Groups:
|
||
┌─────────────────────────────────────┐
|
||
│ Group 1: Predefined Sizes │
|
||
│ Buttons: [32x80, 36x80, other...] │
|
||
│ → Connects to: [q-hinges ▼] │
|
||
│ Circle color: [🔵 Blue] │
|
||
└─────────────────────────────────────┘
|
||
┌─────────────────────────────────────┐
|
||
│ Group 2: Custom Entry │
|
||
│ Buttons: [Enter Custom] │
|
||
│ → Connects to: [q-alt-path ▼] │
|
||
│ Circle color: [🟠 Orange] │
|
||
└─────────────────────────────────────┘
|
||
┌─────────────────────────────────────┐
|
||
│ Default Output: [q-next-step ▼] │
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
### Default Values System
|
||
Users need to set default variable values that apply when reaching certain nodes.
|
||
|
||
**Configuration UI:**
|
||
```
|
||
Default Values to Set:
|
||
┌──────────────────────────────────────────────┐
|
||
│ [doorType ▼] : [storm door ] [✖] │
|
||
│ [measurementSystem ▼]: [imperial ] [✖] │
|
||
│ [region ▼] : [north-america ] [✖] │
|
||
└──────────────────────────────────────────────┘
|
||
[+ Add Another Default Value]
|
||
```
|
||
|
||
**Features:**
|
||
- Variable dropdown populated from:
|
||
- Previously used variables across all navigation nodes
|
||
- Manual entry option to add new variables
|
||
- Suggesting common variables based on question type
|
||
- Value field accepts:
|
||
- Static values
|
||
- References to other variables (e.g., `${previousAnswer}`)
|
||
- Expressions for calculations
|
||
- Remove button to delete a default value assignment
|
||
- Add button to create additional assignments
|
||
|
||
## Technical Implementation
|
||
|
||
### Technology Stack Options
|
||
|
||
#### Option 1: Web-Based Standalone Tool
|
||
**Frontend:**
|
||
- **React** or **Vue.js** for UI framework
|
||
- **React Flow** or **Vue Flow** for node-based canvas
|
||
- Built-in drag-drop, zoom, pan
|
||
- Customizable nodes and connections
|
||
- Good performance with large graphs
|
||
- **Tailwind CSS** or **Material-UI** for styling
|
||
- **Monaco Editor** for any direct JSON editing fallback
|
||
|
||
**Backend:**
|
||
- **Python Flask** (existing stack) with endpoints:
|
||
- `GET /api/editor/navigation` - Load current navigation.json
|
||
- `POST /api/editor/navigation` - Save edited navigation
|
||
- `GET /api/editor/validate` - Validate navigation structure
|
||
- `GET /api/editor/variables` - Get list of known variables
|
||
- File system operations to read/write navigation.json
|
||
- Validation logic to ensure navigation structure is valid
|
||
|
||
**Pros:**
|
||
- Can be integrated into existing Flask app
|
||
- Accessible from any browser
|
||
- No installation required
|
||
- Easy to share and collaborate
|
||
|
||
**Cons:**
|
||
- Requires server to be running
|
||
- Limited offline capability
|
||
|
||
#### Option 2: Desktop Application
|
||
**Framework:**
|
||
- **Electron** (JavaScript-based desktop app)
|
||
- Wrap the web-based editor
|
||
- Direct file system access
|
||
- Can bundle Python scripts
|
||
- **Tauri** (Rust-based, lighter alternative to Electron)
|
||
|
||
**Pros:**
|
||
- Better file system integration
|
||
- Can work offline
|
||
- More desktop-native experience
|
||
- Better performance
|
||
|
||
**Cons:**
|
||
- Requires installation
|
||
- More complex deployment
|
||
- Additional development effort
|
||
|
||
#### Option 3: VS Code Extension
|
||
**Framework:**
|
||
- VS Code Extension API
|
||
- Webview for the visual editor
|
||
- Language server for validation
|
||
|
||
**Pros:**
|
||
- Integrates with existing development environment
|
||
- Can use VS Code's existing features (git, etc.)
|
||
- Access to VS Code extension marketplace
|
||
- Syntax highlighting fallback to JSON
|
||
|
||
**Cons:**
|
||
- Limited to VS Code users
|
||
- Extension development learning curve
|
||
- More restrictive UI capabilities
|
||
|
||
### Recommended Approach: Web-Based (Option 1)
|
||
Most practical for this project given the existing Flask infrastructure.
|
||
|
||
## Implementation Phases
|
||
|
||
### Phase 1: Core Canvas (MVP)
|
||
**Goals:**
|
||
- Display existing navigation.json as visual nodes
|
||
- Basic drag-and-drop positioning
|
||
- Simple click-to-select nodes
|
||
- Read-only view to validate the concept
|
||
|
||
**Deliverables:**
|
||
- Canvas component with nodes
|
||
- JSON parser to convert navigation.json to node graph
|
||
- Basic visual styling for nodes
|
||
|
||
**Estimated Effort:** 20-30 hours
|
||
|
||
### Phase 2: Basic Editing
|
||
**Goals:**
|
||
- Configuration panel for editing node properties
|
||
- Add/delete nodes
|
||
- Simple one-to-one connections
|
||
- Save back to navigation.json
|
||
|
||
**Deliverables:**
|
||
- Config panel UI
|
||
- Node CRUD operations
|
||
- JSON serializer to convert graph back to navigation.json
|
||
- Save functionality
|
||
|
||
**Estimated Effort:** 30-40 hours
|
||
|
||
### Phase 3: Advanced Connections
|
||
**Goals:**
|
||
- Multiple output circles per node
|
||
- Button grouping system
|
||
- Visual connection management (drag-drop arrows)
|
||
- Connection validation
|
||
|
||
**Deliverables:**
|
||
- Output group configuration UI
|
||
- Connection point rendering
|
||
- Arrow drawing and management
|
||
- Validation logic
|
||
|
||
**Estimated Effort:** 25-35 hours
|
||
|
||
### Phase 4: Default Values & Variables
|
||
**Goals:**
|
||
- Default values configuration section
|
||
- Variable management system
|
||
- Variable autocomplete/suggestions
|
||
|
||
**Deliverables:**
|
||
- Default values UI
|
||
- Variable tracking system
|
||
- Smart suggestions
|
||
|
||
**Estimated Effort:** 15-20 hours
|
||
|
||
### Phase 5: Polish & Features
|
||
**Goals:**
|
||
- Auto-layout algorithm
|
||
- Zoom/pan controls
|
||
- Minimap
|
||
- Undo/redo
|
||
- Export/import
|
||
- Validation reporting
|
||
- Help tooltips
|
||
|
||
**Deliverables:**
|
||
- All polish features
|
||
- Documentation
|
||
- User guide
|
||
|
||
**Estimated Effort:** 20-30 hours
|
||
|
||
**Total Estimated Effort:** 110-155 hours (3-4 weeks full-time development)
|
||
|
||
## Data Structure Mapping
|
||
|
||
### Current navigation.json Structure (Runtime Format)
|
||
```json
|
||
{
|
||
"start": {
|
||
"type": "question",
|
||
"inputType": "button",
|
||
"title": "What are you looking for?",
|
||
"subtitle": "Select the product category",
|
||
"answers": [
|
||
{
|
||
"caption": "Door",
|
||
"image": "🚪",
|
||
"next": "q-door-type",
|
||
"filter": { "baseType": "Door" }
|
||
},
|
||
{
|
||
"caption": "Window",
|
||
"image": "🪟",
|
||
"next": "q-window-type",
|
||
"filter": { "baseType": "Window" }
|
||
}
|
||
]
|
||
},
|
||
"q-door-type": {
|
||
"type": "question",
|
||
"inputType": "button",
|
||
"title": "What type of door?",
|
||
"answers": [...]
|
||
}
|
||
}
|
||
```
|
||
|
||
### Project File Structure (Editor Format - `.navproj.json`)
|
||
The editor saves this format, which includes all visual layout and editor-specific data:
|
||
```json
|
||
{
|
||
"version": "1.0",
|
||
"metadata": {
|
||
"created": "2026-04-02",
|
||
"lastModified": "2026-04-02",
|
||
"description": "Product Finder Navigation Flow"
|
||
},
|
||
"nodes": [
|
||
{
|
||
"id": "node-1",
|
||
"tileId": "start",
|
||
"type": "NavigationTile",
|
||
"position": { "x": 100, "y": 200 },
|
||
"config": {
|
||
"type": "question",
|
||
"inputType": "button",
|
||
"title": "What are you looking for?",
|
||
"subtitle": "Select the product category",
|
||
"notes": "<p><strong>Quick Tips...</p>",
|
||
"buttons": [
|
||
{
|
||
"caption": "Door",
|
||
"image": "🚪",
|
||
"outputGroup": "output-door",
|
||
"filter": { "baseType": "Door" }
|
||
},
|
||
{
|
||
"caption": "Window",
|
||
"image": "🪟",
|
||
"outputGroup": "output-window",
|
||
"filter": { "baseType": "Window" }
|
||
}
|
||
]
|
||
},
|
||
"outputs": [
|
||
{
|
||
"id": "output-door",
|
||
"color": "#3b82f6",
|
||
"label": "Door Path",
|
||
"targetTileId": "q-door-type"
|
||
},
|
||
{
|
||
"id": "output-window",
|
||
"color": "#10b981",
|
||
"label": "Window Path",
|
||
"targetTileId": "q-window-type"
|
||
}
|
||
],
|
||
"defaults": []
|
||
}
|
||
],
|
||
"connections": [
|
||
{
|
||
"id": "conn-1",
|
||
"from": "node-1",
|
||
"fromOutput": "output-door",
|
||
"to": "node-2"
|
||
},
|
||
{
|
||
"id": "conn-2",
|
||
"from": "node-1",
|
||
"fromOutput": "output-window",
|
||
"to": "node-3"
|
||
}
|
||
],
|
||
"editorSettings": {
|
||
"zoom": 1.0,
|
||
"panX": 0,
|
||
"panY": 0,
|
||
"selectedNodeId": null
|
||
},
|
||
"customTiles": [
|
||
{
|
||
"id": "custom-size-flow",
|
||
"name": "Size Selection Flow",
|
||
"description": "Handles both standard sizes and custom dimensions",
|
||
"icon": "📏",
|
||
"version": "1.0",
|
||
"entryPoint": "node-size-entry",
|
||
"exitPoints": [
|
||
{
|
||
"id": "exit-standard",
|
||
"label": "Standard Size Selected",
|
||
"color": "#3b82f6"
|
||
},
|
||
{
|
||
"id": "exit-custom",
|
||
"label": "Custom Dimensions",
|
||
"color": "#10b981"
|
||
}
|
||
],
|
||
"nodes": [
|
||
{
|
||
"id": "node-size-entry",
|
||
"tileId": "entry",
|
||
"type": "EntryPoint",
|
||
"position": { "x": 50, "y": 150 },
|
||
"config": {},
|
||
"outputs": [
|
||
{
|
||
"id": "to-size-display",
|
||
"targetTileId": "node-size-buttons"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "node-size-buttons",
|
||
"tileId": "size-options",
|
||
"type": "NavigationTile",
|
||
"position": { "x": 250, "y": 150 },
|
||
"config": {
|
||
"title": "Select Size",
|
||
"buttons": [
|
||
{
|
||
"caption": "30\" x 81\"",
|
||
"outputGroup": "standard",
|
||
"dimensions": { "width": 30, "height": 81 }
|
||
},
|
||
{
|
||
"caption": "32\" x 81\"",
|
||
"outputGroup": "standard",
|
||
"dimensions": { "width": 32, "height": 81 }
|
||
},
|
||
{
|
||
"caption": "Custom Size",
|
||
"outputGroup": "custom"
|
||
}
|
||
]
|
||
},
|
||
"outputs": [
|
||
{
|
||
"id": "standard",
|
||
"exitPoint": "exit-standard"
|
||
},
|
||
{
|
||
"id": "custom",
|
||
"targetTileId": "node-dimension-form"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "node-dimension-form",
|
||
"tileId": "custom-dimensions",
|
||
"type": "FormTile",
|
||
"position": { "x": 450, "y": 250 },
|
||
"config": {
|
||
"title": "Enter Custom Dimensions",
|
||
"fields": [
|
||
{ "name": "width", "type": "number" },
|
||
{ "name": "height", "type": "number" }
|
||
]
|
||
},
|
||
"outputs": [
|
||
{
|
||
"id": "form-complete",
|
||
"exitPoint": "exit-custom"
|
||
}
|
||
]
|
||
}
|
||
],
|
||
"connections": [
|
||
{
|
||
"from": "node-size-entry",
|
||
"to": "node-size-buttons"
|
||
},
|
||
{
|
||
"from": "node-size-buttons",
|
||
"fromOutput": "custom",
|
||
"to": "node-dimension-form"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Custom Tiles in Project File:**
|
||
- **customTiles array**: Stores all reusable sub-flows as separate entities
|
||
- **Entry/Exit Points**: Define how the custom tile interfaces with the main flow
|
||
- **Internal nodes**: Complete flow definition within the custom tile
|
||
- **Versioning**: Track custom tile versions for compatibility
|
||
- **Metadata**: Name, description, icon for library display
|
||
|
||
When a custom tile is used in the main flow:
|
||
```json
|
||
{
|
||
"id": "node-5",
|
||
"tileId": "use-size-flow",
|
||
"type": "CustomTile",
|
||
"customTileRef": "custom-size-flow", // References the custom tile
|
||
"position": { "x": 500, "y": 300 },
|
||
"outputs": [
|
||
{
|
||
"id": "standard-path",
|
||
"exitPoint": "exit-standard", // Maps to custom tile's exit
|
||
"targetTileId": "results"
|
||
},
|
||
{
|
||
"id": "custom-path",
|
||
"exitPoint": "exit-custom",
|
||
"targetTileId": "q-accessories"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**Key Differences:**
|
||
- **Project file** includes: positions, colors, output groups, editor state
|
||
- **Navigation file** includes: only runtime data needed by the app
|
||
- **Export process** converts project format → navigation format using class export methods
|
||
- **Import process** can convert existing navigation.json → project format
|
||
|
||
### Tile Class Architecture
|
||
|
||
Each tile type is a class with its own export logic:
|
||
|
||
```typescript
|
||
// Base class for all tiles
|
||
abstract class BaseTile {
|
||
id: string;
|
||
tileId: string;
|
||
position: Position;
|
||
config: any;
|
||
outputs: Output[];
|
||
defaults: DefaultValue[];
|
||
|
||
// Each tile type implements its own export logic
|
||
abstract export(): object;
|
||
|
||
// Validate tile configuration before export
|
||
abstract validate(): ValidationResult;
|
||
}
|
||
|
||
// Navigation tile - standard question with buttons
|
||
class NavigationTile extends BaseTile {
|
||
export(): object {
|
||
return {
|
||
type: this.config.type,
|
||
inputType: this.config.inputType,
|
||
title: this.config.title,
|
||
subtitle: this.config.subtitle,
|
||
notes: this.config.notes,
|
||
answers: this.config.buttons.map(btn => ({
|
||
caption: btn.caption,
|
||
image: btn.image,
|
||
next: this.outputs.find(o => o.id === btn.outputGroup)?.targetTileId,
|
||
filter: btn.filter,
|
||
dimensions: btn.dimensions
|
||
}))
|
||
};
|
||
}
|
||
}
|
||
|
||
// Form tile - custom input fields
|
||
class FormTile extends BaseTile {
|
||
export(): object {
|
||
return {
|
||
type: "question",
|
||
inputType: "form",
|
||
title: this.config.title,
|
||
subtitle: this.config.subtitle,
|
||
notes: this.config.notes,
|
||
measurementType: this.config.measurementType,
|
||
fields: this.config.fields,
|
||
next: this.outputs[0]?.targetTileId || "results"
|
||
};
|
||
}
|
||
}
|
||
|
||
// Results tile - terminal node
|
||
class ResultsTile extends BaseTile {
|
||
export(): object {
|
||
return {
|
||
type: "results",
|
||
title: this.config.title,
|
||
// No outputs - this is terminal
|
||
};
|
||
}
|
||
}
|
||
|
||
// Custom tile - references a reusable sub-flow
|
||
class CustomTile extends BaseTile {
|
||
customTileRef: string; // ID of custom tile definition
|
||
customTileDef: CustomTileDefinition; // Reference to the actual custom tile
|
||
|
||
export(): object {
|
||
// Custom tiles export by "flattening" their internal flow
|
||
// OR by exporting as a single node that gets expanded at runtime
|
||
|
||
// Option A: Flatten (embed internal nodes into main navigation)
|
||
// This makes the exported JSON have no "custom tile" concept
|
||
const flattened = {};
|
||
|
||
// Generate unique tile IDs for internal nodes
|
||
this.customTileDef.nodes.forEach(node => {
|
||
const uniqueId = `${this.id}_${node.tileId}`;
|
||
flattened[uniqueId] = node.export();
|
||
});
|
||
|
||
// Wire entry point to first internal node
|
||
const entryNode = this.customTileDef.entryPoint;
|
||
// Wire exit points back to main flow
|
||
|
||
return flattened;
|
||
|
||
// Option B: Keep as reference (requires app to support custom tiles)
|
||
// return {
|
||
// type: "customTile",
|
||
// ref: this.customTileRef,
|
||
// outputs: this.outputs.map(o => ({
|
||
// exitPoint: o.exitPoint,
|
||
// next: o.targetTileId
|
||
// }))
|
||
// };
|
||
}
|
||
|
||
validate(): ValidationResult {
|
||
const errors = [];
|
||
|
||
// Validate all outputs map to exit points
|
||
this.outputs.forEach(output => {
|
||
const exitExists = this.customTileDef.exitPoints.find(
|
||
ep => ep.id === output.exitPoint
|
||
);
|
||
if (!exitExists) {
|
||
errors.push(`Output references non-existent exit point: ${output.exitPoint}`);
|
||
}
|
||
});
|
||
|
||
// Validate internal flow is complete
|
||
const internalValidation = this.customTileDef.validate();
|
||
errors.push(...internalValidation.errors);
|
||
|
||
return {
|
||
valid: errors.length === 0,
|
||
errors
|
||
};
|
||
}
|
||
}
|
||
```
|
||
|
||
### Export Workflow
|
||
|
||
```typescript
|
||
class NavigationProject {
|
||
nodes: BaseTile[];
|
||
connections: Connection[];
|
||
|
||
// Export entire project to navigation.json
|
||
exportToNavigation(): object {
|
||
const navigation: any = {};
|
||
|
||
// Each node exports itself to JSON
|
||
this.nodes.forEach(node => {
|
||
navigation[node.tileId] = node.export();
|
||
});
|
||
|
||
return navigation;
|
||
}
|
||
|
||
// Import existing navigation.json to project format
|
||
static importFromNavigation(navData: object): NavigationProject {
|
||
const project = new NavigationProject();
|
||
const layout = new AutoLayout(); // Auto-position nodes
|
||
|
||
Object.entries(navData).forEach(([tileId, data], index) => {
|
||
const position = layout.getNextPosition(index);
|
||
const tile = this.createTileFromData(tileId, data, position);
|
||
project.nodes.push(tile);
|
||
});
|
||
|
||
// Reconstruct connections from 'next' references
|
||
project.reconstructConnections();
|
||
|
||
return project;
|
||
}
|
||
|
||
// Validate entire project before export
|
||
validate(): ValidationReport {
|
||
const report = new ValidationReport();
|
||
|
||
this.nodes.forEach(node => {
|
||
const result = node.validate();
|
||
if (!result.isValid) {
|
||
report.addError(node.tileId, result.errors);
|
||
}
|
||
});
|
||
|
||
// Check for orphaned nodes
|
||
const connectedNodes = new Set(
|
||
this.connections.flatMap(c => [c.from, c.to])
|
||
);
|
||
this.nodes.forEach(node => {
|
||
if (!connectedNodes.has(node.id) && node.tileId !== 'start') {
|
||
report.addWarning(node.tileId, 'Node is not connected');
|
||
}
|
||
});
|
||
|
||
return report;
|
||
}
|
||
}
|
||
```
|
||
|
||
### File Operations
|
||
|
||
```typescript
|
||
// Save project file
|
||
async function saveProject(project: NavigationProject, filepath: string) {
|
||
const projectData = {
|
||
version: "1.0",
|
||
metadata: {
|
||
created: project.metadata.created,
|
||
lastModified: new Date().toISOString(),
|
||
description: project.metadata.description
|
||
},
|
||
nodes: project.nodes.map(n => n.toJSON()),
|
||
connections: project.connections,
|
||
editorSettings: project.editorSettings
|
||
};
|
||
|
||
await fs.writeFile(filepath, JSON.stringify(projectData, null, 2));
|
||
}
|
||
|
||
// Export to navigation.json
|
||
async function exportNavigation(project: NavigationProject, filepath: string) {
|
||
// Validate first
|
||
const validation = project.validate();
|
||
if (!validation.isValid) {
|
||
throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
|
||
}
|
||
|
||
// Export
|
||
const navData = project.exportToNavigation();
|
||
await fs.writeFile(filepath, JSON.stringify(navData, null, 2));
|
||
}
|
||
|
||
// Load project file
|
||
async function loadProject(filepath: string): Promise<NavigationProject> {
|
||
const data = JSON.parse(await fs.readFile(filepath, 'utf-8'));
|
||
return NavigationProject.fromJSON(data);
|
||
}
|
||
```
|
||
```
|
||
|
||
## Key Challenges & Solutions
|
||
|
||
### Challenge 1: Complex Button Grouping
|
||
**Problem:** Multiple buttons can map to different outputs, and the structure can get complex.
|
||
|
||
**Solution A: Color-Coded Output Groups** (Recommended)
|
||
- Each output path gets a unique color (suggested max ~5 colors per tile)
|
||
- User can set/choose the color for each output group
|
||
- Buttons using that output show:
|
||
- Colored rect/border around the button in the config panel
|
||
- Optional: button background tint in that color (browser support varies)
|
||
- Connection arrows from the tile use the corresponding color
|
||
- Provides immediate visual clarity for which buttons lead where
|
||
|
||
**Solution B: Per-Button Output Selector**
|
||
- Each button in the config panel has an "Output Group" dropdown near it
|
||
- Dropdown shows:
|
||
- Existing output groups (with their target tiles)
|
||
- "Add New Output" option
|
||
- Creates output circles on-demand based on unique selections
|
||
- More flexible but requires more user interactions per button
|
||
|
||
**Config Panel Structure (Type-Specific Fields):**
|
||
```
|
||
Button Type: [navigate ▼]
|
||
|
||
IF navigate:
|
||
Output Group: [🟢 Color-1: Next Step ▼] (Add New)
|
||
Target Tile: [Select Storm Door Size ▼]
|
||
Button Label: [Field]
|
||
... other options
|
||
|
||
IF toggle:
|
||
Toggle Group: [☐ Group A ☑ Group B ☐ Group C] (Add New Toggle Group)
|
||
Variable to Set: [doorType ▼]
|
||
Value or Label: [Field]
|
||
... other options
|
||
```
|
||
|
||
**Visual Representation with Colored Outputs:**
|
||
```
|
||
┌─────────────────┐
|
||
│ Door Type │
|
||
│ ┌──────────┐ │ 🟢 (Green - Predefined route)
|
||
│ │ Standard │───┼──🟢
|
||
│ │ Custom │───┼──🔵
|
||
│ │ Skip │───┼──🔴
|
||
│ └──────────┘ │ 🔵 (Blue - Custom route)
|
||
└─────────────────┘ 🔴 (Red - Skip to end)
|
||
```
|
||
|
||
### Challenge 2: Maintaining JSON Compatibility
|
||
**Problem:** The visual editor creates its own structure, but must export to the existing navigation.json format.
|
||
|
||
**Solution: Class-Based Export Architecture**
|
||
|
||
Each tile in the visual editor is a class object with an export function that generates clean JSON. This creates two separate file types:
|
||
|
||
#### 1. Project File (`.navproj.json` or similar)
|
||
Saved/loaded by the editor, contains all editor-specific data:
|
||
```json
|
||
{
|
||
"version": "1.0",
|
||
"metadata": {
|
||
"created": "2026-04-02",
|
||
"lastModified": "2026-04-02",
|
||
"author": "User"
|
||
},
|
||
"nodes": [
|
||
{
|
||
"id": "node-1",
|
||
"tileId": "start",
|
||
"type": "NavigationTile",
|
||
"position": { "x": 100, "y": 200 },
|
||
"config": {
|
||
"type": "question",
|
||
"inputType": "button",
|
||
"title": "What are you looking for?",
|
||
"subtitle": "Select the product category",
|
||
"notes": "<p><strong>Quick Tips:...</p>",
|
||
"buttons": [
|
||
{
|
||
"caption": "Door",
|
||
"image": "🚪",
|
||
"outputGroup": "output-1",
|
||
"filter": { "baseType": "Door" }
|
||
},
|
||
{
|
||
"caption": "Window",
|
||
"image": "🪟",
|
||
"outputGroup": "output-2",
|
||
"filter": { "baseType": "Window" }
|
||
}
|
||
]
|
||
},
|
||
"outputs": [
|
||
{
|
||
"id": "output-1",
|
||
"color": "#3b82f6",
|
||
"label": "Door Path",
|
||
"targetTileId": "q-door-type"
|
||
},
|
||
{
|
||
"id": "output-2",
|
||
"color": "#10b981",
|
||
"label": "Window Path",
|
||
"targetTileId": "q-window-type"
|
||
}
|
||
],
|
||
"defaults": []
|
||
}
|
||
],
|
||
"connections": [
|
||
{
|
||
"id": "conn-1",
|
||
"from": "node-1",
|
||
"fromOutput": "output-1",
|
||
"to": "node-2"
|
||
}
|
||
],
|
||
"editorSettings": {
|
||
"zoom": 1.0,
|
||
"panX": 0,
|
||
"panY": 0
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2. Export File (`navigation.json`)
|
||
Generated from project file during export, clean JSON for runtime:
|
||
```json
|
||
{
|
||
"start": {
|
||
"type": "question",
|
||
"inputType": "button",
|
||
"title": "What are you looking for?",
|
||
"subtitle": "Select the product category",
|
||
"notes": "<p><strong>Quick Tips:...</p>",
|
||
"answers": [
|
||
{
|
||
"caption": "Door",
|
||
"image": "🚪",
|
||
"next": "q-door-type",
|
||
"filter": {
|
||
"baseType": "Door"
|
||
}
|
||
},
|
||
{
|
||
"caption": "Window",
|
||
"image": "🪟",
|
||
"next": "q-window-type",
|
||
"filter": {
|
||
"baseType": "Window"
|
||
}
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Export Function Example (TypeScript/JavaScript)
|
||
```typescript
|
||
class NavigationTile {
|
||
id: string;
|
||
config: TileConfig;
|
||
outputs: Output[];
|
||
|
||
// Export this tile to navigation.json format
|
||
export(): object {
|
||
const navigationNode: any = {
|
||
type: this.config.type,
|
||
inputType: this.config.inputType,
|
||
title: this.config.title,
|
||
subtitle: this.config.subtitle
|
||
};
|
||
|
||
// Add optional fields
|
||
if (this.config.notes) {
|
||
navigationNode.notes = this.config.notes;
|
||
}
|
||
|
||
// Convert buttons with output groups to answers with next references
|
||
if (this.config.buttons) {
|
||
navigationNode.answers = this.config.buttons.map(button => {
|
||
const output = this.outputs.find(o => o.id === button.outputGroup);
|
||
return {
|
||
caption: button.caption,
|
||
image: button.image,
|
||
next: output?.targetTileId || null,
|
||
filter: button.filter
|
||
};
|
||
});
|
||
}
|
||
|
||
// Add conditional logic if present
|
||
if (this.config.conditional) {
|
||
navigationNode.conditional = this.config.conditional;
|
||
}
|
||
|
||
return navigationNode;
|
||
}
|
||
|
||
// Import from navigation.json to project format
|
||
static import(tileId: string, data: any, position: Position): NavigationTile {
|
||
// Convert navigation.json format back to editor format
|
||
// ...
|
||
}
|
||
}
|
||
|
||
// Export entire project to navigation.json
|
||
function exportToNavigation(project: ProjectFile): object {
|
||
const navigation: any = {};
|
||
|
||
project.nodes.forEach(node => {
|
||
const tile = new NavigationTile(node);
|
||
navigation[node.tileId] = tile.export();
|
||
});
|
||
|
||
return navigation;
|
||
}
|
||
```
|
||
|
||
#### Benefits of This Approach:
|
||
✅ **Separation of Concerns** - Editor data (positions, colors) separate from runtime data
|
||
✅ **Clean Exports** - Generated navigation.json contains only what the app needs
|
||
✅ **Version Control Friendly** - Project file can track editor-specific changes
|
||
✅ **Validation** - Export process can validate each tile before generating JSON
|
||
✅ **Backwards Compatible** - Can import existing navigation.json files
|
||
✅ **Extensible** - Easy to add new tile types with their own export logic
|
||
|
||
#### Workflow:
|
||
1. **Edit Mode**: User works with project file (`.navproj.json`)
|
||
2. **Export**: Click "Export" → validates and generates `navigation.json`
|
||
3. **Import**: Can load existing `navigation.json` → converts to project format
|
||
4. **Save/Load**: Project file preserves all editor state between sessions
|
||
|
||
### Challenge 3: Large Navigation Flows & Reusability
|
||
**Problem:** Complex products might have 50+ questions, making the canvas crowded. Common patterns (size selection, color/material combos) are duplicated across flows.
|
||
|
||
**Solution: Hierarchical Sub-Flows (Custom Tiles)**
|
||
|
||
Implement a **nested flow architecture** similar to Node-RED sub-flows, LabVIEW SubVIs, or PLC function blocks used in industrial automation systems.
|
||
|
||
#### Concept: Custom Tiles
|
||
1. **Create a Sub-Flow**: Select 3-5 related tiles and group them into a reusable sub-flow
|
||
2. **Save as Custom Tile**: Name it (e.g., "Size Selection Flow", "Material-Color Picker")
|
||
3. **Use in Main Flow**: The custom tile appears as a single node with defined inputs/outputs
|
||
4. **Drill Down to Edit**: Double-click the custom tile to open its internal flow in a separate view
|
||
5. **Propagate Changes**: Edits to the custom tile automatically update all instances
|
||
|
||
#### Example Use Cases
|
||
|
||
**Size Selection Flow:**
|
||
```
|
||
[Entry Point]
|
||
↓
|
||
[Show Standard Sizes] ──→ [Size Button 1] → [Exit: Standard]
|
||
──→ [Size Button 2] → [Exit: Standard]
|
||
──→ [Size Button 3] → [Exit: Standard]
|
||
──→ [Custom Size] → [Dimension Form] → [Exit: Custom]
|
||
```
|
||
|
||
This 5-tile flow becomes **one custom tile** in the main flow:
|
||
```
|
||
[Color Selection] → [Size Selection Flow] → [Results]
|
||
```
|
||
|
||
**Material-Color Combo Flow:**
|
||
```
|
||
[Entry Point]
|
||
↓
|
||
[Material Selection] → [Aluminum] → [Aluminum Colors] → [Exit]
|
||
→ [Vinyl] → [Vinyl Colors] → [Exit]
|
||
→ [Wood] → [Wood Stains] → [Exit]
|
||
```
|
||
|
||
**Glass Options Flow:**
|
||
```
|
||
[Entry Point]
|
||
↓
|
||
[Glass Type] → [Clear] → [Thickness Options] → [Exit]
|
||
→ [Tinted] → [Tint Colors] → [Exit]
|
||
→ [Obscure] → [Pattern Selection] → [Exit]
|
||
```
|
||
|
||
#### UI/UX for Sub-Flows
|
||
|
||
**Main Flow View:**
|
||
- Custom tiles show with special icon (e.g., 📦 or collapsed group icon)
|
||
- Display name: "Size Selection Flow"
|
||
- Show entry point (green circle) and exit points (colored circles per output group)
|
||
- Quick preview badge: "5 nodes"
|
||
|
||
**Switching Views:**
|
||
- **Option A:** Dropdown menu at top: `Main Flow ▼` → Select custom tile name to switch
|
||
- **Option B:** Double-click custom tile to drill down into its internal flow
|
||
- **Option C:** Breadcrumb navigation: `Main Flow > Size Selection Flow`
|
||
|
||
**Inside Custom Tile View:**
|
||
- Special entry node: "Entry Point" (where main flow enters)
|
||
- Special exit nodes: "Exit: Standard", "Exit: Custom" (where flow returns to main)
|
||
- Edit the internal flow just like the main flow
|
||
- Changes save automatically
|
||
|
||
#### Custom Tile Library
|
||
```
|
||
┌─────────────────────────┐
|
||
│ Custom Tiles Library │
|
||
├─────────────────────────┤
|
||
│ 📦 Size Selection Flow │ [Edit] [Delete]
|
||
│ 📦 Material-Color Combo │ [Edit] [Delete]
|
||
│ 📦 Glass Options Flow │ [Edit] [Delete]
|
||
│ │
|
||
│ [+ Create New Custom Tile]
|
||
└─────────────────────────┘
|
||
```
|
||
|
||
#### Implementation Benefits
|
||
|
||
✅ **Reusability** - Create once, use many times across different product flows
|
||
✅ **Maintainability** - Update the custom tile once, all instances update automatically
|
||
✅ **Complexity Management** - Main flow stays clean; complex logic hidden in sub-flows
|
||
✅ **Organization** - Group related questions logically
|
||
✅ **Scalability** - Handle 100+ node flows by keeping main flow at 10-20 high-level tiles
|
||
✅ **Collaboration** - Team members can work on different custom tiles independently
|
||
|
||
#### Additional Organization Features
|
||
- **Minimap** for navigation in large flows
|
||
- **Search/filter** to find specific nodes or custom tiles
|
||
- **Auto-layout** algorithm to organize nodes cleanly
|
||
- **Zoom controls** (fit to screen, zoom to selection)
|
||
- **Layers or tags** for categorizing nodes (e.g., "Storm Doors", "Patio Doors")
|
||
|
||
### Challenge 4: Path Validation
|
||
**Problem:** Ensuring all paths lead somewhere valid and no orphaned nodes.
|
||
|
||
**Solution:**
|
||
- Real-time validation as user edits
|
||
- Visual indicators: red border on nodes with issues
|
||
- Validation report showing:
|
||
- Orphaned nodes (no incoming connections)
|
||
- Dead ends (no outgoing connections)
|
||
- Missing required fields
|
||
- Circular references
|
||
- Test mode: simulate walking through the navigation
|
||
|
||
## User Experience Flow
|
||
|
||
### Creating a New Node
|
||
1. Click "Add Node" button or drag from palette
|
||
2. Node appears on canvas with default settings
|
||
3. User clicks node to select
|
||
4. Config panel opens on right
|
||
5. User fills in: Question ID, title, type
|
||
6. Based on type, additional fields appear (buttons, input fields, etc.)
|
||
7. User configures output groups
|
||
8. User saves (auto or manual)
|
||
9. Node updates visually on canvas
|
||
|
||
### Connecting Two Nodes
|
||
1. User selects source node
|
||
2. Output circles appear on node edges
|
||
3. User clicks and drags from a circle
|
||
4. Arrow follows mouse cursor
|
||
5. User drops on target node
|
||
6. Connection is created
|
||
7. Arrow persists showing the connection
|
||
8. Clicking arrow allows editing connection details
|
||
|
||
### Setting Default Values
|
||
1. User selects a node
|
||
2. Config panel shows "Default Values" section
|
||
3. User clicks "[+ Add Default Value]"
|
||
4. New row appears with dropdowns and field
|
||
5. User selects variable from dropdown (or types new name)
|
||
6. User enters value in field
|
||
7. Value is stored with the node
|
||
8. When navigation reaches this node, values are set
|
||
|
||
### Testing the Navigation
|
||
1. User clicks "Test Mode" button
|
||
2. Canvas switches to interactive mode
|
||
3. Simulated UI appears showing the current question
|
||
4. User can answer questions, seeing the path highlighted
|
||
5. Path taken is shown with animated arrows
|
||
6. User can reset and try different paths
|
||
7. Exit test mode to return to editing
|
||
|
||
## Additional Features to Consider
|
||
|
||
### Versioning & History
|
||
- Save snapshots of navigation.json before changes
|
||
- Git integration for version control
|
||
- Undo/redo with unlimited history
|
||
- Compare versions side-by-side
|
||
|
||
### Collaboration
|
||
- Multi-user editing with real-time sync
|
||
- Comments on nodes for team discussions
|
||
- Lock nodes being edited by others
|
||
- Change notifications
|
||
|
||
### Import/Export
|
||
- Export to different formats (JSON, YAML, diagram image)
|
||
- Import from other tools or formats
|
||
- Export to documentation (PDF, HTML)
|
||
|
||
### Templates & Snippets
|
||
- Save common question patterns as templates
|
||
- Library of pre-built question flows
|
||
- Duplicate and modify existing flows
|
||
|
||
### Analytics Integration
|
||
- Show usage statistics on nodes (if tracking is implemented)
|
||
- Highlight most/least used paths
|
||
- Identify bottlenecks or drop-off points
|
||
|
||
### Accessibility
|
||
- Keyboard shortcuts for all operations
|
||
- Screen reader support
|
||
- High contrast mode
|
||
- Zoom accessibility
|
||
|
||
## Additional Considerations & Potential Issues
|
||
|
||
### Critical Features Not Yet Detailed
|
||
|
||
#### 1. Undo/Redo System ⚠️ HIGH PRIORITY
|
||
**Why Critical:** Visual editors require robust undo/redo because users make lots of experimental changes, accidental deletions, and need quick recovery from mistakes.
|
||
|
||
**Implementation Approach:**
|
||
- **Command Pattern**: Each action (move node, add connection, edit property) is a reversible command
|
||
- **Action History Stack**: Maintain array of completed actions, track current position
|
||
- **Supported Actions**:
|
||
- Node: Create, Delete, Move, Duplicate, Edit Properties
|
||
- Connection: Add, Remove, Reroute
|
||
- Group Operations: Multi-node moves, bulk edits
|
||
- **UI**: Ctrl+Z (undo), Ctrl+Y/Ctrl+Shift+Z (redo), visual history panel (optional)
|
||
- **State Management**: Snapshot approach vs incremental changes
|
||
- **Snapshot**: Save full state after each action (simple but memory-intensive)
|
||
- **Incremental**: Store delta changes (complex but efficient)
|
||
|
||
**Recommendation:** Start with snapshot approach for MVP, optimize to incremental if performance becomes an issue.
|
||
|
||
**Estimated Effort:** 10-15 hours
|
||
|
||
---
|
||
|
||
#### 2. Copy/Paste & Duplication
|
||
**Why Important:** Users frequently need to duplicate similar question nodes, copy patterns, or reuse configurations.
|
||
|
||
**Features Needed:**
|
||
- **Copy Single Node**: Ctrl+C copies selected node and its config
|
||
- **Copy Multiple Nodes**: Shift-click or drag-select to copy groups
|
||
- **Copy with Connections**: Option to include/exclude connection information
|
||
- **Paste**: Ctrl+V pastes at cursor position or offset from original
|
||
- **Duplicate**: Ctrl+D creates copy immediately (no clipboard)
|
||
- **Between Flows**: Copy from main flow, paste into custom tile (and vice versa)
|
||
- **ID Conflict Resolution**: Auto-generate new unique IDs for pasted nodes
|
||
- **Connection Handling**:
|
||
- Internal connections (between pasted nodes) are preserved
|
||
- External connections (to non-copied nodes) are broken/cleared
|
||
|
||
**Clipboard Format:**
|
||
```json
|
||
{
|
||
"type": "copilot-nav-editor",
|
||
"version": "1.0",
|
||
"nodes": [...],
|
||
"connections": [...],
|
||
"boundingBox": { "width": 500, "height": 300 }
|
||
}
|
||
```
|
||
|
||
**Estimated Effort:** 8-12 hours
|
||
|
||
---
|
||
|
||
#### 3. Keyboard Shortcuts
|
||
**Why Important:** Power users expect keyboard efficiency. Mouse-only interfaces feel slow after extended use.
|
||
|
||
**Essential Shortcuts:**
|
||
```
|
||
Navigation & Selection:
|
||
Tab / Shift+Tab - Select next/previous node
|
||
Arrow Keys - Move selected node (hold Shift for 10px increments)
|
||
Ctrl+A - Select all nodes
|
||
Ctrl+Click - Multi-select nodes
|
||
Shift+Click - Select range between last selected
|
||
|
||
Editing:
|
||
Ctrl+Z - Undo
|
||
Ctrl+Y / Ctrl+Shift+Z - Redo
|
||
Ctrl+C - Copy
|
||
Ctrl+V - Paste
|
||
Ctrl+X - Cut
|
||
Ctrl+D - Duplicate
|
||
Delete / Backspace - Delete selected
|
||
Enter - Edit selected node (open config panel)
|
||
Esc - Deselect / Close panel / Cancel drag
|
||
|
||
View:
|
||
Spacebar (hold) - Pan/Hand tool (drag canvas)
|
||
Ctrl+0 - Zoom to fit all
|
||
Ctrl++ / Ctrl+- - Zoom in/out
|
||
Ctrl+Scroll - Zoom at cursor
|
||
Ctrl+F - Search/Find node
|
||
|
||
File Operations:
|
||
Ctrl+S - Save project
|
||
Ctrl+Shift+S - Save As
|
||
Ctrl+E - Export to navigation.json
|
||
Ctrl+N - New project
|
||
Ctrl+O - Open project
|
||
```
|
||
|
||
**Implementation:** Hook keyboard events at canvas level, prevent conflicts with browser shortcuts.
|
||
|
||
**Estimated Effort:** 5-8 hours
|
||
|
||
---
|
||
|
||
#### 4. Connection Routing Algorithm
|
||
**Why Important:** As flows grow complex, straight-line arrows overlap nodes and cross each other, creating visual mess.
|
||
|
||
**Options:**
|
||
|
||
**Option A: Straight Lines (Simple)**
|
||
- Direct point-to-point lines
|
||
- ✅ Easy to implement
|
||
- ✅ Fast rendering
|
||
- ❌ Overlaps and crosses in complex flows
|
||
|
||
**Option B: Bezier Curves (Smooth)**
|
||
- Curved connections using cubic Bezier paths
|
||
- ✅ Visually appealing
|
||
- ✅ Some automatic avoidance via control points
|
||
- ⚠️ Can still overlap nodes
|
||
- **React Flow default option**
|
||
|
||
**Option C: Orthogonal Routing (Clean)**
|
||
- Only horizontal and vertical lines (like electrical diagrams)
|
||
- ✅ Very clean, professional look
|
||
- ✅ Better node avoidance possible
|
||
- ❌ More complex algorithm
|
||
- ❌ May require more space
|
||
|
||
**Option D: Intelligent Routing (Advanced)**
|
||
- Pathfinding algorithm (A*) to route around nodes
|
||
- ✅ Best visual clarity
|
||
- ✅ No overlaps
|
||
- ❌ Most complex
|
||
- ❌ Performance considerations with many connections
|
||
|
||
**Recommendation for MVP:** Start with Bezier curves (React Flow default), add orthogonal routing in Phase 5 if needed.
|
||
|
||
**Estimated Effort:**
|
||
- Bezier (built-in): 0 hours
|
||
- Orthogonal: 15-20 hours
|
||
- Intelligent: 30-40 hours
|
||
|
||
---
|
||
|
||
#### 5. Auto-Save vs Manual Save
|
||
**Why Important:** Browser crashes, accidental closures, and forgotten saves can lose hours of work.
|
||
|
||
**Recommended Strategy: Hybrid Approach**
|
||
|
||
**Auto-Save to Local Storage:**
|
||
- Save to `localStorage` every 30 seconds (debounced after changes)
|
||
- Keep last 3 auto-save versions with timestamps
|
||
- Recover on next load if detected
|
||
- ⚠️ localStorage limit: ~5-10MB (should be sufficient for most flows)
|
||
|
||
**Manual Save to File:**
|
||
- Ctrl+S saves project file to disk (`.navproj.json`)
|
||
- Visual indicator: "Saved" checkmark vs "Unsaved changes" dot
|
||
- Prompt before closing window if unsaved changes exist
|
||
|
||
**Cloud Sync (Future):**
|
||
- Optional integration with cloud storage (Google Drive, Dropbox, OneDrive)
|
||
- Version history and conflict resolution
|
||
|
||
**UI Feedback:**
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ 🟢 Auto-saved 30 seconds ago │
|
||
│ ⚫ Unsaved manual changes │
|
||
│ [Save Project] [Export Navigation] │
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
**Browser Close Warning:**
|
||
```javascript
|
||
window.addEventListener('beforeunload', (e) => {
|
||
if (hasUnsavedChanges()) {
|
||
e.preventDefault();
|
||
e.returnValue = 'You have unsaved changes. Are you sure?';
|
||
}
|
||
});
|
||
```
|
||
|
||
**Estimated Effort:** 6-10 hours
|
||
|
||
---
|
||
|
||
#### 6. Performance at Scale ⚠️ IMPORTANT
|
||
**Why Important:** A navigation with 100+ nodes and 200+ connections could slow down rendering and interactions.
|
||
|
||
**Potential Issues:**
|
||
- Slow drag-and-drop response
|
||
- Lag when adding/removing connections
|
||
- Sluggish panning and zooming
|
||
- Memory consumption
|
||
|
||
**React Flow Performance Characteristics:**
|
||
- Generally handles 100-300 nodes well
|
||
- Slows down around 500+ nodes
|
||
- Connections are more performant than nodes
|
||
|
||
**Optimization Strategies:**
|
||
|
||
**Level 1: Built-in React Flow Optimizations** (Free)
|
||
- Virtual rendering (only render visible nodes)
|
||
- Memoization of node components
|
||
- Connection edge optimization
|
||
|
||
**Level 2: Custom Optimizations** (If Needed)
|
||
- **Lazy Load Custom Tiles**: Don't load internal nodes until opened
|
||
- **Connection Simplification**: Reduce curve resolution for distant connections
|
||
- **Level of Detail**: Show simplified node representation when zoomed out
|
||
- **Pagination/Filtering**: Show subset of nodes at a time (by category, search)
|
||
- **Canvas Sectioning**: Divide large flows into "pages" or "zones"
|
||
|
||
**Performance Testing Plan:**
|
||
1. Create test navigation with 50, 100, 200, 500 nodes
|
||
2. Measure frame rate during drag operations (target: 60 FPS)
|
||
3. Measure zoom/pan responsiveness
|
||
4. Measure initial load time
|
||
5. Test on lower-end hardware
|
||
|
||
**Recommendation:** Test with realistic data size early (Phase 2), optimize only if needed.
|
||
|
||
**Estimated Effort:**
|
||
- Testing: 3-5 hours
|
||
- Optimizations: 10-20 hours (if needed)
|
||
|
||
---
|
||
|
||
#### 7. Circular Dependency Detection
|
||
**Why Important:** Node A → Node B → Node C → Node A creates infinite loops, breaking navigation.
|
||
|
||
**Detection Algorithm:**
|
||
```typescript
|
||
function detectCycles(nodes: Node[], connections: Connection[]): string[] {
|
||
const cycles: string[] = [];
|
||
|
||
// Build adjacency list
|
||
const graph = buildGraph(connections);
|
||
|
||
// Depth-first search with path tracking
|
||
const visited = new Set<string>();
|
||
const recursionStack = new Set<string>();
|
||
|
||
function dfs(nodeId: string, path: string[]): boolean {
|
||
visited.add(nodeId);
|
||
recursionStack.add(nodeId);
|
||
path.push(nodeId);
|
||
|
||
const neighbors = graph.get(nodeId) || [];
|
||
for (const neighbor of neighbors) {
|
||
if (!visited.has(neighbor)) {
|
||
if (dfs(neighbor, path)) return true;
|
||
} else if (recursionStack.has(neighbor)) {
|
||
// Cycle detected!
|
||
const cycleStart = path.indexOf(neighbor);
|
||
const cycle = path.slice(cycleStart).concat(neighbor);
|
||
cycles.push(cycle.join(' → '));
|
||
return true;
|
||
}
|
||
}
|
||
|
||
recursionStack.delete(nodeId);
|
||
return false;
|
||
}
|
||
|
||
// Check all nodes
|
||
nodes.forEach(node => {
|
||
if (!visited.has(node.id)) {
|
||
dfs(node.id, []);
|
||
}
|
||
});
|
||
|
||
return cycles;
|
||
}
|
||
```
|
||
|
||
**User Experience:**
|
||
- **Real-time Detection**: Run cycle check after each connection change
|
||
- **Visual Warning**: Highlight nodes involved in cycle with red border
|
||
- **Error Message**: "Cycle detected: Start → Door Type → Color → Start"
|
||
- **Suggestion**: "This creates an infinite loop. Remove one of these connections."
|
||
- **Prevent Export**: Don't allow exporting navigation with cycles
|
||
|
||
**Estimated Effort:** 4-6 hours
|
||
|
||
---
|
||
|
||
#### 8. Backwards Compatibility Strategy
|
||
**Why Important:** As the `.navproj.json` format evolves (v1.0 → v1.1 → v2.0), old project files must still load.
|
||
|
||
**Versioning Strategy:**
|
||
```json
|
||
{
|
||
"version": "1.2",
|
||
"formatVersion": "1.2", // Semantic versioning
|
||
"nodes": [...]
|
||
}
|
||
```
|
||
|
||
**Migration System:**
|
||
```typescript
|
||
const migrations = {
|
||
"1.0-to-1.1": (data) => {
|
||
// Add default values structure if missing
|
||
data.nodes.forEach(node => {
|
||
if (!node.defaults) node.defaults = [];
|
||
});
|
||
return data;
|
||
},
|
||
"1.1-to-1.2": (data) => {
|
||
// Add custom tiles structure
|
||
if (!data.customTiles) data.customTiles = [];
|
||
return data;
|
||
}
|
||
};
|
||
|
||
function migrateProjectFile(data: any): any {
|
||
const currentVersion = data.version || "1.0";
|
||
const targetVersion = "1.2";
|
||
|
||
// Apply migrations sequentially
|
||
let migrated = data;
|
||
const path = getMigrationPath(currentVersion, targetVersion);
|
||
|
||
for (const step of path) {
|
||
migrated = migrations[step](migrated);
|
||
}
|
||
|
||
migrated.version = targetVersion;
|
||
return migrated;
|
||
}
|
||
```
|
||
|
||
**Loading Process:**
|
||
1. Detect version in loaded file
|
||
2. If old version, show: "Upgrading project from v1.0 to v1.2..."
|
||
3. Apply migrations sequentially
|
||
4. Create backup of original file before overwriting
|
||
5. Ask user to save upgraded version
|
||
|
||
**Testing:**
|
||
- Keep example projects from each version
|
||
- Automated tests to ensure all old versions load correctly
|
||
|
||
**Estimated Effort:** 5-8 hours (initial setup), 2-3 hours per future migration
|
||
|
||
---
|
||
|
||
#### 9. Error Handling & Recovery
|
||
**Why Important:** Corrupted files, network issues, browser crashes require graceful handling.
|
||
|
||
**Scenarios & Solutions:**
|
||
|
||
**Corrupted Project File:**
|
||
```typescript
|
||
function loadProject(filepath: string): Result<Project, Error> {
|
||
try {
|
||
const data = JSON.parse(fileContent);
|
||
|
||
// Schema validation
|
||
const validation = validateProjectSchema(data);
|
||
if (!validation.valid) {
|
||
return Error(`Invalid project structure: ${validation.errors}`);
|
||
}
|
||
|
||
return Ok(Project.fromJSON(data));
|
||
|
||
} catch (e) {
|
||
// JSON parse error
|
||
if (autoSaveExists()) {
|
||
return showRecoveryDialog();
|
||
} else {
|
||
return Error("Could not load project. File may be corrupted.");
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Auto-Save Recovery Dialog:**
|
||
```
|
||
┌───────────────────────────────────────────┐
|
||
│ ⚠️ Project File Could Not Be Loaded │
|
||
│ │
|
||
│ The file may be corrupted. However, we │
|
||
│ found auto-saved versions: │
|
||
│ │
|
||
│ 🔵 Auto-save from 2 minutes ago │
|
||
│ 🔵 Auto-save from 5 minutes ago │
|
||
│ 🔵 Auto-save from 10 minutes ago │
|
||
│ │
|
||
│ [Recover] [Cancel] │
|
||
└───────────────────────────────────────────┘
|
||
```
|
||
|
||
**Export Validation Error:**
|
||
```
|
||
┌───────────────────────────────────────────┐
|
||
│ ❌ Cannot Export - Validation Errors │
|
||
│ │
|
||
│ The following issues must be fixed: │
|
||
│ │
|
||
│ • Node "q-dimensions" has no connections │
|
||
│ • Cycle detected: start → door → start │
|
||
│ • Node "q-color" missing required title │
|
||
│ │
|
||
│ [Show Details] [Close] │
|
||
└───────────────────────────────────────────┘
|
||
```
|
||
|
||
**Backup Strategy:**
|
||
- Before any save operation, create backup: `project.navproj.backup.json`
|
||
- Keep last 3 backups rotating
|
||
- Manual "Restore from Backup" option in File menu
|
||
|
||
**Estimated Effort:** 8-12 hours
|
||
|
||
---
|
||
|
||
#### 10. Search/Filter in Canvas
|
||
**Why Important:** Finding specific nodes in a 50+ node flow is time-consuming.
|
||
|
||
**Search Features:**
|
||
|
||
**Quick Search (Ctrl+F):**
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ 🔍 Search: door___ │
|
||
│ │
|
||
│ Results (3): │
|
||
│ • q-door-type (Question) │
|
||
│ • q-door-size (Form) │
|
||
│ • door-accessories (Results) │
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
**Search Criteria:**
|
||
- Node title
|
||
- Node ID (tileId)
|
||
- Question type
|
||
- Button captions
|
||
- Notes content
|
||
|
||
**Results Interaction:**
|
||
- Click result → zoom to node and highlight
|
||
- Arrow down/up to cycle through results
|
||
- Esc to close search
|
||
|
||
**Advanced Filter:**
|
||
```
|
||
Filter Nodes:
|
||
☑ Questions ☑ Forms ☑ Results ☑ Custom Tiles
|
||
☑ Connected ☐ Orphaned
|
||
Text: [ ]
|
||
```
|
||
|
||
**Visual Highlighting:**
|
||
- Matching nodes: bright blue border
|
||
- Dimmed view: non-matching nodes become semi-transparent
|
||
- Match count badge: "3 of 47 nodes match"
|
||
|
||
**Estimated Effort:** 6-10 hours
|
||
|
||
---
|
||
|
||
### Nice-to-Have Features
|
||
|
||
#### 11. Comments & Annotations
|
||
**Use Case:** Team collaboration, leaving TODO notes, explaining complex logic.
|
||
|
||
**Implementation:**
|
||
- **Node-level Notes**: Add "Notes" field in config panel (already exists in data)
|
||
- **Canvas Comments**: Floating text boxes on canvas (like sticky notes)
|
||
- **Connection Labels**: Add text labels to connection arrows
|
||
- **Visual Indicators**: 💬 icon on nodes with comments
|
||
|
||
**Estimated Effort:** 5-8 hours
|
||
|
||
---
|
||
|
||
#### 12. Grouping/Visual Containers
|
||
**Use Case:** Visually organize related nodes without creating a custom tile.
|
||
|
||
**Implementation:**
|
||
- **Background Rectangle**: Dotted/dashed border around group of nodes
|
||
- **Group Label**: Title at top of container
|
||
- **Color Coding**: Different colors for different product categories
|
||
- **Collapse/Expand**: Optional ability to collapse group to single icon
|
||
- **Move Group**: Drag group title to move all contained nodes
|
||
|
||
**Example:**
|
||
```
|
||
╭─ Storm Door Questions ──────────────╮
|
||
│ │
|
||
│ [Color] → [Size] → [Accessories] │
|
||
│ │
|
||
╰─────────────────────────────────────╯
|
||
```
|
||
|
||
**Estimated Effort:** 10-15 hours
|
||
|
||
---
|
||
|
||
#### 13. Diff/Compare View
|
||
**Use Case:** Understanding what changed between versions, code review, debugging.
|
||
|
||
**Implementation:**
|
||
- **Side-by-side Canvas**: Show two versions simultaneously
|
||
- **Visual Diff Indicators**:
|
||
- 🟢 Green: New nodes
|
||
- 🔴 Red: Deleted nodes
|
||
- 🟡 Yellow: Modified nodes
|
||
- Updated connections shown with dashed lines
|
||
- **Change Summary**: "5 nodes added, 3 modified, 1 deleted, 7 connections changed"
|
||
|
||
**Estimated Effort:** 15-20 hours
|
||
|
||
---
|
||
|
||
#### 14. Accessibility
|
||
**Why Important:** Legal compliance (ADA/WCAG), inclusive design.
|
||
|
||
**Requirements:**
|
||
|
||
**Keyboard Navigation:**
|
||
- Tab through all interactive elements
|
||
- Arrow keys to navigate between nodes
|
||
- Enter to activate/edit
|
||
- Full functionality without mouse
|
||
|
||
**Screen Reader Support:**
|
||
- Proper ARIA labels on all elements
|
||
- Announce node selections
|
||
- Describe connections meaningfully
|
||
- Accessible forms in config panel
|
||
|
||
**Visual Accessibility:**
|
||
- High contrast mode
|
||
- Color-blind friendly palette options
|
||
- Configurable font sizes
|
||
- Focus indicators (visible keyboard focus)
|
||
|
||
**Estimated Effort:** 15-25 hours
|
||
|
||
---
|
||
|
||
#### 15. Testing/Debugging Mode (Enhanced Details)
|
||
**Why Important:** Validate navigation logic before deployment, catch errors early.
|
||
|
||
**Features:**
|
||
|
||
**Interactive Simulation:**
|
||
- Start at "start" node
|
||
- Display actual question UI as users would see it
|
||
- Click buttons/fill forms to progress
|
||
- Visual path highlighting shows route taken
|
||
|
||
**Breakpoints:**
|
||
- Set breakpoints on specific nodes
|
||
- Execution pauses when reached
|
||
- Inspect current state
|
||
|
||
**Variable Inspector:**
|
||
- Show all current variable values
|
||
- Display filters applied
|
||
- Show which products match current filter
|
||
|
||
**Path History:**
|
||
```
|
||
Path Taken:
|
||
1. start → Door
|
||
2. q-door-type → Storm Door
|
||
3. q-color → White
|
||
4. q-dimensions → Width: 32, Height: 81
|
||
5. results → 3 products match
|
||
```
|
||
|
||
**Test Cases:**
|
||
- Save common test scenarios
|
||
- Replay to verify behavior after changes
|
||
- Automated regression testing
|
||
|
||
**Estimated Effort:** 20-30 hours
|
||
|
||
---
|
||
|
||
#### 16. Export to Diagram Image
|
||
**Use Case:** Documentation, presentations, stakeholder communication.
|
||
|
||
**Export Formats:**
|
||
- **PNG**: Raster image, good for presentations
|
||
- **SVG**: Vector image, scalable, good for print
|
||
- **PDF**: Multi-page for large flows
|
||
|
||
**Export Options:**
|
||
- Full canvas or selected region
|
||
- Current viewport only
|
||
- Include/exclude connection labels
|
||
- Background color/transparency
|
||
- Resolution/DPI settings
|
||
|
||
**Implementation:**
|
||
```typescript
|
||
function exportToPNG(canvas: CanvasElement, options: ExportOptions) {
|
||
const svgData = canvas.toSVG();
|
||
const canvas2d = createCanvas(options.width, options.height);
|
||
const ctx = canvas2d.getContext('2d');
|
||
|
||
// Render SVG to canvas
|
||
renderSVGToCanvas(svgData, ctx);
|
||
|
||
// Export as PNG
|
||
return canvas2d.toDataURL('image/png');
|
||
}
|
||
```
|
||
|
||
**Estimated Effort:** 8-12 hours
|
||
|
||
---
|
||
|
||
### Security & Collaboration Concerns
|
||
|
||
#### 17. Multi-User Collaboration (Future Consideration)
|
||
**Why Important:** Teams working together on complex flows.
|
||
|
||
**Challenges:**
|
||
- **Simultaneous Edits**: Two people editing same node
|
||
- **Conflict Resolution**: Merge conflicts like Git
|
||
- **Real-time Sync**: Show others' cursors and selections
|
||
- **Locking**: Prevent conflicts by locking edited nodes
|
||
|
||
**Implementation Options:**
|
||
- **Operational Transform** (Google Docs approach)
|
||
- **CRDT** (Conflict-Free Replicated Data Types)
|
||
- **Last-Write-Wins** (simplest, but loses data)
|
||
|
||
**Recommendation:** **Not for MVP**. Add in Phase 6+ if team collaboration is critical.
|
||
|
||
**Estimated Effort:** 80-120 hours (complex)
|
||
|
||
---
|
||
|
||
#### 18. Validation & Security
|
||
**Why Important:** Prevent malicious or corrupted files from breaking the editor.
|
||
|
||
**Validation on Load:**
|
||
```typescript
|
||
function validateProjectFile(data: any): ValidationResult {
|
||
const errors: string[] = [];
|
||
|
||
// Schema validation (JSON Schema)
|
||
if (!data.version) errors.push("Missing version");
|
||
if (!data.nodes || !Array.isArray(data.nodes)) {
|
||
errors.push("Invalid nodes structure");
|
||
}
|
||
|
||
// Security checks
|
||
data.nodes?.forEach(node => {
|
||
// Sanitize HTML in notes field
|
||
if (node.config?.notes) {
|
||
node.config.notes = sanitizeHTML(node.config.notes);
|
||
}
|
||
|
||
// Check for excessively large data
|
||
const nodeSize = JSON.stringify(node).length;
|
||
if (nodeSize > 100000) {
|
||
errors.push(`Node ${node.id} exceeds size limit`);
|
||
}
|
||
});
|
||
|
||
// File size limit (prevent DoS)
|
||
const totalSize = JSON.stringify(data).length;
|
||
if (totalSize > 10000000) { // 10MB limit
|
||
errors.push("Project file too large");
|
||
}
|
||
|
||
return {
|
||
valid: errors.length === 0,
|
||
errors
|
||
};
|
||
}
|
||
```
|
||
|
||
**HTML Sanitization:**
|
||
- Use library like DOMPurify for notes field
|
||
- Prevent XSS attacks via malicious HTML
|
||
|
||
**Estimated Effort:** 5-8 hours
|
||
|
||
---
|
||
|
||
## Implementation Priority Recommendations
|
||
|
||
### Phase 1 (MVP Must-Haves):
|
||
- ✅ Core canvas with drag-drop nodes
|
||
- ✅ Basic config panel
|
||
- ✅ Connections (simple)
|
||
- ✅ Save/Load project files
|
||
- ✅ Export to navigation.json
|
||
- ➕ **Undo/Redo** (even basic implementation)
|
||
- ➕ **Keyboard shortcuts** (at least Ctrl+Z, Ctrl+S, Delete)
|
||
- ➕ **Auto-save to localStorage**
|
||
- ➕ **Error handling for corrupted files**
|
||
|
||
**Estimated:** 40-50 hours
|
||
|
||
---
|
||
|
||
### Phase 2 (Important for Usability):
|
||
- ✅ Button grouping & output colors
|
||
- ✅ Default values configuration
|
||
- ✅ Connection routing (Bezier curves)
|
||
- ➕ **Copy/Paste**
|
||
- ➕ **Full keyboard shortcuts**
|
||
- ➕ **Canvas search**
|
||
- ➕ **Circular dependency detection**
|
||
- ➕ **Basic validation & error messages**
|
||
|
||
**Estimated:** 50-65 hours
|
||
|
||
---
|
||
|
||
### Phase 3 (Advanced Features):
|
||
- ✅ Custom tiles (sub-flows)
|
||
- ✅ Hierarchical navigation
|
||
- ➕ **Performance testing & optimization** (if needed)
|
||
- ➕ **Backwards compatibility system**
|
||
- ➕ **Testing/debugging mode**
|
||
- ➕ **Auto-layout algorithm**
|
||
|
||
**Estimated:** 50-70 hours
|
||
|
||
---
|
||
|
||
### Phase 4 (Polish & Optional):
|
||
- ➕ **Comments & annotations**
|
||
- ➕ **Grouping/containers**
|
||
- ➕ **Export to image**
|
||
- ➕ **Accessibility improvements**
|
||
- ➕ **Diff/compare view**
|
||
- ➕ **Advanced routing algorithms**
|
||
|
||
**Estimated:** 40-60 hours
|
||
|
||
---
|
||
|
||
### Future/Optional:
|
||
- Multi-user collaboration (complex, separate project)
|
||
- Cloud storage integration
|
||
- Mobile responsive design
|
||
- Version control UI (Git integration)
|
||
- Analytics dashboard
|
||
|
||
---
|
||
|
||
## Bottom Line: Gaps Addressed
|
||
|
||
**Critical Gaps** (must be added to plan):
|
||
1. ✅ **Undo/Redo** - Absolutely essential, add to Phase 1
|
||
2. ✅ **Copy/Paste** - Users will expect this, add to Phase 2
|
||
3. ✅ **Auto-Save** - Prevent data loss, add to Phase 1
|
||
4. ✅ **Error Handling** - Graceful failures, add to Phase 1
|
||
|
||
**Important Gaps** (should be added):
|
||
5. ✅ **Keyboard Shortcuts** - Power user efficiency, Phase 2
|
||
6. ✅ **Performance Strategy** - Test early, optimize if needed
|
||
7. ✅ **Circular Dependencies** - Validation requirement, Phase 2
|
||
8. ✅ **Search/Filter** - Usability for large flows, Phase 2
|
||
|
||
**Nice-to-Have** (can be deferred):
|
||
9. ✅ **Comments** - Collaboration aid, Phase 4
|
||
10. ✅ **Grouping** - Visual organization, Phase 4
|
||
11. ✅ **Diff View** - Advanced feature, Phase 4
|
||
12. ✅ **Accessibility** - Important but can be iterative
|
||
13. ✅ **Export to Image** - Documentation aid, Phase 4
|
||
|
||
**Out of Scope for Now:**
|
||
14. ✅ **Multi-User Collaboration** - Too complex for initial versions
|
||
15. ✅ **Mobile App** - Desktop-first approach recommended
|
||
|
||
All critical and important gaps are now documented in this comprehensive planning document!
|
||
|
||
---
|
||
|
||
## Conclusion
|
||
|
||
### Feasibility: **Highly Feasible**
|
||
This visual editor is definitely possible and would significantly improve the maintainability of complex navigation flows.
|
||
|
||
### Complexity: **Medium-High**
|
||
- Core functionality (nodes, connections, config panel): Medium complexity
|
||
- Advanced features (grouping, validation, testing): Higher complexity
|
||
- Total project could be completed in 3-4 weeks of focused development
|
||
|
||
### Value Proposition: **Very High**
|
||
- Dramatically reduces time to create/edit navigation flows
|
||
- Visual representation makes logic much clearer
|
||
- Reduces errors from manual JSON editing
|
||
- Lowers barrier to entry for non-technical users
|
||
- Makes complex branching logic manageable
|
||
|
||
### Recommended Next Steps
|
||
1. **Prototype:** Build a simple proof-of-concept with React Flow to validate the approach
|
||
2. **Feedback:** Show prototype to potential users for feedback
|
||
3. **Iterate:** Refine the UI/UX based on feedback
|
||
4. **MVP:** Build Phase 1 & 2 (core canvas + basic editing)
|
||
5. **Evaluate:** Assess if it's worth continuing to advanced features
|
||
6. **Complete:** Finish remaining phases based on needs
|
||
|
||
### Alternative: Lower-Effort Options
|
||
If a full visual editor is too much effort:
|
||
1. **JSON Schema Editor:** Use existing JSON form builders with validation
|
||
2. **Spreadsheet Import:** Design navigation in Excel/Google Sheets, import to JSON
|
||
3. **Template Generator:** Web form to fill in, generates navigation.json
|
||
4. **Better Documentation:** Improve JSON comments and examples for manual editing
|
||
|
||
This visual editor would transform navigation.json management from a manual, error-prone process into an intuitive, visual workflow—similar to how Talend transformed data integration pipelines. |