@treenity/react 3.0.0 → 3.0.1

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 (369) hide show
  1. package/README.md +91 -0
  2. package/dist/AclEditor.d.ts +1 -1
  3. package/dist/AclEditor.d.ts.map +1 -1
  4. package/dist/AclEditor.js +5 -5
  5. package/dist/AclEditor.js.map +1 -1
  6. package/dist/ActionCards.d.ts +9 -0
  7. package/dist/ActionCards.d.ts.map +1 -0
  8. package/dist/ActionCards.js +96 -0
  9. package/dist/ActionCards.js.map +1 -0
  10. package/dist/App.d.ts.map +1 -1
  11. package/dist/App.js +71 -185
  12. package/dist/App.js.map +1 -1
  13. package/dist/ComponentSection.d.ts +15 -0
  14. package/dist/ComponentSection.d.ts.map +1 -0
  15. package/dist/ComponentSection.js +25 -0
  16. package/dist/ComponentSection.js.map +1 -0
  17. package/dist/ErrorBoundary.d.ts +18 -0
  18. package/dist/ErrorBoundary.d.ts.map +1 -0
  19. package/dist/ErrorBoundary.js +17 -0
  20. package/dist/ErrorBoundary.js.map +1 -0
  21. package/dist/Inspector.d.ts +1 -0
  22. package/dist/Inspector.d.ts.map +1 -1
  23. package/dist/Inspector.js +22 -347
  24. package/dist/Inspector.js.map +1 -1
  25. package/dist/Login.d.ts +8 -0
  26. package/dist/Login.d.ts.map +1 -0
  27. package/dist/Login.js +45 -0
  28. package/dist/Login.js.map +1 -0
  29. package/dist/NodeEditor.d.ts +11 -0
  30. package/dist/NodeEditor.d.ts.map +1 -0
  31. package/dist/NodeEditor.js +157 -0
  32. package/dist/NodeEditor.js.map +1 -0
  33. package/dist/Tree.d.ts +1 -0
  34. package/dist/Tree.d.ts.map +1 -1
  35. package/dist/Tree.js +8 -27
  36. package/dist/Tree.js.map +1 -1
  37. package/dist/bind/engine.js +1 -1
  38. package/dist/bind/engine.js.map +1 -1
  39. package/dist/bind/eval.d.ts +1 -1
  40. package/dist/bind/eval.d.ts.map +1 -1
  41. package/dist/bind/hook.d.ts +1 -1
  42. package/dist/bind/hook.d.ts.map +1 -1
  43. package/dist/bind/hook.js +1 -1
  44. package/dist/bind/hook.js.map +1 -1
  45. package/dist/cache.d.ts +1 -1
  46. package/dist/cache.d.ts.map +1 -1
  47. package/dist/cache.js +4 -0
  48. package/dist/cache.js.map +1 -1
  49. package/dist/client-tree.d.ts +1 -2
  50. package/dist/client-tree.d.ts.map +1 -1
  51. package/dist/client-tree.js +12 -5
  52. package/dist/client-tree.js.map +1 -1
  53. package/dist/client.d.ts +1 -1
  54. package/dist/client.d.ts.map +1 -1
  55. package/dist/client.js +2 -4
  56. package/dist/client.js.map +1 -1
  57. package/dist/components/ConfirmDialog.d.ts +9 -0
  58. package/dist/components/ConfirmDialog.d.ts.map +1 -0
  59. package/dist/components/ConfirmDialog.js +6 -0
  60. package/dist/components/ConfirmDialog.js.map +1 -0
  61. package/dist/components/ConfirmPopover.d.ts +8 -0
  62. package/dist/components/ConfirmPopover.d.ts.map +1 -0
  63. package/dist/components/ConfirmPopover.js +9 -0
  64. package/dist/components/ConfirmPopover.js.map +1 -0
  65. package/dist/components/PathBreadcrumb.d.ts +5 -0
  66. package/dist/components/PathBreadcrumb.d.ts.map +1 -0
  67. package/dist/components/PathBreadcrumb.js +16 -0
  68. package/dist/components/PathBreadcrumb.js.map +1 -0
  69. package/dist/components/lib/utils.d.ts +3 -0
  70. package/dist/components/lib/utils.d.ts.map +1 -0
  71. package/dist/components/lib/utils.js +6 -0
  72. package/dist/components/lib/utils.js.map +1 -0
  73. package/dist/components/ui/accordion.js +1 -1
  74. package/dist/components/ui/accordion.js.map +1 -1
  75. package/dist/components/ui/alert-dialog.d.ts +19 -0
  76. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  77. package/dist/components/ui/alert-dialog.js +42 -0
  78. package/dist/components/ui/alert-dialog.js.map +1 -0
  79. package/dist/components/ui/badge.js +1 -1
  80. package/dist/components/ui/badge.js.map +1 -1
  81. package/dist/components/ui/breadcrumb.d.ts +12 -0
  82. package/dist/components/ui/breadcrumb.d.ts.map +1 -0
  83. package/dist/components/ui/breadcrumb.js +28 -0
  84. package/dist/components/ui/breadcrumb.js.map +1 -0
  85. package/dist/components/ui/button.d.ts +9 -8
  86. package/dist/components/ui/button.d.ts.map +1 -1
  87. package/dist/components/ui/button.js +26 -21
  88. package/dist/components/ui/button.js.map +1 -1
  89. package/dist/components/ui/card.d.ts +10 -0
  90. package/dist/components/ui/card.d.ts.map +1 -0
  91. package/dist/components/ui/card.js +25 -0
  92. package/dist/components/ui/card.js.map +1 -0
  93. package/dist/components/ui/checkbox.js +1 -1
  94. package/dist/components/ui/checkbox.js.map +1 -1
  95. package/dist/components/ui/collapsible.d.ts +6 -0
  96. package/dist/components/ui/collapsible.d.ts.map +1 -0
  97. package/dist/components/ui/collapsible.js +13 -0
  98. package/dist/components/ui/collapsible.js.map +1 -0
  99. package/dist/components/ui/command.d.ts +19 -0
  100. package/dist/components/ui/command.d.ts.map +1 -0
  101. package/dist/components/ui/command.js +35 -0
  102. package/dist/components/ui/command.js.map +1 -0
  103. package/dist/components/ui/dialog.d.ts.map +1 -1
  104. package/dist/components/ui/dialog.js +1 -1
  105. package/dist/components/ui/dialog.js.map +1 -1
  106. package/dist/components/ui/drawer.js +1 -1
  107. package/dist/components/ui/drawer.js.map +1 -1
  108. package/dist/components/ui/dropdown-menu.d.ts +26 -0
  109. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  110. package/dist/components/ui/dropdown-menu.js +52 -0
  111. package/dist/components/ui/dropdown-menu.js.map +1 -0
  112. package/dist/components/ui/form-field.d.ts +7 -0
  113. package/dist/components/ui/form-field.d.ts.map +1 -0
  114. package/dist/components/ui/form-field.js +17 -0
  115. package/dist/components/ui/form-field.js.map +1 -0
  116. package/dist/components/ui/input.js +1 -1
  117. package/dist/components/ui/input.js.map +1 -1
  118. package/dist/components/ui/label.js +1 -1
  119. package/dist/components/ui/label.js.map +1 -1
  120. package/dist/components/ui/pagination.d.ts +14 -0
  121. package/dist/components/ui/pagination.d.ts.map +1 -0
  122. package/dist/components/ui/pagination.js +30 -0
  123. package/dist/components/ui/pagination.js.map +1 -0
  124. package/dist/components/ui/popover.js +2 -2
  125. package/dist/components/ui/popover.js.map +1 -1
  126. package/dist/components/ui/progress.js +1 -1
  127. package/dist/components/ui/progress.js.map +1 -1
  128. package/dist/components/ui/resizable.d.ts +8 -0
  129. package/dist/components/ui/resizable.d.ts.map +1 -0
  130. package/dist/components/ui/resizable.js +14 -0
  131. package/dist/components/ui/resizable.js.map +1 -0
  132. package/dist/components/ui/scroll-area.d.ts +6 -0
  133. package/dist/components/ui/scroll-area.d.ts.map +1 -0
  134. package/dist/components/ui/scroll-area.js +13 -0
  135. package/dist/components/ui/scroll-area.js.map +1 -0
  136. package/dist/components/ui/select.js +1 -1
  137. package/dist/components/ui/select.js.map +1 -1
  138. package/dist/components/ui/separator.d.ts +5 -0
  139. package/dist/components/ui/separator.d.ts.map +1 -0
  140. package/dist/components/ui/separator.js +9 -0
  141. package/dist/components/ui/separator.js.map +1 -0
  142. package/dist/components/ui/sheet.d.ts +15 -0
  143. package/dist/components/ui/sheet.d.ts.map +1 -0
  144. package/dist/components/ui/sheet.js +40 -0
  145. package/dist/components/ui/sheet.js.map +1 -0
  146. package/dist/components/ui/skeleton.d.ts +3 -0
  147. package/dist/components/ui/skeleton.d.ts.map +1 -0
  148. package/dist/components/ui/skeleton.js +7 -0
  149. package/dist/components/ui/skeleton.js.map +1 -0
  150. package/dist/components/ui/slider.js +1 -1
  151. package/dist/components/ui/slider.js.map +1 -1
  152. package/dist/components/ui/switch.js +1 -1
  153. package/dist/components/ui/switch.js.map +1 -1
  154. package/dist/components/ui/table.d.ts +11 -0
  155. package/dist/components/ui/table.d.ts.map +1 -0
  156. package/dist/components/ui/table.js +29 -0
  157. package/dist/components/ui/table.js.map +1 -0
  158. package/dist/components/ui/tabs.d.ts +12 -0
  159. package/dist/components/ui/tabs.d.ts.map +1 -0
  160. package/dist/components/ui/tabs.js +29 -0
  161. package/dist/components/ui/tabs.js.map +1 -0
  162. package/dist/components/ui/textarea.js +1 -1
  163. package/dist/components/ui/textarea.js.map +1 -1
  164. package/dist/components/ui/toggle-group.d.ts +10 -0
  165. package/dist/components/ui/toggle-group.d.ts.map +1 -0
  166. package/dist/components/ui/toggle-group.js +23 -0
  167. package/dist/components/ui/toggle-group.js.map +1 -0
  168. package/dist/components/ui/toggle.d.ts +10 -0
  169. package/dist/components/ui/toggle.d.ts.map +1 -0
  170. package/dist/components/ui/toggle.js +27 -0
  171. package/dist/components/ui/toggle.js.map +1 -0
  172. package/dist/components/ui/tooltip.js +1 -1
  173. package/dist/components/ui/tooltip.js.map +1 -1
  174. package/dist/context/index.d.ts +27 -10
  175. package/dist/context/index.d.ts.map +1 -1
  176. package/dist/context/index.js +43 -36
  177. package/dist/context/index.js.map +1 -1
  178. package/dist/events.d.ts +10 -0
  179. package/dist/events.d.ts.map +1 -0
  180. package/dist/events.js +78 -0
  181. package/dist/events.js.map +1 -0
  182. package/dist/fiber-tree.d.ts +3 -0
  183. package/dist/fiber-tree.d.ts.map +1 -0
  184. package/dist/fiber-tree.js +93 -0
  185. package/dist/fiber-tree.js.map +1 -0
  186. package/dist/hooks.d.ts +5 -2
  187. package/dist/hooks.d.ts.map +1 -1
  188. package/dist/hooks.js +66 -6
  189. package/dist/hooks.js.map +1 -1
  190. package/dist/idb.d.ts +1 -1
  191. package/dist/idb.d.ts.map +1 -1
  192. package/dist/lib/to-plain.d.ts +2 -0
  193. package/dist/lib/to-plain.d.ts.map +1 -0
  194. package/dist/lib/to-plain.js +21 -0
  195. package/dist/lib/to-plain.js.map +1 -0
  196. package/dist/main.d.ts +1 -1
  197. package/dist/main.d.ts.map +1 -1
  198. package/dist/main.js +4 -3
  199. package/dist/main.js.map +1 -1
  200. package/dist/mods/clients.d.ts +3 -0
  201. package/dist/mods/clients.d.ts.map +1 -0
  202. package/dist/mods/clients.js +4 -0
  203. package/dist/mods/clients.js.map +1 -0
  204. package/dist/mods/editor-ui/FieldLabel.d.ts +15 -0
  205. package/dist/mods/editor-ui/FieldLabel.d.ts.map +1 -0
  206. package/dist/mods/editor-ui/FieldLabel.js +55 -0
  207. package/dist/mods/editor-ui/FieldLabel.js.map +1 -0
  208. package/dist/mods/editor-ui/client.d.ts +1 -1
  209. package/dist/mods/editor-ui/client.d.ts.map +1 -1
  210. package/dist/mods/editor-ui/client.js +1 -1
  211. package/dist/mods/editor-ui/client.js.map +1 -1
  212. package/dist/mods/editor-ui/default-edit.d.ts +2 -0
  213. package/dist/mods/editor-ui/default-edit.d.ts.map +1 -0
  214. package/dist/mods/editor-ui/default-edit.js +54 -0
  215. package/dist/mods/editor-ui/default-edit.js.map +1 -0
  216. package/dist/mods/editor-ui/default-view.d.ts +8 -1
  217. package/dist/mods/editor-ui/default-view.d.ts.map +1 -1
  218. package/dist/mods/editor-ui/default-view.js +8 -5
  219. package/dist/mods/editor-ui/default-view.js.map +1 -1
  220. package/dist/mods/editor-ui/dir-view.js +0 -2
  221. package/dist/mods/editor-ui/dir-view.js.map +1 -1
  222. package/dist/mods/editor-ui/empty-placeholder.d.ts +5 -0
  223. package/dist/mods/editor-ui/empty-placeholder.d.ts.map +1 -0
  224. package/dist/mods/editor-ui/empty-placeholder.js +14 -0
  225. package/dist/mods/editor-ui/empty-placeholder.js.map +1 -0
  226. package/dist/mods/editor-ui/form-field.d.ts +17 -0
  227. package/dist/mods/editor-ui/form-field.d.ts.map +1 -0
  228. package/dist/mods/editor-ui/form-field.js +68 -0
  229. package/dist/mods/editor-ui/form-field.js.map +1 -0
  230. package/dist/mods/editor-ui/form-fields.d.ts +1 -2
  231. package/dist/mods/editor-ui/form-fields.d.ts.map +1 -1
  232. package/dist/mods/editor-ui/form-fields.js +56 -60
  233. package/dist/mods/editor-ui/form-fields.js.map +1 -1
  234. package/dist/mods/editor-ui/layout-view.js +3 -2
  235. package/dist/mods/editor-ui/layout-view.js.map +1 -1
  236. package/dist/mods/editor-ui/list-items.js +1 -1
  237. package/dist/mods/editor-ui/list-items.js.map +1 -1
  238. package/dist/mods/editor-ui/node-utils.d.ts +2 -2
  239. package/dist/mods/editor-ui/node-utils.d.ts.map +1 -1
  240. package/dist/mods/editor-ui/node-utils.js +4 -5
  241. package/dist/mods/editor-ui/node-utils.js.map +1 -1
  242. package/dist/mods/editor-ui/type-picker.d.ts +15 -0
  243. package/dist/mods/editor-ui/type-picker.d.ts.map +1 -0
  244. package/dist/mods/editor-ui/type-picker.js +69 -0
  245. package/dist/mods/editor-ui/type-picker.js.map +1 -0
  246. package/dist/mods/editor-ui/user-view.js +1 -1
  247. package/dist/mods/editor-ui/user-view.js.map +1 -1
  248. package/dist/mods/servers.d.ts +1 -0
  249. package/dist/mods/servers.d.ts.map +1 -0
  250. package/dist/mods/servers.js +4 -0
  251. package/dist/mods/servers.js.map +1 -0
  252. package/dist/mods/treenity/groups/index.js +1 -1
  253. package/dist/mods/treenity/groups/index.js.map +1 -1
  254. package/dist/mods/treenity/preview.js +1 -1
  255. package/dist/mods/treenity/preview.js.map +1 -1
  256. package/dist/mods/treenity/ref-view.js +1 -1
  257. package/dist/mods/treenity/ref-view.js.map +1 -1
  258. package/dist/mods/treenity/schema-form.js +1 -1
  259. package/dist/mods/treenity/schema-form.js.map +1 -1
  260. package/dist/mods/treenity/seed.js +1 -1
  261. package/dist/mods/treenity/seed.js.map +1 -1
  262. package/dist/mods/treenity/type-view.js +1 -1
  263. package/dist/mods/treenity/type-view.js.map +1 -1
  264. package/dist/schema-loader.d.ts +1 -1
  265. package/dist/schema-loader.d.ts.map +1 -1
  266. package/dist/schema-loader.js +1 -1
  267. package/dist/schema-loader.js.map +1 -1
  268. package/dist/symbols.d.ts +5 -0
  269. package/dist/symbols.d.ts.map +1 -0
  270. package/dist/symbols.js +16 -0
  271. package/dist/symbols.js.map +1 -0
  272. package/dist/trpc.d.ts +10 -3
  273. package/dist/trpc.d.ts.map +1 -1
  274. package/package.json +74 -8
  275. package/src/AclEditor.tsx +11 -18
  276. package/src/ActionCards.tsx +224 -0
  277. package/src/App.tsx +204 -385
  278. package/src/ComponentSection.tsx +113 -0
  279. package/src/ErrorBoundary.tsx +37 -0
  280. package/src/Inspector.css +54 -0
  281. package/src/Inspector.tsx +73 -793
  282. package/src/Login.tsx +97 -0
  283. package/src/NodeEditor.tsx +300 -0
  284. package/src/Tree.css +91 -0
  285. package/src/Tree.tsx +40 -43
  286. package/src/bind/bind.test.ts +1 -1
  287. package/src/bind/engine.ts +1 -1
  288. package/src/bind/eval.ts +1 -1
  289. package/src/bind/hook.ts +1 -1
  290. package/src/bind/pipes.ts +1 -1
  291. package/src/cache.ts +5 -1
  292. package/src/client-tree.test.ts +1 -1
  293. package/src/client-tree.ts +22 -16
  294. package/src/client.ts +2 -4
  295. package/src/components/ConfirmDialog.tsx +34 -0
  296. package/src/components/ConfirmPopover.tsx +41 -0
  297. package/src/components/PathBreadcrumb.tsx +36 -0
  298. package/src/components/lib/utils.ts +6 -0
  299. package/src/components/lib/utils.ts.bak +6 -0
  300. package/src/components/ui/accordion.tsx +1 -1
  301. package/src/components/ui/alert-dialog.tsx +189 -0
  302. package/src/components/ui/badge.tsx +1 -1
  303. package/src/components/ui/breadcrumb.tsx +108 -0
  304. package/src/components/ui/button.tsx +53 -31
  305. package/src/components/ui/card.tsx +91 -0
  306. package/src/components/ui/checkbox.tsx +1 -1
  307. package/src/components/ui/collapsible.tsx +31 -0
  308. package/src/components/ui/command.tsx +177 -0
  309. package/src/components/ui/dialog.tsx +1 -2
  310. package/src/components/ui/drawer.tsx +1 -1
  311. package/src/components/ui/dropdown-menu.tsx +256 -0
  312. package/src/components/ui/form-field.tsx +37 -0
  313. package/src/components/ui/input.tsx +1 -1
  314. package/src/components/ui/label.tsx +1 -1
  315. package/src/components/ui/pagination.tsx +127 -0
  316. package/src/components/ui/popover.tsx +2 -2
  317. package/src/components/ui/progress.tsx +1 -1
  318. package/src/components/ui/resizable.tsx +47 -0
  319. package/src/components/ui/scroll-area.tsx +55 -0
  320. package/src/components/ui/select.tsx +1 -1
  321. package/src/components/ui/separator.tsx +27 -0
  322. package/src/components/ui/sheet.tsx +140 -0
  323. package/src/components/ui/skeleton.tsx +13 -0
  324. package/src/components/ui/slider.tsx +1 -1
  325. package/src/components/ui/switch.tsx +1 -1
  326. package/src/components/ui/table.tsx +115 -0
  327. package/src/components/ui/tabs.tsx +88 -0
  328. package/src/components/ui/textarea.tsx +1 -1
  329. package/src/components/ui/toggle-group.tsx +82 -0
  330. package/src/components/ui/toggle.tsx +46 -0
  331. package/src/components/ui/tooltip.tsx +1 -1
  332. package/src/context/index.tsx +75 -42
  333. package/src/events.ts +81 -0
  334. package/src/fiber-tree.ts +112 -0
  335. package/src/hooks.ts +88 -9
  336. package/src/idb.ts +1 -1
  337. package/src/lib/to-plain.ts +21 -0
  338. package/src/main.tsx +3 -1
  339. package/src/mods/clients.ts +3 -0
  340. package/src/mods/editor-ui/FieldLabel.tsx +124 -0
  341. package/src/mods/editor-ui/client.ts +1 -1
  342. package/src/mods/editor-ui/default-edit.tsx +99 -0
  343. package/src/mods/editor-ui/default-view.tsx +13 -8
  344. package/src/mods/editor-ui/dir-view.tsx +2 -2
  345. package/src/mods/editor-ui/editor-ui.css +174 -0
  346. package/src/mods/editor-ui/empty-placeholder.tsx +39 -0
  347. package/src/mods/editor-ui/form-field.tsx +144 -0
  348. package/src/mods/editor-ui/form-fields.tsx +132 -113
  349. package/src/mods/editor-ui/layout-view.tsx +4 -2
  350. package/src/mods/editor-ui/list-items.tsx +2 -2
  351. package/src/mods/editor-ui/node-utils.ts +4 -5
  352. package/src/mods/editor-ui/type-picker.tsx +147 -0
  353. package/src/mods/editor-ui/user-view.tsx +1 -1
  354. package/src/mods/servers.ts +2 -0
  355. package/src/mods/treenity/groups/index.tsx +1 -1
  356. package/src/mods/treenity/preview.tsx +1 -1
  357. package/src/mods/treenity/ref-view.tsx +1 -1
  358. package/src/mods/treenity/schema-form.tsx +1 -1
  359. package/src/mods/treenity/seed.ts +1 -1
  360. package/src/mods/treenity/type-view.tsx +1 -1
  361. package/src/optimistic.test.ts +111 -0
  362. package/src/remote-tree.test.ts +1 -1
  363. package/src/remote-tree.ts +1 -1
  364. package/src/root.css +117 -0
  365. package/src/schema-loader.ts +1 -1
  366. package/src/symbols.ts +18 -0
  367. package/src/index.html +0 -14
  368. package/src/style.css +0 -1269
  369. package/src/vite-env.d.ts +0 -3
package/src/Login.tsx ADDED
@@ -0,0 +1,97 @@
1
+ import { Button } from '#components/ui/button';
2
+ import { Dialog, DialogContent } from '#components/ui/dialog';
3
+ import { Input } from '#components/ui/input';
4
+ import { Label } from '#components/ui/label';
5
+ import { useState } from 'react';
6
+ import { setToken, trpc } from './trpc';
7
+
8
+ function LoginForm({ onLogin }: { onLogin: (userId: string) => void }) {
9
+ const [mode, setMode] = useState<'login' | 'register'>('login');
10
+ const [userId, setUserId] = useState('');
11
+ const [password, setPassword] = useState('');
12
+ const [err, setErr] = useState<string | null>(null);
13
+ const [loading, setLoading] = useState(false);
14
+
15
+ async function handleSubmit(e: React.FormEvent) {
16
+ e.preventDefault();
17
+ if (!userId.trim() || !password) return;
18
+ setLoading(true);
19
+ setErr(null);
20
+ try {
21
+ const fn = mode === 'register' ? trpc.register : trpc.login;
22
+ const res = await fn.mutate({ userId: userId.trim(), password });
23
+ setToken(res.token);
24
+ onLogin(res.userId);
25
+ } catch (e) {
26
+ setErr(e instanceof Error ? e.message : 'Failed');
27
+ } finally {
28
+ setLoading(false);
29
+ }
30
+ }
31
+
32
+ return (
33
+ <form className="flex flex-col gap-4 w-80 p-8 rounded-lg border border-border bg-card" onSubmit={handleSubmit}>
34
+ <div className="flex items-center justify-center gap-2 mb-2">
35
+ <img src="/treenity.svg" alt="" width="32" height="32" />
36
+ <span className="text-lg font-semibold">Treenity</span>
37
+ </div>
38
+
39
+ <div className="flex flex-col gap-1.5">
40
+ <Label htmlFor="userId">User ID</Label>
41
+ <Input
42
+ id="userId"
43
+ autoFocus
44
+ placeholder="Enter your user ID"
45
+ value={userId}
46
+ onChange={(e) => setUserId(e.target.value)}
47
+ />
48
+ </div>
49
+
50
+ <div className="flex flex-col gap-1.5">
51
+ <Label htmlFor="password">Password</Label>
52
+ <Input
53
+ id="password"
54
+ type="password"
55
+ placeholder="Enter password"
56
+ value={password}
57
+ onChange={(e) => setPassword(e.target.value)}
58
+ />
59
+ </div>
60
+
61
+ {err && <p className="text-sm text-destructive">{err}</p>}
62
+
63
+ <Button type="submit" disabled={loading || !userId.trim() || !password}>
64
+ {loading ? '...' : mode === 'register' ? 'Create account' : 'Sign in'}
65
+ </Button>
66
+
67
+ <Button
68
+ type="button"
69
+ variant="ghost"
70
+ onClick={() => {
71
+ setMode((m) => (m === 'login' ? 'register' : 'login'));
72
+ setErr(null);
73
+ }}
74
+ >
75
+ {mode === 'login' ? 'No account? Register' : 'Have an account? Sign in'}
76
+ </Button>
77
+ </form>
78
+ );
79
+ }
80
+
81
+ export function LoginScreen({ onLogin }: { onLogin: (userId: string) => void }) {
82
+ return (
83
+ <div className="flex items-center justify-center h-screen bg-background">
84
+ <LoginForm onLogin={onLogin} />
85
+ </div>
86
+ );
87
+ }
88
+
89
+ export function LoginModal({ onLogin, onClose }: { onLogin: (userId: string) => void; onClose?: () => void }) {
90
+ return (
91
+ <Dialog open onOpenChange={(open) => { if (!open && onClose) onClose(); }}>
92
+ <DialogContent className="p-0 border-none bg-transparent shadow-none max-w-fit" showCloseButton={!!onClose}>
93
+ <LoginForm onLogin={onLogin} />
94
+ </DialogContent>
95
+ </Dialog>
96
+ );
97
+ }
@@ -0,0 +1,300 @@
1
+ // NodeEditor — self-contained edit panel for a node (properties, components, actions)
2
+ // Reusable: Inspector uses it as a slide-out, but can be embedded anywhere.
3
+
4
+ import { Button } from '#components/ui/button';
5
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '#components/ui/collapsible';
6
+ import { Input } from '#components/ui/input';
7
+ import { ScrollArea } from '#components/ui/scroll-area';
8
+ import { Tabs, TabsList, TabsTrigger } from '#components/ui/tabs';
9
+ import { toPlain } from '#lib/to-plain';
10
+ import { FieldLabel, RefEditor } from '#mods/editor-ui/FieldLabel';
11
+ import { getComponents, getPlainFields, getSchema } from '#mods/editor-ui/node-utils';
12
+ import { type ComponentData, type GroupPerm, isRef, type NodeData, resolve } from '@treenity/core';
13
+ import type { TypeSchema } from '@treenity/core/schema/types';
14
+ import { ChevronRight } from 'lucide-react';
15
+ import { useEffect, useState } from 'react';
16
+ import { proxy, snapshot, useSnapshot } from 'valtio';
17
+ import { AclEditor } from './AclEditor';
18
+ import * as cache from './cache';
19
+ import { ComponentSection } from './ComponentSection';
20
+ import { set } from './hooks';
21
+
22
+ type AnyClass = { new(): Record<string, unknown> };
23
+
24
+ function NodeCard({ path, type, onChangeType }: {
25
+ path: string;
26
+ type: string;
27
+ onChangeType: (t: string) => void;
28
+ }) {
29
+ return (
30
+ <Collapsible className="border-t border-border mt-2 pt-0.5 first:border-t-0 first:mt-0 first:pt-0">
31
+ <CollapsibleTrigger className="flex w-full items-center justify-between py-2 pb-1.5 text-[11px] font-semibold text-muted-foreground uppercase tracking-wider cursor-pointer select-none">
32
+ <span>Node</span>
33
+ <span className="flex items-center gap-2 normal-case tracking-normal font-normal text-[11px] font-mono text-foreground/50">
34
+ {path}
35
+ <span className="text-primary">{type}</span>
36
+ <ChevronRight className="h-3 w-3 transition-transform duration-200 group-data-[state=open]:rotate-90" />
37
+ </span>
38
+ </CollapsibleTrigger>
39
+ <CollapsibleContent>
40
+ <div className="py-0.5 pb-2.5">
41
+ <div className="field">
42
+ <label>$path</label>
43
+ <Input className="h-7 text-xs" value={path} readOnly />
44
+ </div>
45
+ <div className="field">
46
+ <label>$type</label>
47
+ <Input className="h-7 text-xs" value={type} onChange={(e) => onChangeType(e.target.value)} />
48
+ </div>
49
+ </div>
50
+ </CollapsibleContent>
51
+ </Collapsible>
52
+ );
53
+ }
54
+
55
+ export type NodeEditorProps = {
56
+ node: NodeData;
57
+ open: boolean;
58
+ onClose: () => void;
59
+ currentUserId?: string;
60
+ toast: (msg: string) => void;
61
+ onAddComponent: (path: string) => void;
62
+ };
63
+
64
+ export function NodeEditor({ node, open, onClose, currentUserId, toast, onAddComponent }: NodeEditorProps) {
65
+ const [st] = useState(() => proxy({
66
+ nodeType: '',
67
+ compTexts: {} as Record<string, string>,
68
+ compData: {} as Record<string, Record<string, unknown>>,
69
+ plainData: {} as Record<string, unknown>,
70
+ tab: 'properties' as 'properties' | 'json',
71
+ jsonText: '',
72
+ collapsed: { $node: true } as Record<string, boolean>,
73
+ aclOwner: '',
74
+ aclRules: [] as GroupPerm[],
75
+ dirty: false,
76
+ stale: false,
77
+ syncedPath: null as string | null,
78
+ syncedRev: null as unknown,
79
+ }));
80
+ const snap = useSnapshot(st);
81
+
82
+ function syncFromNode(n: NodeData) {
83
+ st.nodeType = n.$type;
84
+ st.aclOwner = (n.$owner as string) ?? '';
85
+ st.aclRules = n.$acl ? [...(n.$acl as GroupPerm[])] : [];
86
+ const texts: Record<string, string> = {};
87
+ const cdata: Record<string, Record<string, unknown>> = {};
88
+ for (const [name, comp] of getComponents(n)) {
89
+ texts[name] = JSON.stringify(comp, null, 2);
90
+ const d: Record<string, unknown> = {};
91
+ for (const [k, v] of Object.entries(comp)) {
92
+ if (!k.startsWith('$')) d[k] = v;
93
+ }
94
+ cdata[name] = d;
95
+ }
96
+ st.compTexts = texts;
97
+ st.compData = cdata;
98
+ st.plainData = getPlainFields(n);
99
+ st.jsonText = JSON.stringify(n, null, 2);
100
+ st.tab = 'properties';
101
+ }
102
+
103
+ useEffect(() => {
104
+ const pathChanged = node.$path !== st.syncedPath;
105
+ if (pathChanged) {
106
+ syncFromNode(node);
107
+ st.syncedPath = node.$path;
108
+ st.syncedRev = node.$rev;
109
+ st.dirty = false;
110
+ st.stale = false;
111
+ return;
112
+ }
113
+
114
+ if (node.$rev !== st.syncedRev) {
115
+ if (st.dirty) {
116
+ st.stale = true;
117
+ } else {
118
+ syncFromNode(node);
119
+ st.syncedRev = node.$rev;
120
+ }
121
+ }
122
+ }, [node.$path, node.$rev]);
123
+
124
+ function handleReset() {
125
+ const current = cache.get(node.$path) ?? node;
126
+ syncFromNode(current);
127
+ st.syncedRev = current.$rev;
128
+ st.dirty = false;
129
+ st.stale = false;
130
+ }
131
+
132
+ const nodeName = node.$path === '/' ? '/' : node.$path.slice(node.$path.lastIndexOf('/') + 1);
133
+ const components = getComponents(node);
134
+ const schemaHandler = resolve(node.$type, 'schema');
135
+ const schema = schemaHandler ? (schemaHandler() as TypeSchema) : null;
136
+ const mainCompCls = resolve(node.$type, 'class') as AnyClass | null;
137
+ const mainCompDefaults = mainCompCls ? new mainCompCls() : null;
138
+
139
+ async function handleSave() {
140
+ const s = toPlain(snapshot(st));
141
+ let toSave: NodeData;
142
+ if (s.tab === 'json') {
143
+ try {
144
+ toSave = JSON.parse(s.jsonText);
145
+ } catch {
146
+ toast('Invalid JSON');
147
+ return;
148
+ }
149
+ } else {
150
+ toSave = { $path: node.$path, $type: s.nodeType, ...s.plainData } as NodeData;
151
+ if (s.aclOwner) toSave.$owner = s.aclOwner;
152
+ if (s.aclRules.length > 0) toSave.$acl = [...s.aclRules] as GroupPerm[];
153
+ for (const [name, comp] of components) {
154
+ const ctype = (comp as ComponentData).$type;
155
+ const cschema = getSchema(ctype);
156
+ const cd = s.compData[name];
157
+ if ((cschema || (cd && Object.keys(cd).length > 0)) && cd) {
158
+ toSave[name] = { $type: ctype, ...cd };
159
+ } else {
160
+ const text = s.compTexts[name];
161
+ if (text === undefined) continue;
162
+ try {
163
+ toSave[name] = JSON.parse(text);
164
+ } catch {
165
+ toast(`Invalid JSON in component: ${name}`);
166
+ return;
167
+ }
168
+ }
169
+ }
170
+ }
171
+ await set(toSave);
172
+ const fresh = cache.get(node.$path);
173
+ if (fresh) {
174
+ syncFromNode(fresh);
175
+ st.syncedRev = fresh.$rev;
176
+ }
177
+ st.dirty = false;
178
+ st.stale = false;
179
+ toast('Saved');
180
+ }
181
+
182
+ function handleRemoveComponent(name: string) {
183
+ const next = { ...node };
184
+ delete next[name];
185
+ set(next);
186
+ }
187
+
188
+ return (
189
+ <div className={`edit-panel${open ? ' open' : ''}`}>
190
+ <div className="edit-panel-header">
191
+ <span>Edit {nodeName}</span>
192
+ <Button variant="ghost" size="sm" className="h-6 w-6 p-0" onClick={onClose}>
193
+ &#10005;
194
+ </Button>
195
+ </div>
196
+
197
+ <Tabs value={snap.tab} onValueChange={(v) => {
198
+ st.tab = v as 'properties' | 'json';
199
+ if (v === 'json') st.jsonText = JSON.stringify({ ...node, ...st.plainData }, null, 2);
200
+ }} className="px-3 pt-2">
201
+ <TabsList className="h-8 bg-secondary">
202
+ <TabsTrigger value="properties" className="text-xs">Properties</TabsTrigger>
203
+ <TabsTrigger value="json" className="text-xs">JSON</TabsTrigger>
204
+ </TabsList>
205
+ </Tabs>
206
+
207
+ <ScrollArea className="flex-1">
208
+ <div className="p-3.5">
209
+ {snap.tab === 'properties' ? (
210
+ <>
211
+ <NodeCard path={node.$path} type={snap.nodeType} onChangeType={(v) => { st.nodeType = v; st.dirty = true; }} />
212
+ <AclEditor
213
+ path={node.$path}
214
+ owner={snap.aclOwner}
215
+ rules={snap.aclRules as GroupPerm[]}
216
+ currentUserId={currentUserId}
217
+ onChange={(o, r) => { st.aclOwner = o; st.aclRules = r; st.dirty = true; }}
218
+ />
219
+
220
+ {/* Main type section */}
221
+ <ComponentSection
222
+ node={node}
223
+ name=""
224
+ compType={node.$type}
225
+ data={snap.plainData as Record<string, unknown>}
226
+ onData={(d) => { st.plainData = d; st.dirty = true; }}
227
+ toast={toast}
228
+ onActionComplete={handleReset}
229
+ />
230
+
231
+ {/* Named components */}
232
+ {components.map(([name, comp]) => (
233
+ <ComponentSection
234
+ key={name}
235
+ node={node}
236
+ name={name}
237
+ compType={(comp as ComponentData).$type}
238
+ data={(snap.compData[name] ?? {}) as Record<string, unknown>}
239
+ onData={(d) => { st.compData[name] = d; st.dirty = true; }}
240
+ collapsed={!!snap.collapsed[name]}
241
+ onToggle={() => { st.collapsed[name] = !st.collapsed[name]; }}
242
+ onRemove={() => handleRemoveComponent(name)}
243
+ toast={toast}
244
+ onActionComplete={handleReset}
245
+ />
246
+ ))}
247
+
248
+ {/* Untyped plain data fallback */}
249
+ {!schema && !mainCompDefaults && Object.keys(snap.plainData).length > 0 && (
250
+ <div className="border-t border-border mt-2 pt-0.5 first:border-t-0 first:mt-0 first:pt-0">
251
+ <div className="flex items-center justify-between py-2 pb-1.5 text-[11px] font-semibold text-muted-foreground uppercase tracking-wider">Data</div>
252
+ <div className="py-0.5 pb-2.5">
253
+ {Object.entries(snap.plainData).map(([k, v]) => {
254
+ const onCh = (next: unknown) => { st.plainData[k] = next; st.dirty = true; };
255
+ return (
256
+ <div key={k} className={`field${typeof v === 'object' && v !== null ? ' stack' : ''}`}>
257
+ <FieldLabel label={k} value={v} onChange={onCh} />
258
+ {typeof v === 'object' && isRef(v) ? (
259
+ <RefEditor value={v as { $ref: string; $map?: string }} onChange={onCh} />
260
+ ) : (
261
+ <Input
262
+ className="h-7 text-xs"
263
+ value={typeof v === 'string' ? v : JSON.stringify(v)}
264
+ onChange={(e) => { st.plainData[k] = e.target.value; st.dirty = true; }}
265
+ />
266
+ )}
267
+ </div>
268
+ );
269
+ })}
270
+ </div>
271
+ </div>
272
+ )}
273
+ </>
274
+ ) : (
275
+ <textarea
276
+ value={snap.jsonText}
277
+ onChange={(e) => { st.jsonText = e.target.value; st.dirty = true; }}
278
+ spellCheck={false}
279
+ />
280
+ )}
281
+ </div>
282
+ </ScrollArea>
283
+
284
+ <div className="edit-panel-actions">
285
+ {snap.stale && (
286
+ <Button variant="ghost" size="sm" onClick={handleReset} title="Node updated externally">
287
+ Reset
288
+ </Button>
289
+ )}
290
+ <Button size="sm" onClick={handleSave}>
291
+ Save
292
+ </Button>
293
+ {snap.tab === 'properties' && (
294
+ <Button variant="outline" size="sm" onClick={() => onAddComponent(node.$path)}>+ Component</Button>
295
+ )}
296
+ </div>
297
+
298
+ </div>
299
+ );
300
+ }
package/src/Tree.css ADDED
@@ -0,0 +1,91 @@
1
+ /* Tree sidebar */
2
+ .tree-node {
3
+ position: relative;
4
+ }
5
+ .tree-children {
6
+ position: relative;
7
+ padding-left: 12px;
8
+ }
9
+ .tree-children::before {
10
+ content: '';
11
+ position: absolute;
12
+ left: 15px;
13
+ top: 0;
14
+ bottom: 8px;
15
+ width: 1px;
16
+ background: var(--border-subtle);
17
+ }
18
+
19
+ .tree-row {
20
+ display: flex;
21
+ align-items: center;
22
+ padding: 1px 8px 1px 0;
23
+ cursor: pointer;
24
+ color: var(--text);
25
+ font-size: 14px;
26
+ height: 28px;
27
+ position: relative;
28
+ user-select: none;
29
+ transition: background var(--transition);
30
+ }
31
+ .tree-row:hover {
32
+ background: var(--surface-2);
33
+ }
34
+ .tree-row.selected {
35
+ background: var(--accent-subtle);
36
+ }
37
+ .tree-row.selected::before {
38
+ content: '';
39
+ position: absolute;
40
+ left: 0;
41
+ top: 2px;
42
+ bottom: 2px;
43
+ width: 2px;
44
+ background: var(--accent);
45
+ border-radius: 1px;
46
+ }
47
+
48
+ .tree-toggle {
49
+ width: 20px;
50
+ height: 28px;
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ color: var(--text-3);
55
+ font-size: 14px;
56
+ flex-shrink: 0;
57
+ transition: color var(--transition);
58
+ }
59
+ .tree-toggle:hover {
60
+ color: var(--text-2);
61
+ }
62
+
63
+ .tree-label {
64
+ flex: 1;
65
+ overflow: hidden;
66
+ text-overflow: ellipsis;
67
+ white-space: nowrap;
68
+ }
69
+ .tree-badge {
70
+ font-size: 10px;
71
+ color: var(--text-3);
72
+ padding: 1px 6px;
73
+ background: var(--surface-3);
74
+ border-radius: 10px;
75
+ flex-shrink: 0;
76
+ font-family: var(--mono);
77
+ cursor: pointer;
78
+ transition: all var(--transition);
79
+ }
80
+ .tree-badge:hover {
81
+ color: var(--text-2);
82
+ background: var(--surface-2);
83
+ }
84
+
85
+ /* Drag */
86
+ .tree-drop-above {
87
+ box-shadow: inset 0 2px 0 var(--accent);
88
+ }
89
+ .tree-drop-below {
90
+ box-shadow: inset 0 -2px 0 var(--accent);
91
+ }
package/src/Tree.tsx CHANGED
@@ -1,4 +1,13 @@
1
- import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';
1
+ import './Tree.css';
2
+ import { ConfirmDialog } from '#components/ConfirmDialog';
3
+ import { Badge } from '#components/ui/badge';
4
+ import {
5
+ DropdownMenu,
6
+ DropdownMenuContent,
7
+ DropdownMenuItem,
8
+ DropdownMenuTrigger,
9
+ } from '#components/ui/dropdown-menu';
10
+ import { useCallback, useRef, useState, useSyncExternalStore } from 'react';
2
11
  import * as cache from './cache';
3
12
 
4
13
  type TreeProps = {
@@ -47,57 +56,45 @@ function BadgeMenu({
47
56
  onCreateChild: (path: string) => void;
48
57
  onDelete?: (path: string) => void;
49
58
  }) {
50
- const [open, setOpen] = useState(false);
51
- const ref = useRef<HTMLDivElement>(null);
52
-
53
- useEffect(() => {
54
- if (!open) return;
55
- const close = (e: MouseEvent) => {
56
- if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
57
- };
58
- document.addEventListener('mousedown', close);
59
- return () => document.removeEventListener('mousedown', close);
60
- }, [open]);
59
+ const [confirmDelete, setConfirmDelete] = useState(false);
61
60
 
62
61
  return (
63
- <div ref={ref} className="tree-badge-wrap">
64
- <span
65
- className="tree-badge"
66
- title={fullType}
67
- onClick={(e) => {
68
- e.stopPropagation();
69
- setOpen(!open);
70
- }}
71
- >
72
- {typeLabel}
73
- </span>
74
- {open && (
75
- <div className="tree-menu">
76
- <button
77
- className="tree-menu-item"
78
- onClick={(e) => {
79
- e.stopPropagation();
80
- setOpen(false);
81
- onCreateChild(path);
82
- }}
62
+ <>
63
+ <DropdownMenu>
64
+ <DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>
65
+ <Badge
66
+ variant="secondary"
67
+ className="tree-badge cursor-pointer text-[10px] px-1.5 py-0 h-5 font-mono font-normal"
68
+ title={fullType}
83
69
  >
70
+ {typeLabel}
71
+ </Badge>
72
+ </DropdownMenuTrigger>
73
+ <DropdownMenuContent align="end" className="min-w-[120px]" onClick={(e) => e.stopPropagation()}>
74
+ <DropdownMenuItem onClick={() => onCreateChild(path)}>
84
75
  + Add child
85
- </button>
76
+ </DropdownMenuItem>
86
77
  {onDelete && (
87
- <button
88
- className="tree-menu-item danger"
89
- onClick={(e) => {
90
- e.stopPropagation();
91
- setOpen(false);
92
- if (confirm(`Delete ${path}?`)) onDelete(path);
93
- }}
78
+ <DropdownMenuItem
79
+ className="text-destructive focus:text-destructive"
80
+ onClick={() => setConfirmDelete(true)}
94
81
  >
95
82
  × Delete
96
- </button>
83
+ </DropdownMenuItem>
97
84
  )}
98
- </div>
85
+ </DropdownMenuContent>
86
+ </DropdownMenu>
87
+ {onDelete && (
88
+ <ConfirmDialog
89
+ open={confirmDelete}
90
+ onOpenChange={setConfirmDelete}
91
+ title={`Delete ${path}?`}
92
+ description="This action cannot be undone."
93
+ variant="destructive"
94
+ onConfirm={() => onDelete(path)}
95
+ />
99
96
  )}
100
- </div>
97
+ </>
101
98
  );
102
99
  }
103
100
 
@@ -1,4 +1,4 @@
1
- import type { NodeData } from '@treenity/core/core';
1
+ import type { NodeData } from '@treenity/core';
2
2
  import assert from 'node:assert/strict';
3
3
  import { describe, it } from 'node:test';
4
4
  import { clearComputed, getComputed, setComputed, subscribeComputed } from './computed';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import * as cache from '#cache';
6
6
  import { trpc } from '#trpc';
7
- import { isRef, type NodeData, type Ref } from '@treenity/core/core';
7
+ import { isRef, type NodeData, type Ref } from '@treenity/core';
8
8
  import { clearComputed, getComputed, setComputed } from './computed';
9
9
  import { evaluateRef, extractArgPaths, hasOnce, isCollectionRef } from './eval';
10
10
 
package/src/bind/eval.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Resolves source from $ref, applies $map pipeline
3
3
  // #field (self) and #/path.field (external) args resolved from context
4
4
 
5
- import type { NodeData, Ref } from '@treenity/core/core';
5
+ import type { NodeData, Ref } from '@treenity/core';
6
6
  import { isRefArg, type MapExpr, parseMapExpr, type PipeArg } from './parse';
7
7
  import { getPipe } from './pipes';
8
8
 
package/src/bind/hook.ts CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as cache from '#cache';
4
4
  import { set, usePath } from '#hooks';
5
- import { isRef, type NodeData } from '@treenity/core/core';
5
+ import { isRef, type NodeData } from '@treenity/core';
6
6
  import { useCallback, useMemo, useSyncExternalStore } from 'react';
7
7
  import { useSnapshot } from 'valtio';
8
8
  import { proxy } from 'valtio/vanilla';
package/src/bind/pipes.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Pipe registry — Angular-style transforms for $map expressions
2
2
 
3
- import type { NodeData } from '@treenity/core/core';
3
+ import type { NodeData } from '@treenity/core';
4
4
 
5
5
  export type PipeFn = (input: unknown, ...args: unknown[]) => unknown;
6
6
 
package/src/cache.ts CHANGED
@@ -2,8 +2,9 @@
2
2
  // useSyncExternalStore-friendly: stable snapshots, targeted notifications
3
3
  // IDB persistence: fire-and-forget writes, hydrate() on startup.
4
4
 
5
- import type { NodeData } from '@treenity/core/core';
5
+ import type { NodeData } from '@treenity/core';
6
6
  import * as idb from './idb';
7
+ import { stampNode } from './symbols';
7
8
 
8
9
  type Sub = () => void;
9
10
 
@@ -109,6 +110,7 @@ export function removeFromParent(path: string, parent: string) {
109
110
  // ── Writes ──
110
111
 
111
112
  export function put(node: NodeData, virtualParent?: string) {
113
+ stampNode(node);
112
114
  nodes.set(node.$path, node);
113
115
  const p = virtualParent ?? parentOf(node.$path);
114
116
  if (p !== null) {
@@ -135,6 +137,7 @@ export function putMany(items: NodeData[], virtualParent?: string) {
135
137
  const ts = Date.now();
136
138
  const idbEntries: idb.IDBEntry[] = [];
137
139
  for (const n of items) {
140
+ stampNode(n);
138
141
  nodes.set(n.$path, n);
139
142
  lastUpdated.set(n.$path, ts);
140
143
  fire(pathSubs, n.$path);
@@ -225,6 +228,7 @@ export async function hydrate(): Promise<void> {
225
228
  try {
226
229
  const entries = await idb.loadAll();
227
230
  for (const { data, lastUpdated: ts, virtualParent } of entries) {
231
+ stampNode(data);
228
232
  nodes.set(data.$path, data);
229
233
  lastUpdated.set(data.$path, ts);
230
234
  const p = virtualParent ?? parentOf(data.$path);
@@ -1,4 +1,4 @@
1
- import type { NodeData } from '@treenity/core/core';
1
+ import type { NodeData } from '@treenity/core';
2
2
  import assert from 'node:assert/strict';
3
3
  import { describe, it } from 'node:test';
4
4
  import { createClientTree } from './client-tree';