@hugobatist/smartcode 0.1.0

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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +292 -0
  3. package/dist/cli.js +4324 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/index.d.ts +374 -0
  6. package/dist/index.js +1167 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/static/annotations-panel.js +133 -0
  9. package/dist/static/annotations-svg.js +108 -0
  10. package/dist/static/annotations.css +367 -0
  11. package/dist/static/annotations.js +367 -0
  12. package/dist/static/app-init.js +497 -0
  13. package/dist/static/breakpoints.css +69 -0
  14. package/dist/static/breakpoints.js +197 -0
  15. package/dist/static/clipboard.js +94 -0
  16. package/dist/static/collapse-ui.js +325 -0
  17. package/dist/static/command-history.js +89 -0
  18. package/dist/static/context-menu.js +334 -0
  19. package/dist/static/custom-renderer.js +201 -0
  20. package/dist/static/dagre-layout.js +291 -0
  21. package/dist/static/diagram-dom.js +241 -0
  22. package/dist/static/diagram-editor.js +368 -0
  23. package/dist/static/editor-panel.js +107 -0
  24. package/dist/static/editor-popovers.js +187 -0
  25. package/dist/static/event-bus.js +57 -0
  26. package/dist/static/export.js +181 -0
  27. package/dist/static/file-tree.js +470 -0
  28. package/dist/static/ghost-paths.js +397 -0
  29. package/dist/static/heatmap.css +116 -0
  30. package/dist/static/heatmap.js +308 -0
  31. package/dist/static/icons.js +66 -0
  32. package/dist/static/inline-edit.js +294 -0
  33. package/dist/static/interaction-state.js +155 -0
  34. package/dist/static/interaction-tracker.js +93 -0
  35. package/dist/static/live.html +239 -0
  36. package/dist/static/main-layout.css +220 -0
  37. package/dist/static/main.css +334 -0
  38. package/dist/static/mcp-sessions.js +202 -0
  39. package/dist/static/modal.css +109 -0
  40. package/dist/static/modal.js +171 -0
  41. package/dist/static/node-drag.js +293 -0
  42. package/dist/static/pan-zoom.js +199 -0
  43. package/dist/static/renderer.js +280 -0
  44. package/dist/static/search.css +103 -0
  45. package/dist/static/search.js +304 -0
  46. package/dist/static/selection.js +353 -0
  47. package/dist/static/session-player.css +137 -0
  48. package/dist/static/session-player.js +411 -0
  49. package/dist/static/sidebar.css +248 -0
  50. package/dist/static/svg-renderer.js +313 -0
  51. package/dist/static/svg-shapes.js +218 -0
  52. package/dist/static/tokens.css +76 -0
  53. package/dist/static/vendor/dagre-bundle.js +43 -0
  54. package/dist/static/vendor/dagre.min.js +3 -0
  55. package/dist/static/vendor/graphlib.min.js +2 -0
  56. package/dist/static/viewport-transform.js +107 -0
  57. package/dist/static/workspace-switcher.js +202 -0
  58. package/dist/static/ws-client.js +71 -0
  59. package/dist/static/ws-handler.js +125 -0
  60. package/package.json +74 -0
@@ -0,0 +1,353 @@
1
+ /**
2
+ * SmartCode Selection -- node/edge selection, visual SVG indicators, keyboard shortcuts.
3
+ * Manages the selection lifecycle: select, deselect, re-apply after re-render.
4
+ *
5
+ * Dependencies:
6
+ * - interaction-state.js (SmartCodeInteraction)
7
+ * - diagram-dom.js (DiagramDOM)
8
+ * - event-bus.js (SmartCodeEventBus)
9
+ * - diagram-editor.js (MmdEditor)
10
+ *
11
+ * Usage:
12
+ * SmartCodeSelection.init();
13
+ * SmartCodeSelection.selectNode('A');
14
+ * SmartCodeSelection.deselectAll();
15
+ * SmartCodeSelection.getSelected(); // { id: 'A', type: 'node' } or null
16
+ */
17
+ (function() {
18
+ 'use strict';
19
+
20
+ var SVG_NS = 'http://www.w3.org/2000/svg';
21
+
22
+ // ── Internal State ──
23
+ var selectedNodeId = null;
24
+ var selectedType = null; // 'node' | 'edge'
25
+ var overlayGroup = null; // SVGGElement for selection indicator
26
+
27
+ // ── Core Functions ──
28
+
29
+ /**
30
+ * Clear existing selection indicator from SVG.
31
+ */
32
+ function clearIndicator() {
33
+ if (overlayGroup && overlayGroup.parentNode) {
34
+ overlayGroup.parentNode.removeChild(overlayGroup);
35
+ }
36
+ overlayGroup = null;
37
+ // Remove any .selected-edge classes
38
+ var svg = DiagramDOM.getSVG();
39
+ if (svg) {
40
+ var selectedEdges = svg.querySelectorAll('.selected-edge');
41
+ for (var i = 0; i < selectedEdges.length; i++) {
42
+ selectedEdges[i].classList.remove('selected-edge');
43
+ }
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Select a node by ID. Creates blue dashed border + 4 corner handles.
49
+ * @param {string} nodeId
50
+ */
51
+ /**
52
+ * Get the absolute position of an SVG element by walking its CTM.
53
+ * Returns { x, y, width, height } in SVG root coordinates.
54
+ */
55
+ function getAbsoluteBBox(el, svg) {
56
+ var bbox = el.getBBox();
57
+ // Use getCTM to transform local bbox to SVG root coordinates
58
+ var ctm = el.getCTM();
59
+ var svgCtm = svg.getCTM();
60
+ if (ctm && svgCtm) {
61
+ var transform = svgCtm.inverse().multiply(ctm);
62
+ return {
63
+ x: transform.a * bbox.x + transform.e,
64
+ y: transform.d * bbox.y + transform.f,
65
+ width: bbox.width * transform.a,
66
+ height: bbox.height * transform.d
67
+ };
68
+ }
69
+ return bbox;
70
+ }
71
+
72
+ function selectNode(nodeId) {
73
+ // Clear pending delete confirmation when selection changes
74
+ if (deleteConfirmTimer) {
75
+ clearTimeout(deleteConfirmTimer);
76
+ deleteConfirmTimer = null;
77
+ deleteConfirmNodeId = null;
78
+ }
79
+ clearIndicator();
80
+
81
+ var el = DiagramDOM.findNodeElement(nodeId);
82
+ if (!el) return;
83
+
84
+ var svg = DiagramDOM.getSVG();
85
+ if (!svg) return;
86
+
87
+ var bbox = getAbsoluteBBox(el, svg);
88
+
89
+ // Create selection indicator group
90
+ var g = document.createElementNS(SVG_NS, 'g');
91
+ g.setAttribute('class', 'selection-indicator');
92
+
93
+ // Blue dashed border rect
94
+ var rect = document.createElementNS(SVG_NS, 'rect');
95
+ rect.setAttribute('x', bbox.x - 4);
96
+ rect.setAttribute('y', bbox.y - 4);
97
+ rect.setAttribute('width', bbox.width + 8);
98
+ rect.setAttribute('height', bbox.height + 8);
99
+ rect.setAttribute('fill', 'none');
100
+ rect.setAttribute('stroke', '#3b82f6');
101
+ rect.setAttribute('stroke-width', '2');
102
+ rect.setAttribute('stroke-dasharray', '6,3');
103
+ rect.setAttribute('rx', '4');
104
+ g.appendChild(rect);
105
+
106
+ // Four corner handles (8x8 px)
107
+ var corners = [
108
+ [bbox.x - 8, bbox.y - 8],
109
+ [bbox.x + bbox.width, bbox.y - 8],
110
+ [bbox.x - 8, bbox.y + bbox.height],
111
+ [bbox.x + bbox.width, bbox.y + bbox.height],
112
+ ];
113
+ for (var i = 0; i < corners.length; i++) {
114
+ var handle = document.createElementNS(SVG_NS, 'rect');
115
+ handle.setAttribute('x', corners[i][0]);
116
+ handle.setAttribute('y', corners[i][1]);
117
+ handle.setAttribute('width', '8');
118
+ handle.setAttribute('height', '8');
119
+ handle.setAttribute('fill', '#3b82f6');
120
+ handle.setAttribute('rx', '2');
121
+ g.appendChild(handle);
122
+ }
123
+
124
+ svg.appendChild(g);
125
+ overlayGroup = g;
126
+
127
+ // Update state
128
+ selectedNodeId = nodeId;
129
+ selectedType = 'node';
130
+ if (window.SmartCodeInteraction) {
131
+ SmartCodeInteraction.select(nodeId, 'node');
132
+ }
133
+
134
+ // Emit event
135
+ if (window.SmartCodeEventBus) {
136
+ SmartCodeEventBus.emit('selection:changed', { id: nodeId, type: 'node' });
137
+ }
138
+
139
+ // Track click for heatmap frequency data
140
+ if (window.SmartCodeInteractionTracker) {
141
+ SmartCodeInteractionTracker.trackClick(nodeId);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Select an edge by ID. Adds .selected-edge CSS class (no handles).
147
+ * @param {string} edgeId
148
+ */
149
+ function selectEdge(edgeId) {
150
+ clearIndicator();
151
+
152
+ var el = DiagramDOM.findEdgeElement(edgeId);
153
+ if (!el) return;
154
+
155
+ el.classList.add('selected-edge');
156
+
157
+ // Update state
158
+ selectedNodeId = edgeId;
159
+ selectedType = 'edge';
160
+ if (window.SmartCodeInteraction) {
161
+ SmartCodeInteraction.select(edgeId, 'edge');
162
+ }
163
+
164
+ // Emit event
165
+ if (window.SmartCodeEventBus) {
166
+ SmartCodeEventBus.emit('selection:changed', { id: edgeId, type: 'edge' });
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Deselect all. Removes indicators, clears state.
172
+ */
173
+ function deselectAll() {
174
+ // Clear pending delete confirmation
175
+ if (deleteConfirmTimer) {
176
+ clearTimeout(deleteConfirmTimer);
177
+ deleteConfirmTimer = null;
178
+ deleteConfirmNodeId = null;
179
+ }
180
+ clearIndicator();
181
+ selectedNodeId = null;
182
+ selectedType = null;
183
+ if (window.SmartCodeInteraction) {
184
+ SmartCodeInteraction.clearSelection();
185
+ }
186
+ if (window.SmartCodeEventBus) {
187
+ SmartCodeEventBus.emit('selection:changed', null);
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Re-apply selection after SVG re-render (called on diagram:rendered).
193
+ * If the selected element still exists, re-create the indicator.
194
+ * If it was removed, deselect and transition FSM to idle.
195
+ */
196
+ function reapplySelection() {
197
+ if (!selectedNodeId) return;
198
+
199
+ if (selectedType === 'node') {
200
+ var nodeEl = DiagramDOM.findNodeElement(selectedNodeId);
201
+ if (nodeEl) {
202
+ // Re-create indicator on new SVG
203
+ var savedId = selectedNodeId;
204
+ clearIndicator();
205
+ selectedNodeId = savedId;
206
+ selectedType = 'node';
207
+ selectNode(savedId);
208
+ } else {
209
+ deselectAll();
210
+ if (window.SmartCodeInteraction && SmartCodeInteraction.getState() === 'selected') {
211
+ SmartCodeInteraction.forceState('idle');
212
+ }
213
+ }
214
+ } else if (selectedType === 'edge') {
215
+ var edgeEl = DiagramDOM.findEdgeElement(selectedNodeId);
216
+ if (edgeEl) {
217
+ var savedEdgeId = selectedNodeId;
218
+ clearIndicator();
219
+ selectedNodeId = savedEdgeId;
220
+ selectedType = 'edge';
221
+ selectEdge(savedEdgeId);
222
+ } else {
223
+ deselectAll();
224
+ if (window.SmartCodeInteraction && SmartCodeInteraction.getState() === 'selected') {
225
+ SmartCodeInteraction.forceState('idle');
226
+ }
227
+ }
228
+ }
229
+ }
230
+
231
+ // ── Click Handler (delegated on #preview-container) ──
232
+
233
+ function handleClick(e) {
234
+ // Check FSM blocking states
235
+ if (window.SmartCodeInteraction && SmartCodeInteraction.isBlocking()) return;
236
+
237
+ // Don't handle in modes that have their own click handlers
238
+ var fsmState = window.SmartCodeInteraction ? SmartCodeInteraction.getState() : 'idle';
239
+ if (fsmState === 'flagging' || fsmState === 'add-node' || fsmState === 'add-edge') return;
240
+
241
+ // Skip UI controls and overlays
242
+ if (e.target.closest('.zoom-controls') ||
243
+ e.target.closest('.flag-popover') ||
244
+ e.target.closest('.editor-popover') ||
245
+ e.target.closest('.context-menu') ||
246
+ e.target.closest('.inline-edit-overlay')) return;
247
+
248
+ // Detect what was clicked
249
+ var nodeInfo = DiagramDOM.extractNodeId(e.target);
250
+
251
+ if (nodeInfo && nodeInfo.type === 'node') {
252
+ selectNode(nodeInfo.id);
253
+ if (window.SmartCodeInteraction) SmartCodeInteraction.transition('click_node', nodeInfo);
254
+ } else if (nodeInfo && nodeInfo.type === 'edge') {
255
+ selectEdge(nodeInfo.id);
256
+ if (window.SmartCodeInteraction) SmartCodeInteraction.transition('click_edge', nodeInfo);
257
+ } else if (nodeInfo && nodeInfo.type === 'subgraph') {
258
+ // Treat subgraph like a node for selection
259
+ selectNode(nodeInfo.id);
260
+ if (window.SmartCodeInteraction) SmartCodeInteraction.transition('click_node', nodeInfo);
261
+ } else {
262
+ // Empty space click
263
+ if (selectedNodeId) {
264
+ deselectAll();
265
+ if (window.SmartCodeInteraction) SmartCodeInteraction.transition('click_empty');
266
+ }
267
+ }
268
+ }
269
+
270
+ // ── Keyboard Handler (document-level) ──
271
+
272
+ var deleteConfirmTimer = null;
273
+ var deleteConfirmNodeId = null;
274
+
275
+ function handleKeydown(e) {
276
+ // Don't handle if focus is on text inputs
277
+ if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT' ||
278
+ e.target.getAttribute('contenteditable')) return;
279
+
280
+ // Don't handle if inline edit is active
281
+ if (window.SmartCodeInlineEdit && SmartCodeInlineEdit.isActive()) return;
282
+
283
+ var fsmState = window.SmartCodeInteraction ? SmartCodeInteraction.getState() : 'idle';
284
+
285
+ if (fsmState === 'selected' && selectedNodeId) {
286
+ if (e.key === 'Delete' || e.key === 'Backspace') {
287
+ e.preventDefault();
288
+ // Require double-press within 2 seconds to confirm deletion
289
+ if (deleteConfirmNodeId === selectedNodeId && deleteConfirmTimer) {
290
+ clearTimeout(deleteConfirmTimer);
291
+ deleteConfirmTimer = null;
292
+ deleteConfirmNodeId = null;
293
+ if (selectedType === 'node' && window.MmdEditor) {
294
+ var nodeToRemove = selectedNodeId;
295
+ deselectAll();
296
+ MmdEditor.doRemoveNode(nodeToRemove);
297
+ }
298
+ if (window.SmartCodeInteraction) SmartCodeInteraction.transition('delete_node');
299
+ } else {
300
+ // First press: show confirmation toast
301
+ deleteConfirmNodeId = selectedNodeId;
302
+ if (deleteConfirmTimer) clearTimeout(deleteConfirmTimer);
303
+ deleteConfirmTimer = setTimeout(function() {
304
+ deleteConfirmTimer = null;
305
+ deleteConfirmNodeId = null;
306
+ }, 2000);
307
+ if (window.toast) window.toast('Press Delete again to remove the node');
308
+ }
309
+ }
310
+ if (e.key === 'Escape') {
311
+ deleteConfirmTimer = null;
312
+ deleteConfirmNodeId = null;
313
+ deselectAll();
314
+ if (window.SmartCodeInteraction) SmartCodeInteraction.transition('escape');
315
+ }
316
+ }
317
+ }
318
+
319
+ // ── Init ──
320
+
321
+ function init() {
322
+ var container = document.getElementById('preview-container');
323
+ if (container) {
324
+ container.addEventListener('click', handleClick);
325
+ }
326
+
327
+ document.addEventListener('keydown', handleKeydown);
328
+
329
+ // Re-apply selection after SVG re-render
330
+ if (window.SmartCodeEventBus) {
331
+ SmartCodeEventBus.on('diagram:rendered', reapplySelection);
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Returns the current selection.
337
+ * @returns {{ id: string, type: string }|null}
338
+ */
339
+ function getSelected() {
340
+ if (!selectedNodeId) return null;
341
+ return { id: selectedNodeId, type: selectedType };
342
+ }
343
+
344
+ // ── Public API ──
345
+ window.SmartCodeSelection = {
346
+ init: init,
347
+ selectNode: selectNode,
348
+ selectEdge: selectEdge,
349
+ deselectAll: deselectAll,
350
+ reapplySelection: reapplySelection,
351
+ getSelected: getSelected,
352
+ };
353
+ })();
@@ -0,0 +1,137 @@
1
+ /* Session Player -- timeline scrubber bar and diff highlight styles */
2
+
3
+ .session-player {
4
+ display: flex;
5
+ align-items: center;
6
+ gap: 8px;
7
+ padding: 8px 12px;
8
+ background: var(--surface-1);
9
+ border-top: 1px solid var(--border-default);
10
+ z-index: 50;
11
+ }
12
+ .session-player.hidden {
13
+ display: none;
14
+ }
15
+
16
+ .sp-btn {
17
+ background: var(--surface-2);
18
+ color: var(--text-secondary);
19
+ border: 1px solid var(--border-default);
20
+ border-radius: 4px;
21
+ padding: 4px 10px;
22
+ font-size: 12px;
23
+ cursor: pointer;
24
+ font-family: 'Inter', sans-serif;
25
+ }
26
+ .sp-btn:hover {
27
+ background: var(--surface-3);
28
+ color: var(--text-primary);
29
+ }
30
+
31
+ .sp-scrubber {
32
+ flex: 1;
33
+ -webkit-appearance: none;
34
+ appearance: none;
35
+ height: 6px;
36
+ background: var(--border-default);
37
+ border-radius: 3px;
38
+ outline: none;
39
+ cursor: pointer;
40
+ }
41
+ .sp-scrubber::-webkit-slider-thumb {
42
+ -webkit-appearance: none;
43
+ appearance: none;
44
+ width: 14px;
45
+ height: 14px;
46
+ background: var(--accent);
47
+ border-radius: 50%;
48
+ cursor: pointer;
49
+ }
50
+ .sp-scrubber::-moz-range-thumb {
51
+ width: 14px;
52
+ height: 14px;
53
+ background: var(--accent);
54
+ border-radius: 50%;
55
+ cursor: pointer;
56
+ border: none;
57
+ }
58
+
59
+ .sp-time {
60
+ font-family: 'JetBrains Mono', monospace;
61
+ font-size: 11px;
62
+ color: var(--text-secondary);
63
+ min-width: 60px;
64
+ text-align: center;
65
+ white-space: nowrap;
66
+ }
67
+
68
+ .sp-speed {
69
+ background: var(--surface-2);
70
+ color: var(--text-secondary);
71
+ border: 1px solid var(--border-default);
72
+ border-radius: 4px;
73
+ padding: 3px 6px;
74
+ font-size: 11px;
75
+ cursor: pointer;
76
+ }
77
+
78
+ /* Diff highlighting */
79
+ .diff-added rect, .diff-added polygon, .diff-added circle,
80
+ .diff-added ellipse, .diff-added path {
81
+ stroke: var(--diff-added) !important;
82
+ stroke-width: 3px !important;
83
+ }
84
+ .diff-removed-ghost {
85
+ opacity: 0.3;
86
+ fill: var(--diff-removed);
87
+ stroke: var(--diff-removed);
88
+ stroke-dasharray: 6,3;
89
+ }
90
+ .diff-modified rect, .diff-modified polygon, .diff-modified circle,
91
+ .diff-modified ellipse, .diff-modified path {
92
+ stroke: var(--diff-modified) !important;
93
+ stroke-width: 3px !important;
94
+ }
95
+
96
+ /* Session dropdown */
97
+ .session-dropdown {
98
+ position: absolute;
99
+ top: 36px;
100
+ right: 0;
101
+ width: 260px;
102
+ max-height: 300px;
103
+ overflow-y: auto;
104
+ background: var(--surface-1);
105
+ border: 1px solid var(--border-default);
106
+ border-radius: 6px;
107
+ z-index: 100;
108
+ box-shadow: 0 4px 12px rgba(0,0,0,0.4);
109
+ }
110
+ .session-dropdown.hidden {
111
+ display: none;
112
+ }
113
+ .session-list-item {
114
+ padding: 8px 12px;
115
+ font-size: 12px;
116
+ color: var(--text-secondary);
117
+ cursor: pointer;
118
+ border-bottom: 1px solid var(--border-default);
119
+ }
120
+ .session-list-item:hover {
121
+ background: var(--surface-2);
122
+ color: var(--text-primary);
123
+ }
124
+ .session-list-item:last-child {
125
+ border-bottom: none;
126
+ }
127
+
128
+ /* Responsive stacking */
129
+ @media (max-width: 600px) {
130
+ .session-player {
131
+ flex-wrap: wrap;
132
+ }
133
+ .sp-scrubber {
134
+ order: 10;
135
+ flex-basis: 100%;
136
+ }
137
+ }