@treenity/mods 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.
- package/board/board.test.ts +212 -0
- package/board/client.ts +1 -0
- package/board/seed.ts +26 -0
- package/board/server.ts +5 -0
- package/board/types.ts +87 -0
- package/board/view.tsx +574 -0
- package/brahman/CLAUDE.md +18 -0
- package/brahman/brahman.test.ts +855 -0
- package/brahman/client.ts +2 -0
- package/brahman/helpers.ts +374 -0
- package/brahman/server.ts +2 -0
- package/brahman/service.ts +328 -0
- package/brahman/types.ts +727 -0
- package/brahman/view.tsx +73 -0
- package/brahman/views/action-cards.tsx +615 -0
- package/brahman/views/bot-view.tsx +76 -0
- package/brahman/views/chat-editor.tsx +782 -0
- package/brahman/views/chat-preview.tsx +266 -0
- package/brahman/views/menu-editor.tsx +451 -0
- package/brahman/views/page-layout.tsx +285 -0
- package/brahman/views/tstring-input.tsx +84 -0
- package/cafe/CLAUDE.md +7 -0
- package/cafe/seed.ts +8 -0
- package/cafe/server.ts +2 -0
- package/cafe/types.ts +32 -0
- package/canary/seed.ts +8 -0
- package/canary/server.ts +3 -0
- package/canary/service.ts +101 -0
- package/canary/types.ts +16 -0
- package/dist/board/client.d.ts +2 -0
- package/dist/board/client.d.ts.map +1 -0
- package/dist/board/client.js +2 -0
- package/dist/board/client.js.map +1 -0
- package/dist/board/seed.d.ts +2 -0
- package/dist/board/seed.d.ts.map +1 -0
- package/dist/board/seed.js +23 -0
- package/dist/board/seed.js.map +1 -0
- package/dist/board/server.d.ts +3 -0
- package/dist/board/server.d.ts.map +1 -0
- package/dist/board/server.js +5 -0
- package/dist/board/server.js.map +1 -0
- package/dist/board/types.d.ts +45 -0
- package/dist/board/types.d.ts.map +1 -0
- package/dist/board/types.js +82 -0
- package/dist/board/types.js.map +1 -0
- package/dist/board/view.d.ts +2 -0
- package/dist/board/view.d.ts.map +1 -0
- package/dist/board/view.js +254 -0
- package/dist/board/view.js.map +1 -0
- package/dist/brahman/client.d.ts +3 -0
- package/dist/brahman/client.d.ts.map +1 -0
- package/dist/brahman/client.js +3 -0
- package/dist/brahman/client.js.map +1 -0
- package/dist/brahman/helpers.d.ts +51 -0
- package/dist/brahman/helpers.d.ts.map +1 -0
- package/dist/brahman/helpers.js +321 -0
- package/dist/brahman/helpers.js.map +1 -0
- package/dist/brahman/server.d.ts +3 -0
- package/dist/brahman/server.d.ts.map +1 -0
- package/dist/brahman/server.js +3 -0
- package/dist/brahman/server.js.map +1 -0
- package/dist/brahman/service.d.ts +2 -0
- package/dist/brahman/service.d.ts.map +1 -0
- package/dist/brahman/service.js +310 -0
- package/dist/brahman/service.js.map +1 -0
- package/dist/brahman/types.d.ts +335 -0
- package/dist/brahman/types.d.ts.map +1 -0
- package/dist/brahman/types.js +633 -0
- package/dist/brahman/types.js.map +1 -0
- package/dist/brahman/view.d.ts +2 -0
- package/dist/brahman/view.d.ts.map +1 -0
- package/dist/brahman/view.js +47 -0
- package/dist/brahman/view.js.map +1 -0
- package/dist/brahman/views/action-cards.d.ts +60 -0
- package/dist/brahman/views/action-cards.d.ts.map +1 -0
- package/dist/brahman/views/action-cards.js +283 -0
- package/dist/brahman/views/action-cards.js.map +1 -0
- package/dist/brahman/views/bot-view.d.ts +5 -0
- package/dist/brahman/views/bot-view.d.ts.map +1 -0
- package/dist/brahman/views/bot-view.js +14 -0
- package/dist/brahman/views/bot-view.js.map +1 -0
- package/dist/brahman/views/chat-editor.d.ts +5 -0
- package/dist/brahman/views/chat-editor.d.ts.map +1 -0
- package/dist/brahman/views/chat-editor.js +306 -0
- package/dist/brahman/views/chat-editor.js.map +1 -0
- package/dist/brahman/views/chat-preview.d.ts +5 -0
- package/dist/brahman/views/chat-preview.d.ts.map +1 -0
- package/dist/brahman/views/chat-preview.js +145 -0
- package/dist/brahman/views/chat-preview.js.map +1 -0
- package/dist/brahman/views/menu-editor.d.ts +11 -0
- package/dist/brahman/views/menu-editor.d.ts.map +1 -0
- package/dist/brahman/views/menu-editor.js +171 -0
- package/dist/brahman/views/menu-editor.js.map +1 -0
- package/dist/brahman/views/page-layout.d.ts +5 -0
- package/dist/brahman/views/page-layout.d.ts.map +1 -0
- package/dist/brahman/views/page-layout.js +114 -0
- package/dist/brahman/views/page-layout.js.map +1 -0
- package/dist/brahman/views/tstring-input.d.ts +15 -0
- package/dist/brahman/views/tstring-input.d.ts.map +1 -0
- package/dist/brahman/views/tstring-input.js +23 -0
- package/dist/brahman/views/tstring-input.js.map +1 -0
- package/dist/cafe/seed.d.ts +2 -0
- package/dist/cafe/seed.d.ts.map +1 -0
- package/dist/cafe/seed.js +7 -0
- package/dist/cafe/seed.js.map +1 -0
- package/dist/cafe/server.d.ts +3 -0
- package/dist/cafe/server.d.ts.map +1 -0
- package/dist/cafe/server.js +3 -0
- package/dist/cafe/server.js.map +1 -0
- package/dist/cafe/types.d.ts +2 -0
- package/dist/cafe/types.d.ts.map +1 -0
- package/dist/cafe/types.js +31 -0
- package/dist/cafe/types.js.map +1 -0
- package/dist/canary/seed.d.ts +2 -0
- package/dist/canary/seed.d.ts.map +1 -0
- package/dist/canary/seed.js +7 -0
- package/dist/canary/seed.js.map +1 -0
- package/dist/canary/server.d.ts +4 -0
- package/dist/canary/server.d.ts.map +1 -0
- package/dist/canary/server.js +4 -0
- package/dist/canary/server.js.map +1 -0
- package/dist/canary/service.d.ts +2 -0
- package/dist/canary/service.d.ts.map +1 -0
- package/dist/canary/service.js +101 -0
- package/dist/canary/service.js.map +1 -0
- package/dist/canary/types.d.ts +9 -0
- package/dist/canary/types.d.ts.map +1 -0
- package/dist/canary/types.js +13 -0
- package/dist/canary/types.js.map +1 -0
- package/dist/doc/client.d.ts +3 -0
- package/dist/doc/client.d.ts.map +1 -0
- package/dist/doc/client.js +5 -0
- package/dist/doc/client.js.map +1 -0
- package/dist/doc/fs-codec.d.ts +2 -0
- package/dist/doc/fs-codec.d.ts.map +1 -0
- package/dist/doc/fs-codec.js +44 -0
- package/dist/doc/fs-codec.js.map +1 -0
- package/dist/doc/markdown.d.ts +13 -0
- package/dist/doc/markdown.d.ts.map +1 -0
- package/dist/doc/markdown.js +250 -0
- package/dist/doc/markdown.js.map +1 -0
- package/dist/doc/prefab.d.ts +2 -0
- package/dist/doc/prefab.d.ts.map +1 -0
- package/dist/doc/prefab.js +23 -0
- package/dist/doc/prefab.js.map +1 -0
- package/dist/doc/renderers.d.ts +2 -0
- package/dist/doc/renderers.d.ts.map +1 -0
- package/dist/doc/renderers.js +94 -0
- package/dist/doc/renderers.js.map +1 -0
- package/dist/doc/seed.d.ts +2 -0
- package/dist/doc/seed.d.ts.map +1 -0
- package/dist/doc/seed.js +9 -0
- package/dist/doc/seed.js.map +1 -0
- package/dist/doc/server.d.ts +6 -0
- package/dist/doc/server.d.ts.map +1 -0
- package/dist/doc/server.js +6 -0
- package/dist/doc/server.js.map +1 -0
- package/dist/doc/slash-command.d.ts +3 -0
- package/dist/doc/slash-command.d.ts.map +1 -0
- package/dist/doc/slash-command.js +123 -0
- package/dist/doc/slash-command.js.map +1 -0
- package/dist/doc/slash-menu.d.ts +15 -0
- package/dist/doc/slash-menu.d.ts.map +1 -0
- package/dist/doc/slash-menu.js +54 -0
- package/dist/doc/slash-menu.js.map +1 -0
- package/dist/doc/text.d.ts +2 -0
- package/dist/doc/text.d.ts.map +1 -0
- package/dist/doc/text.js +22 -0
- package/dist/doc/text.js.map +1 -0
- package/dist/doc/toolbar.d.ts +5 -0
- package/dist/doc/toolbar.d.ts.map +1 -0
- package/dist/doc/toolbar.js +15 -0
- package/dist/doc/toolbar.js.map +1 -0
- package/dist/doc/treenity-block-view.d.ts +2 -0
- package/dist/doc/treenity-block-view.d.ts.map +1 -0
- package/dist/doc/treenity-block-view.js +37 -0
- package/dist/doc/treenity-block-view.js.map +1 -0
- package/dist/doc/treenity-block.d.ts +3 -0
- package/dist/doc/treenity-block.d.ts.map +1 -0
- package/dist/doc/treenity-block.js +27 -0
- package/dist/doc/treenity-block.js.map +1 -0
- package/dist/doc/types.d.ts +2 -0
- package/dist/doc/types.d.ts.map +1 -0
- package/dist/doc/types.js +10 -0
- package/dist/doc/types.js.map +1 -0
- package/dist/launcher/client.d.ts +5 -0
- package/dist/launcher/client.d.ts.map +1 -0
- package/dist/launcher/client.js +5 -0
- package/dist/launcher/client.js.map +1 -0
- package/dist/launcher/icons.d.ts +2 -0
- package/dist/launcher/icons.d.ts.map +1 -0
- package/dist/launcher/icons.js +57 -0
- package/dist/launcher/icons.js.map +1 -0
- package/dist/launcher/seed.d.ts +2 -0
- package/dist/launcher/seed.d.ts.map +1 -0
- package/dist/launcher/seed.js +36 -0
- package/dist/launcher/seed.js.map +1 -0
- package/dist/launcher/server.d.ts +3 -0
- package/dist/launcher/server.d.ts.map +1 -0
- package/dist/launcher/server.js +3 -0
- package/dist/launcher/server.js.map +1 -0
- package/dist/launcher/types.d.ts +21 -0
- package/dist/launcher/types.d.ts.map +1 -0
- package/dist/launcher/types.js +47 -0
- package/dist/launcher/types.js.map +1 -0
- package/dist/launcher/view.d.ts +2 -0
- package/dist/launcher/view.d.ts.map +1 -0
- package/dist/launcher/view.js +187 -0
- package/dist/launcher/view.js.map +1 -0
- package/dist/launcher/widgets.d.ts +2 -0
- package/dist/launcher/widgets.d.ts.map +1 -0
- package/dist/launcher/widgets.js +73 -0
- package/dist/launcher/widgets.js.map +1 -0
- package/dist/mindmap/branch.d.ts +17 -0
- package/dist/mindmap/branch.d.ts.map +1 -0
- package/dist/mindmap/branch.js +47 -0
- package/dist/mindmap/branch.js.map +1 -0
- package/dist/mindmap/client.d.ts +3 -0
- package/dist/mindmap/client.d.ts.map +1 -0
- package/dist/mindmap/client.js +3 -0
- package/dist/mindmap/client.js.map +1 -0
- package/dist/mindmap/radial-tree.d.ts +14 -0
- package/dist/mindmap/radial-tree.d.ts.map +1 -0
- package/dist/mindmap/radial-tree.js +184 -0
- package/dist/mindmap/radial-tree.js.map +1 -0
- package/dist/mindmap/sidebar.d.ts +8 -0
- package/dist/mindmap/sidebar.d.ts.map +1 -0
- package/dist/mindmap/sidebar.js +24 -0
- package/dist/mindmap/sidebar.js.map +1 -0
- package/dist/mindmap/types.d.ts +8 -0
- package/dist/mindmap/types.d.ts.map +1 -0
- package/dist/mindmap/types.js +10 -0
- package/dist/mindmap/types.js.map +1 -0
- package/dist/mindmap/use-tree-data.d.ts +14 -0
- package/dist/mindmap/use-tree-data.d.ts.map +1 -0
- package/dist/mindmap/use-tree-data.js +95 -0
- package/dist/mindmap/use-tree-data.js.map +1 -0
- package/dist/mindmap/view.d.ts +3 -0
- package/dist/mindmap/view.d.ts.map +1 -0
- package/dist/mindmap/view.js +141 -0
- package/dist/mindmap/view.js.map +1 -0
- package/dist/sensor-demo/client.d.ts +2 -0
- package/dist/sensor-demo/client.d.ts.map +1 -0
- package/dist/sensor-demo/client.js +2 -0
- package/dist/sensor-demo/client.js.map +1 -0
- package/dist/sensor-demo/server.d.ts +2 -0
- package/dist/sensor-demo/server.d.ts.map +1 -0
- package/dist/sensor-demo/server.js +2 -0
- package/dist/sensor-demo/server.js.map +1 -0
- package/dist/sensor-demo/service.d.ts +2 -0
- package/dist/sensor-demo/service.d.ts.map +1 -0
- package/dist/sensor-demo/service.js +27 -0
- package/dist/sensor-demo/service.js.map +1 -0
- package/dist/sensor-demo/types.d.ts +15 -0
- package/dist/sensor-demo/types.d.ts.map +1 -0
- package/dist/sensor-demo/types.js +18 -0
- package/dist/sensor-demo/types.js.map +1 -0
- package/dist/sensor-demo/view.d.ts +2 -0
- package/dist/sensor-demo/view.d.ts.map +1 -0
- package/dist/sensor-demo/view.js +33 -0
- package/dist/sensor-demo/view.js.map +1 -0
- package/dist/sensor-generator/action.d.ts +2 -0
- package/dist/sensor-generator/action.d.ts.map +1 -0
- package/dist/sensor-generator/action.js +23 -0
- package/dist/sensor-generator/action.js.map +1 -0
- package/dist/sensor-generator/client.d.ts +2 -0
- package/dist/sensor-generator/client.d.ts.map +1 -0
- package/dist/sensor-generator/client.js +2 -0
- package/dist/sensor-generator/client.js.map +1 -0
- package/dist/sensor-generator/server.d.ts +2 -0
- package/dist/sensor-generator/server.d.ts.map +1 -0
- package/dist/sensor-generator/server.js +2 -0
- package/dist/sensor-generator/server.js.map +1 -0
- package/dist/sensor-generator/view.d.ts +2 -0
- package/dist/sensor-generator/view.d.ts.map +1 -0
- package/dist/sensor-generator/view.js +64 -0
- package/dist/sensor-generator/view.js.map +1 -0
- package/dist/sim/client.d.ts +2 -0
- package/dist/sim/client.d.ts.map +1 -0
- package/dist/sim/client.js +2 -0
- package/dist/sim/client.js.map +1 -0
- package/dist/sim/seed.d.ts +2 -0
- package/dist/sim/seed.d.ts.map +1 -0
- package/dist/sim/seed.js +50 -0
- package/dist/sim/seed.js.map +1 -0
- package/dist/sim/server.d.ts +3 -0
- package/dist/sim/server.d.ts.map +1 -0
- package/dist/sim/server.js +3 -0
- package/dist/sim/server.js.map +1 -0
- package/dist/sim/service.d.ts +4 -0
- package/dist/sim/service.d.ts.map +1 -0
- package/dist/sim/service.js +528 -0
- package/dist/sim/service.js.map +1 -0
- package/dist/sim/types.d.ts +63 -0
- package/dist/sim/types.d.ts.map +1 -0
- package/dist/sim/types.js +57 -0
- package/dist/sim/types.js.map +1 -0
- package/dist/sim/view.d.ts +2 -0
- package/dist/sim/view.d.ts.map +1 -0
- package/dist/sim/view.js +205 -0
- package/dist/sim/view.js.map +1 -0
- package/dist/table/client.d.ts +4 -0
- package/dist/table/client.d.ts.map +1 -0
- package/dist/table/client.js +4 -0
- package/dist/table/client.js.map +1 -0
- package/dist/table/edit.d.ts +2 -0
- package/dist/table/edit.d.ts.map +1 -0
- package/dist/table/edit.js +115 -0
- package/dist/table/edit.js.map +1 -0
- package/dist/table/server.d.ts +2 -0
- package/dist/table/server.d.ts.map +1 -0
- package/dist/table/server.js +2 -0
- package/dist/table/server.js.map +1 -0
- package/dist/table/types.d.ts +18 -0
- package/dist/table/types.d.ts.map +1 -0
- package/dist/table/types.js +13 -0
- package/dist/table/types.js.map +1 -0
- package/dist/table/use-debounced-sync.d.ts +8 -0
- package/dist/table/use-debounced-sync.d.ts.map +1 -0
- package/dist/table/use-debounced-sync.js +56 -0
- package/dist/table/use-debounced-sync.js.map +1 -0
- package/dist/table/view.d.ts +2 -0
- package/dist/table/view.d.ts.map +1 -0
- package/dist/table/view.js +199 -0
- package/dist/table/view.js.map +1 -0
- package/dist/tasks/server.d.ts +2 -0
- package/dist/tasks/server.d.ts.map +1 -0
- package/dist/tasks/server.js +2 -0
- package/dist/tasks/server.js.map +1 -0
- package/dist/tasks/types.d.ts +22 -0
- package/dist/tasks/types.d.ts.map +1 -0
- package/dist/tasks/types.js +34 -0
- package/dist/tasks/types.js.map +1 -0
- package/dist/three/client.d.ts +2 -0
- package/dist/three/client.d.ts.map +1 -0
- package/dist/three/client.js +2 -0
- package/dist/three/client.js.map +1 -0
- package/dist/three/seed.d.ts +2 -0
- package/dist/three/seed.d.ts.map +1 -0
- package/dist/three/seed.js +45 -0
- package/dist/three/seed.js.map +1 -0
- package/dist/three/server.d.ts +3 -0
- package/dist/three/server.d.ts.map +1 -0
- package/dist/three/server.js +3 -0
- package/dist/three/server.js.map +1 -0
- package/dist/three/types.d.ts +178 -0
- package/dist/three/types.d.ts.map +1 -0
- package/dist/three/types.js +209 -0
- package/dist/three/types.js.map +1 -0
- package/dist/three/view.d.ts +2 -0
- package/dist/three/view.d.ts.map +1 -0
- package/dist/three/view.js +307 -0
- package/dist/three/view.js.map +1 -0
- package/dist/todo/client.d.ts +3 -0
- package/dist/todo/client.d.ts.map +1 -0
- package/dist/todo/client.js +3 -0
- package/dist/todo/client.js.map +1 -0
- package/dist/todo/seed.d.ts +2 -0
- package/dist/todo/seed.d.ts.map +1 -0
- package/dist/todo/seed.js +8 -0
- package/dist/todo/seed.js.map +1 -0
- package/dist/todo/server.d.ts +3 -0
- package/dist/todo/server.d.ts.map +1 -0
- package/dist/todo/server.js +3 -0
- package/dist/todo/server.js.map +1 -0
- package/dist/todo/types.d.ts +15 -0
- package/dist/todo/types.d.ts.map +1 -0
- package/dist/todo/types.js +29 -0
- package/dist/todo/types.js.map +1 -0
- package/dist/todo/view.d.ts +2 -0
- package/dist/todo/view.d.ts.map +1 -0
- package/dist/todo/view.js +27 -0
- package/dist/todo/view.js.map +1 -0
- package/dist/whisper/client.d.ts +2 -0
- package/dist/whisper/client.d.ts.map +1 -0
- package/dist/whisper/client.js +2 -0
- package/dist/whisper/client.js.map +1 -0
- package/dist/whisper/inbox.d.ts +2 -0
- package/dist/whisper/inbox.d.ts.map +1 -0
- package/dist/whisper/inbox.js +50 -0
- package/dist/whisper/inbox.js.map +1 -0
- package/dist/whisper/route.d.ts +10 -0
- package/dist/whisper/route.d.ts.map +1 -0
- package/dist/whisper/route.js +154 -0
- package/dist/whisper/route.js.map +1 -0
- package/dist/whisper/seed.d.ts +2 -0
- package/dist/whisper/seed.d.ts.map +1 -0
- package/dist/whisper/seed.js +14 -0
- package/dist/whisper/seed.js.map +1 -0
- package/dist/whisper/server.d.ts +5 -0
- package/dist/whisper/server.d.ts.map +1 -0
- package/dist/whisper/server.js +5 -0
- package/dist/whisper/server.js.map +1 -0
- package/dist/whisper/service.d.ts +2 -0
- package/dist/whisper/service.d.ts.map +1 -0
- package/dist/whisper/service.js +26 -0
- package/dist/whisper/service.js.map +1 -0
- package/dist/whisper/types.d.ts +38 -0
- package/dist/whisper/types.d.ts.map +1 -0
- package/dist/whisper/types.js +45 -0
- package/dist/whisper/types.js.map +1 -0
- package/dist/whisper/view.d.ts +2 -0
- package/dist/whisper/view.d.ts.map +1 -0
- package/dist/whisper/view.js +40 -0
- package/dist/whisper/view.js.map +1 -0
- package/doc/CLAUDE.md +49 -0
- package/doc/client.ts +5 -0
- package/doc/editor.css +283 -0
- package/doc/fs-codec.test.ts +119 -0
- package/doc/fs-codec.ts +50 -0
- package/doc/markdown.test.ts +152 -0
- package/doc/markdown.ts +284 -0
- package/doc/prefab.ts +26 -0
- package/doc/renderers.tsx +126 -0
- package/doc/seed.ts +10 -0
- package/doc/server.ts +5 -0
- package/doc/slash-command.ts +136 -0
- package/doc/slash-menu.tsx +91 -0
- package/doc/text.ts +20 -0
- package/doc/toolbar.tsx +86 -0
- package/doc/treenity-block-view.tsx +116 -0
- package/doc/treenity-block.ts +31 -0
- package/doc/types.ts +10 -0
- package/launcher/client.ts +4 -0
- package/launcher/icons.tsx +234 -0
- package/launcher/launcher.css +64 -0
- package/launcher/seed.ts +41 -0
- package/launcher/server.ts +2 -0
- package/launcher/types.ts +53 -0
- package/launcher/view.tsx +401 -0
- package/launcher/widgets.tsx +171 -0
- package/mindmap/branch.tsx +163 -0
- package/mindmap/client.ts +2 -0
- package/mindmap/mindmap.css +243 -0
- package/mindmap/sidebar.tsx +127 -0
- package/mindmap/types.ts +10 -0
- package/mindmap/view.tsx +247 -0
- package/package.json +75 -0
- package/sensor-demo/CLAUDE.md +3 -0
- package/sensor-demo/client.ts +1 -0
- package/sensor-demo/server.ts +1 -0
- package/sensor-demo/service.ts +27 -0
- package/sensor-demo/types.ts +20 -0
- package/sensor-demo/view.tsx +64 -0
- package/sensor-generator/CLAUDE.md +3 -0
- package/sensor-generator/action.ts +28 -0
- package/sensor-generator/client.ts +1 -0
- package/sensor-generator/server.ts +1 -0
- package/sensor-generator/view.tsx +107 -0
- package/sim/CLAUDE.md +16 -0
- package/sim/client.ts +1 -0
- package/sim/seed.ts +55 -0
- package/sim/server.ts +2 -0
- package/sim/service.ts +594 -0
- package/sim/sim.test.ts +282 -0
- package/sim/types.ts +87 -0
- package/sim/view.tsx +446 -0
- package/table/client.ts +3 -0
- package/table/edit.tsx +241 -0
- package/table/server.ts +1 -0
- package/table/types.ts +22 -0
- package/table/use-debounced-sync.ts +67 -0
- package/table/view.tsx +339 -0
- package/tasks/CLAUDE.md +6 -0
- package/tasks/server.ts +1 -0
- package/tasks/types.ts +36 -0
- package/three/CLAUDE.md +6 -0
- package/three/client.ts +1 -0
- package/three/seed.ts +54 -0
- package/three/server.ts +2 -0
- package/three/types.ts +238 -0
- package/three/view.tsx +453 -0
- package/todo/client.ts +2 -0
- package/todo/seed.ts +9 -0
- package/todo/server.ts +2 -0
- package/todo/types.ts +32 -0
- package/todo/view.tsx +67 -0
- package/whisper/CLAUDE.md +16 -0
- package/whisper/client.ts +1 -0
- package/whisper/inbox.ts +54 -0
- package/whisper/route.ts +182 -0
- package/whisper/seed.ts +15 -0
- package/whisper/server.ts +4 -0
- package/whisper/service.ts +29 -0
- package/whisper/types.ts +51 -0
- package/whisper/view.tsx +81 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
// Launcher view — dashboard with drag-drop from tree + context switching
|
|
2
|
+
|
|
3
|
+
import { isRef, type NodeData, register, resolveExact } from '@treenity/core';
|
|
4
|
+
import { Render, RenderContext, type View } from '@treenity/react/context';
|
|
5
|
+
import { useChildren, useNavigate, usePath } from '@treenity/react/hooks';
|
|
6
|
+
import { cn } from '@treenity/react/lib/utils';
|
|
7
|
+
import { GenerateViewButton } from '@treenity/react/mods/editor-ui/default-view';
|
|
8
|
+
import { Button } from '@treenity/react/ui/button';
|
|
9
|
+
import {
|
|
10
|
+
DropdownMenu,
|
|
11
|
+
DropdownMenuContent,
|
|
12
|
+
DropdownMenuItem,
|
|
13
|
+
DropdownMenuTrigger,
|
|
14
|
+
} from '@treenity/react/ui/dropdown-menu';
|
|
15
|
+
import { GripVertical, Plus, X } from 'lucide-react';
|
|
16
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
17
|
+
import GridLayout from 'react-grid-layout';
|
|
18
|
+
import 'react-grid-layout/css/styles.css';
|
|
19
|
+
import { Launcher } from './types';
|
|
20
|
+
|
|
21
|
+
type LauncherLayoutItem = { i: string; x: number; y: number; w: number; h: number; ctx?: string };
|
|
22
|
+
|
|
23
|
+
// Available rendering contexts for the selector
|
|
24
|
+
const CONTEXTS = ['auto', 'react', 'react:icon', 'react:widget', 'react:compact'] as const;
|
|
25
|
+
|
|
26
|
+
function contextLabel(ctx: string) {
|
|
27
|
+
if (ctx === 'auto') return 'Auto';
|
|
28
|
+
if (ctx === 'react') return 'Full';
|
|
29
|
+
if (ctx === 'react:icon') return 'Icon';
|
|
30
|
+
if (ctx === 'react:widget') return 'Widget';
|
|
31
|
+
if (ctx === 'react:compact') return 'Compact';
|
|
32
|
+
return ctx;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Resolve which context to render based on layout item
|
|
36
|
+
function resolveContext(item: LauncherLayoutItem): string {
|
|
37
|
+
if (item.ctx && item.ctx !== 'auto') return item.ctx;
|
|
38
|
+
return item.w > 1 || item.h > 1 ? 'react:widget' : 'react:icon';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Edit overlay — close + context dropdown menu ──
|
|
42
|
+
|
|
43
|
+
function EditOverlay({
|
|
44
|
+
ctx,
|
|
45
|
+
onRemove,
|
|
46
|
+
onContextChange,
|
|
47
|
+
}: {
|
|
48
|
+
ctx: string;
|
|
49
|
+
onRemove: () => void;
|
|
50
|
+
onContextChange: (ctx: string) => void;
|
|
51
|
+
}) {
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
<button
|
|
55
|
+
className="launcher-btn absolute -right-1 -top-1 z-10 flex h-5 w-5 items-center justify-center rounded-full bg-red-700 shadow-lg p-0"
|
|
56
|
+
onClick={(e) => { e.stopPropagation(); onRemove(); }}
|
|
57
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
58
|
+
>
|
|
59
|
+
<X className="h-3 w-3 text-white" />
|
|
60
|
+
</button>
|
|
61
|
+
|
|
62
|
+
<DropdownMenu>
|
|
63
|
+
<DropdownMenuTrigger asChild>
|
|
64
|
+
<button
|
|
65
|
+
className="launcher-btn absolute -left-1 -top-1 z-10 flex h-auto min-w-5 items-center justify-center rounded-full bg-blue-600 px-1.5 py-0.5 shadow-lg text-[9px] font-medium text-white"
|
|
66
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
67
|
+
>
|
|
68
|
+
{contextLabel(ctx)}
|
|
69
|
+
</button>
|
|
70
|
+
</DropdownMenuTrigger>
|
|
71
|
+
<DropdownMenuContent align="start" className="min-w-24">
|
|
72
|
+
{CONTEXTS.map((c) => (
|
|
73
|
+
<DropdownMenuItem
|
|
74
|
+
key={c}
|
|
75
|
+
className={cn('text-xs', c === ctx && 'font-bold')}
|
|
76
|
+
onSelect={() => onContextChange(c)}
|
|
77
|
+
>
|
|
78
|
+
{contextLabel(c)}
|
|
79
|
+
</DropdownMenuItem>
|
|
80
|
+
))}
|
|
81
|
+
</DropdownMenuContent>
|
|
82
|
+
</DropdownMenu>
|
|
83
|
+
</>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Stored ctx or 'auto' if none
|
|
88
|
+
function getStoredCtx(item: LauncherLayoutItem): string {
|
|
89
|
+
return item.ctx || 'auto';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── App item — resolves ref target and renders via context ──
|
|
93
|
+
|
|
94
|
+
function AppItem({
|
|
95
|
+
refNode,
|
|
96
|
+
context,
|
|
97
|
+
stored,
|
|
98
|
+
editing,
|
|
99
|
+
onRemove,
|
|
100
|
+
onContextChange,
|
|
101
|
+
}: {
|
|
102
|
+
refNode: NodeData;
|
|
103
|
+
context: string;
|
|
104
|
+
stored: string;
|
|
105
|
+
editing?: boolean;
|
|
106
|
+
onRemove?: () => void;
|
|
107
|
+
onContextChange?: (ctx: string) => void;
|
|
108
|
+
}) {
|
|
109
|
+
const targetPath = isRef(refNode) ? refNode.$ref : null;
|
|
110
|
+
const target = usePath(targetPath) as NodeData | undefined;
|
|
111
|
+
const navigate = useNavigate();
|
|
112
|
+
|
|
113
|
+
if (!target) {
|
|
114
|
+
return (
|
|
115
|
+
<div className="flex h-full w-full items-center justify-center rounded-2xl bg-zinc-700/50">
|
|
116
|
+
<span className="text-xs text-zinc-400">...</span>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const label = target.$path.split('/').at(-1) || '?';
|
|
122
|
+
const hasView = !!resolveExact(target.$type, context as 'react');
|
|
123
|
+
|
|
124
|
+
// No view registered for this context — offer AI generation
|
|
125
|
+
const renderContent = hasView
|
|
126
|
+
? <RenderContext name={context}><Render value={target} /></RenderContext>
|
|
127
|
+
: <GenerateViewButton type={target.$type} sample={target} context={context} label="Make AI" />;
|
|
128
|
+
|
|
129
|
+
// Icon-style: centered icon + label
|
|
130
|
+
if (context === 'react:icon') {
|
|
131
|
+
return (
|
|
132
|
+
<div
|
|
133
|
+
className="relative flex h-full w-full cursor-pointer flex-col items-center justify-center gap-1 overflow-visible"
|
|
134
|
+
onClick={() => !editing && navigate(target.$path)}
|
|
135
|
+
>
|
|
136
|
+
{editing && onRemove && onContextChange && (
|
|
137
|
+
<EditOverlay ctx={stored} onRemove={onRemove} onContextChange={onContextChange} />
|
|
138
|
+
)}
|
|
139
|
+
<div className="h-14 w-14 shrink-0">
|
|
140
|
+
{renderContent}
|
|
141
|
+
</div>
|
|
142
|
+
<span className="max-w-16 truncate text-center text-[11px] font-medium text-white/90 drop-shadow-sm">
|
|
143
|
+
{label}
|
|
144
|
+
</span>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Card-style: glass card with content
|
|
150
|
+
return (
|
|
151
|
+
<div
|
|
152
|
+
className="launcher-glass relative h-full w-full cursor-pointer overflow-visible rounded-2xl border border-white/10 bg-white/10 p-3"
|
|
153
|
+
onClick={() => !editing && navigate(target.$path)}
|
|
154
|
+
>
|
|
155
|
+
{editing && onRemove && onContextChange && (
|
|
156
|
+
<EditOverlay ctx={stored} onRemove={onRemove} onContextChange={onContextChange} />
|
|
157
|
+
)}
|
|
158
|
+
<div className="mb-1 text-[10px] font-semibold uppercase tracking-wider text-white/50">
|
|
159
|
+
{label}
|
|
160
|
+
</div>
|
|
161
|
+
<div className="h-[calc(100%-1.5rem)] overflow-hidden text-white">
|
|
162
|
+
{renderContent}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Drop zone overlay ──
|
|
169
|
+
|
|
170
|
+
function DropZone({ visible }: { visible: boolean }) {
|
|
171
|
+
if (!visible) return null;
|
|
172
|
+
return (
|
|
173
|
+
<div className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center rounded-2xl border-2 border-dashed border-blue-400/60 bg-blue-500/10 backdrop-blur-sm">
|
|
174
|
+
<div className="flex flex-col items-center gap-1 text-blue-300">
|
|
175
|
+
<Plus className="h-8 w-8" />
|
|
176
|
+
<span className="text-sm font-medium">Drop node here</span>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Main Launcher View ──
|
|
183
|
+
|
|
184
|
+
const LauncherView: View<NodeData> = ({ value }) => {
|
|
185
|
+
const launcher = usePath(value.$path, Launcher);
|
|
186
|
+
const children = useChildren(value.$path, { watch: true, watchNew: true });
|
|
187
|
+
|
|
188
|
+
const [editing, setEditing] = useState(false);
|
|
189
|
+
const [dragOver, setDragOver] = useState(false);
|
|
190
|
+
|
|
191
|
+
// Measure container width for RGL
|
|
192
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
193
|
+
const [gridWidth, setGridWidth] = useState(400);
|
|
194
|
+
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
const el = containerRef.current;
|
|
197
|
+
if (!el) return;
|
|
198
|
+
const ro = new ResizeObserver((entries) => {
|
|
199
|
+
const w = entries[0]?.contentRect.width;
|
|
200
|
+
if (w && w > 0) setGridWidth(w);
|
|
201
|
+
});
|
|
202
|
+
ro.observe(el);
|
|
203
|
+
return () => ro.disconnect();
|
|
204
|
+
}, []);
|
|
205
|
+
|
|
206
|
+
// Parse layout
|
|
207
|
+
const columns = typeof launcher?.columns === 'number' ? launcher.columns : 4;
|
|
208
|
+
const wallpaper = typeof launcher?.wallpaper === 'string' ? launcher.wallpaper : '';
|
|
209
|
+
const layoutStr = typeof launcher?.layout === 'string' ? launcher.layout : '[]';
|
|
210
|
+
|
|
211
|
+
let layoutItems: LauncherLayoutItem[];
|
|
212
|
+
try {
|
|
213
|
+
layoutItems = JSON.parse(layoutStr);
|
|
214
|
+
} catch {
|
|
215
|
+
layoutItems = [];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Build child map
|
|
219
|
+
const childMap = new Map<string, NodeData>();
|
|
220
|
+
for (const c of children) {
|
|
221
|
+
const id = c.$path.split('/').at(-1) || '';
|
|
222
|
+
childMap.set(id, c);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Auto-generate layout for children without layout entries
|
|
226
|
+
const layoutIds = new Set(layoutItems.map((l) => l.i));
|
|
227
|
+
let nextY = layoutItems.reduce((m, l) => Math.max(m, l.y + l.h), 0);
|
|
228
|
+
let nextX = 0;
|
|
229
|
+
|
|
230
|
+
for (const c of children) {
|
|
231
|
+
const id = c.$path.split('/').at(-1) || '';
|
|
232
|
+
if (!layoutIds.has(id)) {
|
|
233
|
+
layoutItems.push({ i: id, x: nextX, y: nextY, w: 1, h: 1 });
|
|
234
|
+
nextX++;
|
|
235
|
+
if (nextX >= columns) {
|
|
236
|
+
nextX = 0;
|
|
237
|
+
nextY++;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const rowHeight = 90;
|
|
243
|
+
|
|
244
|
+
const persistLayout = useCallback(
|
|
245
|
+
(newLayout: readonly LauncherLayoutItem[]) => {
|
|
246
|
+
// Preserve ctx from current layoutItems
|
|
247
|
+
const ctxMap = new Map(layoutItems.map((l) => [l.i, l.ctx]));
|
|
248
|
+
const clean = newLayout.map(({ i, x, y, w, h }) => ({
|
|
249
|
+
i, x, y, w, h,
|
|
250
|
+
...(ctxMap.get(i) ? { ctx: ctxMap.get(i) } : {}),
|
|
251
|
+
}));
|
|
252
|
+
launcher.updateLayout({ layout: JSON.stringify(clean) });
|
|
253
|
+
},
|
|
254
|
+
[launcher, layoutItems],
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const handleRemove = (id: string) => {
|
|
258
|
+
launcher.removeApp({ id });
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const handleContextChange = (id: string, ctx: string) => {
|
|
262
|
+
const updated = layoutItems.map((l) =>
|
|
263
|
+
l.i === id ? { ...l, ctx: ctx === 'auto' ? undefined : ctx } : l,
|
|
264
|
+
);
|
|
265
|
+
launcher.updateLayout({ layout: JSON.stringify(updated) });
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// ── External drop from tree sidebar ──
|
|
269
|
+
|
|
270
|
+
const handleExternalDrop = useCallback(
|
|
271
|
+
(_layout: unknown, _item: unknown, e: Event) => {
|
|
272
|
+
const de = e as DragEvent;
|
|
273
|
+
const path = de.dataTransfer?.getData('application/treenity-path')
|
|
274
|
+
|| de.dataTransfer?.getData('text/plain');
|
|
275
|
+
if (!path || !path.startsWith('/')) return;
|
|
276
|
+
launcher.addApp({ path });
|
|
277
|
+
setDragOver(false);
|
|
278
|
+
},
|
|
279
|
+
[value.$path],
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// Container-level drop for when grid is empty or drop misses grid items
|
|
283
|
+
const handleContainerDragOver = useCallback((e: React.DragEvent) => {
|
|
284
|
+
if (e.dataTransfer.types.includes('application/treenity-path')) {
|
|
285
|
+
e.preventDefault();
|
|
286
|
+
setDragOver(true);
|
|
287
|
+
}
|
|
288
|
+
}, []);
|
|
289
|
+
|
|
290
|
+
const handleContainerDrop = useCallback(
|
|
291
|
+
(e: React.DragEvent) => {
|
|
292
|
+
e.preventDefault();
|
|
293
|
+
setDragOver(false);
|
|
294
|
+
const path = e.dataTransfer.getData('application/treenity-path')
|
|
295
|
+
|| e.dataTransfer.getData('text/plain');
|
|
296
|
+
if (!path || !path.startsWith('/')) return;
|
|
297
|
+
launcher.addApp({ path });
|
|
298
|
+
},
|
|
299
|
+
[launcher],
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<div
|
|
304
|
+
className={cn(
|
|
305
|
+
'view-full relative min-h-screen select-none overflow-auto',
|
|
306
|
+
editing && 'launcher-editing',
|
|
307
|
+
)}
|
|
308
|
+
style={{ background: wallpaper || undefined }}
|
|
309
|
+
onDragOver={handleContainerDragOver}
|
|
310
|
+
onDragLeave={() => setDragOver(false)}
|
|
311
|
+
onDrop={handleContainerDrop}
|
|
312
|
+
>
|
|
313
|
+
{/* Status bar */}
|
|
314
|
+
<div className="flex items-center justify-between px-5 pb-2 pt-3">
|
|
315
|
+
<span className="text-sm font-semibold text-white/80">Treenity</span>
|
|
316
|
+
<Button
|
|
317
|
+
size="sm"
|
|
318
|
+
variant="ghost"
|
|
319
|
+
className="rounded-full text-xs text-white/60"
|
|
320
|
+
onClick={(e) => {
|
|
321
|
+
e.stopPropagation();
|
|
322
|
+
setEditing(!editing);
|
|
323
|
+
}}
|
|
324
|
+
>
|
|
325
|
+
{editing ? 'Done' : 'Edit'}
|
|
326
|
+
</Button>
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
{/* Drop zone overlay */}
|
|
330
|
+
<DropZone visible={dragOver && children.length === 0} />
|
|
331
|
+
|
|
332
|
+
{/* Grid */}
|
|
333
|
+
<div className="launcher-grid px-3 relative" ref={containerRef}>
|
|
334
|
+
<GridLayout
|
|
335
|
+
layout={layoutItems}
|
|
336
|
+
width={gridWidth}
|
|
337
|
+
gridConfig={{
|
|
338
|
+
cols: columns,
|
|
339
|
+
rowHeight,
|
|
340
|
+
margin: [12, 12] as const,
|
|
341
|
+
containerPadding: null,
|
|
342
|
+
maxRows: Infinity,
|
|
343
|
+
}}
|
|
344
|
+
dragConfig={{ enabled: editing, handle: '.launcher-item', cancel: '.launcher-btn' }}
|
|
345
|
+
resizeConfig={{ enabled: editing }}
|
|
346
|
+
dropConfig={{
|
|
347
|
+
enabled: true,
|
|
348
|
+
defaultItem: { w: 1, h: 1 },
|
|
349
|
+
}}
|
|
350
|
+
onDragStop={(layout) => persistLayout(layout)}
|
|
351
|
+
onResizeStop={(layout) => persistLayout(layout)}
|
|
352
|
+
onDrop={handleExternalDrop}
|
|
353
|
+
>
|
|
354
|
+
{layoutItems.map((item) => {
|
|
355
|
+
const child = childMap.get(item.i);
|
|
356
|
+
if (!child) return <div key={item.i} />;
|
|
357
|
+
|
|
358
|
+
return (
|
|
359
|
+
<div key={item.i} className="launcher-item h-full overflow-visible">
|
|
360
|
+
<div
|
|
361
|
+
className={cn(
|
|
362
|
+
'h-full overflow-visible transition-transform duration-300',
|
|
363
|
+
editing && 'launcher-edit-item',
|
|
364
|
+
)}
|
|
365
|
+
>
|
|
366
|
+
<AppItem
|
|
367
|
+
refNode={child}
|
|
368
|
+
context={resolveContext(item)}
|
|
369
|
+
stored={getStoredCtx(item)}
|
|
370
|
+
editing={editing}
|
|
371
|
+
onRemove={() => handleRemove(item.i)}
|
|
372
|
+
onContextChange={(ctx) => handleContextChange(item.i, ctx)}
|
|
373
|
+
/>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
);
|
|
377
|
+
})}
|
|
378
|
+
</GridLayout>
|
|
379
|
+
|
|
380
|
+
{/* Inline drop hint when grid has items */}
|
|
381
|
+
{dragOver && children.length > 0 && (
|
|
382
|
+
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex justify-center py-4">
|
|
383
|
+
<div className="rounded-full bg-blue-500/30 border border-blue-400/50 px-4 py-2 text-sm text-blue-200 backdrop-blur-sm">
|
|
384
|
+
Drop to add to launcher
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
)}
|
|
388
|
+
</div>
|
|
389
|
+
|
|
390
|
+
{/* Empty state */}
|
|
391
|
+
{children.length === 0 && !dragOver && (
|
|
392
|
+
<div className="flex flex-col items-center justify-center gap-3 py-20 text-white/40">
|
|
393
|
+
<GripVertical className="h-12 w-12" />
|
|
394
|
+
<p className="text-sm">Drag nodes from the tree to add them here</p>
|
|
395
|
+
</div>
|
|
396
|
+
)}
|
|
397
|
+
</div>
|
|
398
|
+
);
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
register('launcher', 'react', LauncherView);
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Launcher widgets — react:widget context for compact live views on the home screen
|
|
2
|
+
|
|
3
|
+
import { type NodeData, register } from '@treenity/core';
|
|
4
|
+
import type { RenderProps } from '@treenity/react/context';
|
|
5
|
+
import { useChildren, usePath } from '@treenity/react/hooks';
|
|
6
|
+
import { cn } from '@treenity/react/lib/utils';
|
|
7
|
+
import type { FC } from 'react';
|
|
8
|
+
import { TodoItem } from '../todo/types';
|
|
9
|
+
|
|
10
|
+
const KanbanWidget: FC<RenderProps> = ({ value }) => {
|
|
11
|
+
const node = value as NodeData;
|
|
12
|
+
return <KanbanWidgetInner path={node.$path} />;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Separate component to use hooks properly per column
|
|
16
|
+
function KanbanWidgetInner({ path }: { path: string }) {
|
|
17
|
+
const columns = useChildren(path, { watch: true, watchNew: true })
|
|
18
|
+
.filter(c => c.$type === 'board.column')
|
|
19
|
+
.sort((a, b) => ((a as any).order ?? 0) - ((b as any).order ?? 0));
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex h-full flex-col justify-between gap-1">
|
|
23
|
+
{columns.map(col => (
|
|
24
|
+
<ColumnRow key={col.$path} col={col} />
|
|
25
|
+
))}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ColumnRow({ col }: { col: NodeData }) {
|
|
31
|
+
const tasks = useChildren(col.$path, { watch: true, watchNew: true });
|
|
32
|
+
const label = typeof col.label === 'string' ? col.label : col.$path.split('/').at(-1) || '?';
|
|
33
|
+
const color = typeof col.color === 'string' ? col.color : 'border-zinc-400';
|
|
34
|
+
// Extract bg color from border color class
|
|
35
|
+
const dotColor = color.replace('border-', 'bg-');
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="flex items-center gap-2">
|
|
39
|
+
<span className={cn('h-2 w-2 shrink-0 rounded-full', dotColor)} />
|
|
40
|
+
<span className="flex-1 truncate text-[11px] text-white/70">{label}</span>
|
|
41
|
+
<span className="text-xs font-semibold text-white/90">{tasks.length}</span>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
register('board.kanban', 'react:widget', KanbanWidget as any);
|
|
47
|
+
|
|
48
|
+
// ── todo.list widget — compact checklist ──
|
|
49
|
+
|
|
50
|
+
const TodoWidget: FC<RenderProps> = ({ value }) => {
|
|
51
|
+
const node = value as NodeData;
|
|
52
|
+
// value might be the todo dir — find the list child
|
|
53
|
+
const children = useChildren(node.$path, { watch: true, watchNew: true });
|
|
54
|
+
const list = children.find(c => c.$type === 'todo.list');
|
|
55
|
+
|
|
56
|
+
if (list) return <TodoListItems path={list.$path} />;
|
|
57
|
+
|
|
58
|
+
// value IS the list
|
|
59
|
+
return <TodoListItems path={node.$path} />;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function TodoListItems({ path }: { path: string }) {
|
|
63
|
+
const items = useChildren(path, { watch: true, watchNew: true });
|
|
64
|
+
const doneCount = items.filter(i => i.done).length;
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div className="flex h-full flex-col">
|
|
68
|
+
<div className="mb-1 text-[10px] text-white/50">
|
|
69
|
+
{doneCount}/{items.length} done
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div className="flex flex-1 flex-col gap-0.5 overflow-hidden">
|
|
73
|
+
{items.slice(0, 5).map(item => (
|
|
74
|
+
<TodoItemCompact key={item.$path} value={item} />
|
|
75
|
+
))}
|
|
76
|
+
{items.length > 5 && (
|
|
77
|
+
<span className="text-[10px] text-white/40">+{items.length - 5} more</span>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function TodoItemCompact({ value }: { value: NodeData }) {
|
|
85
|
+
const item = usePath(value.$path, TodoItem);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className="flex items-center gap-1.5">
|
|
89
|
+
<span className={cn(
|
|
90
|
+
'flex h-3 w-3 shrink-0 items-center justify-center rounded-sm border text-[8px]',
|
|
91
|
+
item.done
|
|
92
|
+
? 'border-green-400/50 bg-green-500/30 text-green-300'
|
|
93
|
+
: 'border-white/20',
|
|
94
|
+
)}>
|
|
95
|
+
{item.done ? '✓' : ''}
|
|
96
|
+
</span>
|
|
97
|
+
<span className={cn(
|
|
98
|
+
'truncate text-[11px]',
|
|
99
|
+
item.done ? 'text-white/30 line-through' : 'text-white/80',
|
|
100
|
+
)}>
|
|
101
|
+
{item.title}
|
|
102
|
+
</span>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
register('todo.list', 'react:widget', TodoWidget as any);
|
|
108
|
+
|
|
109
|
+
// ── examples.demo.sensor widget — last value + mini bars ──
|
|
110
|
+
|
|
111
|
+
const SensorWidget: FC<RenderProps> = ({ value }) => {
|
|
112
|
+
const node = value as NodeData;
|
|
113
|
+
const children = useChildren(node.$path, { watch: true, watchNew: true });
|
|
114
|
+
const last5 = children.slice(-5);
|
|
115
|
+
const latest = last5[last5.length - 1];
|
|
116
|
+
|
|
117
|
+
const latestVal = typeof latest?.value === 'number' ? latest.value : null;
|
|
118
|
+
const latestTs = typeof latest?.ts === 'number' ? new Date(latest.ts).toLocaleTimeString() : '--:--';
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div className="flex h-full items-center gap-3">
|
|
122
|
+
{/* Big number */}
|
|
123
|
+
<div className="flex flex-col">
|
|
124
|
+
<span className="text-2xl font-bold tabular-nums text-white/90">
|
|
125
|
+
{latestVal !== null ? `${latestVal}°` : '—'}
|
|
126
|
+
</span>
|
|
127
|
+
<span className="text-[9px] text-white/40">{latestTs}</span>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{/* Mini bars */}
|
|
131
|
+
<div className="flex flex-1 items-end gap-0.5 self-end pb-1">
|
|
132
|
+
{last5.map((n, i) => {
|
|
133
|
+
const val = typeof n.value === 'number' ? n.value : 20;
|
|
134
|
+
const h = Math.max(4, Math.round(((val - 15) / 15) * 32));
|
|
135
|
+
return (
|
|
136
|
+
<div
|
|
137
|
+
key={n.$path}
|
|
138
|
+
className={cn(
|
|
139
|
+
'flex-1 rounded-sm',
|
|
140
|
+
i === last5.length - 1 ? 'bg-emerald-400' : 'bg-emerald-400/40',
|
|
141
|
+
)}
|
|
142
|
+
style={{ height: h }}
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
})}
|
|
146
|
+
{last5.length === 0 && (
|
|
147
|
+
<span className="text-[10px] text-white/30">No data</span>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
register('examples.demo.sensor', 'react:widget', SensorWidget as any);
|
|
155
|
+
|
|
156
|
+
// ── Default widget fallback — type + icon ──
|
|
157
|
+
|
|
158
|
+
const DefaultWidget: FC<RenderProps> = ({ value }) => {
|
|
159
|
+
const node = value as NodeData;
|
|
160
|
+
const typeParts = node.$type.split('.');
|
|
161
|
+
const name = typeParts[typeParts.length - 1] || node.$type;
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className="flex h-full flex-col items-center justify-center text-white/50">
|
|
165
|
+
<span className="text-lg font-bold capitalize">{name}</span>
|
|
166
|
+
<span className="text-[10px]">{node.$path}</span>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
register('default', 'react:widget', DefaultWidget as any);
|