@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,36 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'primitive',
5
+ name: 'Primitive',
6
+ description: 'Basic drawing primitive: rectangle, circle, line, text-label, or freehand.',
7
+ icon: 'shapes',
8
+ defaultWidth: 120,
9
+ defaultHeight: 80,
10
+ expansionMode: 'inline',
11
+ tags: ['org'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element } = context
17
+ return {
18
+ getViewData() {
19
+ return {
20
+ subtype: element.data.subtype || 'rectangle',
21
+ color: element.data.color || '#3b82f6',
22
+ fill: element.data.fill || 'transparent',
23
+ label: element.data.label || '',
24
+ points: element.data.points || [],
25
+ }
26
+ },
27
+ getPorts() { return [] },
28
+ actions: {
29
+ update(data = {}) {
30
+ const { updateElement } = require('../board-state.js')
31
+ updateElement(element.id, { data: { ...element.data, ...data } })
32
+ },
33
+ },
34
+ }
35
+ },
36
+ }
@@ -0,0 +1,39 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'pro-con-list',
5
+ name: 'Pro / Con List',
6
+ description: 'Weigh the pros and cons of a decision.',
7
+ icon: 'scale',
8
+ defaultWidth: 220,
9
+ defaultHeight: 130,
10
+ expansionMode: 'inline',
11
+ tags: ['thinking'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element } = context
17
+ return {
18
+ getViewData() {
19
+ return {
20
+ title: element.data.title || '',
21
+ pros: element.data.pros || [],
22
+ cons: element.data.cons || [],
23
+ }
24
+ },
25
+ getPorts() {
26
+ return [
27
+ { key: 'items-in', direction: 'input', dataType: 'any', label: 'Items In' },
28
+ { key: 'summary-out', direction: 'output', dataType: 'text', label: 'Summary Out' },
29
+ ]
30
+ },
31
+ actions: {
32
+ update(data = {}) {
33
+ const { updateElement } = require('../board-state.js')
34
+ updateElement(element.id, { data: { ...element.data, ...data } })
35
+ },
36
+ },
37
+ }
38
+ },
39
+ }
@@ -0,0 +1,54 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'processor',
5
+ name: 'Processor',
6
+ description: 'Execute a JavaScript script against input data.',
7
+ icon: 'terminal',
8
+ defaultWidth: 220,
9
+ defaultHeight: 110,
10
+ expansionMode: 'popup',
11
+ tags: ['flow', 'dev'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element, log } = context
17
+ return {
18
+ getViewData() {
19
+ return {
20
+ script: element.data.script || '',
21
+ cached: element.data.cached || false,
22
+ result: element.data.result || null,
23
+ }
24
+ },
25
+ getPorts() {
26
+ return [
27
+ { key: 'input-in', direction: 'input', dataType: 'any', label: 'Input In' },
28
+ { key: 'output-out', direction: 'output', dataType: 'any', label: 'Output Out' },
29
+ ]
30
+ },
31
+ actions: {
32
+ run({ inputs } = {}) {
33
+ const script = element.data.script
34
+ if (!script) throw new Error('No script set')
35
+ let result
36
+ try {
37
+ // eslint-disable-next-line no-new-func
38
+ result = new Function('inputs', script)(inputs)
39
+ } catch (err) {
40
+ log(`Script error: ${err.message}`)
41
+ throw err
42
+ }
43
+ const { updateElement } = require('../board-state.js')
44
+ updateElement(element.id, { data: { ...element.data, result } })
45
+ return result
46
+ },
47
+ update(data = {}) {
48
+ const { updateElement } = require('../board-state.js')
49
+ updateElement(element.id, { data: { ...element.data, ...data } })
50
+ },
51
+ },
52
+ }
53
+ },
54
+ }
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'project',
5
+ name: 'Project',
6
+ description: 'A project with status and milestones.',
7
+ icon: 'folder-kanban',
8
+ defaultWidth: 240,
9
+ defaultHeight: 130,
10
+ expansionMode: 'sidebar',
11
+ tags: ['pm'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element } = context
17
+ return {
18
+ getViewData() {
19
+ return {
20
+ title: element.data.title || '',
21
+ description: element.data.description || '',
22
+ status: element.data.status || 'active',
23
+ milestones: element.data.milestones || [],
24
+ }
25
+ },
26
+ getPorts() {
27
+ return [
28
+ { key: 'tasks-in', direction: 'input', dataType: 'any', label: 'Tasks In' },
29
+ { key: 'status-out', direction: 'output', dataType: 'text', label: 'Status Out' },
30
+ ]
31
+ },
32
+ actions: {
33
+ update(data = {}) {
34
+ const { updateElement } = require('../board-state.js')
35
+ updateElement(element.id, { data: { ...element.data, ...data } })
36
+ },
37
+ },
38
+ }
39
+ },
40
+ }
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'question',
5
+ name: 'Question',
6
+ description: 'An open question with status tracking.',
7
+ icon: 'help-circle',
8
+ defaultWidth: 200,
9
+ defaultHeight: 80,
10
+ expansionMode: 'inline',
11
+ tags: ['research', 'thinking'],
12
+ isBuiltIn: true,
13
+
14
+ createInstance(context) {
15
+ const { element } = context
16
+ return {
17
+ getViewData() {
18
+ return {
19
+ text: element.data.text || '',
20
+ status: element.data.status || 'open',
21
+ answer: element.data.answer || null,
22
+ }
23
+ },
24
+ getPorts() {
25
+ return [
26
+ { key: 'question-out', direction: 'output', dataType: 'text', label: 'Question Out' },
27
+ { key: 'answer-out', direction: 'output', dataType: 'text', label: 'Answer Out' },
28
+ ]
29
+ },
30
+ actions: {
31
+ answer({ text } = {}) {
32
+ const { updateElement } = require('../board-state.js')
33
+ updateElement(element.id, { data: { ...element.data, status: 'answered', answer: text } })
34
+ },
35
+ update(data = {}) {
36
+ const { updateElement } = require('../board-state.js')
37
+ updateElement(element.id, { data: { ...element.data, ...data } })
38
+ },
39
+ },
40
+ }
41
+ },
42
+ }
@@ -0,0 +1,85 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'queue',
5
+ name: 'Queue',
6
+ description: 'A queue of items with manual or timer-based triggering.',
7
+ icon: 'list-ordered',
8
+ defaultWidth: 200,
9
+ defaultHeight: 110,
10
+ expansionMode: 'inline',
11
+ tags: ['flow'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element, emit, log } = context
17
+ let _interval = null
18
+
19
+ function _startTimer() {
20
+ if (_interval) return
21
+ const ms = element.data.intervalMs || 5000
22
+ _interval = setInterval(() => {
23
+ const items = element.data.items || []
24
+ if (items.length === 0) return
25
+ const [next, ...rest] = items
26
+ const { updateElement } = require('../board-state.js')
27
+ updateElement(element.id, { data: { ...element.data, items: rest } })
28
+ emit('element:view', { id: element.id, viewData: { items: rest, lastDequeued: next } })
29
+ }, ms)
30
+ }
31
+
32
+ function _stopTimer() {
33
+ if (_interval) { clearInterval(_interval); _interval = null }
34
+ }
35
+
36
+ if (element.data.triggerType === 'timer') _startTimer()
37
+
38
+ return {
39
+ getViewData() {
40
+ return {
41
+ items: element.data.items || [],
42
+ triggerType: element.data.triggerType || 'manual',
43
+ intervalMs: element.data.intervalMs || 5000,
44
+ }
45
+ },
46
+ getPorts() {
47
+ return [
48
+ { key: 'item-in', direction: 'input', dataType: 'any', label: 'Item In' },
49
+ { key: 'item-out', direction: 'output', dataType: 'any', label: 'Item Out' },
50
+ ]
51
+ },
52
+ actions: {
53
+ enqueue({ item } = {}) {
54
+ const { updateElement } = require('../board-state.js')
55
+ const items = [...(element.data.items || []), item]
56
+ updateElement(element.id, { data: { ...element.data, items } })
57
+ },
58
+ dequeue(_params) {
59
+ const items = element.data.items || []
60
+ if (items.length === 0) return null
61
+ const [next, ...rest] = items
62
+ const { updateElement } = require('../board-state.js')
63
+ updateElement(element.id, { data: { ...element.data, items: rest } })
64
+ return next
65
+ },
66
+ setTimer({ intervalMs, enabled } = {}) {
67
+ const { updateElement } = require('../board-state.js')
68
+ if (enabled) {
69
+ updateElement(element.id, { data: { ...element.data, triggerType: 'timer', intervalMs: intervalMs || 5000 } })
70
+ _stopTimer()
71
+ _startTimer()
72
+ } else {
73
+ updateElement(element.id, { data: { ...element.data, triggerType: 'manual' } })
74
+ _stopTimer()
75
+ }
76
+ },
77
+ update(data = {}) {
78
+ const { updateElement } = require('../board-state.js')
79
+ updateElement(element.id, { data: { ...element.data, ...data } })
80
+ },
81
+ },
82
+ onDestroy() { _stopTimer() },
83
+ }
84
+ },
85
+ }
@@ -0,0 +1,41 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'research-note',
5
+ name: 'Research Note',
6
+ description: 'A note linked to research sources.',
7
+ icon: 'notebook',
8
+ defaultWidth: 220,
9
+ defaultHeight: 110,
10
+ expansionMode: 'sidebar',
11
+ tags: ['research'],
12
+ isBuiltIn: true,
13
+
14
+ createInstance(context) {
15
+ const { element } = context
16
+ return {
17
+ getViewData() {
18
+ return {
19
+ title: element.data.title || '',
20
+ insight: element.data.insight || '',
21
+ content: element.data.content || '',
22
+ source: element.data.source || '',
23
+ status: element.data.status || 'open',
24
+ sourceIds: element.data.sourceIds || [],
25
+ }
26
+ },
27
+ getPorts() {
28
+ return [
29
+ { key: 'sources-in', direction: 'input', dataType: 'json', label: 'Sources In' },
30
+ { key: 'note-out', direction: 'output', dataType: 'text', label: 'Note Out' },
31
+ ]
32
+ },
33
+ actions: {
34
+ update(data = {}) {
35
+ const { updateElement } = require('../board-state.js')
36
+ updateElement(element.id, { data: { ...element.data, ...data } })
37
+ },
38
+ },
39
+ }
40
+ },
41
+ }
@@ -0,0 +1,35 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'section',
5
+ name: 'Section',
6
+ description: 'A document section with title and content.',
7
+ icon: 'pilcrow',
8
+ defaultWidth: 200,
9
+ defaultHeight: 90,
10
+ expansionMode: 'inline',
11
+ tags: ['docs'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element } = context
17
+ return {
18
+ getViewData() {
19
+ return { title: element.data.title || '', content: element.data.content || '' }
20
+ },
21
+ getPorts() {
22
+ return [
23
+ { key: 'content-in', direction: 'input', dataType: 'text', label: 'Content In' },
24
+ { key: 'section-out', direction: 'output', dataType: 'text', label: 'Section Out' },
25
+ ]
26
+ },
27
+ actions: {
28
+ update(data = {}) {
29
+ const { updateElement } = require('../board-state.js')
30
+ updateElement(element.id, { data: { ...element.data, ...data } })
31
+ },
32
+ },
33
+ }
34
+ },
35
+ }
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'source-collection',
5
+ name: 'Source Collection',
6
+ description: 'A collection of research sources (URLs, files, references).',
7
+ icon: 'library',
8
+ defaultWidth: 200,
9
+ defaultHeight: 110,
10
+ expansionMode: 'popup',
11
+ tags: ['research'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element } = context
17
+ return {
18
+ getViewData() {
19
+ return {
20
+ title: element.data.title || '',
21
+ sources: element.data.sources || [],
22
+ }
23
+ },
24
+ getPorts() {
25
+ return [{ key: 'collection-out', direction: 'output', dataType: 'json', label: 'Collection Out' }]
26
+ },
27
+ actions: {
28
+ addSource({ url, title, type } = {}) {
29
+ const { updateElement } = require('../board-state.js')
30
+ const sources = [...(element.data.sources || []), { url, title, type, id: Date.now().toString() }]
31
+ updateElement(element.id, { data: { ...element.data, sources } })
32
+ },
33
+ removeSource({ id } = {}) {
34
+ const { updateElement } = require('../board-state.js')
35
+ const sources = (element.data.sources || []).filter(s => s.id !== id)
36
+ updateElement(element.id, { data: { ...element.data, sources } })
37
+ },
38
+ update(data = {}) {
39
+ const { updateElement } = require('../board-state.js')
40
+ updateElement(element.id, { data: { ...element.data, ...data } })
41
+ },
42
+ },
43
+ }
44
+ },
45
+ }
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'splitter',
5
+ name: 'Splitter',
6
+ description: 'Route input to different outputs based on conditions.',
7
+ icon: 'split',
8
+ defaultWidth: 200,
9
+ defaultHeight: 110,
10
+ expansionMode: 'inline',
11
+ tags: ['flow'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element } = context
17
+ return {
18
+ getViewData() {
19
+ return { rules: element.data.rules || [] }
20
+ },
21
+ getPorts() {
22
+ const rules = element.data.rules || []
23
+ const outPorts = rules.map((r, i) => ({
24
+ key: `out-${i}`,
25
+ direction: 'output',
26
+ dataType: 'any',
27
+ label: r.label || `Out ${i + 1}`,
28
+ }))
29
+ return [
30
+ { key: 'input-in', direction: 'input', dataType: 'any', label: 'Input In' },
31
+ ...outPorts,
32
+ ]
33
+ },
34
+ actions: {
35
+ update(data = {}) {
36
+ const { updateElement } = require('../board-state.js')
37
+ updateElement(element.id, { data: { ...element.data, ...data } })
38
+ },
39
+ },
40
+ }
41
+ },
42
+ }
@@ -0,0 +1,38 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'status-update',
5
+ name: 'Status Update',
6
+ description: 'A timestamped status update for a project.',
7
+ icon: 'megaphone',
8
+ defaultWidth: 220,
9
+ defaultHeight: 90,
10
+ expansionMode: 'inline',
11
+ tags: ['pm'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element } = context
17
+ return {
18
+ getViewData() {
19
+ return {
20
+ content: element.data.content || '',
21
+ timestamp: element.data.timestamp || null,
22
+ }
23
+ },
24
+ getPorts() {
25
+ return [
26
+ { key: 'project-in', direction: 'input', dataType: 'any', label: 'Project In' },
27
+ { key: 'update-out', direction: 'output', dataType: 'text', label: 'Update Out' },
28
+ ]
29
+ },
30
+ actions: {
31
+ update(data = {}) {
32
+ const { updateElement } = require('../board-state.js')
33
+ updateElement(element.id, { data: { ...element.data, ...data } })
34
+ },
35
+ },
36
+ }
37
+ },
38
+ }
@@ -0,0 +1,72 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'task',
5
+ name: 'Task',
6
+ description: 'Run an agent task and track its status.',
7
+ icon: 'play-circle',
8
+ defaultWidth: 220,
9
+ defaultHeight: 120,
10
+ expansionMode: 'popup',
11
+ tags: ['cli', 'task'],
12
+ isBuiltIn: true,
13
+
14
+ createInstance(context) {
15
+ const { element, veil, emit, log } = context
16
+ let _taskTimeout = null
17
+
18
+ return {
19
+ getViewData() {
20
+ return {
21
+ agentName: element.data.agentName || null,
22
+ taskId: element.data.taskId || null,
23
+ prompt: element.data.prompt || '',
24
+ spawnOutput: element.data.spawnOutput || false,
25
+ result: element.data.result || null,
26
+ }
27
+ },
28
+
29
+ getPorts() {
30
+ return [
31
+ { key: 'prompt-in', direction: 'input', dataType: 'text', label: 'Prompt In' },
32
+ { key: 'data-in', direction: 'input', dataType: 'any', label: 'Data In' },
33
+ { key: 'result', direction: 'output', dataType: 'any', label: 'Result' },
34
+ ]
35
+ },
36
+
37
+ actions: {
38
+ async run({ prompt: overridePrompt } = {}) {
39
+ const agentName = element.data.agentName
40
+ if (!agentName) throw new Error('No agent name set')
41
+ const prompt = overridePrompt || element.data.prompt
42
+ if (!prompt) throw new Error('No prompt set')
43
+
44
+ log(`Creating task for agent "${agentName}"`)
45
+ const task = await veil.post('/tasks', { agent: agentName, prompt })
46
+
47
+ const { updateElement, updateElementState } = require('../board-state.js')
48
+ updateElement(element.id, { data: { ...element.data, taskId: task.id }, state: 'running' })
49
+ updateElementState(element.id, 'running')
50
+
51
+ // Schedule fade-out after 10 min if completed
52
+ if (_taskTimeout) clearTimeout(_taskTimeout)
53
+ _taskTimeout = setTimeout(() => {
54
+ updateElementState(element.id, 'completed')
55
+ }, 10 * 60 * 1000)
56
+ },
57
+
58
+ async cancel(_params) {
59
+ const taskId = element.data.taskId
60
+ if (!taskId) throw new Error('No active task')
61
+ await veil.post(`/tasks/${encodeURIComponent(taskId)}/cancel`)
62
+ const { updateElementState } = require('../board-state.js')
63
+ updateElementState(element.id, 'default')
64
+ },
65
+ },
66
+
67
+ onDestroy() {
68
+ if (_taskTimeout) clearTimeout(_taskTimeout)
69
+ },
70
+ }
71
+ },
72
+ }
@@ -0,0 +1,46 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'template',
5
+ name: 'Template',
6
+ description: 'A text template with variable substitution.',
7
+ icon: 'file-code',
8
+ defaultWidth: 220,
9
+ defaultHeight: 90,
10
+ expansionMode: 'popup',
11
+ tags: ['docs'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element } = context
17
+ return {
18
+ getViewData() {
19
+ return {
20
+ title: element.data.title || '',
21
+ template: element.data.template || '',
22
+ variables: element.data.variables || [],
23
+ result: element.data.result || null,
24
+ }
25
+ },
26
+ getPorts() {
27
+ return [
28
+ { key: 'vars-in', direction: 'input', dataType: 'json', label: 'Variables In' },
29
+ { key: 'filled-out', direction: 'output', dataType: 'text', label: 'Filled Out' },
30
+ ]
31
+ },
32
+ actions: {
33
+ fill({ vars } = {}) {
34
+ const tmpl = element.data.template || ''
35
+ const result = tmpl.replace(/\{\{(\w+)\}\}/g, (_, key) => vars?.[key] ?? `{{${key}}}`)
36
+ const { updateElement } = require('../board-state.js')
37
+ updateElement(element.id, { data: { ...element.data, result } })
38
+ },
39
+ update(data = {}) {
40
+ const { updateElement } = require('../board-state.js')
41
+ updateElement(element.id, { data: { ...element.data, ...data } })
42
+ },
43
+ },
44
+ }
45
+ },
46
+ }
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'test-case',
5
+ name: 'Test Case',
6
+ description: 'Track a test case with input, expected, and actual output.',
7
+ icon: 'flask-conical',
8
+ defaultWidth: 240,
9
+ defaultHeight: 120,
10
+ expansionMode: 'popup',
11
+ tags: ['dev'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element } = context
17
+ return {
18
+ getViewData() {
19
+ return {
20
+ title: element.data.title || '',
21
+ input: element.data.input || '',
22
+ expected: element.data.expected || '',
23
+ actual: element.data.actual || null,
24
+ passed: element.data.passed ?? null,
25
+ }
26
+ },
27
+ getPorts() {
28
+ return [
29
+ { key: 'input-in', direction: 'input', dataType: 'any', label: 'Input In' },
30
+ { key: 'expected-in', direction: 'input', dataType: 'any', label: 'Expected In' },
31
+ { key: 'result-out', direction: 'output', dataType: 'json', label: 'Result Out' },
32
+ ]
33
+ },
34
+ actions: {
35
+ update(data = {}) {
36
+ const { updateElement } = require('../board-state.js')
37
+ updateElement(element.id, { data: { ...element.data, ...data } })
38
+ },
39
+ },
40
+ }
41
+ },
42
+ }