@newsails/veil-studio 1.0.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 (87) hide show
  1. package/README.md +181 -0
  2. package/bin/veil-studio.js +142 -0
  3. package/nuxt-app/.output/public/200.html +13 -0
  4. package/nuxt-app/.output/public/404.html +13 -0
  5. package/nuxt-app/.output/public/_nuxt/builds/latest.json +1 -0
  6. package/nuxt-app/.output/public/_nuxt/builds/meta/6b28df26-54af-4fad-a1f0-38808960d9fe.json +1 -0
  7. package/nuxt-app/.output/public/_nuxt/entry.BrrOeBSX.js +120 -0
  8. package/nuxt-app/.output/public/_nuxt/entry.CYnp7zY5.css +1 -0
  9. package/nuxt-app/.output/public/_nuxt/error-404.BbdzCaXe.js +1 -0
  10. package/nuxt-app/.output/public/_nuxt/error-404.JekaaCis.css +1 -0
  11. package/nuxt-app/.output/public/_nuxt/error-500.CNP9nqm1.css +1 -0
  12. package/nuxt-app/.output/public/_nuxt/error-500.DbOlBIIY.js +1 -0
  13. package/nuxt-app/.output/public/_nuxt/index.BEoXSIOu.css +1 -0
  14. package/nuxt-app/.output/public/_nuxt/index.CNms2yAq.js +1 -0
  15. package/nuxt-app/.output/public/_nuxt/materialdesignicons-webfont.B7mPwVP_.ttf +0 -0
  16. package/nuxt-app/.output/public/_nuxt/materialdesignicons-webfont.CSr8KVlo.eot +0 -0
  17. package/nuxt-app/.output/public/_nuxt/materialdesignicons-webfont.Dp5v-WZN.woff2 +0 -0
  18. package/nuxt-app/.output/public/_nuxt/materialdesignicons-webfont.PXm3-2wK.woff +0 -0
  19. package/nuxt-app/.output/public/_nuxt/vue.-sixQ7xP.BlWffD__.js +1 -0
  20. package/nuxt-app/.output/public/index.html +13 -0
  21. package/package.json +37 -0
  22. package/server/index.js +184 -0
  23. package/server/routes/files.js +80 -0
  24. package/server/socket.js +507 -0
  25. package/server/utils/board-state.js +357 -0
  26. package/server/utils/config.js +51 -0
  27. package/server/utils/db.js +484 -0
  28. package/server/utils/element-instances.js +104 -0
  29. package/server/utils/element-registry.js +127 -0
  30. package/server/utils/elements/agent-instance.js +62 -0
  31. package/server/utils/elements/agent.js +93 -0
  32. package/server/utils/elements/annotation.js +30 -0
  33. package/server/utils/elements/approval-gate.js +49 -0
  34. package/server/utils/elements/assumption.js +32 -0
  35. package/server/utils/elements/blocker.js +36 -0
  36. package/server/utils/elements/chat-room.js +47 -0
  37. package/server/utils/elements/chat-view.js +33 -0
  38. package/server/utils/elements/code-block.js +39 -0
  39. package/server/utils/elements/collapsible-group.js +37 -0
  40. package/server/utils/elements/comparison-table.js +38 -0
  41. package/server/utils/elements/constraint.js +32 -0
  42. package/server/utils/elements/decision.js +39 -0
  43. package/server/utils/elements/diff-patch.js +38 -0
  44. package/server/utils/elements/divider.js +30 -0
  45. package/server/utils/elements/document-draft.js +35 -0
  46. package/server/utils/elements/fact-claim.js +36 -0
  47. package/server/utils/elements/feedback-request.js +43 -0
  48. package/server/utils/elements/file-reference.js +31 -0
  49. package/server/utils/elements/filter.js +48 -0
  50. package/server/utils/elements/generator.js +51 -0
  51. package/server/utils/elements/goal.js +36 -0
  52. package/server/utils/elements/html.js +35 -0
  53. package/server/utils/elements/idea.js +32 -0
  54. package/server/utils/elements/image-local.js +21 -0
  55. package/server/utils/elements/image.js +34 -0
  56. package/server/utils/elements/json-object.js +47 -0
  57. package/server/utils/elements/label-tag.js +30 -0
  58. package/server/utils/elements/markdown.js +37 -0
  59. package/server/utils/elements/merger.js +36 -0
  60. package/server/utils/elements/message.js +40 -0
  61. package/server/utils/elements/milestone.js +44 -0
  62. package/server/utils/elements/notification.js +34 -0
  63. package/server/utils/elements/outline.js +35 -0
  64. package/server/utils/elements/primitive.js +36 -0
  65. package/server/utils/elements/pro-con-list.js +39 -0
  66. package/server/utils/elements/processor.js +54 -0
  67. package/server/utils/elements/project.js +40 -0
  68. package/server/utils/elements/question.js +42 -0
  69. package/server/utils/elements/queue.js +85 -0
  70. package/server/utils/elements/research-note.js +41 -0
  71. package/server/utils/elements/section.js +35 -0
  72. package/server/utils/elements/source-collection.js +45 -0
  73. package/server/utils/elements/splitter.js +42 -0
  74. package/server/utils/elements/status-update.js +38 -0
  75. package/server/utils/elements/task.js +72 -0
  76. package/server/utils/elements/template.js +46 -0
  77. package/server/utils/elements/test-case.js +42 -0
  78. package/server/utils/elements/text.js +29 -0
  79. package/server/utils/elements/todo-list.js +57 -0
  80. package/server/utils/elements/url-card.js +37 -0
  81. package/server/utils/elements/web-search-query.js +46 -0
  82. package/server/utils/elements/web-snapshot.js +37 -0
  83. package/server/utils/file-utils.js +88 -0
  84. package/server/utils/session-watcher.js +108 -0
  85. package/server/utils/socket-io.js +14 -0
  86. package/server/utils/veil-client.js +185 -0
  87. package/server/utils/veil-ws.js +207 -0
@@ -0,0 +1,484 @@
1
+ 'use strict'
2
+
3
+ const path = require('path')
4
+ const fs = require('fs')
5
+ const Database = require('better-sqlite3')
6
+ const { DB_PATH, PROJECT_DIR } = require('./config.js')
7
+
8
+ let db = null
9
+
10
+ function initDB() {
11
+ const studioDir = path.join(PROJECT_DIR, '.veil-studio')
12
+ fs.mkdirSync(studioDir, { recursive: true })
13
+
14
+ db = new Database(DB_PATH)
15
+ db.pragma('journal_mode = WAL')
16
+ db.pragma('foreign_keys = ON')
17
+
18
+ db.exec(`
19
+ CREATE TABLE IF NOT EXISTS elements (
20
+ id TEXT PRIMARY KEY,
21
+ type TEXT NOT NULL,
22
+ x REAL NOT NULL,
23
+ y REAL NOT NULL,
24
+ width REAL NOT NULL,
25
+ height REAL NOT NULL,
26
+ data TEXT NOT NULL DEFAULT '{}',
27
+ zone_id TEXT,
28
+ expansion_mode TEXT NOT NULL DEFAULT 'inline',
29
+ expanded INTEGER NOT NULL DEFAULT 0,
30
+ state TEXT NOT NULL DEFAULT 'default',
31
+ created_at INTEGER NOT NULL,
32
+ updated_at INTEGER NOT NULL
33
+ );
34
+
35
+ CREATE TABLE IF NOT EXISTS links (
36
+ id TEXT PRIMARY KEY,
37
+ from_element_id TEXT NOT NULL REFERENCES elements(id) ON DELETE CASCADE,
38
+ from_port_key TEXT NOT NULL,
39
+ to_element_id TEXT NOT NULL REFERENCES elements(id) ON DELETE CASCADE,
40
+ to_port_key TEXT NOT NULL,
41
+ type TEXT NOT NULL,
42
+ created_at INTEGER NOT NULL
43
+ );
44
+
45
+ CREATE TABLE IF NOT EXISTS zones (
46
+ id TEXT PRIMARY KEY,
47
+ name TEXT NOT NULL,
48
+ x REAL NOT NULL,
49
+ y REAL NOT NULL,
50
+ width REAL NOT NULL,
51
+ height REAL NOT NULL,
52
+ color TEXT NOT NULL DEFAULT '#3b82f6',
53
+ config TEXT NOT NULL DEFAULT '{}',
54
+ collapsed INTEGER NOT NULL DEFAULT 0,
55
+ created_at INTEGER NOT NULL,
56
+ updated_at INTEGER NOT NULL
57
+ );
58
+
59
+ CREATE TABLE IF NOT EXISTS zone_inputs (
60
+ id TEXT PRIMARY KEY,
61
+ zone_id TEXT NOT NULL REFERENCES zones(id) ON DELETE CASCADE,
62
+ key TEXT NOT NULL,
63
+ label TEXT NOT NULL,
64
+ data_type TEXT NOT NULL DEFAULT 'any'
65
+ );
66
+
67
+ CREATE TABLE IF NOT EXISTS zone_input_links (
68
+ id TEXT PRIMARY KEY,
69
+ zone_input_id TEXT NOT NULL REFERENCES zone_inputs(id) ON DELETE CASCADE,
70
+ element_id TEXT NOT NULL,
71
+ port_key TEXT NOT NULL
72
+ );
73
+
74
+ CREATE TABLE IF NOT EXISTS views (
75
+ id TEXT PRIMARY KEY,
76
+ name TEXT NOT NULL,
77
+ camera_x REAL NOT NULL,
78
+ camera_y REAL NOT NULL,
79
+ zoom REAL NOT NULL,
80
+ created_at INTEGER NOT NULL
81
+ );
82
+
83
+ CREATE TABLE IF NOT EXISTS archive (
84
+ id TEXT PRIMARY KEY,
85
+ element_data TEXT NOT NULL,
86
+ archived_at INTEGER NOT NULL
87
+ );
88
+
89
+ CREATE TABLE IF NOT EXISTS deleted (
90
+ id TEXT PRIMARY KEY,
91
+ element_data TEXT NOT NULL,
92
+ deleted_at INTEGER NOT NULL
93
+ );
94
+
95
+ CREATE TABLE IF NOT EXISTS board_meta (
96
+ key TEXT PRIMARY KEY,
97
+ value TEXT NOT NULL
98
+ );
99
+
100
+ CREATE TABLE IF NOT EXISTS shapes (
101
+ id TEXT PRIMARY KEY,
102
+ subtype TEXT NOT NULL DEFAULT 'rectangle',
103
+ x REAL NOT NULL,
104
+ y REAL NOT NULL,
105
+ width REAL NOT NULL,
106
+ height REAL NOT NULL,
107
+ fill_color TEXT NOT NULL DEFAULT 'transparent',
108
+ stroke_color TEXT NOT NULL DEFAULT '#6b7280',
109
+ stroke_width REAL NOT NULL DEFAULT 1.5,
110
+ text TEXT NOT NULL DEFAULT '',
111
+ title TEXT NOT NULL DEFAULT '',
112
+ text_align TEXT NOT NULL DEFAULT 'center',
113
+ created_at INTEGER NOT NULL,
114
+ updated_at INTEGER NOT NULL
115
+ );
116
+ `)
117
+
118
+ // Seed board_meta if empty
119
+ const metaCount = db.prepare('SELECT COUNT(*) as c FROM board_meta').get()
120
+ if (metaCount.c === 0) {
121
+ const now = Date.now().toString()
122
+ db.prepare("INSERT INTO board_meta (key, value) VALUES ('created_at', ?)").run(now)
123
+ db.prepare("INSERT INTO board_meta (key, value) VALUES ('first_opened', 'false')").run()
124
+ db.prepare("INSERT INTO board_meta (key, value) VALUES ('projectDir', ?)").run(PROJECT_DIR)
125
+ }
126
+
127
+ _prepareStatements()
128
+ }
129
+
130
+ // Prepared statements
131
+ let stmts = {}
132
+
133
+ function _prepareStatements() {
134
+ stmts = {
135
+ // Elements
136
+ getElements: db.prepare('SELECT * FROM elements'),
137
+ getElementById: db.prepare('SELECT * FROM elements WHERE id = ?'),
138
+ insertElement: db.prepare(`
139
+ INSERT INTO elements (id, type, x, y, width, height, data, zone_id, expansion_mode, expanded, state, created_at, updated_at)
140
+ VALUES (@id, @type, @x, @y, @width, @height, @data, @zone_id, @expansion_mode, @expanded, @state, @created_at, @updated_at)
141
+ `),
142
+ updateElement: db.prepare(`
143
+ UPDATE elements SET x=@x, y=@y, width=@width, height=@height, data=@data,
144
+ zone_id=@zone_id, expansion_mode=@expansion_mode, expanded=@expanded,
145
+ state=@state, updated_at=@updated_at
146
+ WHERE id=@id
147
+ `),
148
+ deleteElement: db.prepare('DELETE FROM elements WHERE id = ?'),
149
+
150
+ // Links
151
+ getLinks: db.prepare('SELECT * FROM links'),
152
+ insertLink: db.prepare(`
153
+ INSERT INTO links (id, from_element_id, from_port_key, to_element_id, to_port_key, type, created_at)
154
+ VALUES (@id, @from_element_id, @from_port_key, @to_element_id, @to_port_key, @type, @created_at)
155
+ `),
156
+ deleteLink: db.prepare('DELETE FROM links WHERE id = ?'),
157
+
158
+ // Zones
159
+ getZones: db.prepare('SELECT * FROM zones'),
160
+ insertZone: db.prepare(`
161
+ INSERT INTO zones (id, name, x, y, width, height, color, config, collapsed, created_at, updated_at)
162
+ VALUES (@id, @name, @x, @y, @width, @height, @color, @config, @collapsed, @created_at, @updated_at)
163
+ `),
164
+ updateZone: db.prepare(`
165
+ UPDATE zones SET name=@name, x=@x, y=@y, width=@width, height=@height,
166
+ color=@color, config=@config, collapsed=@collapsed, updated_at=@updated_at
167
+ WHERE id=@id
168
+ `),
169
+ deleteZone: db.prepare('DELETE FROM zones WHERE id = ?'),
170
+
171
+ // Views
172
+ getViews: db.prepare('SELECT * FROM views'),
173
+ insertView: db.prepare(`
174
+ INSERT INTO views (id, name, camera_x, camera_y, zoom, created_at)
175
+ VALUES (@id, @name, @camera_x, @camera_y, @zoom, @created_at)
176
+ `),
177
+ deleteView: db.prepare('DELETE FROM views WHERE id = ?'),
178
+
179
+ // Archive
180
+ getArchive: db.prepare('SELECT * FROM archive ORDER BY archived_at DESC'),
181
+ insertArchive: db.prepare('INSERT INTO archive (id, element_data, archived_at) VALUES (@id, @element_data, @archived_at)'),
182
+ removeArchive: db.prepare('DELETE FROM archive WHERE id = ?'),
183
+
184
+ // Deleted
185
+ getDeleted: db.prepare('SELECT * FROM deleted ORDER BY deleted_at DESC'),
186
+ insertDeleted: db.prepare('INSERT INTO deleted (id, element_data, deleted_at) VALUES (@id, @element_data, @deleted_at)'),
187
+ removeDeleted: db.prepare('DELETE FROM deleted WHERE id = ?'),
188
+
189
+ // Meta
190
+ getMeta: db.prepare('SELECT value FROM board_meta WHERE key = ?'),
191
+ setMeta: db.prepare('INSERT INTO board_meta (key, value) VALUES (@key, @value) ON CONFLICT(key) DO UPDATE SET value=@value'),
192
+
193
+ // Shapes
194
+ getShapes: db.prepare('SELECT * FROM shapes ORDER BY created_at ASC'),
195
+ insertShape: db.prepare(`
196
+ INSERT INTO shapes (id, subtype, x, y, width, height, fill_color, stroke_color, stroke_width, text, title, text_align, created_at, updated_at)
197
+ VALUES (@id, @subtype, @x, @y, @width, @height, @fill_color, @stroke_color, @stroke_width, @text, @title, @text_align, @created_at, @updated_at)
198
+ `),
199
+ updateShape: db.prepare(`
200
+ UPDATE shapes SET subtype=@subtype, x=@x, y=@y, width=@width, height=@height,
201
+ fill_color=@fill_color, stroke_color=@stroke_color, stroke_width=@stroke_width,
202
+ text=@text, title=@title, text_align=@text_align, updated_at=@updated_at
203
+ WHERE id=@id
204
+ `),
205
+ deleteShape: db.prepare('DELETE FROM shapes WHERE id = ?'),
206
+ }
207
+ }
208
+
209
+ // Helper: row → BoardElement
210
+ function rowToElement(row) {
211
+ return {
212
+ id: row.id,
213
+ type: row.type,
214
+ x: row.x,
215
+ y: row.y,
216
+ width: row.width,
217
+ height: row.height,
218
+ data: JSON.parse(row.data),
219
+ zoneId: row.zone_id,
220
+ expansionMode: row.expansion_mode,
221
+ expanded: row.expanded === 1,
222
+ state: row.state,
223
+ createdAt: row.created_at,
224
+ updatedAt: row.updated_at,
225
+ }
226
+ }
227
+
228
+ // Helper: row → Link
229
+ function rowToLink(row) {
230
+ return {
231
+ id: row.id,
232
+ fromElementId: row.from_element_id,
233
+ fromPortKey: row.from_port_key,
234
+ toElementId: row.to_element_id,
235
+ toPortKey: row.to_port_key,
236
+ type: row.type,
237
+ createdAt: row.created_at,
238
+ }
239
+ }
240
+
241
+ // Helper: row → Zone
242
+ function rowToZone(row) {
243
+ return {
244
+ id: row.id,
245
+ name: row.name,
246
+ x: row.x,
247
+ y: row.y,
248
+ width: row.width,
249
+ height: row.height,
250
+ color: row.color,
251
+ config: JSON.parse(row.config),
252
+ collapsed: row.collapsed === 1,
253
+ createdAt: row.created_at,
254
+ updatedAt: row.updated_at,
255
+ }
256
+ }
257
+
258
+ // Helper: row → SavedView
259
+ function rowToView(row) {
260
+ return {
261
+ id: row.id,
262
+ name: row.name,
263
+ cameraX: row.camera_x,
264
+ cameraY: row.camera_y,
265
+ zoom: row.zoom,
266
+ createdAt: row.created_at,
267
+ }
268
+ }
269
+
270
+ // Helper: row → BinItem
271
+ function rowToBinItem(row, timestampField) {
272
+ return {
273
+ id: row.id,
274
+ elementData: JSON.parse(row.element_data),
275
+ timestamp: row[timestampField],
276
+ }
277
+ }
278
+
279
+ // Exported DB helpers
280
+ function dbGetElements() { return stmts.getElements.all().map(rowToElement) }
281
+ function dbGetElement(id) { const r = stmts.getElementById.get(id); return r ? rowToElement(r) : null }
282
+
283
+ function dbInsertElement(el) {
284
+ stmts.insertElement.run({
285
+ id: el.id,
286
+ type: el.type,
287
+ x: el.x,
288
+ y: el.y,
289
+ width: el.width,
290
+ height: el.height,
291
+ data: JSON.stringify(el.data || {}),
292
+ zone_id: el.zoneId || null,
293
+ expansion_mode: el.expansionMode || 'inline',
294
+ expanded: el.expanded ? 1 : 0,
295
+ state: el.state || 'default',
296
+ created_at: el.createdAt,
297
+ updated_at: el.updatedAt,
298
+ })
299
+ }
300
+
301
+ function dbUpdateElement(id, el) {
302
+ stmts.updateElement.run({
303
+ id,
304
+ x: el.x,
305
+ y: el.y,
306
+ width: el.width,
307
+ height: el.height,
308
+ data: JSON.stringify(el.data || {}),
309
+ zone_id: el.zoneId || null,
310
+ expansion_mode: el.expansionMode || 'inline',
311
+ expanded: el.expanded ? 1 : 0,
312
+ state: el.state || 'default',
313
+ updated_at: el.updatedAt,
314
+ })
315
+ }
316
+
317
+ function dbDeleteElement(id) { stmts.deleteElement.run(id) }
318
+
319
+ function dbGetLinks() { return stmts.getLinks.all().map(rowToLink) }
320
+
321
+ function dbInsertLink(link) {
322
+ stmts.insertLink.run({
323
+ id: link.id,
324
+ from_element_id: link.fromElementId,
325
+ from_port_key: link.fromPortKey,
326
+ to_element_id: link.toElementId,
327
+ to_port_key: link.toPortKey,
328
+ type: link.type,
329
+ created_at: link.createdAt,
330
+ })
331
+ }
332
+
333
+ function dbDeleteLink(id) { stmts.deleteLink.run(id) }
334
+
335
+ function dbGetZones() { return stmts.getZones.all().map(rowToZone) }
336
+
337
+ function dbInsertZone(zone) {
338
+ stmts.insertZone.run({
339
+ id: zone.id,
340
+ name: zone.name,
341
+ x: zone.x,
342
+ y: zone.y,
343
+ width: zone.width,
344
+ height: zone.height,
345
+ color: zone.color || '#3b82f6',
346
+ config: JSON.stringify(zone.config || {}),
347
+ collapsed: zone.collapsed ? 1 : 0,
348
+ created_at: zone.createdAt,
349
+ updated_at: zone.updatedAt,
350
+ })
351
+ }
352
+
353
+ function dbUpdateZone(id, zone) {
354
+ stmts.updateZone.run({
355
+ id,
356
+ name: zone.name,
357
+ x: zone.x,
358
+ y: zone.y,
359
+ width: zone.width,
360
+ height: zone.height,
361
+ color: zone.color || '#3b82f6',
362
+ config: JSON.stringify(zone.config || {}),
363
+ collapsed: zone.collapsed ? 1 : 0,
364
+ updated_at: zone.updatedAt,
365
+ })
366
+ }
367
+
368
+ function dbDeleteZone(id) { stmts.deleteZone.run(id) }
369
+
370
+ function dbGetViews() { return stmts.getViews.all().map(rowToView) }
371
+
372
+ function dbInsertView(v) {
373
+ stmts.insertView.run({
374
+ id: v.id,
375
+ name: v.name,
376
+ camera_x: v.cameraX,
377
+ camera_y: v.cameraY,
378
+ zoom: v.zoom,
379
+ created_at: v.createdAt,
380
+ })
381
+ }
382
+
383
+ function dbDeleteView(id) { stmts.deleteView.run(id) }
384
+
385
+ function dbGetArchive() { return stmts.getArchive.all().map(r => rowToBinItem(r, 'archived_at')) }
386
+
387
+ function dbInsertArchive(item) {
388
+ stmts.insertArchive.run({
389
+ id: item.id,
390
+ element_data: JSON.stringify(item.elementData),
391
+ archived_at: item.timestamp,
392
+ })
393
+ }
394
+
395
+ function dbRemoveArchive(id) { stmts.removeArchive.run(id) }
396
+
397
+ function dbGetDeleted() { return stmts.getDeleted.all().map(r => rowToBinItem(r, 'deleted_at')) }
398
+
399
+ function dbInsertDeleted(item) {
400
+ stmts.insertDeleted.run({
401
+ id: item.id,
402
+ element_data: JSON.stringify(item.elementData),
403
+ deleted_at: item.timestamp,
404
+ })
405
+ }
406
+
407
+ function dbRemoveDeleted(id) { stmts.removeDeleted.run(id) }
408
+
409
+ function dbGetMeta(key) { const r = stmts.getMeta.get(key); return r ? r.value : null }
410
+ function dbSetMeta(key, value) { stmts.setMeta.run({ key, value }) }
411
+
412
+ // Helper: row → BoardShape
413
+ function rowToShape(row) {
414
+ return {
415
+ id: row.id,
416
+ subtype: row.subtype,
417
+ x: row.x,
418
+ y: row.y,
419
+ width: row.width,
420
+ height: row.height,
421
+ fillColor: row.fill_color,
422
+ strokeColor: row.stroke_color,
423
+ strokeWidth: row.stroke_width,
424
+ text: row.text,
425
+ title: row.title,
426
+ textAlign: row.text_align,
427
+ createdAt: row.created_at,
428
+ updatedAt: row.updated_at,
429
+ }
430
+ }
431
+
432
+ function dbGetShapes() { return stmts.getShapes.all().map(rowToShape) }
433
+
434
+ function dbInsertShape(s) {
435
+ stmts.insertShape.run({
436
+ id: s.id,
437
+ subtype: s.subtype || 'rectangle',
438
+ x: s.x,
439
+ y: s.y,
440
+ width: s.width,
441
+ height: s.height,
442
+ fill_color: s.fillColor || 'transparent',
443
+ stroke_color: s.strokeColor || '#6b7280',
444
+ stroke_width: s.strokeWidth ?? 1.5,
445
+ text: s.text || '',
446
+ title: s.title || '',
447
+ text_align: s.textAlign || 'center',
448
+ created_at: s.createdAt,
449
+ updated_at: s.updatedAt,
450
+ })
451
+ }
452
+
453
+ function dbUpdateShape(id, s) {
454
+ stmts.updateShape.run({
455
+ id,
456
+ subtype: s.subtype || 'rectangle',
457
+ x: s.x,
458
+ y: s.y,
459
+ width: s.width,
460
+ height: s.height,
461
+ fill_color: s.fillColor || 'transparent',
462
+ stroke_color: s.strokeColor || '#6b7280',
463
+ stroke_width: s.strokeWidth ?? 1.5,
464
+ text: s.text || '',
465
+ title: s.title || '',
466
+ text_align: s.textAlign || 'center',
467
+ updated_at: s.updatedAt,
468
+ })
469
+ }
470
+
471
+ function dbDeleteShape(id) { stmts.deleteShape.run(id) }
472
+
473
+ module.exports = {
474
+ initDB,
475
+ get db() { return db },
476
+ dbGetElements, dbGetElement, dbInsertElement, dbUpdateElement, dbDeleteElement,
477
+ dbGetLinks, dbInsertLink, dbDeleteLink,
478
+ dbGetZones, dbInsertZone, dbUpdateZone, dbDeleteZone,
479
+ dbGetViews, dbInsertView, dbDeleteView,
480
+ dbGetArchive, dbInsertArchive, dbRemoveArchive,
481
+ dbGetDeleted, dbInsertDeleted, dbRemoveDeleted,
482
+ dbGetMeta, dbSetMeta,
483
+ dbGetShapes, dbInsertShape, dbUpdateShape, dbDeleteShape,
484
+ }
@@ -0,0 +1,104 @@
1
+ 'use strict'
2
+
3
+ const { getElementType } = require('./element-registry.js')
4
+ const boardState = require('./board-state.js')
5
+ const { veilGet, veilPost, veilPut, veilDelete } = require('./veil-client.js')
6
+ const { getIO } = require('./socket-io.js')
7
+
8
+ const instances = new Map()
9
+
10
+ function _buildContext(element) {
11
+ return {
12
+ element,
13
+ board: {
14
+ getElements: () => boardState.getElements(),
15
+ getLinks: () => boardState.getLinks(),
16
+ getZones: () => boardState.getZones(),
17
+ },
18
+ links: boardState.getLinks().filter(
19
+ l => l.fromElementId === element.id || l.toElementId === element.id
20
+ ),
21
+ zone: element.zoneId
22
+ ? boardState.getZones().find(z => z.id === element.zoneId) || null
23
+ : null,
24
+ veil: { get: veilGet, post: veilPost, put: veilPut, delete: veilDelete },
25
+ emit: (event, data) => {
26
+ try { getIO().emit(event, data) } catch (_) {}
27
+ },
28
+ log: (msg) => console.log(`[${element.type}:${element.id.slice(0, 8)}] ${msg}`),
29
+ }
30
+ }
31
+
32
+ function createInstance(element) {
33
+ const def = getElementType(element.type)
34
+ if (!def || typeof def.createInstance !== 'function') return null
35
+
36
+ try {
37
+ const ctx = _buildContext(element)
38
+ const instance = def.createInstance(ctx)
39
+ instances.set(element.id, instance)
40
+ return instance
41
+ } catch (err) {
42
+ console.error(`[element-instances] Failed to create instance for ${element.type}:${element.id}:`, err.message)
43
+ return null
44
+ }
45
+ }
46
+
47
+ function getInstance(elementId) {
48
+ return instances.get(elementId) || null
49
+ }
50
+
51
+ async function destroyInstance(elementId) {
52
+ const instance = instances.get(elementId)
53
+ if (!instance) return
54
+ try {
55
+ if (typeof instance.onDestroy === 'function') await instance.onDestroy()
56
+ } catch (err) {
57
+ console.error(`[element-instances] onDestroy error for ${elementId}:`, err.message)
58
+ }
59
+ instances.delete(elementId)
60
+ }
61
+
62
+ async function invokeAction(elementId, action, params) {
63
+ const instance = instances.get(elementId)
64
+ if (!instance) throw new Error(`No instance for element: ${elementId}`)
65
+ if (!instance.actions || typeof instance.actions[action] !== 'function') {
66
+ throw new Error(`Action "${action}" not found on element ${elementId}`)
67
+ }
68
+ return instance.actions[action](params)
69
+ }
70
+
71
+ function getViewData(elementId) {
72
+ const instance = instances.get(elementId)
73
+ if (!instance || typeof instance.getViewData !== 'function') return {}
74
+ try { return instance.getViewData() } catch (_) { return {} }
75
+ }
76
+
77
+ function getPorts(elementId) {
78
+ const instance = instances.get(elementId)
79
+ if (!instance || typeof instance.getPorts !== 'function') return []
80
+ try { return instance.getPorts() } catch (_) { return [] }
81
+ }
82
+
83
+ function recreateInstance(element) {
84
+ destroyInstance(element.id)
85
+ return createInstance(element)
86
+ }
87
+
88
+ function initAllInstances() {
89
+ const elements = boardState.getElements()
90
+ for (const el of elements) {
91
+ if (!instances.has(el.id)) createInstance(el)
92
+ }
93
+ }
94
+
95
+ module.exports = {
96
+ getInstance,
97
+ createInstance,
98
+ destroyInstance,
99
+ invokeAction,
100
+ getViewData,
101
+ getPorts,
102
+ recreateInstance,
103
+ initAllInstances,
104
+ }
@@ -0,0 +1,127 @@
1
+ 'use strict'
2
+
3
+ const path = require('path')
4
+ const fs = require('fs')
5
+ const { ELEMENTS_DIR } = require('./config.js')
6
+
7
+ const builtins = new Map()
8
+ const customs = new Map()
9
+
10
+ function registerBuiltin(def) {
11
+ builtins.set(def.type, def)
12
+ }
13
+
14
+ function getElementType(type) {
15
+ return customs.get(type) || builtins.get(type) || null
16
+ }
17
+
18
+ function getAllTypes() {
19
+ const result = new Map(builtins)
20
+ for (const [k, v] of customs) result.set(k, v)
21
+ return Array.from(result.values()).map(def => ({
22
+ type: def.type,
23
+ name: def.name,
24
+ description: def.description,
25
+ icon: def.icon,
26
+ defaultWidth: def.defaultWidth,
27
+ defaultHeight: def.defaultHeight,
28
+ expansionMode: def.expansionMode,
29
+ tags: def.tags || [],
30
+ isBuiltIn: def.isBuiltIn !== false,
31
+ hiddenFromPalette: def.hiddenFromPalette === true,
32
+ componentSource: def.componentSource || undefined,
33
+ }))
34
+ }
35
+
36
+ function loadCustomElements() {
37
+ customs.clear()
38
+ if (!fs.existsSync(ELEMENTS_DIR)) return
39
+
40
+ let entries
41
+ try {
42
+ entries = fs.readdirSync(ELEMENTS_DIR, { withFileTypes: true })
43
+ } catch (_) { return }
44
+
45
+ for (const entry of entries) {
46
+ if (!entry.isDirectory()) continue
47
+ const typeDir = path.join(ELEMENTS_DIR, entry.name)
48
+ const metaPath = path.join(typeDir, 'element.json')
49
+ const idxPath = path.join(typeDir, 'index.js')
50
+ const vuePath = path.join(typeDir, 'vue-component.js')
51
+
52
+ if (!fs.existsSync(metaPath) || !fs.existsSync(idxPath)) continue
53
+
54
+ try {
55
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'))
56
+ const factory = require(idxPath)
57
+ const componentSource = fs.existsSync(vuePath)
58
+ ? fs.readFileSync(vuePath, 'utf8')
59
+ : undefined
60
+
61
+ customs.set(meta.type, {
62
+ ...meta,
63
+ isBuiltIn: false,
64
+ createInstance: factory.createInstance,
65
+ componentSource,
66
+ })
67
+ } catch (err) {
68
+ console.error(`[element-registry] Failed to load custom element "${entry.name}":`, err.message)
69
+ }
70
+ }
71
+ }
72
+
73
+ function reloadCustomElements() {
74
+ // Clear require cache for custom elements
75
+ if (fs.existsSync(ELEMENTS_DIR)) {
76
+ try {
77
+ const entries = fs.readdirSync(ELEMENTS_DIR, { withFileTypes: true })
78
+ for (const entry of entries) {
79
+ if (!entry.isDirectory()) continue
80
+ const idxPath = path.join(ELEMENTS_DIR, entry.name, 'index.js')
81
+ if (require.cache[idxPath]) delete require.cache[idxPath]
82
+ }
83
+ } catch (_) {}
84
+ }
85
+ loadCustomElements()
86
+ }
87
+
88
+ function getClientComponentDefinitions() {
89
+ return Array.from(customs.values())
90
+ .filter(def => def.componentSource)
91
+ .map(def => ({ type: def.type, componentSource: def.componentSource }))
92
+ }
93
+
94
+ // ─── Register all built-in element types ────────────────────────────────────
95
+
96
+ const ELEMENTS_SERVER_DIR = path.join(__dirname, 'elements')
97
+
98
+ const BUILTIN_TYPES = [
99
+ 'agent', 'agent-instance', 'chat-view', 'task', 'todo-list',
100
+ 'text', 'markdown', 'code-block', 'json-object', 'html', 'file-reference', 'image', 'image-local', 'url-card', 'web-snapshot',
101
+ 'source-collection', 'research-note', 'comparison-table', 'fact-claim', 'question', 'web-search-query',
102
+ 'document-draft', 'outline', 'section', 'template',
103
+ 'diff-patch', 'notification', 'test-case',
104
+ 'idea', 'pro-con-list', 'decision', 'assumption', 'constraint', 'goal', 'blocker',
105
+ 'project', 'milestone', 'status-update',
106
+ 'chat-room', 'message', 'feedback-request', 'approval-gate',
107
+ 'generator', 'processor', 'splitter', 'merger', 'filter', 'queue',
108
+ 'collapsible-group', 'annotation', 'label-tag', 'divider',
109
+ ]
110
+
111
+ for (const type of BUILTIN_TYPES) {
112
+ try {
113
+ const def = require(path.join(ELEMENTS_SERVER_DIR, `${type}.js`))
114
+ registerBuiltin(def)
115
+ } catch (err) {
116
+ console.error(`[element-registry] Failed to load built-in "${type}":`, err.message)
117
+ }
118
+ }
119
+
120
+ module.exports = {
121
+ registerBuiltin,
122
+ getElementType,
123
+ getAllTypes,
124
+ loadCustomElements,
125
+ reloadCustomElements,
126
+ getClientComponentDefinitions,
127
+ }