@treenity/mods 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/board/view.tsx +1 -1
  2. package/brahman/helpers.ts +7 -7
  3. package/brahman/service.ts +24 -24
  4. package/brahman/types.ts +21 -21
  5. package/brahman/views/action-cards.tsx +33 -23
  6. package/brahman/views/bot-view.tsx +3 -2
  7. package/brahman/views/chat-editor.tsx +119 -124
  8. package/brahman/views/menu-editor.tsx +75 -89
  9. package/brahman/views/page-layout.tsx +10 -8
  10. package/brahman/views/tstring-input.tsx +25 -15
  11. package/canary/service.ts +18 -18
  12. package/dist/board/view.js +1 -1
  13. package/dist/board/view.js.map +1 -1
  14. package/dist/brahman/helpers.d.ts +1 -1
  15. package/dist/brahman/helpers.d.ts.map +1 -1
  16. package/dist/brahman/helpers.js +6 -6
  17. package/dist/brahman/helpers.js.map +1 -1
  18. package/dist/brahman/service.js +24 -24
  19. package/dist/brahman/service.js.map +1 -1
  20. package/dist/brahman/types.d.ts +1 -1
  21. package/dist/brahman/types.d.ts.map +1 -1
  22. package/dist/brahman/types.js +21 -21
  23. package/dist/brahman/types.js.map +1 -1
  24. package/dist/brahman/views/action-cards.d.ts.map +1 -1
  25. package/dist/brahman/views/action-cards.js +7 -4
  26. package/dist/brahman/views/action-cards.js.map +1 -1
  27. package/dist/brahman/views/bot-view.d.ts.map +1 -1
  28. package/dist/brahman/views/bot-view.js +2 -1
  29. package/dist/brahman/views/bot-view.js.map +1 -1
  30. package/dist/brahman/views/chat-editor.d.ts.map +1 -1
  31. package/dist/brahman/views/chat-editor.js +27 -18
  32. package/dist/brahman/views/chat-editor.js.map +1 -1
  33. package/dist/brahman/views/menu-editor.d.ts.map +1 -1
  34. package/dist/brahman/views/menu-editor.js +12 -16
  35. package/dist/brahman/views/menu-editor.js.map +1 -1
  36. package/dist/brahman/views/page-layout.d.ts.map +1 -1
  37. package/dist/brahman/views/page-layout.js +1 -1
  38. package/dist/brahman/views/page-layout.js.map +1 -1
  39. package/dist/brahman/views/tstring-input.d.ts.map +1 -1
  40. package/dist/brahman/views/tstring-input.js +7 -3
  41. package/dist/brahman/views/tstring-input.js.map +1 -1
  42. package/dist/canary/service.js +18 -18
  43. package/dist/canary/service.js.map +1 -1
  44. package/dist/doc/fs-codec.js +1 -1
  45. package/dist/doc/fs-codec.js.map +1 -1
  46. package/dist/doc/renderers.d.ts.map +1 -1
  47. package/dist/doc/renderers.js +2 -1
  48. package/dist/doc/renderers.js.map +1 -1
  49. package/dist/doc/toolbar.d.ts.map +1 -1
  50. package/dist/doc/toolbar.js +5 -5
  51. package/dist/doc/toolbar.js.map +1 -1
  52. package/dist/launcher/types.js +2 -2
  53. package/dist/launcher/types.js.map +1 -1
  54. package/dist/launcher/view.js +2 -2
  55. package/dist/launcher/view.js.map +1 -1
  56. package/dist/mindmap/branch.d.ts +10 -0
  57. package/dist/mindmap/branch.d.ts.map +1 -1
  58. package/dist/mindmap/branch.js +42 -9
  59. package/dist/mindmap/branch.js.map +1 -1
  60. package/dist/mindmap/sidebar.d.ts.map +1 -1
  61. package/dist/mindmap/sidebar.js +4 -3
  62. package/dist/mindmap/sidebar.js.map +1 -1
  63. package/dist/mindmap/view.d.ts.map +1 -1
  64. package/dist/mindmap/view.js +35 -4
  65. package/dist/mindmap/view.js.map +1 -1
  66. package/dist/sensor-demo/service.js +6 -5
  67. package/dist/sensor-demo/service.js.map +1 -1
  68. package/dist/sensor-generator/action.js +1 -1
  69. package/dist/sensor-generator/action.js.map +1 -1
  70. package/dist/sim/service.js +41 -41
  71. package/dist/sim/service.js.map +1 -1
  72. package/dist/table/view.js.map +1 -1
  73. package/dist/todo/types.js +2 -2
  74. package/dist/todo/types.js.map +1 -1
  75. package/dist/todo/view.js +6 -4
  76. package/dist/todo/view.js.map +1 -1
  77. package/dist/whisper/inbox.js +3 -3
  78. package/dist/whisper/inbox.js.map +1 -1
  79. package/dist/whisper/route.d.ts +1 -1
  80. package/dist/whisper/route.d.ts.map +1 -1
  81. package/dist/whisper/route.js +13 -13
  82. package/dist/whisper/route.js.map +1 -1
  83. package/doc/CLAUDE.md +1 -1
  84. package/doc/fs-codec.ts +1 -1
  85. package/doc/renderers.tsx +4 -3
  86. package/doc/toolbar.tsx +12 -9
  87. package/launcher/types.ts +2 -2
  88. package/launcher/view.tsx +12 -8
  89. package/mindmap/branch.tsx +121 -22
  90. package/mindmap/mindmap.css +52 -0
  91. package/mindmap/sidebar.tsx +9 -6
  92. package/mindmap/view.tsx +40 -4
  93. package/package.json +27 -3
  94. package/sensor-demo/service.ts +6 -5
  95. package/sensor-generator/action.ts +1 -1
  96. package/sim/service.ts +41 -41
  97. package/table/view.tsx +7 -2
  98. package/todo/types.ts +2 -2
  99. package/todo/view.tsx +9 -10
  100. package/whisper/inbox.ts +3 -3
  101. package/whisper/route.ts +13 -13
  102. package/board/board.test.ts +0 -212
  103. package/brahman/brahman.test.ts +0 -855
  104. package/doc/fs-codec.test.ts +0 -119
  105. package/doc/markdown.test.ts +0 -152
  106. package/sim/sim.test.ts +0 -282
package/board/view.tsx CHANGED
@@ -532,7 +532,7 @@ const KanbanView: View<BoardKanban> = ({ value, ctx }) => {
532
532
 
533
533
  {selectedTask && selectedNode && (
534
534
  <Dialog open onOpenChange={open => { if (!open) setSelectedTask(null); }}>
535
- <DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-lg" aria-describedby={undefined}>
535
+ <DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-[960px]" aria-describedby={undefined}>
536
536
  <DialogTitle className="sr-only">Task</DialogTitle>
537
537
  <Render value={selectedNode} />
538
538
  </DialogContent>
@@ -170,7 +170,7 @@ export function buildReplyMarkup(rows: MenuRow[], type: MenuType, lang: string,
170
170
  /** @opaque Runtime-injected, not part of public schema */
171
171
  export type BrahmanCtx = {
172
172
  ctx: Context;
173
- store: Tree;
173
+ tree: Tree;
174
174
  session: Record<string, unknown>;
175
175
  sessionNode: NodeData;
176
176
  user: NodeData;
@@ -254,7 +254,7 @@ export function buildLangKeyboard(langs: string[]) {
254
254
  // ── Page execution ──
255
255
 
256
256
  export async function executePage(pagePath: string, bCtx: BrahmanCtx): Promise<void> {
257
- const pageNode = await bCtx.store.get(pagePath);
257
+ const pageNode = await bCtx.tree.get(pagePath);
258
258
  if (!pageNode) return;
259
259
 
260
260
  const pageComp = getComponent(pageNode, PageConfig);
@@ -264,7 +264,7 @@ export async function executePage(pagePath: string, bCtx: BrahmanCtx): Promise<v
264
264
  const history = (bCtx.session.history ?? []) as string[];
265
265
  history.push(pagePath);
266
266
 
267
- const { items } = await bCtx.store.getChildren(pagePath + '/_actions');
267
+ const { items } = await bCtx.tree.getChildren(pagePath + '/_actions');
268
268
  const positions = pageComp.positions ?? [];
269
269
  const tracked = new Set(positions);
270
270
  const sorted = [
@@ -286,7 +286,7 @@ export async function executePage(pagePath: string, bCtx: BrahmanCtx): Promise<v
286
286
  const userData = getComponent(bCtx.user, BrahmanUser);
287
287
  if (userData) {
288
288
  (userData as any).blocked = true;
289
- await bCtx.store.set(bCtx.user);
289
+ await bCtx.tree.set(bCtx.user);
290
290
  }
291
291
  return;
292
292
  }
@@ -296,7 +296,7 @@ export async function executePage(pagePath: string, bCtx: BrahmanCtx): Promise<v
296
296
  bCtx.session.error = { message: errorMsg };
297
297
 
298
298
  try {
299
- const { items: pages } = await bCtx.store.getChildren(`${bCtx.botPath}/pages`);
299
+ const { items: pages } = await bCtx.tree.getChildren(`${bCtx.botPath}/pages`);
300
300
  const errorPage = pages.find(p => getComponent(p, PageConfig)?.command === '/error');
301
301
  if (errorPage) await executePage(errorPage.$path, bCtx);
302
302
  } catch (innerErr) {
@@ -313,7 +313,7 @@ const _neverAbort = new AbortController().signal;
313
313
  export async function executeAction(node: NodeData, bCtx: BrahmanCtx): Promise<void> {
314
314
  const handler = resolveCtx(node.$type, 'action:run');
315
315
  if (!handler) return;
316
- await (handler as any)({ node, store: bCtx.store, signal: _neverAbort }, bCtx);
316
+ await (handler as any)({ node, tree: bCtx.tree, signal: _neverAbort }, bCtx);
317
317
  }
318
318
 
319
319
  // ── Sequential action runner with wait support ──
@@ -366,7 +366,7 @@ export async function resolveWait(bCtx: BrahmanCtx, gCtx: Context): Promise<bool
366
366
 
367
367
  // Execute remaining actions (may set a new wait if another question is encountered)
368
368
  if (remaining.length) {
369
- const nodes = (await Promise.all(remaining.map(p => bCtx.store.get(p)))).filter(Boolean) as NodeData[];
369
+ const nodes = (await Promise.all(remaining.map(p => bCtx.tree.get(p)))).filter(Boolean) as NodeData[];
370
370
  await executeActions(nodes, bCtx);
371
371
  }
372
372
 
@@ -30,8 +30,8 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
30
30
  const dirs = ['pages', 'users', 'sessions'];
31
31
  for (const d of dirs) {
32
32
  const dirPath = `${botPath}/${d}`;
33
- if (!(await svcCtx.store.get(dirPath))) {
34
- await svcCtx.store.set({ $path: dirPath, $type: 'dir' } as NodeData);
33
+ if (!(await svcCtx.tree.get(dirPath))) {
34
+ await svcCtx.tree.set({ $path: dirPath, $type: 'dir' } as NodeData);
35
35
  }
36
36
  }
37
37
 
@@ -49,18 +49,18 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
49
49
 
50
50
  // Load/create session
51
51
  const sessionPath = `${botPath}/sessions/${userId}`;
52
- let sessionNode = await svcCtx.store.get(sessionPath);
52
+ let sessionNode = await svcCtx.tree.get(sessionPath);
53
53
  if (!sessionNode) {
54
54
  sessionNode = {
55
55
  $path: sessionPath, $type: 'brahman.session',
56
56
  tid: userId, data: {}, history: [], callbacks: {},
57
57
  } as NodeData;
58
- await svcCtx.store.set(sessionNode);
58
+ await svcCtx.tree.set(sessionNode);
59
59
  }
60
60
 
61
61
  // Load/create user
62
62
  const userPath = `${botPath}/users/${userId}`;
63
- let userNode = await svcCtx.store.get(userPath);
63
+ let userNode = await svcCtx.tree.get(userPath);
64
64
  if (!userNode) {
65
65
  userNode = {
66
66
  $path: userPath, $type: 'brahman.user',
@@ -71,7 +71,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
71
71
  lang: gCtx.from.language_code ?? defaultLang,
72
72
  isAdmin: false, blocked: false, banned: false, tags: [],
73
73
  } as NodeData;
74
- await svcCtx.store.set(userNode);
74
+ await svcCtx.tree.set(userNode);
75
75
  }
76
76
 
77
77
  const userData = getComponent(userNode, BrahmanUser);
@@ -80,7 +80,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
80
80
  // Mark as unblocked if previously blocked (user restarted bot)
81
81
  if (userData?.blocked) {
82
82
  (userData as any).blocked = false;
83
- await svcCtx.store.set(userNode);
83
+ await svcCtx.tree.set(userNode);
84
84
  }
85
85
 
86
86
  const sessionComp = getComponent(sessionNode, BrahmanSession);
@@ -88,7 +88,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
88
88
  if (!sessionData.history) sessionData.history = (sessionComp as any)?.history ?? [];
89
89
 
90
90
  const bCtx: BrahmanCtx = {
91
- ctx: gCtx, store: svcCtx.store,
91
+ ctx: gCtx, tree: svcCtx.tree,
92
92
  session: sessionData,
93
93
  sessionNode,
94
94
  user: userNode,
@@ -111,13 +111,13 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
111
111
  history: sessionData.history ?? [],
112
112
  callbacks: sessionData.callbacks ?? {},
113
113
  });
114
- await svcCtx.store.set(sessionNode);
114
+ await svcCtx.tree.set(sessionNode);
115
115
  });
116
116
 
117
117
  // ── Dynamic page lookup (pages can be added/changed at runtime) ──
118
118
 
119
119
  async function getPages() {
120
- const { items } = await svcCtx.store.getChildren(`${botPath}/pages`, { depth: 10 });
120
+ const { items } = await svcCtx.tree.getChildren(`${botPath}/pages`, { depth: 10 });
121
121
  return items.filter(n => n.$type === 'brahman.page');
122
122
  }
123
123
 
@@ -153,7 +153,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
153
153
  const btnId = parseInt(data.slice(4), 10);
154
154
  const lastMenuPath = bCtx.session._lastMenu as string | undefined;
155
155
  if (lastMenuPath) {
156
- const menuNode = await svcCtx.store.get(lastMenuPath);
156
+ const menuNode = await svcCtx.tree.get(lastMenuPath);
157
157
  const menuComp = menuNode ? findActionComp(menuNode) as any : null;
158
158
  if (menuComp?.rows) {
159
159
  for (const row of menuComp.rows) {
@@ -162,7 +162,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
162
162
  if (btn.action.type === 'brahman.action.page' && btn.action.target) {
163
163
  await executePage(btn.action.target, bCtx);
164
164
  } else if (btn.action.target) {
165
- const actionNode = await svcCtx.store.get(btn.action.target);
165
+ const actionNode = await svcCtx.tree.get(btn.action.target);
166
166
  if (actionNode) await executeAction(actionNode, bCtx);
167
167
  }
168
168
  }
@@ -176,7 +176,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
176
176
  const userData = getComponent(bCtx.user, BrahmanUser);
177
177
  if (userData) {
178
178
  (userData as any).lang = newLang;
179
- await svcCtx.store.set(bCtx.user);
179
+ await svcCtx.tree.set(bCtx.user);
180
180
  bCtx.lang = newLang;
181
181
  }
182
182
  bCtx.session.langSelected = newLang;
@@ -212,9 +212,9 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
212
212
  const history = (bCtx.session.history ?? []) as string[];
213
213
  if (history.length > 0) {
214
214
  const lastPagePath = history[history.length - 1];
215
- const pageNode = await svcCtx.store.get(lastPagePath);
215
+ const pageNode = await svcCtx.tree.get(lastPagePath);
216
216
  if (pageNode) {
217
- const { items: children } = await svcCtx.store.getChildren(lastPagePath + '/_actions');
217
+ const { items: children } = await svcCtx.tree.getChildren(lastPagePath + '/_actions');
218
218
  for (const child of children) {
219
219
  const actionComp = findActionComp(child) as any;
220
220
  if (!actionComp) continue;
@@ -227,7 +227,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
227
227
  await executePage(btn.action.target, bCtx);
228
228
  return;
229
229
  } else if (btn.action.target) {
230
- const actionNode = await svcCtx.store.get(btn.action.target);
230
+ const actionNode = await svcCtx.tree.get(btn.action.target);
231
231
  if (actionNode) await executeAction(actionNode, bCtx);
232
232
  return;
233
233
  }
@@ -242,7 +242,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
242
242
  // Fallback: check _lastMenu for backward compat
243
243
  const lastMenuPath = bCtx.session._lastMenu as string | undefined;
244
244
  if (lastMenuPath) {
245
- const menuNode = await svcCtx.store.get(lastMenuPath);
245
+ const menuNode = await svcCtx.tree.get(lastMenuPath);
246
246
  const menuComp = menuNode ? findActionComp(menuNode) as any : null;
247
247
  if (menuComp?.menuType === 'keyboard' && menuComp.rows) {
248
248
  for (const row of menuComp.rows) {
@@ -276,8 +276,8 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
276
276
  if (info.username && !config.alias) updates.alias = `@${info.username}`;
277
277
  if (info.first_name && !config.name) updates.name = info.first_name;
278
278
  if (Object.keys(updates).length > 0) {
279
- const fresh = await svcCtx.store.get(botPath);
280
- if (fresh) await svcCtx.store.set({ ...fresh, ...updates });
279
+ const fresh = await svcCtx.tree.get(botPath);
280
+ if (fresh) await svcCtx.tree.set({ ...fresh, ...updates });
281
281
  }
282
282
  }
283
283
 
@@ -289,7 +289,7 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
289
289
 
290
290
  // React to start/stop actions toggling the running flag
291
291
  const unsub = svcCtx.subscribe(botPath, async () => {
292
- const fresh = await svcCtx.store.get(botPath);
292
+ const fresh = await svcCtx.tree.get(botPath);
293
293
  if (!fresh) return;
294
294
  const cfg = getComponent(fresh, BotConfig);
295
295
  const shouldRun = cfg?.running !== false;
@@ -316,13 +316,13 @@ register('brahman.bot', 'service', async (node: NodeData, svcCtx: ServiceCtx): P
316
316
  register('brahman.bot', 'action:start', async (ctx: ActionCtx) => {
317
317
  await ctx.nc('/sys/autostart').get(Autostart).start({ path: ctx.node.$path });
318
318
  // Set running flag — service subscription reacts and calls bot.start()
319
- const fresh = await ctx.store.get(ctx.node.$path);
320
- if (fresh) await ctx.store.set({ ...fresh, running: true });
319
+ const fresh = await ctx.tree.get(ctx.node.$path);
320
+ if (fresh) await ctx.tree.set({ ...fresh, running: true });
321
321
  });
322
322
 
323
323
  /** @description Stop the Telegram bot polling loop via autostart service */
324
324
  register('brahman.bot', 'action:stop', async (ctx: ActionCtx) => {
325
325
  await ctx.nc('/sys/autostart').get(Autostart).stop({ path: ctx.node.$path });
326
- const fresh = await ctx.store.get(ctx.node.$path);
327
- if (fresh) await ctx.store.set({ ...fresh, running: false });
326
+ const fresh = await ctx.tree.get(ctx.node.$path);
327
+ if (fresh) await ctx.tree.set({ ...fresh, running: false });
328
328
  });
package/brahman/types.ts CHANGED
@@ -210,7 +210,7 @@ export class IfElseAction {
210
210
 
211
211
  async run(bCtx: BrahmanCtx) {
212
212
  const { format, executeAction, StopProcess } = await import('./helpers');
213
- const { store } = bCtx;
213
+ const { tree } = bCtx;
214
214
  const session = bCtx.session;
215
215
 
216
216
  let result = false;
@@ -223,7 +223,7 @@ export class IfElseAction {
223
223
 
224
224
  const target = result ? this.actionIf : this.actionElse;
225
225
  if (target) {
226
- const targetNode = await store.get(target);
226
+ const targetNode = await tree.get(target);
227
227
  if (targetNode) await executeAction(targetNode, bCtx);
228
228
  }
229
229
 
@@ -268,7 +268,7 @@ export class TagAction {
268
268
 
269
269
  async run(bCtx: BrahmanCtx) {
270
270
  const { format } = await import('./helpers');
271
- const { store } = bCtx;
271
+ const { tree } = bCtx;
272
272
  const session = bCtx.session;
273
273
 
274
274
  if (!this.tag) return;
@@ -291,7 +291,7 @@ export class TagAction {
291
291
  const userComp = getComponent(bCtx.user, BrahmanUser);
292
292
  if (userComp) {
293
293
  (userComp as any).tags = tags;
294
- await store.set(bCtx.user);
294
+ await tree.set(bCtx.user);
295
295
  }
296
296
  }
297
297
  }
@@ -306,13 +306,13 @@ export class BroadcastAction {
306
306
 
307
307
  async run(bCtx: BrahmanCtx) {
308
308
  const { checkTags, executeAction } = await import('./helpers');
309
- const { store, lang } = bCtx;
309
+ const { tree, lang } = bCtx;
310
310
 
311
311
  if (!this.action) return;
312
- const actionNode = await store.get(this.action);
312
+ const actionNode = await tree.get(this.action);
313
313
  if (!actionNode) return;
314
314
 
315
- const { items: users } = await store.getChildren(`${bCtx.botPath}/users`);
315
+ const { items: users } = await tree.getChildren(`${bCtx.botPath}/users`);
316
316
  const filterTags = this.userTags ?? [];
317
317
 
318
318
  for (const userNode of users) {
@@ -327,7 +327,7 @@ export class BroadcastAction {
327
327
  const msg = err instanceof Error ? err.message : String(err);
328
328
  if (msg.includes('403') || msg.includes('Forbidden')) {
329
329
  (userData as any).blocked = true;
330
- await store.set(userNode);
330
+ await tree.set(userNode);
331
331
  }
332
332
  }
333
333
  }
@@ -356,7 +356,7 @@ export class GetValueAction {
356
356
  }
357
357
  registerType('brahman.action.getvalue', GetValueAction);
358
358
 
359
- /** Write session value — store data in user session */
359
+ /** Write session value — tree data in user session */
360
360
  export class SetValueAction {
361
361
  /** @title Value expression */
362
362
  value = '';
@@ -423,11 +423,11 @@ export class FileAction {
423
423
 
424
424
  async run(bCtx: BrahmanCtx) {
425
425
  const { format } = await import('./helpers');
426
- const { ctx, store } = bCtx;
426
+ const { ctx, tree } = bCtx;
427
427
 
428
428
  if (!this.fileId) return;
429
429
  const resolvedId = format(this.fileId, bCtx);
430
- const fileNode = await store.get(resolvedId);
430
+ const fileNode = await tree.get(resolvedId);
431
431
  const fileId = (fileNode as any)?.fileId ?? resolvedId;
432
432
  const asType = this.asType || 'document';
433
433
 
@@ -450,15 +450,15 @@ export class EvalAction {
450
450
  value = '';
451
451
 
452
452
  async run(bCtx: BrahmanCtx) {
453
- const { ctx, store } = bCtx;
453
+ const { ctx, tree } = bCtx;
454
454
  const session = bCtx.session;
455
455
 
456
456
  if (!this.value) return;
457
457
  try {
458
- const fn = new Function('ctx', 'session', 'data', 'user', 'store',
458
+ const fn = new Function('ctx', 'session', 'data', 'user', 'tree',
459
459
  `return (async function() { ${this.value} }).call(null)`);
460
460
  const userData = getComponent(bCtx.user, BrahmanUser);
461
- await fn(ctx, session, session, userData, store);
461
+ await fn(ctx, session, session, userData, tree);
462
462
  } catch (err) {
463
463
  console.error(`[brahman:eval]`, err);
464
464
  throw err;
@@ -488,7 +488,7 @@ export class EmitTextAction {
488
488
 
489
489
  async run(bCtx: BrahmanCtx) {
490
490
  const { format, executePage } = await import('./helpers');
491
- const { store } = bCtx;
491
+ const { tree } = bCtx;
492
492
  const session = bCtx.session;
493
493
 
494
494
  if (!this.from) return;
@@ -497,7 +497,7 @@ export class EmitTextAction {
497
497
 
498
498
  if (text.startsWith('/')) {
499
499
  const cmd = text.slice(1).split(/\s/)[0];
500
- const { items: pages } = await store.getChildren(`${bCtx.botPath}/pages`);
500
+ const { items: pages } = await tree.getChildren(`${bCtx.botPath}/pages`);
501
501
  const page = pages.find(p => {
502
502
  const pc = getComponent(p, PageConfig);
503
503
  return pc?.command === `/${cmd}` || pc?.command === cmd;
@@ -579,7 +579,7 @@ export class OnErrorAction {
579
579
 
580
580
  async run(bCtx: BrahmanCtx) {
581
581
  const { executeAction } = await import('./helpers');
582
- const { store } = bCtx;
582
+ const { tree } = bCtx;
583
583
  const session = bCtx.session;
584
584
 
585
585
  const errorInfo = session.error as { message?: string } | undefined;
@@ -588,7 +588,7 @@ export class OnErrorAction {
588
588
  if (this.error && !errorInfo.message.includes(this.error)) return;
589
589
 
590
590
  if (this.action) {
591
- const actionNode = await store.get(this.action);
591
+ const actionNode = await tree.get(this.action);
592
592
  if (actionNode) await executeAction(actionNode, bCtx);
593
593
  }
594
594
  }
@@ -611,7 +611,7 @@ export class KeywordSelectAction {
611
611
 
612
612
  async run(bCtx: BrahmanCtx) {
613
613
  const { format, executePage } = await import('./helpers');
614
- const { ctx, store } = bCtx;
614
+ const { ctx, tree } = bCtx;
615
615
  const session = bCtx.session;
616
616
 
617
617
  const sourceText = this.textFrom
@@ -625,7 +625,7 @@ export class KeywordSelectAction {
625
625
  if (match && el.message) {
626
626
  if (el.message.startsWith('/')) {
627
627
  const cmd = el.message.slice(1);
628
- const { items: pages } = await store.getChildren(`${bCtx.botPath}/pages`);
628
+ const { items: pages } = await tree.getChildren(`${bCtx.botPath}/pages`);
629
629
  const page = pages.find(p => {
630
630
  const pc = getComponent(p, PageConfig);
631
631
  return pc?.command === `/${cmd}` || pc?.command === cmd;
@@ -672,7 +672,7 @@ export class CallAction {
672
672
  }
673
673
 
674
674
  const result = await executeAction(
675
- bCtx.store, path,
675
+ bCtx.tree, path,
676
676
  this.type || undefined,
677
677
  this.key || undefined,
678
678
  this.action, data,
@@ -2,8 +2,11 @@
2
2
  // Each action type gets: icon helper, summary helper, full editor, list item
3
3
 
4
4
  import type { NodeData } from '@treenity/core';
5
+ import { Button } from '@treenity/react/components/ui/button';
5
6
  import { Checkbox } from '@treenity/react/components/ui/checkbox';
6
7
  import { Input } from '@treenity/react/components/ui/input';
8
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@treenity/react/components/ui/select';
9
+ import { Textarea } from '@treenity/react/components/ui/textarea';
7
10
  import { set, usePath } from '@treenity/react/hooks';
8
11
  import { trpc } from '@treenity/react/trpc';
9
12
  import {
@@ -221,11 +224,15 @@ export function QuestionEditor({ value }: { value: NodeData }) {
221
224
  </FieldRow>
222
225
 
223
226
  <FieldRow label="Input type">
224
- <select className="h-8 text-sm border border-border rounded-md bg-background px-2"
225
- value={q.inputType} onChange={e => update({ inputType: e.target.value })}>
226
- <option value="text">Text</option>
227
- <option value="photo">Photo</option>
228
- </select>
227
+ <Select value={q.inputType} onValueChange={v => update({ inputType: v })}>
228
+ <SelectTrigger size="sm">
229
+ <SelectValue />
230
+ </SelectTrigger>
231
+ <SelectContent>
232
+ <SelectItem value="text">Text</SelectItem>
233
+ <SelectItem value="photo">Photo</SelectItem>
234
+ </SelectContent>
235
+ </Select>
229
236
  </FieldRow>
230
237
 
231
238
  <FieldRow label="Save to">
@@ -419,15 +426,19 @@ export function FileEditor({ value }: { value: NodeData }) {
419
426
  value={f.fileId} onChange={e => update({ fileId: e.target.value })} />
420
427
  </FieldRow>
421
428
  <FieldRow label="Send as">
422
- <select className="h-8 text-sm border border-border rounded-md bg-background px-2"
423
- value={f.asType} onChange={e => update({ asType: e.target.value })}>
424
- <option value="">Auto-detect</option>
425
- <option value="photo">Photo</option>
426
- <option value="document">Document</option>
427
- <option value="video">Video</option>
428
- <option value="audio">Audio</option>
429
- <option value="voice">Voice</option>
430
- </select>
429
+ <Select value={f.asType ?? ''} onValueChange={v => update({ asType: v })}>
430
+ <SelectTrigger size="sm">
431
+ <SelectValue />
432
+ </SelectTrigger>
433
+ <SelectContent>
434
+ <SelectItem value="">Auto-detect</SelectItem>
435
+ <SelectItem value="photo">Photo</SelectItem>
436
+ <SelectItem value="document">Document</SelectItem>
437
+ <SelectItem value="video">Video</SelectItem>
438
+ <SelectItem value="audio">Audio</SelectItem>
439
+ <SelectItem value="voice">Voice</SelectItem>
440
+ </SelectContent>
441
+ </Select>
431
442
  </FieldRow>
432
443
  </div>
433
444
  );
@@ -443,9 +454,9 @@ export function EvalEditor({ value }: { value: NodeData }) {
443
454
  return (
444
455
  <div className="space-y-4 max-w-2xl">
445
456
  <FieldRow label="JavaScript">
446
- <textarea
447
- className="w-full h-40 text-sm font-mono border border-border rounded-md bg-background p-2 resize-y"
448
- placeholder="// async function body&#10;await ctx.reply('Hello');"
457
+ <Textarea
458
+ className="h-40 font-mono text-sm resize-y"
459
+ placeholder={"// async function body\nawait ctx.reply('Hello');"}
449
460
  value={ev.value}
450
461
  onChange={e => update({ value: e.target.value })}
451
462
  />
@@ -571,16 +582,15 @@ export function KeywordSelectEditor({ value }: { value: NodeData }) {
571
582
  <Input className="font-mono" placeholder="/command or reply text"
572
583
  value={el.message} onChange={e => updateElement(i, { message: e.target.value })} />
573
584
  </div>
574
- <button onClick={() => removeElement(i)}
575
- className="text-muted-foreground hover:text-destructive p-1">
585
+ <Button variant="ghost" size="sm" onClick={() => removeElement(i)}
586
+ className="text-muted-foreground hover:text-destructive p-1 h-auto">
576
587
  <Trash2 className="h-4 w-4" />
577
- </button>
588
+ </Button>
578
589
  </div>
579
590
  ))}
580
- <button onClick={addElement}
581
- className="text-xs text-primary hover:underline">
591
+ <Button variant="link" size="sm" onClick={addElement} className="text-xs p-0 h-auto">
582
592
  + Add keyword entry
583
- </button>
593
+ </Button>
584
594
  </div>
585
595
  </div>
586
596
  );
@@ -1,6 +1,7 @@
1
1
  // Bot view — read-only display of bot config + pages list
2
2
 
3
3
  import type { NodeData } from '@treenity/core';
4
+ import { Badge } from '@treenity/react/components/ui/badge';
4
5
  import { Render, RenderContext } from '@treenity/react/context';
5
6
  import { useChildren } from '@treenity/react/hooks';
6
7
  import { Bot } from 'lucide-react';
@@ -31,9 +32,9 @@ export function BotView({ value }: { value: NodeData }) {
31
32
  <div className="text-lg font-semibold">{bot.name || bot.alias || 'Untitled Bot'}</div>
32
33
  <div className="flex items-center gap-2">
33
34
  {bot.alias && <span className="text-sm text-muted-foreground">{bot.alias}</span>}
34
- <span className={`text-xs px-1.5 py-0.5 rounded ${isActive ? 'bg-green-500/10 text-green-600' : 'bg-yellow-500/10 text-yellow-600'}`}>
35
+ <Badge variant={isActive ? 'default' : 'secondary'} className={isActive ? 'bg-green-500/10 text-green-600 hover:bg-green-500/10' : 'bg-yellow-500/10 text-yellow-600 hover:bg-yellow-500/10'}>
35
36
  {isActive ? 'Active' : 'Maintenance'}
36
- </span>
37
+ </Badge>
37
38
  </div>
38
39
  </div>
39
40
  </div>