@runcore-sh/runcore 0.5.6 → 0.5.8

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 (191) hide show
  1. package/dictionary.json +2 -2
  2. package/dist/.extensions/ext-byok.json +1 -0
  3. package/dist/.extensions/ext-hosted.json +1 -0
  4. package/dist/.extensions/ext-spawn.json +1 -0
  5. package/dist/agents/autonomous.d.ts.map +1 -1
  6. package/dist/agents/runtime/bus.d.ts +1 -0
  7. package/dist/agents/runtime/bus.d.ts.map +1 -1
  8. package/dist/agents/spawn.d.ts.map +1 -1
  9. package/dist/auth/middleware.d.ts.map +1 -1
  10. package/dist/auth/middleware.js +3 -2
  11. package/dist/auth/middleware.js.map +1 -1
  12. package/dist/calendar/routes.d.ts.map +1 -1
  13. package/dist/calendar/routes.js +8 -7
  14. package/dist/calendar/routes.js.map +1 -1
  15. package/dist/files/registry.d.ts +4 -5
  16. package/dist/files/registry.d.ts.map +1 -1
  17. package/dist/files/registry.js +4 -5
  18. package/dist/files/registry.js.map +1 -1
  19. package/dist/files/store.d.ts +2 -0
  20. package/dist/files/store.d.ts.map +1 -1
  21. package/dist/files/store.js +5 -1
  22. package/dist/files/store.js.map +1 -1
  23. package/dist/instance.d.ts +2 -0
  24. package/dist/instance.d.ts.map +1 -1
  25. package/dist/instance.js +2 -0
  26. package/dist/instance.js.map +1 -1
  27. package/dist/lib/paths.d.ts.map +1 -1
  28. package/dist/lib/paths.js +15 -1
  29. package/dist/lib/paths.js.map +1 -1
  30. package/dist/library/brain-shadow.d.ts.map +1 -1
  31. package/dist/library/brain-shadow.js +5 -4
  32. package/dist/library/brain-shadow.js.map +1 -1
  33. package/dist/library/routes.d.ts.map +1 -1
  34. package/dist/library/routes.js +10 -9
  35. package/dist/library/routes.js.map +1 -1
  36. package/dist/llm/cache.d.ts.map +1 -1
  37. package/dist/llm/cache.js +7 -5
  38. package/dist/llm/cache.js.map +1 -1
  39. package/dist/llm/complete.js +2 -0
  40. package/dist/llm/complete.js.map +1 -1
  41. package/dist/llm/providers/ollama.d.ts.map +1 -1
  42. package/dist/llm/providers/ollama.js +32 -7
  43. package/dist/llm/providers/ollama.js.map +1 -1
  44. package/dist/llm/providers/openrouter.d.ts.map +1 -1
  45. package/dist/llm/providers/openrouter.js +105 -12
  46. package/dist/llm/providers/openrouter.js.map +1 -1
  47. package/dist/llm/providers/types.d.ts +18 -0
  48. package/dist/llm/providers/types.d.ts.map +1 -1
  49. package/dist/llm/retry.d.ts.map +1 -1
  50. package/dist/llm/retry.js +6 -0
  51. package/dist/llm/retry.js.map +1 -1
  52. package/dist/llm/tools/handlers.d.ts +27 -0
  53. package/dist/llm/tools/handlers.d.ts.map +1 -0
  54. package/dist/llm/tools/handlers.js +842 -0
  55. package/dist/llm/tools/handlers.js.map +1 -0
  56. package/dist/llm/tools/index.d.ts +12 -0
  57. package/dist/llm/tools/index.d.ts.map +1 -0
  58. package/dist/llm/tools/index.js +10 -0
  59. package/dist/llm/tools/index.js.map +1 -0
  60. package/dist/llm/tools/loop.d.ts +47 -0
  61. package/dist/llm/tools/loop.d.ts.map +1 -0
  62. package/dist/llm/tools/loop.js +126 -0
  63. package/dist/llm/tools/loop.js.map +1 -0
  64. package/dist/llm/tools/registry.d.ts +27 -0
  65. package/dist/llm/tools/registry.d.ts.map +1 -0
  66. package/dist/llm/tools/registry.js +60 -0
  67. package/dist/llm/tools/registry.js.map +1 -0
  68. package/dist/llm/tools/schemas.d.ts +92 -0
  69. package/dist/llm/tools/schemas.d.ts.map +1 -0
  70. package/dist/llm/tools/schemas.js +154 -0
  71. package/dist/llm/tools/schemas.js.map +1 -0
  72. package/dist/llm/tools/types.d.ts +44 -0
  73. package/dist/llm/tools/types.d.ts.map +1 -0
  74. package/dist/llm/tools/types.js +9 -0
  75. package/dist/llm/tools/types.js.map +1 -0
  76. package/dist/mcp-server.d.ts +1 -1
  77. package/dist/mcp-server.js +249 -5
  78. package/dist/mcp-server.js.map +1 -1
  79. package/dist/memory/visual.d.ts.map +1 -1
  80. package/dist/memory/visual.js +3 -6
  81. package/dist/memory/visual.js.map +1 -1
  82. package/dist/openloop/foldback.js +1 -1
  83. package/dist/openloop/foldback.js.map +1 -1
  84. package/dist/openloop/resolution-scanner.d.ts.map +1 -1
  85. package/dist/openloop/resolution-scanner.js +76 -63
  86. package/dist/openloop/resolution-scanner.js.map +1 -1
  87. package/dist/plugins/github/index.d.ts +49 -0
  88. package/dist/plugins/github/index.d.ts.map +1 -0
  89. package/dist/plugins/github/index.js +153 -0
  90. package/dist/plugins/github/index.js.map +1 -0
  91. package/dist/plugins/index.d.ts +1 -2
  92. package/dist/plugins/index.d.ts.map +1 -1
  93. package/dist/plugins/index.js +79 -2
  94. package/dist/plugins/index.js.map +1 -1
  95. package/dist/plugins/slack/index.d.ts +43 -0
  96. package/dist/plugins/slack/index.d.ts.map +1 -0
  97. package/dist/plugins/slack/index.js +158 -0
  98. package/dist/plugins/slack/index.js.map +1 -0
  99. package/dist/plugins/twilio/index.d.ts +41 -0
  100. package/dist/plugins/twilio/index.d.ts.map +1 -0
  101. package/dist/plugins/twilio/index.js +102 -0
  102. package/dist/plugins/twilio/index.js.map +1 -0
  103. package/dist/pulse/tier.d.ts +1 -1
  104. package/dist/pulse/tier.js +2 -2
  105. package/dist/pulse/tier.js.map +1 -1
  106. package/dist/search/gemini.d.ts +27 -0
  107. package/dist/search/gemini.d.ts.map +1 -0
  108. package/dist/search/gemini.js +103 -0
  109. package/dist/search/gemini.js.map +1 -0
  110. package/dist/server.d.ts.map +1 -1
  111. package/dist/server.js +850 -536
  112. package/dist/server.js.map +1 -1
  113. package/dist/services/routine-patterns.d.ts.map +1 -1
  114. package/dist/services/routine-patterns.js +6 -0
  115. package/dist/services/routine-patterns.js.map +1 -1
  116. package/dist/services/traceInsights.d.ts +5 -0
  117. package/dist/services/traceInsights.d.ts.map +1 -1
  118. package/dist/services/traceInsights.js +18 -1
  119. package/dist/services/traceInsights.js.map +1 -1
  120. package/dist/settings.d.ts +1 -1
  121. package/dist/settings.d.ts.map +1 -1
  122. package/dist/types.d.ts +26 -2
  123. package/dist/types.d.ts.map +1 -1
  124. package/dist/vault/store.d.ts +1 -1
  125. package/dist/vault/store.d.ts.map +1 -1
  126. package/dist/webhooks/mount.d.ts.map +1 -1
  127. package/dist/whiteboard/store.d.ts +40 -0
  128. package/dist/whiteboard/store.d.ts.map +1 -0
  129. package/dist/whiteboard/store.js +280 -0
  130. package/dist/whiteboard/store.js.map +1 -0
  131. package/dist/whiteboard/types.d.ts +55 -0
  132. package/dist/whiteboard/types.d.ts.map +1 -0
  133. package/dist/whiteboard/types.js +9 -0
  134. package/dist/whiteboard/types.js.map +1 -0
  135. package/dist/whiteboard/weight.d.ts +23 -0
  136. package/dist/whiteboard/weight.d.ts.map +1 -0
  137. package/dist/whiteboard/weight.js +126 -0
  138. package/dist/whiteboard/weight.js.map +1 -0
  139. package/package.json +3 -2
  140. package/public/index.html +225 -51
  141. package/public/search-flyout.js +324 -0
  142. package/public/whiteboard.html +915 -0
  143. package/public/avatar/Hey-Dash_en_windows_v4_0_0.zip +0 -0
  144. package/public/avatar/README.md +0 -43
  145. package/public/avatar/cache/06fa55aececcc478.mp4 +0 -0
  146. package/public/avatar/cache/07a65738ba170827.mp4 +0 -0
  147. package/public/avatar/cache/08b6f4880f59a385.mp4 +0 -0
  148. package/public/avatar/cache/0ef9e0e78d715af4.mp4 +0 -0
  149. package/public/avatar/cache/0fa85e9e8f444a8b.mp4 +0 -0
  150. package/public/avatar/cache/1184385ec5522b57.mp4 +0 -0
  151. package/public/avatar/cache/1185fd491f413406.mp4 +0 -0
  152. package/public/avatar/cache/1b374d5390258fea.mp4 +0 -0
  153. package/public/avatar/cache/1e2367029b92f8aa.mp4 +0 -0
  154. package/public/avatar/cache/1f15f6a1ebd7e439.mp4 +0 -0
  155. package/public/avatar/cache/272c004a41087de5.mp4 +0 -0
  156. package/public/avatar/cache/2a0f3ff34d92521a.mp4 +0 -0
  157. package/public/avatar/cache/2c7e47ff0bdeb8d1.mp4 +0 -0
  158. package/public/avatar/cache/307a6f70859aeab8.mp4 +0 -0
  159. package/public/avatar/cache/332384e088ca214b.mp4 +0 -0
  160. package/public/avatar/cache/39fc4e81574d14ed.mp4 +0 -0
  161. package/public/avatar/cache/4a5c6051c1ef6a71.mp4 +0 -0
  162. package/public/avatar/cache/51f4aa76398c8c29.mp4 +0 -0
  163. package/public/avatar/cache/5d9a960bbf71732c.mp4 +0 -0
  164. package/public/avatar/cache/5e0954401e15af89.mp4 +0 -0
  165. package/public/avatar/cache/5f308566f7abb8f2.mp4 +0 -0
  166. package/public/avatar/cache/62f9cfba848d724e.mp4 +0 -0
  167. package/public/avatar/cache/6d64e657e6bf2aab.mp4 +0 -0
  168. package/public/avatar/cache/763ad0349e0b6f26.mp4 +0 -0
  169. package/public/avatar/cache/81a516cfd461b2b9.mp4 +0 -0
  170. package/public/avatar/cache/884ae6717fcacdd5.mp4 +0 -0
  171. package/public/avatar/cache/8ea0b7220d139615.mp4 +0 -0
  172. package/public/avatar/cache/9366de15fd6910ca.mp4 +0 -0
  173. package/public/avatar/cache/9b9c4f7b8508eecc.mp4 +0 -0
  174. package/public/avatar/cache/9be1030ec2aa2b01.mp4 +0 -0
  175. package/public/avatar/cache/ade41a846b283895.mp4 +0 -0
  176. package/public/avatar/cache/b35f7a3d558f22cb.mp4 +0 -0
  177. package/public/avatar/cache/b6066e5c65383eec.mp4 +0 -0
  178. package/public/avatar/cache/be89f49970672374.mp4 +0 -0
  179. package/public/avatar/cache/c11fdc99479492b6.mp4 +0 -0
  180. package/public/avatar/cache/c900811e3382ac6d.mp4 +0 -0
  181. package/public/avatar/cache/d42a73667acf5716.mp4 +0 -0
  182. package/public/avatar/cache/e539f247a8908603.mp4 +0 -0
  183. package/public/avatar/cache/e78fceae2373b7c1.mp4 +0 -0
  184. package/public/avatar/cache/ec95af57d33b3f07.mp4 +0 -0
  185. package/public/avatar/cache/edadb75d37891fc7.mp4 +0 -0
  186. package/public/avatar/cache/eeb8d775f40dbe2c.mp4 +0 -0
  187. package/public/avatar/cache/f0ae159640621dd9.mp4 +0 -0
  188. package/public/avatar/cache/fc2e5419adf29d96.mp4 +0 -0
  189. package/public/avatar/dash_headhshot_v1.png +0 -0
  190. package/public/avatar/idle.mp4 +0 -0
  191. package/public/avatar/photo.png +0 -0
@@ -0,0 +1,915 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{INSTANCE_NAME}} — Whiteboard</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0e0e10;
10
+ --surface: #18181b;
11
+ --surface2: #222226;
12
+ --border: #2e2e33;
13
+ --text: #e4e4e7;
14
+ --text-dim: #8b8b94;
15
+ --accent: #6d5dfc;
16
+ --green: #22c55e;
17
+ --amber: #f59e0b;
18
+ --blue: #3b82f6;
19
+ --mono: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
20
+ }
21
+
22
+ * { margin: 0; padding: 0; box-sizing: border-box; }
23
+
24
+ body {
25
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
26
+ background: var(--bg);
27
+ color: var(--text);
28
+ height: 100vh;
29
+ display: flex;
30
+ flex-direction: column;
31
+ overflow: hidden;
32
+ }
33
+
34
+ /* --- Header --- */
35
+ header {
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: space-between;
39
+ padding: 14px 24px;
40
+ border-bottom: 1px solid var(--border);
41
+ background: var(--surface);
42
+ flex-shrink: 0;
43
+ gap: 16px;
44
+ }
45
+ header h1 { font-size: 18px; font-weight: 600; }
46
+ header h1 a { color: inherit; text-decoration: none; }
47
+ .header-views { display: flex; gap: 4px; }
48
+ .header-views button {
49
+ background: none;
50
+ border: 1px solid transparent;
51
+ color: var(--text-dim);
52
+ padding: 5px 14px;
53
+ border-radius: 6px;
54
+ cursor: pointer;
55
+ font-size: 13px;
56
+ transition: all 0.15s;
57
+ }
58
+ .header-views button:hover { color: var(--text); background: var(--surface2); }
59
+ .header-views button.active { color: var(--text); border-color: var(--border); background: var(--surface2); }
60
+ .header-right { display: flex; align-items: center; gap: 8px; }
61
+ .btn-new {
62
+ background: var(--accent);
63
+ color: #fff;
64
+ border: none;
65
+ padding: 6px 14px;
66
+ border-radius: 6px;
67
+ cursor: pointer;
68
+ font-size: 13px;
69
+ font-weight: 500;
70
+ }
71
+ .btn-new:hover { filter: brightness(1.15); }
72
+ .back-link { color: var(--text-dim); text-decoration: none; font-size: 13px; }
73
+ .back-link:hover { color: var(--text); }
74
+
75
+ /* --- Summary bar --- */
76
+ .summary-bar {
77
+ display: flex;
78
+ gap: 20px;
79
+ padding: 10px 24px;
80
+ border-bottom: 1px solid var(--border);
81
+ font-size: 12px;
82
+ color: var(--text-dim);
83
+ font-family: var(--mono);
84
+ background: var(--surface);
85
+ }
86
+ .summary-bar .stat { display: flex; gap: 4px; }
87
+ .summary-bar .stat-val { color: var(--text); font-weight: 600; }
88
+
89
+ /* --- Main content --- */
90
+ .content {
91
+ flex: 1;
92
+ overflow-y: auto;
93
+ padding: 16px 24px 32px;
94
+ scrollbar-width: thin;
95
+ scrollbar-color: #333 transparent;
96
+ }
97
+
98
+ /* --- Tree view --- */
99
+ .tree-root { list-style: none; }
100
+ .tree-node { margin: 0; }
101
+ .tree-item {
102
+ display: flex;
103
+ align-items: flex-start;
104
+ gap: 8px;
105
+ padding: 6px 8px;
106
+ border-radius: 6px;
107
+ cursor: pointer;
108
+ transition: background 0.1s;
109
+ position: relative;
110
+ }
111
+ .tree-item:hover { background: var(--surface2); }
112
+ .tree-toggle {
113
+ width: 18px;
114
+ height: 18px;
115
+ flex-shrink: 0;
116
+ display: flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ color: var(--text-dim);
120
+ font-size: 12px;
121
+ cursor: pointer;
122
+ user-select: none;
123
+ margin-top: 1px;
124
+ }
125
+ .tree-toggle.leaf { visibility: hidden; }
126
+ .tree-icon {
127
+ flex-shrink: 0;
128
+ font-size: 14px;
129
+ margin-top: 2px;
130
+ width: 16px;
131
+ text-align: center;
132
+ }
133
+ .tree-icon.done { color: var(--green); }
134
+ .tree-icon.question { color: var(--amber); }
135
+ .tree-icon.decision { color: var(--blue); }
136
+ .tree-icon.open { color: var(--text-dim); }
137
+ .tree-title {
138
+ flex: 1;
139
+ font-size: 14px;
140
+ line-height: 1.4;
141
+ }
142
+ .tree-title.done { color: var(--text-dim); text-decoration: line-through; }
143
+ .tree-tags {
144
+ display: flex;
145
+ gap: 4px;
146
+ flex-shrink: 0;
147
+ }
148
+ .tree-tag {
149
+ font-size: 11px;
150
+ color: var(--text-dim);
151
+ background: var(--surface2);
152
+ padding: 1px 7px;
153
+ border-radius: 4px;
154
+ font-family: var(--mono);
155
+ }
156
+ .tree-weight {
157
+ width: 50px;
158
+ height: 4px;
159
+ background: var(--surface2);
160
+ border-radius: 2px;
161
+ overflow: hidden;
162
+ flex-shrink: 0;
163
+ margin-top: 7px;
164
+ }
165
+ .tree-weight-fill {
166
+ height: 100%;
167
+ border-radius: 2px;
168
+ transition: width 0.3s;
169
+ }
170
+ .tree-weight-fill.low { background: var(--text-dim); }
171
+ .tree-weight-fill.mid { background: var(--amber); }
172
+ .tree-weight-fill.high { background: var(--accent); animation: weight-glow 2s ease-in-out infinite; }
173
+ @keyframes weight-glow {
174
+ 0%, 100% { opacity: 0.8; }
175
+ 50% { opacity: 1; }
176
+ }
177
+ .tree-children {
178
+ list-style: none;
179
+ margin-left: 26px;
180
+ border-left: 1px solid var(--border);
181
+ padding-left: 0;
182
+ }
183
+ .tree-children.collapsed { display: none; }
184
+
185
+ /* --- Detail panel (inline expand) --- */
186
+ .tree-detail {
187
+ margin: 4px 0 8px 44px;
188
+ padding: 12px 14px;
189
+ background: var(--surface);
190
+ border: 1px solid var(--border);
191
+ border-radius: 8px;
192
+ font-size: 13px;
193
+ line-height: 1.6;
194
+ display: none;
195
+ }
196
+ .tree-detail.open { display: block; }
197
+ .tree-detail-question {
198
+ color: var(--amber);
199
+ font-style: italic;
200
+ margin-bottom: 8px;
201
+ }
202
+ .tree-detail-body {
203
+ color: var(--text-dim);
204
+ margin-bottom: 8px;
205
+ }
206
+ .tree-detail-answer {
207
+ display: flex;
208
+ gap: 8px;
209
+ margin-top: 8px;
210
+ }
211
+ .tree-detail-answer input {
212
+ flex: 1;
213
+ background: var(--surface2);
214
+ border: 1px solid var(--border);
215
+ border-radius: 6px;
216
+ color: var(--text);
217
+ padding: 6px 10px;
218
+ font-size: 13px;
219
+ outline: none;
220
+ }
221
+ .tree-detail-answer input:focus { border-color: var(--accent); }
222
+ .tree-detail-answer button {
223
+ background: var(--accent);
224
+ color: #fff;
225
+ border: none;
226
+ padding: 6px 14px;
227
+ border-radius: 6px;
228
+ cursor: pointer;
229
+ font-size: 13px;
230
+ white-space: nowrap;
231
+ }
232
+ .tree-detail-meta {
233
+ font-size: 11px;
234
+ color: var(--text-dim);
235
+ margin-top: 8px;
236
+ font-family: var(--mono);
237
+ }
238
+ .tree-detail-answered {
239
+ color: var(--green);
240
+ margin-top: 6px;
241
+ font-size: 13px;
242
+ }
243
+ .tree-detail-actions {
244
+ display: flex;
245
+ gap: 8px;
246
+ margin-top: 10px;
247
+ padding-top: 10px;
248
+ border-top: 1px solid var(--border);
249
+ }
250
+ .tree-detail-actions button {
251
+ background: var(--surface2);
252
+ color: var(--text-dim);
253
+ border: 1px solid var(--border);
254
+ padding: 4px 12px;
255
+ border-radius: 5px;
256
+ cursor: pointer;
257
+ font-size: 12px;
258
+ font-family: var(--mono);
259
+ transition: border-color 0.15s, color 0.15s;
260
+ }
261
+ .tree-detail-actions button:hover { border-color: var(--text-dim); color: var(--text); }
262
+ .tree-detail-actions .btn-delete:hover { border-color: #e57373; color: #e57373; }
263
+ .tree-detail-actions .btn-done { color: var(--green); }
264
+
265
+ /* --- Edit modal --- */
266
+ .edit-overlay {
267
+ position: fixed; inset: 0; z-index: 9100;
268
+ display: none; align-items: center; justify-content: center;
269
+ background: rgba(0,0,0,0.5); backdrop-filter: blur(4px);
270
+ }
271
+ .edit-overlay.open { display: flex; }
272
+ .edit-modal {
273
+ width: 460px; max-width: 92vw;
274
+ background: var(--surface); border: 1px solid var(--border);
275
+ border-radius: 12px; padding: 20px 22px;
276
+ }
277
+ .edit-modal h3 { margin-bottom: 14px; font-size: 16px; }
278
+ .edit-modal label { display: block; font-size: 12px; color: var(--text-dim); margin-bottom: 4px; margin-top: 10px; }
279
+ .edit-modal input, .edit-modal select, .edit-modal textarea {
280
+ width: 100%; background: var(--surface2); border: 1px solid var(--border);
281
+ border-radius: 6px; color: var(--text); padding: 8px 10px; font-size: 13px; outline: none;
282
+ font-family: inherit;
283
+ }
284
+ .edit-modal textarea { resize: vertical; min-height: 60px; }
285
+ .edit-modal input:focus, .edit-modal select:focus, .edit-modal textarea:focus { border-color: var(--accent); }
286
+ .edit-modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
287
+ .edit-modal-actions button {
288
+ padding: 7px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; border: none;
289
+ }
290
+ .edit-modal-actions .cancel { background: var(--surface2); color: var(--text-dim); }
291
+ .edit-modal-actions .submit { background: var(--accent); color: #fff; font-weight: 500; }
292
+
293
+ /* --- Questions view --- */
294
+ .question-card {
295
+ background: var(--surface);
296
+ border: 1px solid var(--border);
297
+ border-radius: 10px;
298
+ padding: 16px 18px;
299
+ margin-bottom: 12px;
300
+ transition: border-color 0.15s;
301
+ }
302
+ .question-card:hover { border-color: var(--accent); }
303
+ .question-path {
304
+ font-size: 11px;
305
+ color: var(--text-dim);
306
+ margin-bottom: 6px;
307
+ font-family: var(--mono);
308
+ }
309
+ .question-text {
310
+ font-size: 15px;
311
+ color: var(--amber);
312
+ margin-bottom: 10px;
313
+ line-height: 1.5;
314
+ }
315
+ .question-age {
316
+ font-size: 11px;
317
+ color: var(--text-dim);
318
+ margin-bottom: 10px;
319
+ font-family: var(--mono);
320
+ }
321
+ .question-answer-row {
322
+ display: flex;
323
+ gap: 8px;
324
+ }
325
+ .question-answer-row input {
326
+ flex: 1;
327
+ background: var(--surface2);
328
+ border: 1px solid var(--border);
329
+ border-radius: 6px;
330
+ color: var(--text);
331
+ padding: 8px 12px;
332
+ font-size: 14px;
333
+ outline: none;
334
+ }
335
+ .question-answer-row input:focus { border-color: var(--accent); }
336
+ .question-answer-row button {
337
+ background: var(--accent);
338
+ color: #fff;
339
+ border: none;
340
+ padding: 8px 16px;
341
+ border-radius: 6px;
342
+ cursor: pointer;
343
+ font-size: 13px;
344
+ font-weight: 500;
345
+ }
346
+
347
+ /* --- Weighted view --- */
348
+ .weighted-item {
349
+ display: flex;
350
+ align-items: center;
351
+ gap: 12px;
352
+ padding: 10px 14px;
353
+ border-radius: 8px;
354
+ margin-bottom: 4px;
355
+ transition: background 0.1s;
356
+ }
357
+ .weighted-item:hover { background: var(--surface2); }
358
+ .weighted-weight {
359
+ width: 40px;
360
+ font-family: var(--mono);
361
+ font-size: 13px;
362
+ color: var(--accent);
363
+ text-align: right;
364
+ flex-shrink: 0;
365
+ }
366
+ .weighted-icon { width: 16px; text-align: center; font-size: 14px; flex-shrink: 0; }
367
+ .weighted-title { flex: 1; font-size: 14px; }
368
+ .weighted-tags { display: flex; gap: 4px; }
369
+
370
+ /* --- Create modal --- */
371
+ .create-overlay {
372
+ position: fixed; inset: 0; z-index: 9000;
373
+ display: none; align-items: center; justify-content: center;
374
+ background: rgba(0,0,0,0.5); backdrop-filter: blur(4px);
375
+ }
376
+ .create-overlay.open { display: flex; }
377
+ .create-modal {
378
+ width: 460px; max-width: 92vw;
379
+ background: var(--surface); border: 1px solid var(--border);
380
+ border-radius: 12px; padding: 20px 22px;
381
+ animation: modal-in 0.15s ease-out;
382
+ }
383
+ @keyframes modal-in {
384
+ from { opacity: 0; transform: translateY(-8px); }
385
+ to { opacity: 1; transform: translateY(0); }
386
+ }
387
+ .create-modal h3 { margin-bottom: 14px; font-size: 16px; }
388
+ .create-modal label { display: block; font-size: 12px; color: var(--text-dim); margin-bottom: 4px; margin-top: 10px; }
389
+ .create-modal input, .create-modal select, .create-modal textarea {
390
+ width: 100%; background: var(--surface2); border: 1px solid var(--border);
391
+ border-radius: 6px; color: var(--text); padding: 8px 10px; font-size: 13px; outline: none;
392
+ font-family: inherit;
393
+ }
394
+ .create-modal textarea { resize: vertical; min-height: 60px; }
395
+ .create-modal input:focus, .create-modal select:focus, .create-modal textarea:focus { border-color: var(--accent); }
396
+ .create-modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
397
+ .create-modal-actions button {
398
+ padding: 7px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; border: none;
399
+ }
400
+ .create-modal-actions .cancel { background: var(--surface2); color: var(--text-dim); }
401
+ .create-modal-actions .submit { background: var(--accent); color: #fff; font-weight: 500; }
402
+
403
+ .empty-state { text-align: center; padding: 60px 20px; color: var(--text-dim); }
404
+ .empty-state p { margin-bottom: 12px; }
405
+ </style>
406
+ </head>
407
+ <body>
408
+ <header>
409
+ <h1><a href="/">{{INSTANCE_NAME}}</a> <span style="color:var(--text-dim);font-weight:400;">/ Whiteboard</span></h1>
410
+ <div class="header-views">
411
+ <button class="active" data-view="tree">Tree</button>
412
+ <button data-view="questions">Questions</button>
413
+ <button data-view="weighted">Weighted</button>
414
+ </div>
415
+ <div class="header-right">
416
+ <button class="btn-new" id="btn-new">+ Plant</button>
417
+ <a href="/" class="back-link">Back to chat</a>
418
+ </div>
419
+ </header>
420
+
421
+ <div class="summary-bar" id="summary-bar"></div>
422
+
423
+ <div class="content" id="content"></div>
424
+
425
+ <!-- Create modal -->
426
+ <div class="create-overlay" id="create-overlay">
427
+ <div class="create-modal">
428
+ <h3>Plant a node</h3>
429
+ <label>Title</label>
430
+ <input type="text" id="create-title" placeholder="Short label..." />
431
+ <label>Type</label>
432
+ <select id="create-type">
433
+ <option value="task">Task</option>
434
+ <option value="goal">Goal</option>
435
+ <option value="question">Question</option>
436
+ <option value="decision">Decision</option>
437
+ <option value="note">Note</option>
438
+ </select>
439
+ <label>Parent (optional)</label>
440
+ <select id="create-parent"><option value="">Root (top level)</option></select>
441
+ <label>Tags (comma-separated)</label>
442
+ <input type="text" id="create-tags" placeholder="engineering, p1..." />
443
+ <label>Question text (if type = question)</label>
444
+ <input type="text" id="create-question" placeholder="What needs answering?" />
445
+ <label>Detail (optional)</label>
446
+ <textarea id="create-body" placeholder="Markdown..."></textarea>
447
+ <div class="create-modal-actions">
448
+ <button type="button" class="cancel" id="btn-cancel-create">Cancel</button>
449
+ <button type="button" class="submit" id="btn-submit-create">Plant</button>
450
+ </div>
451
+ </div>
452
+ </div>
453
+
454
+ <!-- Edit modal -->
455
+ <div class="edit-overlay" id="edit-overlay">
456
+ <div class="edit-modal">
457
+ <h3>Edit node</h3>
458
+ <input type="hidden" id="edit-id" />
459
+ <label>Title</label>
460
+ <input type="text" id="edit-title" />
461
+ <label>Type</label>
462
+ <select id="edit-type">
463
+ <option value="task">Task</option>
464
+ <option value="goal">Goal</option>
465
+ <option value="question">Question</option>
466
+ <option value="decision">Decision</option>
467
+ <option value="note">Note</option>
468
+ </select>
469
+ <label>Tags (comma-separated)</label>
470
+ <input type="text" id="edit-tags" />
471
+ <label>Question text</label>
472
+ <input type="text" id="edit-question" />
473
+ <label>Detail</label>
474
+ <textarea id="edit-body"></textarea>
475
+ <div class="edit-modal-actions">
476
+ <button type="button" class="cancel" id="btn-cancel-edit">Cancel</button>
477
+ <button type="button" class="submit" id="btn-submit-edit">Save</button>
478
+ </div>
479
+ </div>
480
+ </div>
481
+
482
+ <script>
483
+ (function() {
484
+ "use strict";
485
+
486
+ var currentView = "tree";
487
+ var treeData = [];
488
+ var questionsData = [];
489
+ var weightedData = [];
490
+ var summaryData = null;
491
+ var expandedNodes = {};
492
+ var openDetails = {};
493
+
494
+ function getSessionId() {
495
+ return sessionStorage.getItem("dash_sid") || new URLSearchParams(window.location.search).get("sessionId") || "";
496
+ }
497
+ function authHeaders() {
498
+ var sid = getSessionId();
499
+ return sid ? { "x-session-id": sid } : {};
500
+ }
501
+ function qs(params) {
502
+ params.sessionId = getSessionId();
503
+ return "?" + new URLSearchParams(params).toString();
504
+ }
505
+ async function api(method, path, body) {
506
+ var opts = {
507
+ method: method,
508
+ headers: Object.assign({ "Content-Type": "application/json" }, authHeaders()),
509
+ };
510
+ if (body) opts.body = JSON.stringify(body);
511
+ var sep = path.includes("?") ? "&" : "?";
512
+ var res = await fetch(path + sep + "sessionId=" + encodeURIComponent(getSessionId()), opts);
513
+ if (!res.ok) {
514
+ var text = "";
515
+ try { var j = await res.json(); text = j.error || j.message || res.statusText; } catch { text = res.statusText; }
516
+ console.error("API error:", method, path, res.status, text);
517
+ return { error: text || "Request failed (" + res.status + ")" };
518
+ }
519
+ return res.json();
520
+ }
521
+
522
+ // --- Load data ---
523
+ async function loadSummary() {
524
+ summaryData = await api("GET", "/api/whiteboard/summary");
525
+ renderSummary();
526
+ }
527
+
528
+ async function loadTree() {
529
+ var data = await api("GET", "/api/whiteboard?view=tree");
530
+ treeData = data.nodes || [];
531
+ if (currentView === "tree") renderTree();
532
+ }
533
+
534
+ async function loadQuestions() {
535
+ var data = await api("GET", "/api/whiteboard?view=questions");
536
+ questionsData = data.questions || [];
537
+ if (currentView === "questions") renderQuestions();
538
+ }
539
+
540
+ async function loadWeighted() {
541
+ var data = await api("GET", "/api/whiteboard?view=weighted");
542
+ weightedData = data.nodes || [];
543
+ if (currentView === "weighted") renderWeighted();
544
+ }
545
+
546
+ async function loadAll() {
547
+ await Promise.all([loadSummary(), loadTree(), loadQuestions(), loadWeighted()]);
548
+ }
549
+
550
+ // --- Summary bar ---
551
+ function renderSummary() {
552
+ if (!summaryData) return;
553
+ var el = document.getElementById("summary-bar");
554
+ var s = summaryData;
555
+ el.innerHTML =
556
+ '<div class="stat"><span class="stat-val">' + s.total + '</span> items</div>' +
557
+ '<div class="stat"><span class="stat-val">' + s.open + '</span> open</div>' +
558
+ '<div class="stat"><span class="stat-val">' + s.done + '</span> done</div>' +
559
+ '<div class="stat"><span class="stat-val" style="color:var(--amber)">' + s.openQuestions + '</span> questions</div>' +
560
+ Object.entries(s.byTag || {}).map(function(e) {
561
+ return '<div class="stat">' + e[0] + ' <span class="stat-val">' + e[1] + '</span></div>';
562
+ }).join("");
563
+ }
564
+
565
+ // --- Icons ---
566
+ function nodeIcon(node) {
567
+ if (node.status === "done") return '<span class="tree-icon done">&#10003;</span>';
568
+ if (node.type === "question" && !node.answer) return '<span class="tree-icon question">&#9670;</span>';
569
+ if (node.type === "decision") return '<span class="tree-icon decision">&#9632;</span>';
570
+ if (node.type === "goal") return '<span class="tree-icon open">&#9679;</span>';
571
+ return '<span class="tree-icon open">&#9675;</span>';
572
+ }
573
+
574
+ function weightBar(weight) {
575
+ if (!weight || weight <= 0) return "";
576
+ var pct = Math.round(weight * 100);
577
+ var cls = weight > 0.6 ? "high" : weight > 0.3 ? "mid" : "low";
578
+ return '<div class="tree-weight"><div class="tree-weight-fill ' + cls + '" style="width:' + pct + '%"></div></div>';
579
+ }
580
+
581
+ // --- Tree view ---
582
+ function renderTree() {
583
+ var el = document.getElementById("content");
584
+ if (treeData.length === 0) {
585
+ el.innerHTML = '<div class="empty-state"><p>No items on the whiteboard yet.</p><p>Click <strong>+ Plant</strong> to add a goal or task.</p></div>';
586
+ return;
587
+ }
588
+ el.innerHTML = '<ul class="tree-root">' + treeData.map(function(n) { return renderTreeNode(n, 0); }).join("") + '</ul>';
589
+ }
590
+
591
+ function renderTreeNode(node, depth) {
592
+ var hasChildren = node.children && node.children.length > 0;
593
+ var expanded = expandedNodes[node.id] !== false; // default expanded
594
+ var detailOpen = openDetails[node.id];
595
+ var toggleIcon = hasChildren ? (expanded ? "&#9660;" : "&#9654;") : "";
596
+ var toggleClass = hasChildren ? "tree-toggle" : "tree-toggle leaf";
597
+
598
+ var html = '<li class="tree-node" data-id="' + node.id + '">';
599
+ html += '<div class="tree-item" data-id="' + node.id + '">';
600
+ html += '<span class="' + toggleClass + '" data-id="' + node.id + '">' + toggleIcon + '</span>';
601
+ html += nodeIcon(node);
602
+ html += '<span class="tree-title' + (node.status === "done" ? " done" : "") + '">' + esc(node.title) + '</span>';
603
+ if (node.tags && node.tags.length) {
604
+ html += '<span class="tree-tags">' + node.tags.map(function(t) { return '<span class="tree-tag">' + esc(t) + '</span>'; }).join("") + '</span>';
605
+ }
606
+ html += weightBar(node.weight);
607
+ html += '</div>';
608
+
609
+ // Detail panel
610
+ html += '<div class="tree-detail' + (detailOpen ? " open" : "") + '" data-detail="' + node.id + '">';
611
+ if (node.question) {
612
+ html += '<div class="tree-detail-question">' + esc(node.question) + '</div>';
613
+ }
614
+ if (node.body) {
615
+ html += '<div class="tree-detail-body">' + esc(node.body) + '</div>';
616
+ }
617
+ if (node.answer) {
618
+ html += '<div class="tree-detail-answered">Answer: ' + esc(node.answer) + '</div>';
619
+ } else if (node.type === "question") {
620
+ html += '<div class="tree-detail-answer"><input type="text" placeholder="Your answer..." data-answer-id="' + node.id + '" /><button onclick="answerFromTree(\'' + node.id + '\')">Answer</button></div>';
621
+ }
622
+ html += '<div class="tree-detail-meta">' + node.type + ' &middot; planted by ' + node.plantedBy + ' &middot; ' + timeAgo(node.createdAt) + '</div>';
623
+ html += '<div class="tree-detail-actions">';
624
+ if (node.status === "done") {
625
+ html += '<button class="btn-done" data-action="reopen" data-id="' + node.id + '">reopen</button>';
626
+ } else {
627
+ html += '<button class="btn-done" data-action="done" data-id="' + node.id + '">mark done</button>';
628
+ }
629
+ html += '<button data-action="edit" data-id="' + node.id + '">edit</button>';
630
+ html += '<button class="btn-delete" data-action="delete" data-id="' + node.id + '">delete</button>';
631
+ html += '</div>';
632
+ html += '</div>';
633
+
634
+ // Children
635
+ if (hasChildren) {
636
+ html += '<ul class="tree-children' + (expanded ? "" : " collapsed") + '">';
637
+ html += node.children.map(function(c) { return renderTreeNode(c, depth + 1); }).join("");
638
+ html += '</ul>';
639
+ }
640
+
641
+ html += '</li>';
642
+ return html;
643
+ }
644
+
645
+ // --- Questions view ---
646
+ function renderQuestions() {
647
+ var el = document.getElementById("content");
648
+ if (questionsData.length === 0) {
649
+ el.innerHTML = '<div class="empty-state"><p>No open questions.</p><p>The agent plants questions when it needs your input to unstick work.</p></div>';
650
+ return;
651
+ }
652
+ el.innerHTML = questionsData.map(function(q) {
653
+ var path = (q.path || []).map(function(p) { return esc(p.title); }).join(" &rarr; ");
654
+ return '<div class="question-card" data-id="' + q.id + '">' +
655
+ (path ? '<div class="question-path">' + path + '</div>' : '') +
656
+ '<div class="question-text">' + esc(q.question || q.title) + '</div>' +
657
+ '<div class="question-age">' + timeAgo(q.createdAt) + ' &middot; weight ' + (q.weight || 0) + '</div>' +
658
+ '<div class="question-answer-row">' +
659
+ '<input type="text" placeholder="Your answer..." data-answer-id="' + q.id + '" />' +
660
+ '<button onclick="answerQuestion(\'' + q.id + '\')">Answer</button>' +
661
+ '</div>' +
662
+ '</div>';
663
+ }).join("");
664
+ }
665
+
666
+ // --- Weighted view ---
667
+ function renderWeighted() {
668
+ var el = document.getElementById("content");
669
+ if (weightedData.length === 0) {
670
+ el.innerHTML = '<div class="empty-state"><p>No open items.</p></div>';
671
+ return;
672
+ }
673
+ el.innerHTML = weightedData.filter(function(n) { return n.weight > 0; }).map(function(n) {
674
+ return '<div class="weighted-item">' +
675
+ '<span class="weighted-weight">' + n.weight + '</span>' +
676
+ nodeIcon(n).replace("tree-icon", "weighted-icon") +
677
+ '<span class="weighted-title">' + esc(n.title) + '</span>' +
678
+ (n.tags && n.tags.length ? '<span class="weighted-tags">' + n.tags.map(function(t) { return '<span class="tree-tag">' + esc(t) + '</span>'; }).join("") + '</span>' : '') +
679
+ weightBar(n.weight) +
680
+ '</div>';
681
+ }).join("");
682
+ }
683
+
684
+ // --- Answer ---
685
+ window.answerQuestion = async function(id) {
686
+ var inp = document.querySelector('[data-answer-id="' + id + '"]');
687
+ if (!inp || !inp.value.trim()) return;
688
+ await api("POST", "/api/whiteboard/" + id + "/answer", { answer: inp.value.trim() });
689
+ loadAll();
690
+ };
691
+ window.answerFromTree = window.answerQuestion;
692
+
693
+ // --- Node actions ---
694
+ async function deleteNode(id) {
695
+ if (!confirm("Delete this node? Children will be archived too.")) return;
696
+ await api("DELETE", "/api/whiteboard/" + id + "?cascade=true");
697
+ loadAll();
698
+ }
699
+
700
+ async function markDone(id) {
701
+ await api("PATCH", "/api/whiteboard/" + id, { status: "done" });
702
+ loadAll();
703
+ }
704
+
705
+ async function reopenNode(id) {
706
+ await api("PATCH", "/api/whiteboard/" + id, { status: "open" });
707
+ loadAll();
708
+ }
709
+
710
+ // Flat node lookup for edit modal
711
+ function findNodeFlat(nodes, id) {
712
+ for (var i = 0; i < nodes.length; i++) {
713
+ if (nodes[i].id === id) return nodes[i];
714
+ if (nodes[i].children) {
715
+ var found = findNodeFlat(nodes[i].children, id);
716
+ if (found) return found;
717
+ }
718
+ }
719
+ return null;
720
+ }
721
+
722
+ function openEditModal(id) {
723
+ var node = findNodeFlat(treeData, id);
724
+ if (!node) return;
725
+ document.getElementById("edit-id").value = node.id;
726
+ document.getElementById("edit-title").value = node.title || "";
727
+ document.getElementById("edit-type").value = node.type || "task";
728
+ document.getElementById("edit-tags").value = (node.tags || []).join(", ");
729
+ document.getElementById("edit-question").value = node.question || "";
730
+ document.getElementById("edit-body").value = node.body || "";
731
+ document.getElementById("edit-overlay").classList.add("open");
732
+ setTimeout(function() { document.getElementById("edit-title").focus(); }, 50);
733
+ }
734
+
735
+ function closeEditModal() {
736
+ document.getElementById("edit-overlay").classList.remove("open");
737
+ }
738
+
739
+ async function submitEdit() {
740
+ var id = document.getElementById("edit-id").value;
741
+ var title = document.getElementById("edit-title").value.trim();
742
+ var type = document.getElementById("edit-type").value;
743
+ var tags = document.getElementById("edit-tags").value.split(",").map(function(t) { return t.trim(); }).filter(Boolean);
744
+ var question = document.getElementById("edit-question").value.trim() || undefined;
745
+ var body = document.getElementById("edit-body").value.trim() || undefined;
746
+
747
+ if (!title) { document.getElementById("edit-title").focus(); return; }
748
+
749
+ try {
750
+ var payload = { title: title, type: type, tags: tags };
751
+ if (question !== undefined) payload.question = question;
752
+ if (body !== undefined) payload.body = body;
753
+ var result = await api("PATCH", "/api/whiteboard/" + id, payload);
754
+ if (result.error) { alert("Error: " + result.error); return; }
755
+ closeEditModal();
756
+ loadAll();
757
+ } catch (err) {
758
+ alert("Failed to update: " + (err.message || err));
759
+ }
760
+ }
761
+
762
+ document.getElementById("edit-overlay").addEventListener("click", function(e) {
763
+ if (e.target === this) closeEditModal();
764
+ });
765
+ document.getElementById("btn-cancel-edit").addEventListener("click", closeEditModal);
766
+ document.getElementById("btn-submit-edit").addEventListener("click", submitEdit);
767
+
768
+ // --- Event delegation ---
769
+ document.getElementById("content").addEventListener("click", function(e) {
770
+ // Action buttons (edit, delete, done, reopen)
771
+ var actionBtn = e.target.closest("[data-action]");
772
+ if (actionBtn) {
773
+ var action = actionBtn.getAttribute("data-action");
774
+ var id = actionBtn.getAttribute("data-id");
775
+ if (action === "delete") deleteNode(id);
776
+ else if (action === "edit") openEditModal(id);
777
+ else if (action === "done") markDone(id);
778
+ else if (action === "reopen") reopenNode(id);
779
+ return;
780
+ }
781
+ // Toggle expand/collapse
782
+ var toggle = e.target.closest(".tree-toggle");
783
+ if (toggle && !toggle.classList.contains("leaf")) {
784
+ var id2 = toggle.getAttribute("data-id");
785
+ expandedNodes[id2] = expandedNodes[id2] === false ? true : false;
786
+ renderTree();
787
+ return;
788
+ }
789
+ // Toggle detail
790
+ var item = e.target.closest(".tree-item");
791
+ if (item && !e.target.closest(".tree-toggle") && !e.target.closest("button") && !e.target.closest("input")) {
792
+ var nid = item.getAttribute("data-id");
793
+ openDetails[nid] = !openDetails[nid];
794
+ var detail = document.querySelector('[data-detail="' + nid + '"]');
795
+ if (detail) detail.classList.toggle("open");
796
+ }
797
+ });
798
+
799
+ // Enter key in answer inputs
800
+ document.getElementById("content").addEventListener("keydown", function(e) {
801
+ if (e.key === "Enter" && e.target.matches("[data-answer-id]")) {
802
+ var id = e.target.getAttribute("data-answer-id");
803
+ window.answerQuestion(id);
804
+ }
805
+ });
806
+
807
+ // --- View switching ---
808
+ document.querySelector(".header-views").addEventListener("click", function(e) {
809
+ var btn = e.target.closest("button");
810
+ if (!btn) return;
811
+ var view = btn.getAttribute("data-view");
812
+ if (!view || view === currentView) return;
813
+ currentView = view;
814
+ document.querySelectorAll(".header-views button").forEach(function(b) { b.classList.remove("active"); });
815
+ btn.classList.add("active");
816
+ if (view === "tree") renderTree();
817
+ else if (view === "questions") renderQuestions();
818
+ else if (view === "weighted") renderWeighted();
819
+ });
820
+
821
+ // --- Create modal ---
822
+ document.getElementById("btn-new").addEventListener("click", function() {
823
+ openCreateModal();
824
+ });
825
+
826
+ function openCreateModal() {
827
+ // Populate parent dropdown from flat list of open items
828
+ var sel = document.getElementById("create-parent");
829
+ sel.innerHTML = '<option value="">Root (top level)</option>';
830
+ function addOptions(nodes, prefix) {
831
+ if (!nodes) return;
832
+ nodes.forEach(function(n) {
833
+ sel.innerHTML += '<option value="' + n.id + '">' + prefix + esc(n.title) + '</option>';
834
+ if (n.children) addOptions(n.children, prefix + " ");
835
+ });
836
+ }
837
+ addOptions(treeData, "");
838
+ document.getElementById("create-title").value = "";
839
+ document.getElementById("create-tags").value = "";
840
+ document.getElementById("create-question").value = "";
841
+ document.getElementById("create-body").value = "";
842
+ document.getElementById("create-type").value = "task";
843
+ document.getElementById("create-overlay").classList.add("open");
844
+ setTimeout(function() { document.getElementById("create-title").focus(); }, 50);
845
+ }
846
+ function closeCreateModal() {
847
+ document.getElementById("create-overlay").classList.remove("open");
848
+ }
849
+ window.closeCreateModal = closeCreateModal;
850
+ document.getElementById("create-overlay").addEventListener("click", function(e) {
851
+ if (e.target === this) closeCreateModal();
852
+ });
853
+
854
+ async function submitCreate() {
855
+ var title = document.getElementById("create-title").value.trim();
856
+ var type = document.getElementById("create-type").value;
857
+ var parentId = document.getElementById("create-parent").value || null;
858
+ var tags = document.getElementById("create-tags").value.split(",").map(function(t) { return t.trim(); }).filter(Boolean);
859
+ var question = document.getElementById("create-question").value.trim() || undefined;
860
+ var body = document.getElementById("create-body").value.trim() || undefined;
861
+
862
+ if (!title) { document.getElementById("create-title").focus(); return; }
863
+
864
+ try {
865
+ var payload = { title: title, type: type, parentId: parentId, tags: tags, plantedBy: "human" };
866
+ if (question) payload.question = question;
867
+ if (body) payload.body = body;
868
+
869
+ var result = await api("POST", "/api/whiteboard", payload);
870
+ if (result.error) {
871
+ alert("Error: " + result.error);
872
+ return;
873
+ }
874
+ closeCreateModal();
875
+ await loadAll();
876
+ } catch (err) {
877
+ alert("Failed to create: " + (err.message || err));
878
+ }
879
+ }
880
+ window.submitCreate = submitCreate;
881
+
882
+ // Wire modal buttons
883
+ document.getElementById("btn-cancel-create").addEventListener("click", function() { closeCreateModal(); });
884
+ document.getElementById("btn-submit-create").addEventListener("click", submitCreate);
885
+
886
+ // Escape key closes modal
887
+ document.addEventListener("keydown", function(e) {
888
+ if (e.key === "Escape") closeCreateModal();
889
+ });
890
+
891
+ // --- Helpers ---
892
+ function esc(s) {
893
+ if (!s) return "";
894
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
895
+ }
896
+
897
+ function timeAgo(iso) {
898
+ if (!iso) return "";
899
+ var diff = Date.now() - new Date(iso).getTime();
900
+ var mins = Math.floor(diff / 60000);
901
+ if (mins < 1) return "just now";
902
+ if (mins < 60) return mins + "m ago";
903
+ var hrs = Math.floor(mins / 60);
904
+ if (hrs < 24) return hrs + "h ago";
905
+ var days = Math.floor(hrs / 24);
906
+ return days + "d ago";
907
+ }
908
+
909
+ // --- Init ---
910
+ loadAll();
911
+ })();
912
+ </script>
913
+ <script src="/public/search-flyout.js"></script>
914
+ </body>
915
+ </html>