@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
@@ -99,6 +99,58 @@
99
99
  cursor: pointer;
100
100
  }
101
101
 
102
+ /* ── Add child button ── */
103
+
104
+ .mm-add-btn {
105
+ opacity: 0;
106
+ cursor: pointer;
107
+ transition: opacity 0.15s;
108
+ }
109
+
110
+ .mm-branch:hover > .mm-add-btn {
111
+ opacity: 1;
112
+ }
113
+
114
+ .mm-add-bg {
115
+ fill: var(--surface-2);
116
+ stroke: var(--border);
117
+ stroke-width: 1;
118
+ transition: fill 0.15s;
119
+ }
120
+
121
+ .mm-add-btn:hover .mm-add-bg {
122
+ fill: var(--accent);
123
+ stroke: var(--accent);
124
+ }
125
+
126
+ .mm-add-icon {
127
+ font-size: 12px;
128
+ font-weight: 700;
129
+ fill: var(--text-3);
130
+ font-family: var(--font);
131
+ }
132
+
133
+ .mm-add-btn:hover .mm-add-icon {
134
+ fill: var(--bg);
135
+ }
136
+
137
+ /* ── Inline input ── */
138
+
139
+ .mm-inline-input {
140
+ width: 100%;
141
+ height: 100%;
142
+ background: var(--surface);
143
+ border: 1.5px solid var(--accent);
144
+ border-radius: 4px;
145
+ color: var(--text);
146
+ font-family: var(--font);
147
+ font-size: 13px;
148
+ font-weight: 500;
149
+ padding: 0 6px;
150
+ outline: none;
151
+ box-sizing: border-box;
152
+ }
153
+
102
154
  /* ── Toolbar ── */
103
155
 
104
156
  .mm-toolbar {
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { execute, usePath } from '@treenity/react/hooks';
5
5
  import { getActions, getComponents, getPlainFields, getSchema } from '@treenity/react/mods/editor-ui/node-utils';
6
+ import { Button } from '@treenity/react/ui/button';
6
7
 
7
8
  type Props = {
8
9
  path: string;
@@ -22,8 +23,8 @@ export function MindMapSidebar({ path, onClose, onNavigate }: Props) {
22
23
  return (
23
24
  <div className="mm-sidebar">
24
25
  <div className="mm-sidebar-header">
25
- <span className="text-[var(--text-3)]">Loading...</span>
26
- <button className="mm-btn" onClick={onClose}>&times;</button>
26
+ <span className="text-muted-foreground">Loading...</span>
27
+ <Button variant="ghost" size="sm" className="mm-btn" onClick={onClose}>&times;</Button>
27
28
  </div>
28
29
  </div>
29
30
  );
@@ -45,9 +46,9 @@ export function MindMapSidebar({ path, onClose, onNavigate }: Props) {
45
46
  >
46
47
  {path.split('/').at(-1) || '/'}
47
48
  </span>
48
- <span className="text-[11px] text-[var(--text-3)] truncate">{path}</span>
49
+ <span className="text-[11px] text-muted-foreground truncate">{path}</span>
49
50
  </div>
50
- <button className="mm-btn shrink-0" onClick={onClose}>&times;</button>
51
+ <Button variant="ghost" size="sm" className="mm-btn shrink-0" onClick={onClose}>&times;</Button>
51
52
  </div>
52
53
 
53
54
  <div className="mm-sidebar-body">
@@ -110,13 +111,15 @@ export function MindMapSidebar({ path, onClose, onNavigate }: Props) {
110
111
  <div className="mm-section-label">Actions</div>
111
112
  <div className="flex flex-wrap gap-1">
112
113
  {actions.map(a => (
113
- <button
114
+ <Button
114
115
  key={a}
116
+ variant="ghost"
117
+ size="sm"
115
118
  className="mm-action-btn"
116
119
  onClick={() => execute(path, a).catch(console.error)}
117
120
  >
118
121
  {a}
119
- </button>
122
+ </Button>
120
123
  ))}
121
124
  </div>
122
125
  </div>
package/mindmap/view.tsx CHANGED
@@ -1,14 +1,16 @@
1
1
  // MindMap View — Miro-style horizontal tree with organic curves
2
2
  // Each node fetches its own children via useChildren when expanded
3
3
 
4
+ import type { NodeData } from '@treenity/core';
4
5
  import { register } from '@treenity/core';
5
6
  import type { View } from '@treenity/react/context';
6
7
  import { useChildren } from '@treenity/react/hooks';
8
+ import { trpc } from '@treenity/react/trpc';
7
9
  import { select } from 'd3-selection';
8
10
  import 'd3-transition';
9
11
  import { zoom as d3zoom, type ZoomBehavior, zoomIdentity } from 'd3-zoom';
10
12
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
11
- import { MindMapBranch, MindMapCtx, type MindMapState } from './branch';
13
+ import { type EditingAt, MindMapBranch, MindMapCtx, type MindMapState } from './branch';
12
14
  import { MindMapSidebar } from './sidebar';
13
15
  import type { MindMapConfig } from './types';
14
16
  import './mindmap.css';
@@ -38,6 +40,7 @@ const MindMapView: View<MindMapConfig> = ({ value, ctx }) => {
38
40
 
39
41
  const [expanded, setExpanded] = useState<Set<string>>(() => new Set([rootPath]));
40
42
  const [selectedPath, setSelectedPath] = useState<string | null>(null);
43
+ const [editingAt, setEditingAt] = useState<EditingAt>(null);
41
44
  const containerRef = useRef<HTMLDivElement>(null);
42
45
  const svgRef = useRef<SVGSVGElement>(null);
43
46
  const gRef = useRef<SVGGElement>(null);
@@ -117,19 +120,52 @@ const MindMapView: View<MindMapConfig> = ({ value, ctx }) => {
117
120
  window.dispatchEvent(new PopStateEvent('popstate'));
118
121
  }, []);
119
122
 
123
+ const handleAddChild = useCallback((parentPath: string, side: 'left' | 'right', color: string) => {
124
+ setEditingAt({ parentPath, side, color });
125
+ setExpanded(prev => new Set([...prev, parentPath]));
126
+ }, []);
127
+
128
+ const handleCommitAdd = useCallback((parentPath: string, name: string) => {
129
+ const childPath = `${parentPath}/${name}`;
130
+ trpc.set.mutate({ node: { $path: childPath, $type: 'dir' } as NodeData });
131
+ setEditingAt(null);
132
+ }, []);
133
+
134
+ const handleCancelAdd = useCallback(() => setEditingAt(null), []);
135
+
136
+ const handleDelete = useCallback((path: string) => {
137
+ if (path === rootPath) return;
138
+ if (!confirm(`Delete ${basename(path)}?`)) return;
139
+ trpc.remove.mutate({ path });
140
+ setSelectedPath(null);
141
+ }, [rootPath]);
142
+
120
143
  useEffect(() => {
121
144
  const handler = (e: KeyboardEvent) => {
122
145
  if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
123
146
  if (e.key === 'Enter' && selectedPath) { e.preventDefault(); handleToggle(selectedPath); }
124
147
  if (e.key === 'Escape') setSelectedPath(null);
148
+ if ((e.key === 'Delete' || e.key === 'Backspace') && selectedPath && selectedPath !== rootPath) {
149
+ e.preventDefault();
150
+ handleDelete(selectedPath);
151
+ }
152
+ if (e.key === 'Tab' && selectedPath && !editingAt) {
153
+ e.preventDefault();
154
+ handleAddChild(selectedPath, 'right', PALETTE[0]);
155
+ }
125
156
  };
126
157
  window.addEventListener('keydown', handler);
127
158
  return () => window.removeEventListener('keydown', handler);
128
- }, [selectedPath, handleToggle]);
159
+ }, [selectedPath, handleToggle, handleDelete, handleAddChild, rootPath, editingAt]);
129
160
 
130
161
  const mmState: MindMapState = useMemo(
131
- () => ({ expanded, selectedPath, onToggle: handleToggle, onSelect: handleSelect }),
132
- [expanded, selectedPath, handleToggle, handleSelect],
162
+ () => ({
163
+ expanded, selectedPath, editingAt,
164
+ onToggle: handleToggle, onSelect: handleSelect,
165
+ onAddChild: handleAddChild, onCommitAdd: handleCommitAdd, onCancelAdd: handleCancelAdd,
166
+ onDelete: handleDelete,
167
+ }),
168
+ [expanded, selectedPath, editingAt, handleToggle, handleSelect, handleAddChild, handleCommitAdd, handleCancelAdd, handleDelete],
133
169
  );
134
170
 
135
171
  const svgW = selectedPath ? dims.w - 280 : dims.w;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@treenity/mods",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Official Treenity modules — board, sim, cafe, doc, mindmap, and more.",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
- "license": "AGPL-3.0",
9
+ "license": "FSL-1.1-MIT",
10
10
  "author": "Treenity <hello@treenity.ai> (https://treenity.ai)",
11
11
  "homepage": "https://github.com/treenity-ai/treenity",
12
12
  "repository": {
@@ -53,19 +53,43 @@
53
53
  "three",
54
54
  "todo",
55
55
  "whisper",
56
+ "!**/*.test.ts",
57
+ "!**/*.test.tsx",
56
58
  "LICENSE"
57
59
  ],
58
60
  "scripts": {
59
61
  "build": "tsc -p tsconfig.build.json",
60
62
  "prepack": "npm run build"
61
63
  },
64
+ "imports": {
65
+ "#*": {
66
+ "development": [
67
+ "./*.ts",
68
+ "./*.tsx"
69
+ ],
70
+ "default": [
71
+ "./dist/*.js"
72
+ ]
73
+ }
74
+ },
75
+ "dependencies": {
76
+ "@grammyjs/runner": "^2.0.3"
77
+ },
78
+ "optionalDependencies": {
79
+ "@huggingface/transformers": "^3.8.1",
80
+ "wavefile": "^11.0.0"
81
+ },
62
82
  "peerDependencies": {
63
83
  "@treenity/core": "^3.0.0",
64
- "@treenity/react": "^3.0.0"
84
+ "@treenity/react": "^3.0.0",
85
+ "grammy": "^1.39.0"
65
86
  },
66
87
  "peerDependenciesMeta": {
67
88
  "@treenity/react": {
68
89
  "optional": true
90
+ },
91
+ "grammy": {
92
+ "optional": true
69
93
  }
70
94
  },
71
95
  "devDependencies": {
@@ -2,26 +2,27 @@
2
2
 
3
3
  import { createNode, register } from '@treenity/core';
4
4
  import '@treenity/core/contexts/service';
5
+ import { safeInterval } from '@treenity/core/util/safe-timers';
5
6
  import { SensorReading } from './types';
6
7
 
7
8
  register('examples.demo.sensor', 'service', async (node, ctx) => {
8
9
  let seq = 0;
9
10
  const MAX = 10;
10
- const timer = setInterval(async () => {
11
+ const timer = safeInterval(async () => {
11
12
  const ts = Date.now();
12
13
  const name = String(ts).slice(-6).padStart(6, '0');
13
- await ctx.store.set(createNode(`${node.$path}/${name}`, SensorReading, {
14
+ await ctx.tree.set(createNode(`${node.$path}/${name}`, SensorReading, {
14
15
  ts,
15
16
  value: +(20 + Math.sin(seq * 0.1) * 5 + Math.random() * 2).toFixed(1),
16
17
  seq: seq++,
17
18
  }));
18
19
  // Trim old readings
19
- const { items } = await ctx.store.getChildren(node.$path);
20
+ const { items } = await ctx.tree.getChildren(node.$path);
20
21
  if (items.length > MAX) {
21
22
  items.sort((a, b) => (a.ts as number) - (b.ts as number));
22
- for (const old of items.slice(0, items.length - MAX)) await ctx.store.remove(old.$path);
23
+ for (const old of items.slice(0, items.length - MAX)) await ctx.tree.remove(old.$path);
23
24
  }
24
- }, 1000);
25
+ }, 1000, 'sensor-demo.tick');
25
26
  console.log(`[sensor-demo] started on ${node.$path}`);
26
27
  return { stop: async () => clearInterval(timer) };
27
28
  });
@@ -20,7 +20,7 @@ register(
20
20
  value: +(20 + Math.sin(i * 0.3) * 5 + Math.random() * 2).toFixed(1),
21
21
  seq: i,
22
22
  } as NodeData;
23
- await ctx.store.set(node);
23
+ await ctx.tree.set(node);
24
24
  yield node;
25
25
  await new Promise((r) => setTimeout(r, delay));
26
26
  }
package/sim/service.ts CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  resolve,
15
15
  } from '@treenity/core';
16
16
  import '@treenity/core/contexts/service';
17
- import { newComp, setComp } from '@treenity/core/comp';
17
+ import { newComponent, setComponent } from '@treenity/core/comp';
18
18
  import { type ActionCtx, serverNodeHandle } from '@treenity/core/server/actions';
19
19
  import {
20
20
  type AgentEvent,
@@ -108,7 +108,7 @@ function getAgentEvents(n: NodeData): AgentEvent[] {
108
108
  function pushAgentEvent(n: NodeData, event: AgentEvent) {
109
109
  const ev = getComponent(n, SimEvents);
110
110
  const entries = [...(ev?.entries ?? []), event].slice(-30);
111
- setComp(n, SimEvents, { entries });
111
+ setComponent(n, SimEvents, { entries });
112
112
  }
113
113
 
114
114
  // ── LLM tools — base + interact ──
@@ -281,13 +281,13 @@ function mockThink(agent: NodeData, all: NodeData[]): Tool[] {
281
281
  /** @description Start the agent simulation loop */
282
282
  register('sim.world', 'action:start', async (ctx: ActionCtx) => {
283
283
  const cfg = getComponent(ctx.node, SimConfig)!;
284
- setComp(ctx.node, SimConfig, { ...cfg, running: true });
284
+ setComponent(ctx.node, SimConfig, { ...cfg, running: true });
285
285
  }, { description: 'Start the simulation' });
286
286
 
287
287
  /** @description Stop the agent simulation loop */
288
288
  register('sim.world', 'action:stop', async (ctx: ActionCtx) => {
289
289
  const cfg = getComponent(ctx.node, SimConfig)!;
290
- setComp(ctx.node, SimConfig, { ...cfg, running: false });
290
+ setComponent(ctx.node, SimConfig, { ...cfg, running: false });
291
291
  }, { description: 'Stop the simulation' });
292
292
 
293
293
  /** @description Update simulation settings (roundDelay, model, dimensions) */
@@ -298,7 +298,7 @@ register('sim.world', 'action:set-config', async (ctx: ActionCtx, params: any) =
298
298
  if (params.model !== undefined) next.model = String(params.model);
299
299
  if (params.width !== undefined) next.width = Number(params.width);
300
300
  if (params.height !== undefined) next.height = Number(params.height);
301
- setComp(ctx.node, SimConfig, next);
301
+ setComponent(ctx.node, SimConfig, next);
302
302
  }, { description: 'Update simulation settings', params: 'roundDelay?, model?, width?, height?' });
303
303
 
304
304
  /** @description Add an agent or item entity to the simulation world */
@@ -307,23 +307,23 @@ register('sim.world', 'action:add-entity', async (ctx: ActionCtx, params: any) =
307
307
  const type = params.type || 'sim.item';
308
308
  const path = `${ctx.node.$path}/${id}`;
309
309
  const components: Record<string, any> = {
310
- descriptive: newComp(SimDescriptive, {
310
+ descriptive: newComponent(SimDescriptive, {
311
311
  name: params.name || id,
312
312
  icon: params.icon || '?',
313
313
  description: params.description || '',
314
314
  }),
315
- position: newComp(SimPosition, {
315
+ position: newComponent(SimPosition, {
316
316
  x: params.x ?? 300,
317
317
  y: params.y ?? 200,
318
318
  radius: params.radius ?? 100,
319
319
  }),
320
320
  };
321
321
  if (type === 'sim.agent') {
322
- components.ai = newComp(SimAi, { systemPrompt: params.systemPrompt || `You are ${params.name}.` });
323
- components.memory = newComp(SimMemory, { entries: [] });
324
- components.events = newComp(SimEvents, { entries: [] });
322
+ components.ai = newComponent(SimAi, { systemPrompt: params.systemPrompt || `You are ${params.name}.` });
323
+ components.memory = newComponent(SimMemory, { entries: [] });
324
+ components.events = newComponent(SimEvents, { entries: [] });
325
325
  }
326
- await ctx.store.set(createNode(path, type, {}, components));
326
+ await ctx.tree.set(createNode(path, type, {}, components));
327
327
  }, { description: 'Add agent or item to the world', params: 'name, icon, type?, x?, y?, radius?, systemPrompt?, description?' });
328
328
 
329
329
  // Agent actions (callable by other agents via interact, or by UI)
@@ -333,18 +333,18 @@ register('sim.agent', 'action:move', async (ctx: ActionCtx, params: any) => {
333
333
  const next = { ...pos };
334
334
  if (params.x !== undefined) next.x = Math.round(Number(params.x));
335
335
  if (params.y !== undefined) next.y = Math.round(Number(params.y));
336
- setComp(ctx.node, SimPosition, next);
336
+ setComponent(ctx.node, SimPosition, next);
337
337
  }, { description: 'Move to position', params: 'x, y' });
338
338
 
339
339
  /** @description Update agent properties (systemPrompt, radius, name, icon, description) */
340
340
  register('sim.agent', 'action:update', async (ctx: ActionCtx, params: any) => {
341
341
  if (params.systemPrompt !== undefined) {
342
342
  const ai = getComponent(ctx.node, SimAi)!;
343
- setComp(ctx.node, SimAi, { ...ai, systemPrompt: String(params.systemPrompt) });
343
+ setComponent(ctx.node, SimAi, { ...ai, systemPrompt: String(params.systemPrompt) });
344
344
  }
345
345
  if (params.radius !== undefined) {
346
346
  const pos = getComponent(ctx.node, SimPosition)!;
347
- setComp(ctx.node, SimPosition, { ...pos, radius: Number(params.radius) });
347
+ setComponent(ctx.node, SimPosition, { ...pos, radius: Number(params.radius) });
348
348
  }
349
349
  if (params.name !== undefined || params.icon !== undefined || params.description !== undefined) {
350
350
  const desc = getComponent(ctx.node, SimDescriptive)!;
@@ -352,7 +352,7 @@ register('sim.agent', 'action:update', async (ctx: ActionCtx, params: any) => {
352
352
  if (params.name !== undefined) next.name = String(params.name);
353
353
  if (params.icon !== undefined) next.icon = String(params.icon);
354
354
  if (params.description !== undefined) next.description = String(params.description);
355
- setComp(ctx.node, SimDescriptive, next);
355
+ setComponent(ctx.node, SimDescriptive, next);
356
356
  }
357
357
  }, { description: 'Update agent properties', params: 'systemPrompt?, radius?, name?, icon?, description?' });
358
358
 
@@ -376,17 +376,17 @@ register('sim.world', 'service', async (node, ctx) => {
376
376
  const wp = node.$path;
377
377
 
378
378
  async function getAllEntities() {
379
- return (await ctx.store.getChildren(wp)).items.filter(
379
+ return (await ctx.tree.getChildren(wp)).items.filter(
380
380
  (n) => n.$type === 'sim.agent' || n.$type === 'sim.item',
381
381
  );
382
382
  }
383
383
 
384
384
  async function getAgents() {
385
- return (await ctx.store.getChildren(wp)).items.filter((n) => n.$type === 'sim.agent');
385
+ return (await ctx.tree.getChildren(wp)).items.filter((n) => n.$type === 'sim.agent');
386
386
  }
387
387
 
388
388
  async function runRound() {
389
- const world = await ctx.store.get(wp);
389
+ const world = await ctx.tree.get(wp);
390
390
  if (!world) return;
391
391
  const cfg = getComponent(world, SimConfig);
392
392
  if (!cfg?.running) return;
@@ -398,8 +398,8 @@ register('sim.world', 'service', async (node, ctx) => {
398
398
  if (!agents.length) return;
399
399
 
400
400
  // Phase: thinking
401
- setComp(world, SimRound, { ...round, phase: 'thinking' });
402
- await ctx.store.set(world);
401
+ setComponent(world, SimRound, { ...round, phase: 'thinking' });
402
+ await ctx.tree.set(world);
403
403
 
404
404
  const log = round.log ?? [];
405
405
  const model = cfg.model ?? 'claude-haiku-4-5-20251001';
@@ -434,7 +434,7 @@ register('sim.world', 'service', async (node, ctx) => {
434
434
 
435
435
  // Push event to directed target or all hearers
436
436
  for (const hearer of nearAgents) {
437
- const fresh = await ctx.store.get(hearer.$path);
437
+ const fresh = await ctx.tree.get(hearer.$path);
438
438
  if (!fresh) continue;
439
439
  const hName = eName(fresh);
440
440
  const eventType = to && to === hName ? 'speak' : 'hear';
@@ -446,7 +446,7 @@ register('sim.world', 'service', async (node, ctx) => {
446
446
  data: { message: t.message as string },
447
447
  ts,
448
448
  });
449
- await ctx.store.set(fresh);
449
+ await ctx.tree.set(fresh);
450
450
  }
451
451
 
452
452
  newEntries.push({
@@ -461,15 +461,15 @@ register('sim.world', 'service', async (node, ctx) => {
461
461
  break;
462
462
  }
463
463
  case 'move': {
464
- const fresh = await ctx.store.get(agent.$path);
464
+ const fresh = await ctx.tree.get(agent.$path);
465
465
  if (!fresh) break;
466
466
  const pos = getComponent(fresh, SimPosition)!;
467
- setComp(fresh, SimPosition, {
467
+ setComponent(fresh, SimPosition, {
468
468
  ...pos,
469
469
  x: Math.max(0, Math.min(cfg.width, t.x as number)),
470
470
  y: Math.max(0, Math.min(cfg.height, t.y as number)),
471
471
  });
472
- await ctx.store.set(fresh);
472
+ await ctx.tree.set(fresh);
473
473
  newEntries.push({
474
474
  round: num,
475
475
  agent: agentName,
@@ -481,13 +481,13 @@ register('sim.world', 'service', async (node, ctx) => {
481
481
  break;
482
482
  }
483
483
  case 'remember': {
484
- const fresh = await ctx.store.get(agent.$path);
484
+ const fresh = await ctx.tree.get(agent.$path);
485
485
  if (!fresh) break;
486
486
  const mem = getComponent(fresh, SimMemory);
487
- setComp(fresh, SimMemory, {
487
+ setComponent(fresh, SimMemory, {
488
488
  entries: [...(mem?.entries ?? []), t.text as string].slice(-20),
489
489
  });
490
- await ctx.store.set(fresh);
490
+ await ctx.tree.set(fresh);
491
491
  break;
492
492
  }
493
493
  case 'interact': {
@@ -497,7 +497,7 @@ register('sim.world', 'service', async (node, ctx) => {
497
497
  const targetPath = nameToPath.get(targetName);
498
498
  if (!targetPath) break;
499
499
 
500
- const targetNode = await ctx.store.get(targetPath);
500
+ const targetNode = await ctx.tree.get(targetPath);
501
501
  if (!targetNode) break;
502
502
 
503
503
  // Check proximity
@@ -510,17 +510,17 @@ register('sim.world', 'service', async (node, ctx) => {
510
510
 
511
511
  const actx: ActionCtx = {
512
512
  node: targetNode,
513
- store: ctx.store,
513
+ tree: ctx.tree,
514
514
  signal: AbortSignal.timeout(5000),
515
- nc: serverNodeHandle(ctx.store),
515
+ nc: serverNodeHandle(ctx.tree),
516
516
  };
517
517
  const result = await (handler as any)(actx, data);
518
- // Persist handler mutations (handlers don't call store.set — executeAction/caller does)
519
- await ctx.store.set(targetNode);
518
+ // Persist handler mutations (handlers don't call tree.set — executeAction/caller does)
519
+ await ctx.tree.set(targetNode);
520
520
 
521
521
  // Push event to target agent's inbox
522
522
  if (targetNode.$type === 'sim.agent') {
523
- const freshTarget = await ctx.store.get(targetPath);
523
+ const freshTarget = await ctx.tree.get(targetPath);
524
524
  if (freshTarget) {
525
525
  pushAgentEvent(freshTarget, {
526
526
  round: num,
@@ -529,7 +529,7 @@ register('sim.world', 'service', async (node, ctx) => {
529
529
  data: { ...data, result },
530
530
  ts,
531
531
  });
532
- await ctx.store.set(freshTarget);
532
+ await ctx.tree.set(freshTarget);
533
533
  }
534
534
  }
535
535
 
@@ -551,19 +551,19 @@ register('sim.world', 'service', async (node, ctx) => {
551
551
  const freshEntities = await getAllEntities();
552
552
  for (const a of freshEntities) {
553
553
  const near = getNearby(a, freshEntities);
554
- setComp(a, SimNearby, { agents: near.map(eName) });
555
- await ctx.store.set(a);
554
+ setComponent(a, SimNearby, { agents: near.map(eName) });
555
+ await ctx.tree.set(a);
556
556
  }
557
557
 
558
558
  // Advance round
559
- const worldFresh = await ctx.store.get(wp);
559
+ const worldFresh = await ctx.tree.get(wp);
560
560
  if (!worldFresh) return;
561
- setComp(worldFresh, SimRound, {
561
+ setComponent(worldFresh, SimRound, {
562
562
  current: num + 1,
563
563
  phase: 'idle',
564
564
  log: [...log, ...newEntries].slice(-50),
565
565
  });
566
- await ctx.store.set(worldFresh);
566
+ await ctx.tree.set(worldFresh);
567
567
  console.log(`[sim] round ${num} done: ${newEntries.length} events`);
568
568
  }
569
569
 
@@ -572,7 +572,7 @@ register('sim.world', 'service', async (node, ctx) => {
572
572
  console.log(`[sim] started on ${wp}`);
573
573
  while (!stopped) {
574
574
  try {
575
- const w = await ctx.store.get(wp);
575
+ const w = await ctx.tree.get(wp);
576
576
  const cfg = w ? getComponent(w, SimConfig) : null;
577
577
  if (cfg?.running) await runRound();
578
578
  await new Promise((r) => setTimeout(r, cfg?.roundDelay ?? 5000));
package/table/view.tsx CHANGED
@@ -6,8 +6,13 @@ import { useSchema } from '@treenity/react/schema-loader';
6
6
  import { Button } from '@treenity/react/ui/button';
7
7
  import { Input } from '@treenity/react/ui/input';
8
8
  import {
9
- Pagination, PaginationContent, PaginationEllipsis,
10
- PaginationItem, PaginationLink, PaginationNext, PaginationPrevious,
9
+ Pagination,
10
+ PaginationContent,
11
+ PaginationEllipsis,
12
+ PaginationItem,
13
+ PaginationLink,
14
+ PaginationNext,
15
+ PaginationPrevious,
11
16
  } from '@treenity/react/ui/pagination';
12
17
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@treenity/react/ui/table';
13
18
  import { useMemo } from 'react';
package/todo/types.ts CHANGED
@@ -16,9 +16,9 @@ class TodoList {
16
16
  /** @description Add a new todo item */
17
17
  async add(data: { title: string }) {
18
18
  if (!data.title?.trim()) throw new Error('Title required');
19
- const { node, store } = getCtx();
19
+ const { node, tree } = getCtx();
20
20
  const id = Date.now().toString(36);
21
- await store.set({
21
+ await tree.set({
22
22
  $path: `${node.$path}/${id}`,
23
23
  $type: 'todo.item',
24
24
  title: data.title.trim(),
package/todo/view.tsx CHANGED
@@ -1,5 +1,7 @@
1
1
  import { type NodeData, register } from '@treenity/core';
2
2
  import { useChildren, usePath } from '@treenity/react/hooks';
3
+ import { Button } from '@treenity/react/ui/button';
4
+ import { Input } from '@treenity/react/ui/input';
3
5
  import { useState } from 'react';
4
6
  import { TodoItem, TodoList } from './types';
5
7
 
@@ -19,17 +21,14 @@ function TodoListView({ value }: { value: NodeData }) {
19
21
  <h2 className="text-xl font-bold mb-4">{list.title}</h2>
20
22
 
21
23
  <div className="flex gap-2 mb-4">
22
- <input
23
- className="flex-1 border rounded px-3 py-1.5 text-sm"
24
+ <Input
25
+ className="flex-1"
24
26
  placeholder="What needs to be done?"
25
27
  value={draft}
26
28
  onChange={e => setDraft(e.target.value)}
27
29
  onKeyDown={e => e.key === 'Enter' && handleAdd()}
28
30
  />
29
- <button
30
- className="bg-blue-600 text-white px-3 py-1.5 rounded text-sm"
31
- onClick={handleAdd}
32
- >Add</button>
31
+ <Button size="sm" onClick={handleAdd}>Add</Button>
33
32
  </div>
34
33
 
35
34
  <ul className="space-y-1">
@@ -47,16 +46,16 @@ function TodoItemRow({ value }: { value: NodeData }) {
47
46
  return (
48
47
  <li
49
48
  className="flex items-center gap-2 px-3 py-2 rounded
50
- hover:bg-neutral-100 cursor-pointer"
49
+ hover:bg-muted cursor-pointer"
51
50
  onClick={() => item.toggle()}
52
51
  >
53
52
  <span className={`w-4 h-4 rounded border flex items-center
54
53
  justify-center text-xs ${item.done
55
- ? 'bg-blue-600 border-blue-600 text-white'
56
- : 'border-neutral-300'}`}>
54
+ ? 'bg-primary border-primary text-primary-foreground'
55
+ : 'border-input'}`}>
57
56
  {item.done ? '✓' : ''}
58
57
  </span>
59
- <span className={item.done ? 'line-through text-neutral-400' : ''}>
58
+ <span className={item.done ? 'line-through text-muted-foreground' : ''}>
60
59
  {item.title}
61
60
  </span>
62
61
  </li>
package/whisper/inbox.ts CHANGED
@@ -19,7 +19,7 @@ register('whisper.inbox', 'service', async (node, ctx) => {
19
19
  const sent = new Set<string>();
20
20
 
21
21
  // Mark existing COMPLETED transcriptions as already processed
22
- const { items } = await ctx.store.getChildren(config.source);
22
+ const { items } = await ctx.tree.getChildren(config.source);
23
23
  for (const child of items) {
24
24
  if (child.$type !== 'whisper.transcription') continue;
25
25
  if (getText(child)) sent.add(child.$path);
@@ -29,7 +29,7 @@ register('whisper.inbox', 'service', async (node, ctx) => {
29
29
  const unsub = ctx.subscribe(config.source, (event) => {
30
30
  if (event.type !== 'set' && event.type !== 'patch') return;
31
31
 
32
- ctx.store.get(event.path).then(async (n) => {
32
+ ctx.tree.get(event.path).then(async (n) => {
33
33
  if (!n || n.$type !== 'whisper.transcription') return;
34
34
  if (sent.has(n.$path)) return;
35
35
 
@@ -40,7 +40,7 @@ register('whisper.inbox', 'service', async (node, ctx) => {
40
40
 
41
41
  const taskId = `t-${Date.now()}`;
42
42
  const taskPath = `${config.target}/tasks/${taskId}`;
43
- await ctx.store.set(createNode(taskPath, 'agent.task', {
43
+ await ctx.tree.set(createNode(taskPath, 'agent.task', {
44
44
  prompt: text,
45
45
  status: 'pending',
46
46
  createdAt: Date.now(),