@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
package/board/view.tsx
ADDED
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
// Board views — kanban board + task detail (editable)
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
closestCorners,
|
|
5
|
+
DndContext,
|
|
6
|
+
type DragEndEvent,
|
|
7
|
+
type DragOverEvent,
|
|
8
|
+
DragOverlay,
|
|
9
|
+
type DragStartEvent,
|
|
10
|
+
PointerSensor,
|
|
11
|
+
useDroppable,
|
|
12
|
+
useSensor,
|
|
13
|
+
useSensors,
|
|
14
|
+
} from '@dnd-kit/core';
|
|
15
|
+
import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
16
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
17
|
+
import { type ComponentData, type NodeData, register } from '@treenity/core';
|
|
18
|
+
import { Render, RenderContext, type View } from '@treenity/react/context';
|
|
19
|
+
import { set, useChildren, useNavigate, usePath } from '@treenity/react/hooks';
|
|
20
|
+
import { minimd } from '@treenity/react/lib/minimd';
|
|
21
|
+
import { cn } from '@treenity/react/lib/utils';
|
|
22
|
+
import { trpc } from '@treenity/react/trpc';
|
|
23
|
+
import { Button } from '@treenity/react/ui/button';
|
|
24
|
+
import { Dialog, DialogContent, DialogTitle } from '@treenity/react/ui/dialog';
|
|
25
|
+
import { FormField } from '@treenity/react/ui/form-field';
|
|
26
|
+
import { Input } from '@treenity/react/ui/input';
|
|
27
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@treenity/react/ui/select';
|
|
28
|
+
import { Textarea } from '@treenity/react/ui/textarea';
|
|
29
|
+
import { useMemo, useRef, useState } from 'react';
|
|
30
|
+
import { BoardColumn, BoardKanban, BoardTask } from './types';
|
|
31
|
+
|
|
32
|
+
type TaskStatus = BoardTask['status'];
|
|
33
|
+
|
|
34
|
+
const PRIORITY_COLOR: Record<string, string> = {
|
|
35
|
+
urgent: 'bg-red-500',
|
|
36
|
+
high: 'bg-orange-400',
|
|
37
|
+
normal: 'bg-blue-400',
|
|
38
|
+
low: 'bg-zinc-400',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const PRIORITIES: { value: string; label: string }[] = [
|
|
42
|
+
{ value: 'urgent', label: 'Urgent' },
|
|
43
|
+
{ value: 'high', label: 'High' },
|
|
44
|
+
{ value: 'normal', label: 'Normal' },
|
|
45
|
+
{ value: 'low', label: 'Low' },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
function PriorityDot({ priority }: { priority: string }) {
|
|
49
|
+
return (
|
|
50
|
+
<span
|
|
51
|
+
className={cn('inline-block h-2 w-2 rounded-full', PRIORITY_COLOR[priority] ?? PRIORITY_COLOR.normal)}
|
|
52
|
+
title={priority}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function AiBadge() {
|
|
58
|
+
return (
|
|
59
|
+
<span className="rounded bg-violet-500/20 px-1.5 py-0.5 text-[10px] font-medium text-violet-400">
|
|
60
|
+
AI
|
|
61
|
+
</span>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── board.task view — editable task detail ──
|
|
66
|
+
|
|
67
|
+
const TaskView: View<BoardTask> = ({ ctx }) => {
|
|
68
|
+
const path = ctx?.path ?? '';
|
|
69
|
+
const node = usePath<NodeData>(path);
|
|
70
|
+
const proxy = usePath(path, BoardTask);
|
|
71
|
+
const [editingDesc, setEditingDesc] = useState(false);
|
|
72
|
+
if (!node || !proxy) return null;
|
|
73
|
+
|
|
74
|
+
const title = typeof proxy.title === 'string' ? proxy.title : '';
|
|
75
|
+
const description = typeof proxy.description === 'string' ? proxy.description : '';
|
|
76
|
+
const status = (typeof proxy.status === 'string' ? proxy.status : 'backlog') as TaskStatus;
|
|
77
|
+
const priority = typeof proxy.priority === 'string' ? proxy.priority : 'normal';
|
|
78
|
+
const assignee = typeof proxy.assignee === 'string' ? proxy.assignee : '';
|
|
79
|
+
const result = typeof proxy.result === 'string' ? proxy.result : '';
|
|
80
|
+
|
|
81
|
+
const save = (patch: Record<string, unknown>) => {
|
|
82
|
+
set({ ...node, ...patch, updatedAt: Date.now() });
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className="space-y-4">
|
|
87
|
+
<BlurInput
|
|
88
|
+
value={title}
|
|
89
|
+
placeholder="Task title..."
|
|
90
|
+
className="border-none bg-transparent p-0 text-lg font-semibold shadow-none focus-visible:ring-0"
|
|
91
|
+
onSave={v => save({ title: v })}
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
{editingDesc ? (
|
|
95
|
+
<Textarea
|
|
96
|
+
key={`desc-${node.$path}`}
|
|
97
|
+
defaultValue={description}
|
|
98
|
+
placeholder="Add a description..."
|
|
99
|
+
className="max-h-60 min-h-20 resize-none text-sm"
|
|
100
|
+
autoFocus
|
|
101
|
+
onBlur={e => {
|
|
102
|
+
if (e.target.value !== description) save({ description: e.target.value });
|
|
103
|
+
setEditingDesc(false);
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
106
|
+
) : description ? (
|
|
107
|
+
<div
|
|
108
|
+
className="cursor-pointer whitespace-pre-wrap rounded border border-transparent p-2 text-sm line-clamp-4 hover:border-border"
|
|
109
|
+
onClick={() => setEditingDesc(true)}
|
|
110
|
+
title="Click to edit"
|
|
111
|
+
>
|
|
112
|
+
{description}
|
|
113
|
+
</div>
|
|
114
|
+
) : (
|
|
115
|
+
<div
|
|
116
|
+
className="cursor-pointer rounded border border-dashed border-border p-2 text-sm text-muted-foreground hover:border-foreground/30"
|
|
117
|
+
onClick={() => setEditingDesc(true)}
|
|
118
|
+
>
|
|
119
|
+
Add a description...
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
|
|
123
|
+
<div className="space-y-2 text-sm">
|
|
124
|
+
<FormField label="Status">
|
|
125
|
+
<div className="flex items-center gap-2">
|
|
126
|
+
<span className="rounded bg-muted px-2 py-0.5 text-xs font-medium capitalize">{status}</span>
|
|
127
|
+
<TaskActions proxy={proxy} status={status} />
|
|
128
|
+
</div>
|
|
129
|
+
</FormField>
|
|
130
|
+
|
|
131
|
+
<FormField label="Priority">
|
|
132
|
+
<Select value={priority} onValueChange={v => save({ priority: v })}>
|
|
133
|
+
<SelectTrigger className="h-8 w-32">
|
|
134
|
+
<SelectValue />
|
|
135
|
+
</SelectTrigger>
|
|
136
|
+
<SelectContent>
|
|
137
|
+
{PRIORITIES.map(p => (
|
|
138
|
+
<SelectItem key={p.value} value={p.value}>
|
|
139
|
+
<span className="flex items-center gap-1.5">
|
|
140
|
+
<PriorityDot priority={p.value} /> {p.label}
|
|
141
|
+
</span>
|
|
142
|
+
</SelectItem>
|
|
143
|
+
))}
|
|
144
|
+
</SelectContent>
|
|
145
|
+
</Select>
|
|
146
|
+
</FormField>
|
|
147
|
+
|
|
148
|
+
<FormField label="Assignee">
|
|
149
|
+
<BlurInput
|
|
150
|
+
value={assignee}
|
|
151
|
+
placeholder="Unassigned"
|
|
152
|
+
className="h-8 text-sm"
|
|
153
|
+
onSave={v => save({ assignee: v })}
|
|
154
|
+
/>
|
|
155
|
+
</FormField>
|
|
156
|
+
|
|
157
|
+
{(status === 'review' || status === 'done' || result) && (
|
|
158
|
+
<FormField label="Result">
|
|
159
|
+
<ResultField path={node.$path} result={result} onSave={v => save({ result: v })} />
|
|
160
|
+
</FormField>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
{/* Named components (ai.plan, ai.thread, etc.) */}
|
|
165
|
+
<NamedComponents node={node} />
|
|
166
|
+
|
|
167
|
+
<EmbeddedTaskLog taskRef={typeof node.taskRef === 'string' ? node.taskRef : ''} />
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
function NamedComponents({ node }: { node: NodeData }) {
|
|
173
|
+
const keys = Object.keys(node).filter(k => {
|
|
174
|
+
if (k.startsWith('$') || k === 'taskRef') return false;
|
|
175
|
+
const v = node[k];
|
|
176
|
+
return v && typeof v === 'object' && '$type' in v;
|
|
177
|
+
});
|
|
178
|
+
if (!keys.length) return null;
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div className="flex flex-col gap-3">
|
|
182
|
+
{keys.map(k => (
|
|
183
|
+
<Render key={k} value={node[k] as ComponentData} />
|
|
184
|
+
))}
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function ResultField({ path, result, onSave }: { path: string; result: string; onSave: (v: string) => void }) {
|
|
190
|
+
const [editing, setEditing] = useState(false);
|
|
191
|
+
const html = useMemo(() => result ? minimd(result) : '', [result]);
|
|
192
|
+
|
|
193
|
+
if (editing) {
|
|
194
|
+
return (
|
|
195
|
+
<Textarea
|
|
196
|
+
key={`res-${path}`}
|
|
197
|
+
defaultValue={result}
|
|
198
|
+
placeholder="Result notes..."
|
|
199
|
+
className="min-h-20 max-h-60 resize-none text-sm"
|
|
200
|
+
autoFocus
|
|
201
|
+
onBlur={e => {
|
|
202
|
+
if (e.target.value !== result) onSave(e.target.value);
|
|
203
|
+
setEditing(false);
|
|
204
|
+
}}
|
|
205
|
+
/>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!result) {
|
|
210
|
+
return (
|
|
211
|
+
<div
|
|
212
|
+
className="cursor-pointer rounded border border-dashed border-border p-2 text-sm text-muted-foreground hover:border-foreground/30"
|
|
213
|
+
onClick={() => setEditing(true)}
|
|
214
|
+
>
|
|
215
|
+
Add result...
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<div
|
|
222
|
+
className="minimd cursor-pointer rounded border border-transparent p-2 text-sm max-h-60 overflow-y-auto hover:border-border"
|
|
223
|
+
onClick={() => setEditing(true)}
|
|
224
|
+
title="Click to edit"
|
|
225
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
226
|
+
/>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function EmbeddedTaskLog({ taskRef }: { taskRef: string }) {
|
|
231
|
+
const mtTask = usePath(taskRef || null) as NodeData | undefined;
|
|
232
|
+
if (!mtTask) return null;
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div className="mt-2 rounded-md border border-border p-2">
|
|
236
|
+
<div className="mb-1 flex items-center gap-2">
|
|
237
|
+
<span className="text-[10px] font-medium uppercase text-muted-foreground">Agent Log</span>
|
|
238
|
+
<span className={cn(
|
|
239
|
+
'rounded px-1.5 py-0.5 text-[10px] font-medium',
|
|
240
|
+
mtTask.status === 'running' ? 'bg-sky-500/20 text-sky-400' :
|
|
241
|
+
mtTask.status === 'done' ? 'bg-emerald-500/20 text-emerald-400' :
|
|
242
|
+
'bg-red-500/20 text-red-400',
|
|
243
|
+
)}>
|
|
244
|
+
{String(mtTask.status)}
|
|
245
|
+
</span>
|
|
246
|
+
</div>
|
|
247
|
+
<Render value={mtTask} />
|
|
248
|
+
</div>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
register('board.task', 'react', TaskView);
|
|
253
|
+
|
|
254
|
+
function TaskListItem({ value }: { value: NodeData }) {
|
|
255
|
+
const nav = useNavigate();
|
|
256
|
+
const priority = typeof value.priority === 'string' ? value.priority : 'normal';
|
|
257
|
+
const title = typeof value.title === 'string' && value.title
|
|
258
|
+
? value.title
|
|
259
|
+
: value.$path.split('/').at(-1);
|
|
260
|
+
const aiStatus = typeof (value as any).aiStatus === 'string' ? (value as any).aiStatus : '';
|
|
261
|
+
const assignee = typeof value.assignee === 'string' ? value.assignee : '';
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<button
|
|
265
|
+
onClick={() => nav(value.$path)}
|
|
266
|
+
className="flex w-full items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-left transition-colors hover:bg-accent/50"
|
|
267
|
+
>
|
|
268
|
+
<PriorityDot priority={priority} />
|
|
269
|
+
<span className="flex-1 truncate text-sm font-medium">{title}</span>
|
|
270
|
+
{aiStatus && (
|
|
271
|
+
<span className="rounded bg-violet-500/20 px-1.5 py-0.5 text-[10px] font-medium text-violet-400">
|
|
272
|
+
{aiStatus}
|
|
273
|
+
</span>
|
|
274
|
+
)}
|
|
275
|
+
{assignee && !aiStatus && (
|
|
276
|
+
<span className="text-xs text-muted-foreground">{assignee}</span>
|
|
277
|
+
)}
|
|
278
|
+
</button>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
register('board.task', 'react:list', TaskListItem as any);
|
|
283
|
+
|
|
284
|
+
// ── Helpers ──
|
|
285
|
+
|
|
286
|
+
function TaskActions({ proxy, status }: { proxy: ReturnType<typeof usePath<BoardTask>>; status: TaskStatus }) {
|
|
287
|
+
const btn = 'h-7 text-xs';
|
|
288
|
+
switch (status) {
|
|
289
|
+
case 'backlog':
|
|
290
|
+
return <Button variant="outline" size="sm" className={btn} onClick={() => proxy.start()}>Start</Button>;
|
|
291
|
+
case 'todo':
|
|
292
|
+
return <Button variant="outline" size="sm" className={btn} onClick={() => proxy.start()}>Begin</Button>;
|
|
293
|
+
case 'doing':
|
|
294
|
+
return <Button variant="outline" size="sm" className={btn} onClick={() => proxy.submit()}>Submit</Button>;
|
|
295
|
+
case 'review':
|
|
296
|
+
return (
|
|
297
|
+
<div className="flex gap-1">
|
|
298
|
+
<Button variant="outline" size="sm" className={btn} onClick={() => proxy.approve()}>Approve</Button>
|
|
299
|
+
<Button variant="ghost" size="sm" className={btn} onClick={() => proxy.reject()}>Reject</Button>
|
|
300
|
+
</div>
|
|
301
|
+
);
|
|
302
|
+
case 'done':
|
|
303
|
+
return <Button variant="ghost" size="sm" className={btn} onClick={() => proxy.reopen()}>Reopen</Button>;
|
|
304
|
+
default:
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function BlurInput({ value, onSave, ...props }: {
|
|
310
|
+
value: string; onSave: (v: string) => void;
|
|
311
|
+
} & Omit<React.ComponentProps<typeof Input>, 'value' | 'onChange' | 'onBlur' | 'onKeyDown'>) {
|
|
312
|
+
const ref = useRef<HTMLInputElement>(null);
|
|
313
|
+
return (
|
|
314
|
+
<Input
|
|
315
|
+
ref={ref}
|
|
316
|
+
defaultValue={value}
|
|
317
|
+
onBlur={e => { if (e.target.value !== value) onSave(e.target.value); }}
|
|
318
|
+
onKeyDown={e => { if (e.key === 'Enter') ref.current?.blur(); }}
|
|
319
|
+
{...props}
|
|
320
|
+
/>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ── Task card (kanban) ──
|
|
325
|
+
|
|
326
|
+
function TaskCardContent({ task, isDragging }: { task: NodeData; isDragging?: boolean }) {
|
|
327
|
+
const proxy = usePath(task.$path, BoardTask);
|
|
328
|
+
|
|
329
|
+
const title = typeof proxy?.title === 'string' && proxy.title
|
|
330
|
+
? proxy.title
|
|
331
|
+
: task.$path.split('/').at(-1);
|
|
332
|
+
const assignee = typeof proxy?.assignee === 'string' ? proxy.assignee : '';
|
|
333
|
+
const priority = typeof proxy?.priority === 'string' ? proxy.priority : 'normal';
|
|
334
|
+
const result = typeof proxy?.result === 'string' ? proxy.result : '';
|
|
335
|
+
const aiStatus = typeof task.aiStatus === 'string' ? task.aiStatus : '';
|
|
336
|
+
const isAi = assignee === 'metatron' || !!aiStatus;
|
|
337
|
+
|
|
338
|
+
return (
|
|
339
|
+
<div className={cn(
|
|
340
|
+
'mb-2 rounded-md border border-border bg-card p-3 shadow-sm transition-colors',
|
|
341
|
+
isDragging ? 'opacity-50' : 'hover:bg-accent/50',
|
|
342
|
+
)}>
|
|
343
|
+
<div className="mb-1 flex items-center gap-2">
|
|
344
|
+
<PriorityDot priority={priority} />
|
|
345
|
+
<span className="flex-1 text-sm font-semibold leading-tight">{title}</span>
|
|
346
|
+
{aiStatus ? (
|
|
347
|
+
<span className="rounded bg-violet-500/20 px-1.5 py-0.5 text-[10px] font-medium text-violet-400">
|
|
348
|
+
{String(aiStatus)}
|
|
349
|
+
</span>
|
|
350
|
+
) : isAi ? <AiBadge /> : null}
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
{assignee && !isAi && (
|
|
354
|
+
<div className="mb-1 text-xs text-muted-foreground">{assignee}</div>
|
|
355
|
+
)}
|
|
356
|
+
|
|
357
|
+
{result && (
|
|
358
|
+
<div className="line-clamp-2 text-xs text-muted-foreground">{result}</div>
|
|
359
|
+
)}
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function TaskCard({ task, onSelect, colStatus }: { task: NodeData; onSelect: (path: string) => void; colStatus: string }) {
|
|
365
|
+
const proxy = usePath(task.$path, BoardTask);
|
|
366
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
367
|
+
id: task.$path,
|
|
368
|
+
data: { task, status: colStatus, move: proxy.move },
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// dnd-kit requires inline style for runtime-computed transforms
|
|
372
|
+
const style = { transform: CSS.Transform.toString(transform), transition };
|
|
373
|
+
|
|
374
|
+
return (
|
|
375
|
+
<div
|
|
376
|
+
ref={setNodeRef}
|
|
377
|
+
{...listeners}
|
|
378
|
+
{...attributes}
|
|
379
|
+
className="cursor-grab active:cursor-grabbing"
|
|
380
|
+
style={style}
|
|
381
|
+
onClick={() => { if (!isDragging) onSelect(task.$path); }}
|
|
382
|
+
>
|
|
383
|
+
<TaskCardContent task={task} isDragging={isDragging} />
|
|
384
|
+
</div>
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ── Column ──
|
|
389
|
+
|
|
390
|
+
function Column({ col, onSelect, highlighted }: { col: NodeData; onSelect: (path: string) => void; highlighted?: boolean }) {
|
|
391
|
+
const proxy = usePath(col.$path, BoardColumn);
|
|
392
|
+
const tasks = useChildren(col.$path, { watch: true, watchNew: true });
|
|
393
|
+
const status = col.$path.split('/').at(-1) ?? '';
|
|
394
|
+
const { setNodeRef } = useDroppable({ id: `col:${status}`, data: { status } });
|
|
395
|
+
|
|
396
|
+
const label = typeof proxy?.label === 'string' ? proxy.label : status;
|
|
397
|
+
const color = typeof proxy?.color === 'string' ? proxy.color : 'border-zinc-400';
|
|
398
|
+
const taskIds = useMemo(() => tasks.map(t => t.$path), [tasks]);
|
|
399
|
+
|
|
400
|
+
return (
|
|
401
|
+
<div className="flex min-w-40 flex-1 flex-col">
|
|
402
|
+
<div className={cn('mb-2 flex items-center gap-2 border-b-2 pb-1.5', color)}>
|
|
403
|
+
<span className="text-sm font-bold">{label}</span>
|
|
404
|
+
<span className="text-xs text-muted-foreground">({tasks.length})</span>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<SortableContext items={taskIds} strategy={verticalListSortingStrategy}>
|
|
408
|
+
<div
|
|
409
|
+
ref={setNodeRef}
|
|
410
|
+
className={cn(
|
|
411
|
+
'flex-1 rounded-md p-1 transition-colors min-h-16',
|
|
412
|
+
highlighted && 'bg-accent/30 ring-1 ring-accent',
|
|
413
|
+
)}
|
|
414
|
+
>
|
|
415
|
+
{tasks.map(task => (
|
|
416
|
+
<TaskCard key={task.$path} task={task} onSelect={onSelect} colStatus={status} />
|
|
417
|
+
))}
|
|
418
|
+
|
|
419
|
+
{tasks.length === 0 && (
|
|
420
|
+
<div className="rounded-md border border-dashed border-border py-6 text-center text-xs text-muted-foreground">
|
|
421
|
+
{highlighted ? 'Drop here' : 'Empty'}
|
|
422
|
+
</div>
|
|
423
|
+
)}
|
|
424
|
+
</div>
|
|
425
|
+
</SortableContext>
|
|
426
|
+
</div>
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ── Kanban Board ──
|
|
431
|
+
|
|
432
|
+
const KanbanView: View<BoardKanban> = ({ value, ctx }) => {
|
|
433
|
+
const [selectedTask, setSelectedTask] = useState<string | null>(null);
|
|
434
|
+
const [activeTask, setActiveTask] = useState<NodeData | null>(null);
|
|
435
|
+
const [overColumn, setOverColumn] = useState<string | null>(null);
|
|
436
|
+
const selectedNode = usePath(selectedTask ?? '') as NodeData | undefined;
|
|
437
|
+
const basePath = ctx!.path;
|
|
438
|
+
|
|
439
|
+
const sensors = useSensors(
|
|
440
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
const children = useChildren(basePath, { watch: true, watchNew: true });
|
|
444
|
+
const columns = children
|
|
445
|
+
.filter(c => c.$type === 'board.column')
|
|
446
|
+
.sort((a, b) => {
|
|
447
|
+
const oa = typeof a.order === 'number' ? a.order : 0;
|
|
448
|
+
const ob = typeof b.order === 'number' ? b.order : 0;
|
|
449
|
+
return oa - ob;
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const createTask = async () => {
|
|
453
|
+
const id = Date.now().toString(36).toUpperCase();
|
|
454
|
+
await trpc.set.mutate({
|
|
455
|
+
node: {
|
|
456
|
+
$path: `${basePath}/data/${id}`,
|
|
457
|
+
$type: 'board.task',
|
|
458
|
+
title: `Task #${id}`,
|
|
459
|
+
status: 'backlog',
|
|
460
|
+
priority: 'normal',
|
|
461
|
+
assignee: '',
|
|
462
|
+
description: '',
|
|
463
|
+
result: '',
|
|
464
|
+
createdAt: Date.now(),
|
|
465
|
+
updatedAt: Date.now(),
|
|
466
|
+
} as NodeData,
|
|
467
|
+
});
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const handleDragStart = (event: DragStartEvent) => {
|
|
471
|
+
const task = event.active.data.current?.task;
|
|
472
|
+
if (task && typeof task === 'object' && '$path' in task) {
|
|
473
|
+
setActiveTask(task as NodeData);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const handleDragOver = (event: DragOverEvent) => {
|
|
478
|
+
const over = event.over;
|
|
479
|
+
if (!over) { setOverColumn(null); return; }
|
|
480
|
+
|
|
481
|
+
// Over a column droppable
|
|
482
|
+
const overId = String(over.id);
|
|
483
|
+
if (overId.startsWith('col:')) { setOverColumn(overId.slice(4)); return; }
|
|
484
|
+
|
|
485
|
+
// Over a card — get its column status
|
|
486
|
+
const status = over.data.current?.status;
|
|
487
|
+
setOverColumn(typeof status === 'string' ? status : null);
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const handleDragEnd = (event: DragEndEvent) => {
|
|
491
|
+
setActiveTask(null);
|
|
492
|
+
setOverColumn(null);
|
|
493
|
+
const { active, over } = event;
|
|
494
|
+
if (!over) return;
|
|
495
|
+
|
|
496
|
+
// Target is either a column droppable (col:status) or a card sortable (has data.status)
|
|
497
|
+
const overId = String(over.id);
|
|
498
|
+
const targetStatus = over.data.current?.status
|
|
499
|
+
?? (overId.startsWith('col:') ? overId.slice(4) : undefined);
|
|
500
|
+
if (typeof targetStatus !== 'string') return;
|
|
501
|
+
|
|
502
|
+
const src = active.data.current;
|
|
503
|
+
if (!src?.task || typeof src.task !== 'object' || !('status' in src.task)) return;
|
|
504
|
+
if (src.task.status === targetStatus) return;
|
|
505
|
+
|
|
506
|
+
const move = src.move;
|
|
507
|
+
if (typeof move === 'function') move({ status: targetStatus });
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
return (
|
|
511
|
+
<div className="view-full px-4 py-3">
|
|
512
|
+
<div className="mb-3 flex items-center justify-between">
|
|
513
|
+
<h2 className="text-lg font-bold">Task Board</h2>
|
|
514
|
+
<Button onClick={createTask}>+ New Task</Button>
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
<DndContext sensors={sensors} collisionDetection={closestCorners} onDragStart={handleDragStart} onDragOver={handleDragOver} onDragEnd={handleDragEnd}>
|
|
518
|
+
<div className="flex gap-3 overflow-x-auto pb-2">
|
|
519
|
+
{columns.map(col => (
|
|
520
|
+
<Column key={col.$path} col={col} onSelect={setSelectedTask} highlighted={overColumn === (col.$path.split('/').at(-1) ?? '')} />
|
|
521
|
+
))}
|
|
522
|
+
</div>
|
|
523
|
+
|
|
524
|
+
<DragOverlay>
|
|
525
|
+
{activeTask && (
|
|
526
|
+
<div className="w-60 rotate-2 opacity-90">
|
|
527
|
+
<TaskCardContent task={activeTask} />
|
|
528
|
+
</div>
|
|
529
|
+
)}
|
|
530
|
+
</DragOverlay>
|
|
531
|
+
</DndContext>
|
|
532
|
+
|
|
533
|
+
{selectedTask && selectedNode && (
|
|
534
|
+
<Dialog open onOpenChange={open => { if (!open) setSelectedTask(null); }}>
|
|
535
|
+
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-lg" aria-describedby={undefined}>
|
|
536
|
+
<DialogTitle className="sr-only">Task</DialogTitle>
|
|
537
|
+
<Render value={selectedNode} />
|
|
538
|
+
</DialogContent>
|
|
539
|
+
</Dialog>
|
|
540
|
+
)}
|
|
541
|
+
</div>
|
|
542
|
+
);
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
register('board.kanban', 'react', KanbanView);
|
|
546
|
+
|
|
547
|
+
// ── board.column view ──
|
|
548
|
+
|
|
549
|
+
function ColumnView({ value }: { value: NodeData & BoardColumn }) {
|
|
550
|
+
const tasks = useChildren(value.$path, { watch: true, watchNew: true });
|
|
551
|
+
const label = typeof value.label === 'string' ? value.label : value.$path.split('/').at(-1);
|
|
552
|
+
const color = typeof value.color === 'string' ? value.color : 'border-zinc-400';
|
|
553
|
+
|
|
554
|
+
return (
|
|
555
|
+
<div className="flex flex-col gap-2 p-3">
|
|
556
|
+
<div className={cn('flex items-center gap-2 border-b-2 pb-1.5', color)}>
|
|
557
|
+
<span className="text-sm font-bold">{label}</span>
|
|
558
|
+
<span className="text-xs text-muted-foreground">({tasks.length})</span>
|
|
559
|
+
</div>
|
|
560
|
+
<RenderContext name="react:list">
|
|
561
|
+
{tasks.map(task => (
|
|
562
|
+
<Render key={task.$path} value={task} />
|
|
563
|
+
))}
|
|
564
|
+
{tasks.length === 0 && (
|
|
565
|
+
<div className="rounded-md border border-dashed border-border py-4 text-center text-xs text-muted-foreground">
|
|
566
|
+
Empty
|
|
567
|
+
</div>
|
|
568
|
+
)}
|
|
569
|
+
</RenderContext>
|
|
570
|
+
</div>
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
register('board.column', 'react', ColumnView as any);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## brahman
|
|
2
|
+
Telegram bot constructor. Pages with ordered actions, DnD menu editor, session/user management.
|
|
3
|
+
|
|
4
|
+
### Файлы
|
|
5
|
+
- types.ts — 24 component classes (BotConfig, PageConfig, 20 action types, User, Session)
|
|
6
|
+
- service.ts — Grammy bot runtime: middleware, template engine, 20 action handlers via register()
|
|
7
|
+
- action.ts — server-side action:run for brahman.page (tRPC execute)
|
|
8
|
+
- views/action-cards.tsx — icons, summaries, editors for all 20 action types
|
|
9
|
+
- views/page-layout.tsx — DnD sortable action list (@dnd-kit)
|
|
10
|
+
- views/menu-editor.tsx — DnD button/row editor for inline/reply keyboards
|
|
11
|
+
- views/bot-view.tsx — bot config view (token, langs, maintenance)
|
|
12
|
+
- views/tstring-input.tsx — TString multilingual text editor
|
|
13
|
+
|
|
14
|
+
### Конвенции
|
|
15
|
+
- Action handlers registered via `register('brahman.action.X', 'brahman:run', handler)` — no switch/case
|
|
16
|
+
- TString = Record<string, string> for multilingual text (keyed by lang code)
|
|
17
|
+
- Template engine: Handlebars-like {var}, {{#ifEquals}}, {{#tag}}, {{eval expr}}
|
|
18
|
+
- Views go in views/, registered in view.ts for 'react' and 'react:list' contexts
|