xcomponent-ai 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/QUICKSTART.md CHANGED
@@ -18,7 +18,7 @@ Use a provided example:
18
18
  ls $(npm root -g)/xcomponent-ai/examples/
19
19
 
20
20
  # Load an example to see its structure
21
- xcomponent-ai load examples/trading.yaml
21
+ xcomponent-ai load examples/explicit-transitions-demo.yaml
22
22
  ```
23
23
 
24
24
  Or create your own project:
@@ -35,7 +35,7 @@ cd my-project
35
35
  - ✅ Web dashboard (for real-time visualization)
36
36
 
37
37
  ```bash
38
- xcomponent-ai serve examples/trading.yaml
38
+ xcomponent-ai serve examples/explicit-transitions-demo.yaml
39
39
  ```
40
40
 
41
41
  **Expected output:**
@@ -43,10 +43,10 @@ xcomponent-ai serve examples/trading.yaml
43
43
  🚀 xcomponent-ai Runtime Started
44
44
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
45
45
 
46
- 📦 Component: TradingComponent
46
+ 📦 Component: ExplicitTransitionsComponent
47
47
  Machines:
48
- - OrderEntry (5 states, 7 transitions)
49
- - Settlement (3 states, 3 transitions)
48
+ - Order (6 states, 7 transitions)
49
+ - RiskMonitor (3 states, 3 transitions)
50
50
 
51
51
  🌐 API Server: http://localhost:3000
52
52
  📊 Dashboard: http://localhost:3000/dashboard.html
@@ -76,21 +76,24 @@ You'll see:
76
76
  curl -X POST http://localhost:3000/api/instances \
77
77
  -H "Content-Type: application/json" \
78
78
  -d '{
79
- "machineName": "OrderEntry",
79
+ "machineName": "Order",
80
80
  "context": {
81
81
  "orderId": "ORD-001",
82
- "amount": 1000,
83
- "symbol": "AAPL"
82
+ "customerId": "CUST-123",
83
+ "symbol": "AAPL",
84
+ "totalQuantity": 1000,
85
+ "side": "BUY",
86
+ "startTime": 0
84
87
  }
85
88
  }'
86
89
 
87
90
  # Response: {"instanceId": "abc-123"}
88
91
 
89
- # Send an event
92
+ # Send an event to submit the order
90
93
  curl -X POST http://localhost:3000/api/instances/abc-123/events \
91
94
  -H "Content-Type: application/json" \
92
95
  -d '{
93
- "type": "VALIDATE",
96
+ "type": "SUBMIT",
94
97
  "payload": {}
95
98
  }'
96
99
 
@@ -101,86 +104,87 @@ curl http://localhost:3000/api/instances/abc-123
101
104
  curl http://localhost:3000/api/instances
102
105
  ```
103
106
 
104
- **Option B: Via CLI (interactive mode)**
105
-
106
- ```bash
107
- # Start REPL mode
108
- xcomponent-ai repl examples/trading.yaml
109
-
110
- # Then type commands:
111
- > create OrderEntry { orderId: "ORD-001", amount: 1000 }
112
- Instance created: abc-123
113
-
114
- > send abc-123 VALIDATE
115
- Transition: Pending → Validated
116
-
117
- > list
118
- Instances:
119
- - abc-123 (OrderEntry) : Validated
120
-
121
- > inspect abc-123
122
- Instance: abc-123
123
- Machine: OrderEntry
124
- State: Validated
125
- Context: { orderId: "ORD-001", amount: 1000, symbol: "AAPL" }
126
- ```
127
-
128
- **Option C: Via Web Dashboard**
107
+ **Option B: Via Web Dashboard**
129
108
 
130
109
  1. Open http://localhost:3000/dashboard.html
131
- 2. Click **"Create Instance"** button
132
- 3. Select machine: `OrderEntry`
133
- 4. Enter context: `{ "orderId": "ORD-001", "amount": 1000 }`
134
- 5. Click **"Create"**
135
- 6. Watch the instance appear in the table
136
- 7. Click on instance to send events
110
+ 2. Go to the **"FSM Diagram"** tab (default view)
111
+ 3. Select machine: `Order` from the dropdown
112
+ 4. Fill in the context fields:
113
+ - orderId: ORD-001
114
+ - customerId: CUST-123
115
+ - symbol: AAPL
116
+ - totalQuantity: 1000
117
+ - side: BUY (from dropdown)
118
+ - startTime: 0
119
+ 5. Click **"Create Instance"**
120
+ 6. Watch the instance appear in the "Active Instances" list
121
+ 7. Click on instance to view details
137
122
 
138
123
  ## 🔍 Monitor FSM
139
124
 
140
125
  ### View real-time logs
141
126
 
142
- In the terminal where `xcomponent-ai serve` is running:
127
+ In the terminal where `xcomponent-ai serve` is running, you'll see real-time activity:
143
128
  ```
144
- [14:32:15] Instance abc-123 created (OrderEntry)
145
- [14:32:18] abc-123: PendingValidated (event: VALIDATE)
146
- [14:32:20] abc-123: ValidatedExecuted (event: EXECUTE)
129
+ [14:32:15] Instance abc-123 created (Order)
130
+ [14:32:18] abc-123: CreatedSubmitted (event: SUBMIT)
131
+ [14:32:20] abc-123: SubmittedPartiallyExecuted (event: EXECUTION_NOTIFICATION)
132
+ [14:32:22] abc-123: PartiallyExecuted → PartiallyExecuted (event: EXECUTION_NOTIFICATION)
133
+ [14:32:22] abc-123: PartiallyExecuted → FullyExecuted (event: FULLY_EXECUTED)
147
134
  ```
148
135
 
149
- ### Analyze logs
136
+ ### Monitor via Dashboard
137
+
138
+ The web dashboard at **http://localhost:3000/dashboard.html** provides:
139
+ - **📊 Event Blotter**: Real-time event stream with filtering
140
+ - **📈 Statistics**: Instance counts by state (active, final, error)
141
+ - **🎨 FSM Diagrams**: Visual state machine representations
142
+ - **🔍 Traceability**: Instance history and transitions
143
+
144
+ ### Check Instance Status via API
150
145
 
151
146
  ```bash
152
- # In another terminal
153
- xcomponent-ai logs --component TradingComponent
147
+ # Get specific instance details
148
+ curl http://localhost:3000/api/instances/abc-123
154
149
 
155
- # Filter by instance
156
- xcomponent-ai logs --instance abc-123
150
+ # List all instances
151
+ curl http://localhost:3000/api/instances
157
152
 
158
- # View statistics
159
- xcomponent-ai stats
153
+ # Get component info
154
+ curl http://localhost:3000/api/components
160
155
  ```
161
156
 
162
157
  ## 🧪 Test Complete Scenario
163
158
 
164
159
  ```bash
165
160
  # 1. Start runtime
166
- xcomponent-ai serve examples/trading.yaml &
161
+ xcomponent-ai serve examples/explicit-transitions-demo.yaml &
167
162
 
168
163
  # 2. Create instance
169
164
  INSTANCE=$(curl -s -X POST http://localhost:3000/api/instances \
170
165
  -H "Content-Type: application/json" \
171
- -d '{"machineName": "OrderEntry", "context": {"orderId": "ORD-001"}}' \
166
+ -d '{"machineName": "Order", "context": {"orderId": "ORD-001", "customerId": "CUST-123", "symbol": "AAPL", "totalQuantity": 1000, "side": "BUY", "startTime": 0}}' \
172
167
  | jq -r '.instanceId')
173
168
 
174
169
  # 3. Send events in sequence
170
+ # Submit the order
171
+ curl -X POST http://localhost:3000/api/instances/$INSTANCE/events \
172
+ -H "Content-Type: application/json" \
173
+ -d '{"type": "SUBMIT", "payload": {}}'
174
+
175
+ sleep 1
176
+
177
+ # Send execution notification (partial fill)
175
178
  curl -X POST http://localhost:3000/api/instances/$INSTANCE/events \
176
179
  -H "Content-Type: application/json" \
177
- -d '{"type": "VALIDATE"}'
180
+ -d '{"type": "EXECUTION_NOTIFICATION", "payload": {"quantity": 600, "price": 150.0, "executionId": "EXEC-001"}}'
178
181
 
179
182
  sleep 1
180
183
 
184
+ # Send another execution notification (complete fill)
181
185
  curl -X POST http://localhost:3000/api/instances/$INSTANCE/events \
182
186
  -H "Content-Type: application/json" \
183
- -d '{"type": "EXECUTE"}'
187
+ -d '{"type": "EXECUTION_NOTIFICATION", "payload": {"quantity": 400, "price": 150.5, "executionId": "EXEC-002"}}'
184
188
 
185
189
  # 4. Check final state
186
190
  curl http://localhost:3000/api/instances/$INSTANCE
@@ -1 +1 @@
1
- {"version":3,"file":"mermaid-generator.d.ts","sourceRoot":"","sources":["../src/mermaid-generator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAsDpE;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CA4B1E"}
1
+ {"version":3,"file":"mermaid-generator.d.ts","sourceRoot":"","sources":["../src/mermaid-generator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CA0BpE;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CA6C1E"}
@@ -12,13 +12,6 @@ function generateMermaidDiagram(machine) {
12
12
  const lines = [];
13
13
  lines.push('stateDiagram-v2');
14
14
  lines.push('');
15
- // Add note at the top if there's a description in metadata
16
- if (machine.metadata?.description) {
17
- lines.push(` note right of ${machine.initialState}`);
18
- lines.push(` ${machine.metadata.description}`);
19
- lines.push(` end note`);
20
- lines.push('');
21
- }
22
15
  // Mark initial state
23
16
  lines.push(` [*] --> ${machine.initialState}`);
24
17
  lines.push('');
@@ -34,24 +27,6 @@ function generateMermaidDiagram(machine) {
34
27
  lines.push(` ${state.name} --> [*]`);
35
28
  }
36
29
  });
37
- lines.push('');
38
- // Add state descriptions as notes
39
- machine.states.forEach(state => {
40
- if (state.metadata?.description) {
41
- const desc = state.metadata.description;
42
- lines.push(` note right of ${state.name}`);
43
- // Handle multi-line descriptions
44
- if (desc.includes('\n')) {
45
- desc.split('\n').forEach((line) => {
46
- lines.push(` ${line.trim()}`);
47
- });
48
- }
49
- else {
50
- lines.push(` ${desc}`);
51
- }
52
- lines.push(` end note`);
53
- }
54
- });
55
30
  return lines.join('\n');
56
31
  }
57
32
  /**
@@ -60,28 +35,41 @@ function generateMermaidDiagram(machine) {
60
35
  function generateStyledMermaidDiagram(machine) {
61
36
  const baseDiagram = generateMermaidDiagram(machine);
62
37
  const styleLines = [];
63
- // Add styling based on state type and metadata
64
- machine.states.forEach((state, index) => {
65
- const stateClass = `state${index}`;
38
+ // Define color classes once at the end
39
+ const usedClasses = new Set();
40
+ // Collect state styles
41
+ const stateStyles = [];
42
+ machine.states.forEach(state => {
43
+ let className = '';
66
44
  if (state.type === 'entry') {
67
- styleLines.push(` class ${state.name} ${stateClass}`);
68
- styleLines.push(` classDef ${stateClass} fill:#FFA500,stroke:#FF8C00,stroke-width:2px,color:#000`);
45
+ className = 'entryState';
46
+ usedClasses.add('entryState');
69
47
  }
70
48
  else if (state.type === 'final') {
71
- styleLines.push(` class ${state.name} ${stateClass}`);
72
- styleLines.push(` classDef ${stateClass} fill:#27ae60,stroke:#229954,stroke-width:2px,color:#fff`);
49
+ className = 'finalState';
50
+ usedClasses.add('finalState');
73
51
  }
74
52
  else if (state.type === 'error') {
75
- styleLines.push(` class ${state.name} ${stateClass}`);
76
- styleLines.push(` classDef ${stateClass} fill:#e74c3c,stroke:#c0392b,stroke-width:2px,color:#fff`);
53
+ className = 'errorState';
54
+ usedClasses.add('errorState');
77
55
  }
78
- else if (state.metadata?.displayColor) {
79
- styleLines.push(` class ${state.name} ${stateClass}`);
80
- styleLines.push(` classDef ${stateClass} fill:${state.metadata.displayColor},stroke-width:2px`);
56
+ if (className) {
57
+ stateStyles.push(` class ${state.name} ${className}`);
81
58
  }
82
59
  });
83
- if (styleLines.length > 0) {
84
- return baseDiagram + '\n\n' + styleLines.join('\n');
60
+ // Add class definitions
61
+ if (usedClasses.has('entryState')) {
62
+ styleLines.push(` classDef entryState fill:#fbbf24,stroke:#f59e0b,stroke-width:3px,color:#000`);
63
+ }
64
+ if (usedClasses.has('finalState')) {
65
+ styleLines.push(` classDef finalState fill:#10b981,stroke:#059669,stroke-width:3px,color:#fff`);
66
+ }
67
+ if (usedClasses.has('errorState')) {
68
+ styleLines.push(` classDef errorState fill:#ef4444,stroke:#dc2626,stroke-width:3px,color:#fff`);
69
+ }
70
+ // Combine: base diagram + class definitions + state class applications
71
+ if (styleLines.length > 0 || stateStyles.length > 0) {
72
+ return baseDiagram + '\n\n' + styleLines.join('\n') + '\n' + stateStyles.join('\n');
85
73
  }
86
74
  return baseDiagram;
87
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mermaid-generator.js","sourceRoot":"","sources":["../src/mermaid-generator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mermaid-generator.js","sourceRoot":"","sources":["../src/mermaid-generator.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xcomponent-ai",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "LLM-first framework for AI agents (Claude, GPT) to build apps with sanctuarized business logic. Event-driven FSM runtime with multi-instance state machines, cross-component communication, event sourcing, and production-ready persistence (PostgreSQL, MongoDB)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,14 +10,14 @@
10
10
  /* Reset & Base */
11
11
  * { margin: 0; padding: 0; box-sizing: border-box; }
12
12
 
13
- /* Animated Gradient Background */
13
+ /* Dark Background */
14
14
  body {
15
15
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
16
- background: linear-gradient(-45deg, #667eea, #764ba2, #f093fb, #4facfe);
16
+ background: linear-gradient(-45deg, #0a0a0a, #1a1a1a, #2d2d2d, #1f1f1f);
17
17
  background-size: 400% 400%;
18
18
  animation: gradientShift 15s ease infinite;
19
19
  min-height: 100vh;
20
- color: #1a202c;
20
+ color: #e0e0e0;
21
21
  }
22
22
 
23
23
  @keyframes gradientShift {
@@ -143,7 +143,7 @@
143
143
  left: 0;
144
144
  right: 0;
145
145
  height: 4px;
146
- background: linear-gradient(90deg, #667eea, #764ba2);
146
+ background: linear-gradient(90deg, #fbbf24, #f59e0b);
147
147
  transform: scaleX(0);
148
148
  transition: transform 0.3s ease;
149
149
  }
@@ -160,7 +160,7 @@
160
160
  .stat-value {
161
161
  font-size: 36px;
162
162
  font-weight: 800;
163
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
163
+ background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
164
164
  -webkit-background-clip: text;
165
165
  -webkit-text-fill-color: transparent;
166
166
  background-clip: text;
@@ -209,7 +209,7 @@
209
209
  left: 50%;
210
210
  width: 0;
211
211
  height: 3px;
212
- background: linear-gradient(90deg, #667eea, #764ba2);
212
+ background: linear-gradient(90deg, #fbbf24, #f59e0b);
213
213
  transform: translateX(-50%);
214
214
  transition: width 0.3s ease;
215
215
  }
@@ -316,8 +316,8 @@
316
316
  .form-group textarea:focus {
317
317
  outline: none;
318
318
  background: rgba(255, 255, 255, 0.2);
319
- border-color: #667eea;
320
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
319
+ border-color: #fbbf24;
320
+ box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.2);
321
321
  }
322
322
 
323
323
  .form-help {
@@ -360,13 +360,13 @@
360
360
  }
361
361
 
362
362
  .btn-primary {
363
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
364
- color: white;
363
+ background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
364
+ color: #1a1a1a;
365
365
  }
366
366
 
367
367
  .btn-primary:hover {
368
368
  transform: translateY(-2px);
369
- box-shadow: 0 8px 25px 0 rgba(102, 126, 234, 0.4);
369
+ box-shadow: 0 8px 25px 0 rgba(251, 191, 36, 0.4);
370
370
  }
371
371
 
372
372
  .btn-success {
@@ -437,14 +437,14 @@
437
437
  left: 0;
438
438
  width: 4px;
439
439
  height: 100%;
440
- background: linear-gradient(180deg, #667eea, #764ba2);
440
+ background: linear-gradient(180deg, #fbbf24, #f59e0b);
441
441
  opacity: 0;
442
442
  transition: opacity 0.3s ease;
443
443
  }
444
444
 
445
445
  .instance-item:hover {
446
446
  background: rgba(255, 255, 255, 0.15);
447
- box-shadow: 0 8px 30px rgba(102, 126, 234, 0.2);
447
+ box-shadow: 0 8px 30px rgba(251, 191, 36, 0.2);
448
448
  transform: translateY(-3px) translateX(4px);
449
449
  }
450
450
 
@@ -453,9 +453,9 @@
453
453
  }
454
454
 
455
455
  .instance-item.selected {
456
- border-color: #667eea;
457
- background: rgba(102, 126, 234, 0.2);
458
- box-shadow: 0 8px 30px rgba(102, 126, 234, 0.3);
456
+ border-color: #fbbf24;
457
+ background: rgba(251, 191, 36, 0.2);
458
+ box-shadow: 0 8px 30px rgba(251, 191, 36, 0.3);
459
459
  }
460
460
 
461
461
  .instance-header {
@@ -519,7 +519,7 @@
519
519
  }
520
520
 
521
521
  .blotter-item {
522
- border-left: 4px solid #667eea;
522
+ border-left: 4px solid #fbbf24;
523
523
  background: rgba(255, 255, 255, 0.05);
524
524
  padding: 12px 16px;
525
525
  margin-bottom: 10px;
@@ -638,14 +638,13 @@
638
638
  </div>
639
639
 
640
640
  <div class="tabs">
641
- <div class="tab active" onclick="switchTab('overview')">Overview</div>
642
- <div class="tab" onclick="switchTab('diagram')">FSM Diagram</div>
643
- <div class="tab" onclick="switchTab('blotter')">Event Blotter</div>
644
- <div class="tab" onclick="switchTab('traceability')">Traceability</div>
645
- <div class="tab" onclick="switchTab('create')">Create Instance</div>
641
+ <div class="tab active" onclick="switchTab('diagram')">🎨 FSM Diagram</div>
642
+ <div class="tab" onclick="switchTab('overview')">📋 Instances</div>
643
+ <div class="tab" onclick="switchTab('blotter')">📊 Event Blotter</div>
644
+ <div class="tab" onclick="switchTab('traceability')">🔍 Traceability</div>
646
645
  </div>
647
646
 
648
- <div class="tab-content active" id="tab-overview">
647
+ <div class="tab-content" id="tab-overview">
649
648
  <div class="filter-bar">
650
649
  <input type="text" id="filter-instance" placeholder="Filter by instance ID..." oninput="filterInstances()">
651
650
  <select id="filter-machine" onchange="filterInstances()">
@@ -658,22 +657,48 @@
658
657
  <div class="instance-list" id="instance-list">
659
658
  <div class="empty-state">
660
659
  <div class="empty-state-icon">📭</div>
661
- <div>No instances yet. Create one in the "Create Instance" tab.</div>
660
+ <div>No instances yet. Go to the "FSM Diagram" tab to create one!</div>
662
661
  </div>
663
662
  </div>
664
663
  </div>
665
664
 
666
- <div class="tab-content" id="tab-diagram">
667
- <div class="form-group">
668
- <label for="diagram-machine">State Machine:</label>
669
- <select id="diagram-machine" onchange="renderDiagram()">
670
- <option value="">Select a state machine...</option>
671
- </select>
672
- </div>
673
- <div class="mermaid-container" id="diagram-container">
674
- <div class="empty-state">
675
- <div class="empty-state-icon">🎨</div>
676
- <div>Select a state machine to view its diagram</div>
665
+ <div class="tab-content active" id="tab-diagram">
666
+ <div class="grid-2">
667
+ <div>
668
+ <div class="form-group">
669
+ <label for="diagram-machine">State Machine:</label>
670
+ <select id="diagram-machine" onchange="renderDiagram()">
671
+ <option value="">Select a state machine...</option>
672
+ </select>
673
+ </div>
674
+ <div class="mermaid-container" id="diagram-container">
675
+ <div class="empty-state">
676
+ <div class="empty-state-icon">🎨</div>
677
+ <div>Select a state machine to view its diagram</div>
678
+ </div>
679
+ </div>
680
+ </div>
681
+
682
+ <div>
683
+ <div class="card">
684
+ <h2>Create New Instance</h2>
685
+ <div id="diagram-create-form">
686
+ <div class="empty-state" style="padding: 40px 20px;">
687
+ <div class="empty-state-icon">👈</div>
688
+ <div>Select a state machine to create an instance</div>
689
+ </div>
690
+ </div>
691
+ </div>
692
+
693
+ <div class="card" style="margin-top: 20px;" id="diagram-instances-card">
694
+ <h2>Active Instances</h2>
695
+ <div id="diagram-instances-list">
696
+ <div class="empty-state" style="padding: 20px;">
697
+ <div class="empty-state-icon">📭</div>
698
+ <div>No instances for this state machine</div>
699
+ </div>
700
+ </div>
701
+ </div>
677
702
  </div>
678
703
  </div>
679
704
  </div>
@@ -710,19 +735,6 @@
710
735
  </div>
711
736
  </div>
712
737
 
713
- <div class="tab-content" id="tab-create">
714
- <div class="card">
715
- <h2>Create New Instance</h2>
716
- <div class="form-group">
717
- <label for="create-machine">State Machine *</label>
718
- <select id="create-machine" onchange="updateCreateForm()">
719
- <option value="">Select a state machine...</option>
720
- </select>
721
- </div>
722
- <div id="create-form-fields"></div>
723
- <button class="btn btn-primary" onclick="createInstance()">Create Instance</button>
724
- </div>
725
- </div>
726
738
  </div>
727
739
 
728
740
  <script>
@@ -798,6 +810,12 @@
798
810
  updateStats();
799
811
  renderInstances();
800
812
  updateTraceSelector();
813
+
814
+ // Update diagram instances list if a machine is selected
815
+ const selectedMachine = document.getElementById('diagram-machine')?.value;
816
+ if (selectedMachine) {
817
+ updateDiagramInstancesList(selectedMachine);
818
+ }
801
819
  }
802
820
 
803
821
  function updateStats() {
@@ -811,7 +829,7 @@
811
829
  function renderInstances() {
812
830
  const container = document.getElementById('instance-list');
813
831
  if (instances.length === 0) {
814
- container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">📭</div><div>No instances yet. Create one in the "Create Instance" tab.</div></div>';
832
+ container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">📭</div><div>No instances yet. Go to the "FSM Diagram" tab to create one!</div></div>';
815
833
  return;
816
834
  }
817
835
 
@@ -896,7 +914,11 @@
896
914
  async function renderDiagram() {
897
915
  const machineName = document.getElementById('diagram-machine').value;
898
916
  const component = getCurrentComponent();
899
- if (!machineName || !component) return;
917
+ if (!machineName || !component) {
918
+ document.getElementById('diagram-create-form').innerHTML = '<div class="empty-state" style="padding: 40px 20px;"><div class="empty-state-icon">👈</div><div>Select a state machine to create an instance</div></div>';
919
+ document.getElementById('diagram-instances-list').innerHTML = '<div class="empty-state" style="padding: 20px;"><div class="empty-state-icon">📭</div><div>No instances for this state machine</div></div>';
920
+ return;
921
+ }
900
922
 
901
923
  const machine = component.stateMachines.find(m => m.name === machineName);
902
924
  if (!machine) return;
@@ -906,102 +928,154 @@
906
928
  const data = await res.json();
907
929
 
908
930
  if (data.diagram) {
909
- // Use Mermaid v10 API
910
- const container = document.getElementById('diagram-container');
911
- container.innerHTML = '<pre class="mermaid">' + data.diagram + '</pre>';
912
-
913
- // Re-render mermaid diagrams
914
- await mermaid.run({
915
- querySelector: '#diagram-container .mermaid'
916
- });
931
+ try {
932
+ // Use Mermaid v10 API
933
+ const container = document.getElementById('diagram-container');
934
+ container.innerHTML = '<div class="mermaid">' + data.diagram + '</div>';
935
+
936
+ // Re-render mermaid diagrams
937
+ await mermaid.run({
938
+ querySelector: '#diagram-container .mermaid'
939
+ });
940
+ } catch (error) {
941
+ console.error('Mermaid rendering error:', error);
942
+ const container = document.getElementById('diagram-container');
943
+ container.innerHTML = `
944
+ <div style="color: #ef4444; padding: 20px; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border: 1px solid #ef4444;">
945
+ <strong>Diagram Rendering Error:</strong> ${error.message || 'Unknown error'}<br><br>
946
+ <details style="margin-top: 10px;">
947
+ <summary style="cursor: pointer;">Show diagram source</summary>
948
+ <pre style="margin-top: 10px; padding: 10px; background: rgba(0,0,0,0.3); border-radius: 4px; overflow-x: auto;">${data.diagram}</pre>
949
+ </details>
950
+ </div>
951
+ `;
952
+ }
917
953
  }
954
+
955
+ // Populate create instance form for this machine
956
+ updateDiagramCreateForm(machine);
957
+
958
+ // Show instances for this machine
959
+ updateDiagramInstancesList(machineName);
918
960
  }
919
-
920
- // Traceability
921
- async function loadTrace() {
922
- if (!selectedInstance) return;
923
-
924
- const container = document.getElementById('trace-container');
925
- // In real version, fetch from /api/instances/:id/history
926
- container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div>Traceability history would appear here</div></div>';
927
- }
928
-
929
- function updateTraceSelector() {
930
- const select = document.getElementById('trace-instance');
931
- select.innerHTML = '<option value="">Select an instance...</option>' +
932
- instances.map(i => `<option value="${i.id}">${i.id.substring(0, 8)} - ${i.machineName}</option>`).join('');
933
- }
934
-
935
- // Create Instance
936
- function updateCreateForm() {
937
- const machineName = document.getElementById('create-machine').value;
938
- const component = getCurrentComponent();
939
- if (!machineName || !component) return;
940
961
 
941
- const machine = component.stateMachines.find(m => m.name === machineName);
942
- if (!machine || !machine.contextSchema) {
943
- document.getElementById('create-form-fields').innerHTML = '<div class="form-group"><label>Context (JSON)</label><textarea id="context-json" rows="4"></textarea></div>';
944
- return;
945
- }
946
-
947
- // Generate form from schema
962
+ function updateDiagramCreateForm(machine) {
948
963
  let html = '';
949
- for (const [key, field] of Object.entries(machine.contextSchema)) {
950
- html += `<div class="form-group">`;
951
- html += `<label for="ctx_${key}">${field.label || key}${field.required ? ' *' : ''}</label>`;
952
- if (field.type === 'select') {
953
- html += `<select id="ctx_${key}">`;
954
- field.options.forEach(opt => html += `<option value="${opt.value}">${opt.label}</option>`);
955
- html += `</select>`;
956
- } else {
957
- html += `<input type="${field.type || 'text'}" id="ctx_${key}" placeholder="${field.placeholder || ''}" ${field.required ? 'required' : ''}>`;
964
+
965
+ if (!machine.contextSchema) {
966
+ html = `
967
+ <div class="form-group">
968
+ <label>Context (JSON)</label>
969
+ <textarea id="diagram-context-json" rows="6" placeholder='{"property": "value"}'></textarea>
970
+ <div class="form-help">Enter a JSON object with the context properties for this instance</div>
971
+ </div>
972
+ <button class="btn btn-primary" onclick="createInstanceFromDiagram()">Create Instance</button>
973
+ `;
974
+ } else {
975
+ html = '<div style="background: rgba(251, 191, 36, 0.1); border-left: 3px solid #fbbf24; padding: 12px; margin-bottom: 20px; border-radius: 4px; color: rgba(255,255,255,0.9);"><strong>📝 Context Properties:</strong> Fill in the fields below</div>';
976
+
977
+ for (const [key, field] of Object.entries(machine.contextSchema)) {
978
+ html += `<div class="form-group">`;
979
+ html += `<label for="diagram_ctx_${key}">${field.label || key}${field.required ? ' *' : ''}</label>`;
980
+ if (field.type === 'select') {
981
+ html += `<select id="diagram_ctx_${key}">`;
982
+ field.options.forEach(opt => html += `<option value="${opt.value}">${opt.label}</option>`);
983
+ html += `</select>`;
984
+ } else {
985
+ html += `<input type="${field.type || 'text'}" id="diagram_ctx_${key}" placeholder="${field.placeholder || ''}" ${field.required ? 'required' : ''}>`;
986
+ }
987
+ if (field.description) html += `<div class="form-help">${field.description}</div>`;
988
+ html += `</div>`;
958
989
  }
959
- if (field.description) html += `<div class="form-help">${field.description}</div>`;
960
- html += `</div>`;
990
+ html += `<button class="btn btn-primary" onclick="createInstanceFromDiagram()">Create Instance</button>`;
961
991
  }
962
- document.getElementById('create-form-fields').innerHTML = html;
992
+
993
+ document.getElementById('diagram-create-form').innerHTML = html;
963
994
  }
964
-
965
- async function createInstance() {
966
- const machineName = document.getElementById('create-machine').value;
995
+
996
+ function updateDiagramInstancesList(machineName) {
997
+ const machineInstances = instances.filter(i => i.machineName === machineName);
998
+
999
+ if (machineInstances.length === 0) {
1000
+ document.getElementById('diagram-instances-list').innerHTML = '<div class="empty-state" style="padding: 20px;"><div class="empty-state-icon">📭</div><div>No instances yet. Create one above!</div></div>';
1001
+ return;
1002
+ }
1003
+
1004
+ const html = machineInstances.map(inst => `
1005
+ <div class="instance-item" onclick="selectInstance('${inst.id}'); switchTab('overview');" style="margin-bottom: 10px;">
1006
+ <div class="instance-header">
1007
+ <span class="instance-id">${inst.id.substring(0, 8)}</span>
1008
+ <span class="badge ${inst.status}">${inst.currentState}</span>
1009
+ </div>
1010
+ </div>
1011
+ `).join('');
1012
+
1013
+ document.getElementById('diagram-instances-list').innerHTML = html;
1014
+ }
1015
+
1016
+ async function createInstanceFromDiagram() {
1017
+ const machineName = document.getElementById('diagram-machine').value;
967
1018
  const component = getCurrentComponent();
968
- if (!machineName) return alert('Please select a state machine');
969
- if (!component) return alert('No component loaded');
1019
+ if (!machineName || !component) return alert('Please select a state machine');
970
1020
 
971
1021
  const machine = component.stateMachines.find(m => m.name === machineName);
972
1022
  let context = {};
973
1023
 
974
1024
  if (machine?.contextSchema) {
975
1025
  for (const key of Object.keys(machine.contextSchema)) {
976
- const input = document.getElementById('ctx_' + key);
1026
+ const input = document.getElementById('diagram_ctx_' + key);
977
1027
  if (input && input.value) {
978
1028
  context[key] = input.type === 'number' ? parseFloat(input.value) : input.value;
979
1029
  }
980
1030
  }
981
1031
  } else {
982
- const json = document.getElementById('context-json')?.value;
983
- if (json) context = JSON.parse(json);
1032
+ const json = document.getElementById('diagram-context-json')?.value;
1033
+ if (json) {
1034
+ try {
1035
+ context = JSON.parse(json);
1036
+ } catch (e) {
1037
+ return alert('Invalid JSON: ' + e.message);
1038
+ }
1039
+ }
984
1040
  }
985
1041
 
986
-
987
1042
  await fetch(`/api/components/${component.name}/instances`, {
988
1043
  method: 'POST',
989
1044
  headers: {'Content-Type': 'application/json'},
990
1045
  body: JSON.stringify({machineName, context})
991
1046
  });
992
-
1047
+
993
1048
  // Clear form
994
1049
  if (machine?.contextSchema) {
995
1050
  for (const key of Object.keys(machine.contextSchema)) {
996
- const input = document.getElementById('ctx_' + key);
1051
+ const input = document.getElementById('diagram_ctx_' + key);
997
1052
  if (input) input.value = '';
998
1053
  }
1054
+ } else {
1055
+ const textarea = document.getElementById('diagram-context-json');
1056
+ if (textarea) textarea.value = '';
999
1057
  }
1000
-
1001
- switchTab('overview');
1058
+
1059
+ // Reload instances to update the list
1002
1060
  loadInstances();
1003
1061
  }
1004
1062
 
1063
+ // Traceability
1064
+ async function loadTrace() {
1065
+ if (!selectedInstance) return;
1066
+
1067
+ const container = document.getElementById('trace-container');
1068
+ // In real version, fetch from /api/instances/:id/history
1069
+ container.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div>Traceability history would appear here</div></div>';
1070
+ }
1071
+
1072
+ function updateTraceSelector() {
1073
+ const select = document.getElementById('trace-instance');
1074
+ select.innerHTML = '<option value="">Select an instance...</option>' +
1075
+ instances.map(i => `<option value="${i.id}">${i.id.substring(0, 8)} - ${i.machineName}</option>`).join('');
1076
+ }
1077
+
1078
+
1005
1079
  function populateSelectors() {
1006
1080
  if (!componentsData || componentsData.length === 0) return;
1007
1081
 
@@ -1017,7 +1091,6 @@
1017
1091
  const machines = component.stateMachines.map(m => `<option value="${m.name}">${m.name}</option>`).join('');
1018
1092
  document.getElementById('filter-machine').innerHTML = '<option value="">All Machines</option>' + machines;
1019
1093
  document.getElementById('diagram-machine').innerHTML = '<option value="">Select...</option>' + machines;
1020
- document.getElementById('create-machine').innerHTML = '<option value="">Select...</option>' + machines;
1021
1094
  }
1022
1095
  }
1023
1096
 
@@ -1027,8 +1100,8 @@
1027
1100
  console.log('Component selected:', selected);
1028
1101
  // Refresh selectors for the new component
1029
1102
  populateSelectors();
1030
- // Clear create form when switching components
1031
- document.getElementById('create-form-fields').innerHTML = '';
1103
+ // Clear diagram create form when switching components
1104
+ document.getElementById('diagram-create-form').innerHTML = '<div class="empty-state" style="padding: 40px 20px;"><div class="empty-state-icon">👈</div><div>Select a state machine to create an instance</div></div>';
1032
1105
  }
1033
1106
 
1034
1107
  function exportState() {