makersclaw 0.7.0 → 0.8.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/README.md +17 -4
- package/package.json +1 -1
- package/r/activity-log.json +52 -0
- package/r/agent-changes.json +51 -0
- package/r/agent-chat.json +53 -0
- package/r/agent-column.json +85 -0
- package/r/agent-dock.json +57 -0
- package/r/agent-kit.json +49 -0
- package/r/agent-marks.json +49 -0
- package/r/agent-pane.json +93 -0
- package/r/ai-action.json +56 -0
- package/r/annotate.json +4 -4
- package/r/approval-request.json +2 -2
- package/r/approval.json +54 -0
- package/r/ask-bar.json +50 -0
- package/r/audio-player.json +2 -2
- package/r/auth-form.json +2 -2
- package/r/automation-editor.json +66 -0
- package/r/automation-list.json +57 -0
- package/r/automation-model.json +118 -0
- package/r/automations-screen.json +71 -0
- package/r/board.json +2 -2
- package/r/calendar-view.json +3 -3
- package/r/cart.json +2 -2
- package/r/chart.json +5 -5
- package/r/chat-pane.json +84 -0
- package/r/chat.json +3 -3
- package/r/code-editor.json +2 -2
- package/r/comment-box.json +3 -3
- package/r/connect-card.json +51 -0
- package/r/contact-section.json +2 -2
- package/r/core.json +18 -6
- package/r/cost.json +51 -0
- package/r/csv-import.json +3 -3
- package/r/data-picker.json +60 -0
- package/r/data-table.json +2 -2
- package/r/diagram.json +2 -2
- package/r/doc-editor.json +2 -2
- package/r/doc-page.json +3 -3
- package/r/email-render.json +2 -2
- package/r/faq.json +2 -2
- package/r/file-upload.json +2 -2
- package/r/first-run.json +63 -0
- package/r/fix-note.json +57 -0
- package/r/flow-canvas.json +3 -3
- package/r/flow-editor.json +79 -0
- package/r/footer.json +3 -3
- package/r/gantt.json +3 -3
- package/r/job-panel.json +2 -2
- package/r/json-editor.json +3 -3
- package/r/library-shelf.json +56 -0
- package/r/map.json +2 -2
- package/r/margin-notes.json +67 -0
- package/r/menubar.json +2 -2
- package/r/mind-map.json +4 -4
- package/r/navigation-menu.json +2 -2
- package/r/needs-you.json +55 -0
- package/r/order-detail.json +2 -2
- package/r/org-chart.json +4 -4
- package/r/pace-bar.json +69 -0
- package/r/page-builder.json +3 -3
- package/r/payment-methods.json +2 -2
- package/r/pdf-document.json +2 -2
- package/r/pdf-viewer.json +3 -3
- package/r/product-detail.json +2 -2
- package/r/product-grid.json +3 -3
- package/r/record-form.json +2 -2
- package/r/record-grid.json +52 -0
- package/r/record-notes.json +4 -4
- package/r/registry.json +4089 -1510
- package/r/run-history.json +52 -0
- package/r/run-view.json +80 -0
- package/r/schedule-card.json +54 -0
- package/r/schedule-engine.json +79 -0
- package/r/schedule-gantt.json +83 -0
- package/r/schedule-ruler.json +64 -0
- package/r/search-dialog.json +2 -2
- package/r/settings-list.json +2 -2
- package/r/setup-checklist.json +2 -2
- package/r/share-dialog.json +2 -2
- package/r/sidebar.json +3 -3
- package/r/signature-pad.json +2 -2
- package/r/sketch-canvas.json +3 -3
- package/r/source-receipt.json +52 -0
- package/r/spreadsheet.json +3 -3
- package/r/stat-cards.json +3 -3
- package/r/step-picker.json +53 -0
- package/r/step-settings.json +72 -0
- package/r/suggestion.json +58 -0
- package/r/sum-bar.json +64 -0
- package/r/task-list.json +3 -3
- package/r/task-model.json +2 -2
- package/r/testimonials.json +2 -2
- package/r/tier-map.json +67 -0
- package/r/time-grid.json +73 -0
- package/r/time-strip.json +64 -0
- package/r/top-bar.json +66 -0
- package/r/transaction-list.json +2 -2
- package/r/trigger-picker.json +61 -0
- package/r/whiteboard.json +4 -4
- package/r/workflow-view.json +55 -0
- package/registry/blocks/activity-log/README.md +112 -0
- package/registry/blocks/activity-log/activity-log.jsx +351 -0
- package/registry/blocks/activity-log/block.json +33 -0
- package/registry/blocks/activity-log/demo.jsx +48 -0
- package/registry/blocks/activity-log/index.js +4 -0
- package/registry/blocks/agent-changes/README.md +94 -0
- package/registry/blocks/agent-changes/agent-changes.jsx +118 -0
- package/registry/blocks/agent-changes/block.json +28 -0
- package/registry/blocks/agent-changes/demo.jsx +39 -0
- package/registry/blocks/agent-changes/index.js +3 -0
- package/registry/blocks/agent-chat/README.md +145 -0
- package/registry/blocks/agent-chat/agent-chat.jsx +415 -0
- package/registry/blocks/agent-chat/block.json +30 -0
- package/registry/blocks/agent-chat/demo.jsx +165 -0
- package/registry/blocks/agent-chat/index.js +3 -0
- package/registry/blocks/agent-column/README.md +197 -0
- package/registry/blocks/agent-column/agent-column.jsx +492 -0
- package/registry/blocks/agent-column/block.json +67 -0
- package/registry/blocks/agent-column/column-picker.jsx +163 -0
- package/registry/blocks/agent-column/demo.jsx +50 -0
- package/registry/blocks/agent-column/index.js +7 -0
- package/registry/blocks/agent-column/model.js +164 -0
- package/registry/blocks/agent-column/receipt.jsx +483 -0
- package/registry/blocks/agent-column/spreadsheet-type.jsx +24 -0
- package/registry/blocks/agent-dock/README.md +210 -0
- package/registry/blocks/agent-dock/agent-dock.jsx +356 -0
- package/registry/blocks/agent-dock/block.json +47 -0
- package/registry/blocks/agent-dock/demo.jsx +153 -0
- package/registry/blocks/agent-dock/dock-model.js +118 -0
- package/registry/blocks/agent-dock/index.js +5 -0
- package/registry/blocks/agent-kit/README.md +73 -0
- package/registry/blocks/agent-kit/agent-kit.jsx +86 -0
- package/registry/blocks/agent-kit/block.json +10 -0
- package/registry/blocks/agent-kit/demo.jsx +35 -0
- package/registry/blocks/agent-kit/index.js +3 -0
- package/registry/blocks/agent-marks/README.md +80 -0
- package/registry/blocks/agent-marks/agent-marks.jsx +133 -0
- package/registry/blocks/agent-marks/block.json +27 -0
- package/registry/blocks/agent-marks/demo.jsx +49 -0
- package/registry/blocks/agent-marks/index.js +3 -0
- package/registry/blocks/agent-pane/README.md +225 -0
- package/registry/blocks/agent-pane/agent-pane.jsx +244 -0
- package/registry/blocks/agent-pane/block.json +41 -0
- package/registry/blocks/agent-pane/composer.jsx +6 -0
- package/registry/blocks/agent-pane/demo.jsx +233 -0
- package/registry/blocks/agent-pane/dock-context.js +10 -0
- package/registry/blocks/agent-pane/index.js +4 -0
- package/registry/blocks/agent-pane/prose.jsx +84 -0
- package/registry/blocks/agent-pane/questions.jsx +134 -0
- package/registry/blocks/agent-pane/transcript.jsx +268 -0
- package/registry/blocks/agent-pane/work.jsx +368 -0
- package/registry/blocks/ai-action/README.md +94 -0
- package/registry/blocks/ai-action/ai-action.jsx +188 -0
- package/registry/blocks/ai-action/block.json +34 -0
- package/registry/blocks/ai-action/demo.jsx +93 -0
- package/registry/blocks/ai-action/index.js +3 -0
- package/registry/blocks/annotate/README.md +14 -11
- package/registry/blocks/approval/README.md +145 -0
- package/registry/blocks/approval/approval.jsx +163 -0
- package/registry/blocks/approval/block.json +31 -0
- package/registry/blocks/approval/demo.jsx +121 -0
- package/registry/blocks/approval/index.js +3 -0
- package/registry/blocks/ask-bar/README.md +105 -0
- package/registry/blocks/ask-bar/ask-bar.jsx +166 -0
- package/registry/blocks/ask-bar/block.json +26 -0
- package/registry/blocks/ask-bar/demo.jsx +78 -0
- package/registry/blocks/ask-bar/index.js +3 -0
- package/registry/blocks/automation-editor/README.md +291 -0
- package/registry/blocks/automation-editor/automation-editor.jsx +656 -0
- package/registry/blocks/automation-editor/block.json +39 -0
- package/registry/blocks/automation-editor/demo.jsx +284 -0
- package/registry/blocks/automation-editor/index.js +4 -0
- package/registry/blocks/automation-editor/run-inputs.jsx +82 -0
- package/registry/blocks/automation-list/README.md +129 -0
- package/registry/blocks/automation-list/automation-list.jsx +476 -0
- package/registry/blocks/automation-list/block.json +35 -0
- package/registry/blocks/automation-list/demo.jsx +67 -0
- package/registry/blocks/automation-list/index.js +3 -0
- package/registry/blocks/automation-model/README.md +109 -0
- package/registry/blocks/automation-model/block.json +135 -0
- package/registry/blocks/automation-model/catalog.js +256 -0
- package/registry/blocks/automation-model/check.js +161 -0
- package/registry/blocks/automation-model/demo.jsx +42 -0
- package/registry/blocks/automation-model/edit.js +262 -0
- package/registry/blocks/automation-model/fixes.js +287 -0
- package/registry/blocks/automation-model/glyph.jsx +13 -0
- package/registry/blocks/automation-model/graph.js +410 -0
- package/registry/blocks/automation-model/index.js +33 -0
- package/registry/blocks/automation-model/overlay.js +175 -0
- package/registry/blocks/automation-model/parts.jsx +94 -0
- package/registry/blocks/automation-model/schedule.js +53 -0
- package/registry/blocks/automation-model/templates.js +128 -0
- package/registry/blocks/automation-model/values.js +29 -0
- package/registry/blocks/automation-model/words.js +439 -0
- package/registry/blocks/automations-screen/README.md +207 -0
- package/registry/blocks/automations-screen/automations-screen.jsx +452 -0
- package/registry/blocks/automations-screen/block.json +47 -0
- package/registry/blocks/automations-screen/demo.jsx +364 -0
- package/registry/blocks/automations-screen/index.js +4 -0
- package/registry/blocks/automations-screen/screen-model.js +129 -0
- package/registry/blocks/chart/cartesian.jsx +1 -1
- package/registry/blocks/chart/core.jsx +33 -23
- package/registry/blocks/chart/pie.jsx +1 -1
- package/registry/blocks/chat/README.md +4 -1
- package/registry/blocks/chat/demo.jsx +2 -2
- package/registry/blocks/chat-pane/README.md +143 -0
- package/registry/blocks/chat-pane/block.json +48 -0
- package/registry/blocks/chat-pane/chat-attach.js +166 -0
- package/registry/blocks/chat-pane/chat-composer.jsx +195 -0
- package/registry/blocks/chat-pane/chat-context.js +17 -0
- package/registry/blocks/chat-pane/chat-model.js +43 -0
- package/registry/blocks/chat-pane/chat-pane.jsx +398 -0
- package/registry/blocks/chat-pane/chats-list.jsx +52 -0
- package/registry/blocks/chat-pane/demo.jsx +103 -0
- package/registry/blocks/chat-pane/index.js +9 -0
- package/registry/blocks/connect-card/README.md +99 -0
- package/registry/blocks/connect-card/block.json +10 -0
- package/registry/blocks/connect-card/connect-card.jsx +103 -0
- package/registry/blocks/connect-card/demo.jsx +49 -0
- package/registry/blocks/connect-card/index.js +3 -0
- package/registry/blocks/cost/README.md +115 -0
- package/registry/blocks/cost/block.json +10 -0
- package/registry/blocks/cost/cost.jsx +176 -0
- package/registry/blocks/cost/demo.jsx +66 -0
- package/registry/blocks/cost/index.js +3 -0
- package/registry/blocks/data-picker/README.md +81 -0
- package/registry/blocks/data-picker/block.json +38 -0
- package/registry/blocks/data-picker/data-picker.jsx +210 -0
- package/registry/blocks/data-picker/demo.jsx +34 -0
- package/registry/blocks/data-picker/fields.js +89 -0
- package/registry/blocks/data-picker/index.js +4 -0
- package/registry/blocks/data-table/demo.jsx +1 -1
- package/registry/blocks/first-run/README.md +118 -0
- package/registry/blocks/first-run/block.json +11 -0
- package/registry/blocks/first-run/demo.jsx +88 -0
- package/registry/blocks/first-run/first-run-model.js +51 -0
- package/registry/blocks/first-run/first-run.jsx +156 -0
- package/registry/blocks/first-run/index.js +5 -0
- package/registry/blocks/first-run/probe-rules.js +26 -0
- package/registry/blocks/fix-note/README.md +79 -0
- package/registry/blocks/fix-note/block.json +35 -0
- package/registry/blocks/fix-note/demo.jsx +30 -0
- package/registry/blocks/fix-note/fix-note.jsx +235 -0
- package/registry/blocks/fix-note/index.js +3 -0
- package/registry/blocks/flow-canvas/README.md +15 -8
- package/registry/blocks/flow-editor/README.md +108 -0
- package/registry/blocks/flow-editor/block.json +44 -0
- package/registry/blocks/flow-editor/demo.jsx +115 -0
- package/registry/blocks/flow-editor/flow-canvas.jsx +568 -0
- package/registry/blocks/flow-editor/flow-editor.jsx +154 -0
- package/registry/blocks/flow-editor/index.js +6 -0
- package/registry/blocks/flow-editor/minimap.jsx +78 -0
- package/registry/blocks/flow-editor/outline.jsx +134 -0
- package/registry/blocks/footer/README.md +3 -2
- package/registry/blocks/library-shelf/README.md +117 -0
- package/registry/blocks/library-shelf/block.json +28 -0
- package/registry/blocks/library-shelf/demo.jsx +68 -0
- package/registry/blocks/library-shelf/index.js +3 -0
- package/registry/blocks/library-shelf/library-shelf.jsx +211 -0
- package/registry/blocks/library-shelf/probe-rules.js +15 -0
- package/registry/blocks/margin-notes/README.md +222 -0
- package/registry/blocks/margin-notes/block.json +10 -0
- package/registry/blocks/margin-notes/demo.jsx +144 -0
- package/registry/blocks/margin-notes/index.js +20 -0
- package/registry/blocks/margin-notes/margin-model.js +224 -0
- package/registry/blocks/margin-notes/margin-notes.jsx +465 -0
- package/registry/blocks/mind-map/README.md +4 -2
- package/registry/blocks/needs-you/README.md +177 -0
- package/registry/blocks/needs-you/block.json +34 -0
- package/registry/blocks/needs-you/demo.jsx +143 -0
- package/registry/blocks/needs-you/index.js +3 -0
- package/registry/blocks/needs-you/needs-you.jsx +441 -0
- package/registry/blocks/org-chart/README.md +4 -2
- package/registry/blocks/pace-bar/README.md +230 -0
- package/registry/blocks/pace-bar/block.json +10 -0
- package/registry/blocks/pace-bar/demo.jsx +255 -0
- package/registry/blocks/pace-bar/index.js +22 -0
- package/registry/blocks/pace-bar/pace-bar.jsx +461 -0
- package/registry/blocks/pace-bar/pace-model.js +288 -0
- package/registry/blocks/page-builder/README.md +1 -1
- package/registry/blocks/page-builder/demo.jsx +3 -3
- package/registry/blocks/pdf-viewer/README.md +4 -1
- package/registry/blocks/record-grid/README.md +100 -0
- package/registry/blocks/record-grid/block.json +28 -0
- package/registry/blocks/record-grid/demo.jsx +43 -0
- package/registry/blocks/record-grid/index.js +3 -0
- package/registry/blocks/record-grid/record-grid.jsx +251 -0
- package/registry/blocks/run-history/README.md +138 -0
- package/registry/blocks/run-history/block.json +10 -0
- package/registry/blocks/run-history/demo.jsx +74 -0
- package/registry/blocks/run-history/index.js +3 -0
- package/registry/blocks/run-history/run-history.jsx +218 -0
- package/registry/blocks/run-view/README.md +174 -0
- package/registry/blocks/run-view/block.json +50 -0
- package/registry/blocks/run-view/demo.jsx +286 -0
- package/registry/blocks/run-view/index.js +5 -0
- package/registry/blocks/run-view/run-controls.jsx +266 -0
- package/registry/blocks/run-view/run-view.jsx +236 -0
- package/registry/blocks/run-view/runs-list.jsx +99 -0
- package/registry/blocks/schedule-card/README.md +121 -0
- package/registry/blocks/schedule-card/block.json +10 -0
- package/registry/blocks/schedule-card/demo.jsx +82 -0
- package/registry/blocks/schedule-card/index.js +3 -0
- package/registry/blocks/schedule-card/schedule-card.jsx +136 -0
- package/registry/blocks/schedule-engine/README.md +137 -0
- package/registry/blocks/schedule-engine/block.json +10 -0
- package/registry/blocks/schedule-engine/demo.jsx +82 -0
- package/registry/blocks/schedule-engine/engine.js +769 -0
- package/registry/blocks/schedule-engine/index.js +9 -0
- package/registry/blocks/schedule-engine/ops.js +71 -0
- package/registry/blocks/schedule-engine/place.js +294 -0
- package/registry/blocks/schedule-engine/tree.js +79 -0
- package/registry/blocks/schedule-engine/waits.js +80 -0
- package/registry/blocks/schedule-gantt/README.md +159 -0
- package/registry/blocks/schedule-gantt/block.json +10 -0
- package/registry/blocks/schedule-gantt/demo.jsx +223 -0
- package/registry/blocks/schedule-gantt/gantt-model.js +219 -0
- package/registry/blocks/schedule-gantt/geometry.js +176 -0
- package/registry/blocks/schedule-gantt/index.js +26 -0
- package/registry/blocks/schedule-gantt/schedule-gantt.jsx +882 -0
- package/registry/blocks/schedule-gantt/sheet.jsx +372 -0
- package/registry/blocks/schedule-gantt/track.jsx +190 -0
- package/registry/blocks/schedule-ruler/README.md +148 -0
- package/registry/blocks/schedule-ruler/block.json +10 -0
- package/registry/blocks/schedule-ruler/demo.jsx +181 -0
- package/registry/blocks/schedule-ruler/index.js +21 -0
- package/registry/blocks/schedule-ruler/ruler-model.js +239 -0
- package/registry/blocks/schedule-ruler/schedule-ruler.jsx +548 -0
- package/registry/blocks/sidebar/README.md +23 -13
- package/registry/blocks/sketch-canvas/README.md +15 -9
- package/registry/blocks/source-receipt/README.md +97 -0
- package/registry/blocks/source-receipt/block.json +30 -0
- package/registry/blocks/source-receipt/demo.jsx +71 -0
- package/registry/blocks/source-receipt/index.js +3 -0
- package/registry/blocks/source-receipt/source-receipt.jsx +107 -0
- package/registry/blocks/spreadsheet/spreadsheet.jsx +15 -1
- package/registry/blocks/step-picker/README.md +78 -0
- package/registry/blocks/step-picker/block.json +31 -0
- package/registry/blocks/step-picker/demo.jsx +23 -0
- package/registry/blocks/step-picker/index.js +3 -0
- package/registry/blocks/step-picker/step-picker.jsx +144 -0
- package/registry/blocks/step-settings/README.md +100 -0
- package/registry/blocks/step-settings/block.json +44 -0
- package/registry/blocks/step-settings/demo.jsx +93 -0
- package/registry/blocks/step-settings/fields.jsx +58 -0
- package/registry/blocks/step-settings/flow-settings.jsx +115 -0
- package/registry/blocks/step-settings/index.js +5 -0
- package/registry/blocks/step-settings/step-settings.jsx +290 -0
- package/registry/blocks/suggestion/README.md +171 -0
- package/registry/blocks/suggestion/block.json +40 -0
- package/registry/blocks/suggestion/demo.jsx +91 -0
- package/registry/blocks/suggestion/index.js +3 -0
- package/registry/blocks/suggestion/suggestion.jsx +436 -0
- package/registry/blocks/sum-bar/README.md +122 -0
- package/registry/blocks/sum-bar/block.json +10 -0
- package/registry/blocks/sum-bar/demo.jsx +110 -0
- package/registry/blocks/sum-bar/index.js +5 -0
- package/registry/blocks/sum-bar/sum-bar.jsx +209 -0
- package/registry/blocks/sum-bar/sum-model.js +126 -0
- package/registry/blocks/tier-map/README.md +217 -0
- package/registry/blocks/tier-map/block.json +10 -0
- package/registry/blocks/tier-map/demo.jsx +222 -0
- package/registry/blocks/tier-map/index.js +24 -0
- package/registry/blocks/tier-map/tier-map.jsx +574 -0
- package/registry/blocks/tier-map/tier-model.js +318 -0
- package/registry/blocks/time-grid/README.md +305 -0
- package/registry/blocks/time-grid/block.json +10 -0
- package/registry/blocks/time-grid/demo.jsx +279 -0
- package/registry/blocks/time-grid/grid-model.js +322 -0
- package/registry/blocks/time-grid/index.js +25 -0
- package/registry/blocks/time-grid/time-grid.jsx +1325 -0
- package/registry/blocks/time-strip/README.md +141 -0
- package/registry/blocks/time-strip/block.json +10 -0
- package/registry/blocks/time-strip/demo.jsx +129 -0
- package/registry/blocks/time-strip/index.js +19 -0
- package/registry/blocks/time-strip/strip-model.js +178 -0
- package/registry/blocks/time-strip/time-strip.jsx +260 -0
- package/registry/blocks/top-bar/README.md +149 -0
- package/registry/blocks/top-bar/block.json +35 -0
- package/registry/blocks/top-bar/demo.jsx +78 -0
- package/registry/blocks/top-bar/index.js +5 -0
- package/registry/blocks/top-bar/probe-rules.js +74 -0
- package/registry/blocks/top-bar/top-bar-model.js +152 -0
- package/registry/blocks/top-bar/top-bar.jsx +244 -0
- package/registry/blocks/trigger-picker/README.md +98 -0
- package/registry/blocks/trigger-picker/block.json +38 -0
- package/registry/blocks/trigger-picker/demo.jsx +59 -0
- package/registry/blocks/trigger-picker/index.js +3 -0
- package/registry/blocks/trigger-picker/trigger-picker.jsx +374 -0
- package/registry/blocks/whiteboard/README.md +20 -14
- package/registry/blocks/workflow-view/README.md +123 -0
- package/registry/blocks/workflow-view/block.json +10 -0
- package/registry/blocks/workflow-view/demo.jsx +90 -0
- package/registry/blocks/workflow-view/index.js +3 -0
- package/registry/blocks/workflow-view/workflow-view.jsx +203 -0
- package/src/cli.mjs +2 -2
- package/src/install.mjs +100 -22
- package/src/registry.mjs +37 -3
- package/templates/next/browser/lib/data.js +6 -1
- package/templates/next/files/README.md +909 -26
- package/templates/next/files/app/shell.jsx +54 -37
- package/templates/next/files/check/automation-format.ts +2103 -0
- package/templates/next/files/check/automation-package.ts +142 -0
- package/templates/next/files/check/run.mjs +1043 -12
- package/templates/next/files/components/ui/field.jsx +17 -4
- package/templates/next/files/components/ui/icon.jsx +13 -0
- package/templates/next/files/components/ui/index.js +3 -1
- package/templates/next/files/components/ui/layout.jsx +382 -0
- package/templates/next/files/components/ui/notice.jsx +70 -0
- package/templates/next/files/components/ui/page.jsx +124 -0
- package/templates/next/files/components/ui/stat.jsx +22 -7
- package/templates/next/files/components/ui/toast.jsx +6 -3
- package/templates/next/files/lib/automations.js +660 -0
- package/templates/next/files/lib/claw.js +244 -5
- package/templates/next/files/lib/door.js +92 -0
- package/templates/next/files/lib/hooks.js +1694 -6
- package/templates/next/files/lib/platform.js +611 -14
- package/templates/next/template.json +22 -3
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// The tier map's data and arithmetic — plain functions, no React, no DOM.
|
|
2
|
+
// They run anywhere (the server, an agent's tool, a test).
|
|
3
|
+
//
|
|
4
|
+
// Items come in flat, the way a table holds them: each row names its
|
|
5
|
+
// parent. buildTierModel turns them into a forest and sorts every item into
|
|
6
|
+
// one of three places:
|
|
7
|
+
//
|
|
8
|
+
// tree reachable from a top: drawn in tiers, one layer open at a time
|
|
9
|
+
// unparented real (not a draft), not reachable from any top
|
|
10
|
+
// ghost a draft (`ghost: true`): drawn apart, dashed, never counted
|
|
11
|
+
//
|
|
12
|
+
// A parent that is not in the list is no parent. A loop of parents is
|
|
13
|
+
// broken at the item that comes first in the list, which becomes parentless.
|
|
14
|
+
// Nothing here hangs on bad data.
|
|
15
|
+
|
|
16
|
+
/** Equal columns of this many rem, at least, are what a tier needs to sit in one row. */
|
|
17
|
+
export const CARD_REM = 11;
|
|
18
|
+
/** A tier of more cards than this never sits in one row; it stacks. */
|
|
19
|
+
export const MAX_COLUMNS = 8;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The key a tier's layout classes are looked up by: how many equal columns
|
|
23
|
+
* the widest tier so far needs, 1 to MAX_COLUMNS, or NEVER when a tier has
|
|
24
|
+
* too many cards to sit in one row.
|
|
25
|
+
*/
|
|
26
|
+
export const NEVER = MAX_COLUMNS + 1;
|
|
27
|
+
export function fitKey(count) {
|
|
28
|
+
const n = Math.floor(Number(count));
|
|
29
|
+
if (!(n > 1)) return 1;
|
|
30
|
+
return n > MAX_COLUMNS ? NEVER : n;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The container width, in rem, from which a tier of this fit key sits in one row (Infinity: never). */
|
|
34
|
+
export function thresholdRem(key) {
|
|
35
|
+
if (key >= NEVER) return Infinity;
|
|
36
|
+
return key <= 1 ? 0 : key * CARD_REM;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const idOf = (value) => (value == null || value === "" ? null : String(value));
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Flat items to a model.
|
|
43
|
+
*
|
|
44
|
+
* items [{ id, parent?, ghost?, ... }]; any other key is kept on `item`
|
|
45
|
+
* tops ids drawn as the first tier, in that order. Default: the real
|
|
46
|
+
* items that have no parent and have children.
|
|
47
|
+
*
|
|
48
|
+
* Returns { nodes, order, tops, unparented, ghostRoots, ghostOrder }.
|
|
49
|
+
* A node is { id, item, ghost, parent, up, kids, tier, group, total }:
|
|
50
|
+
* parent the id it names, after bad ones and loops are dropped (or null)
|
|
51
|
+
* up who it hangs under on screen (null for a top, an unparented card)
|
|
52
|
+
* kids ids drawn under it when it is opened (a draft's kids are drafts)
|
|
53
|
+
* tier 0 for a top, 1 for its kids… (-1 for what is not in the tree)
|
|
54
|
+
* group "tree" | "unparented" | "ghost"
|
|
55
|
+
* total everything under it, counting kids of kids
|
|
56
|
+
*/
|
|
57
|
+
export function buildTierModel(items, { tops } = {}) {
|
|
58
|
+
const nodes = new Map();
|
|
59
|
+
const order = [];
|
|
60
|
+
for (const item of Array.isArray(items) ? items : []) {
|
|
61
|
+
const id = idOf(item?.id);
|
|
62
|
+
if (id == null || nodes.has(id)) continue;
|
|
63
|
+
nodes.set(id, { id, item, ghost: item.ghost === true, parent: null, up: null, kids: [], tier: -1, group: "unparented", total: 0 });
|
|
64
|
+
order.push(id);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The parent each item names, if it is somebody in the list and not itself.
|
|
68
|
+
for (const id of order) {
|
|
69
|
+
const node = nodes.get(id);
|
|
70
|
+
const parent = idOf(node.item.parent);
|
|
71
|
+
node.parent = parent != null && parent !== id && nodes.has(parent) ? parent : null;
|
|
72
|
+
}
|
|
73
|
+
breakLoops(nodes, order);
|
|
74
|
+
|
|
75
|
+
// Kids: a real item under a real parent; a draft under a draft.
|
|
76
|
+
for (const id of order) {
|
|
77
|
+
const node = nodes.get(id);
|
|
78
|
+
const parent = node.parent == null ? null : nodes.get(node.parent);
|
|
79
|
+
if (parent && parent.ghost === node.ghost) parent.kids.push(id);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// The tops.
|
|
83
|
+
const real = (id) => nodes.has(id) && !nodes.get(id).ghost;
|
|
84
|
+
let topIds;
|
|
85
|
+
if (Array.isArray(tops)) topIds = [...new Set(tops.map(idOf).filter((id) => id != null && real(id)))];
|
|
86
|
+
else topIds = order.filter((id) => real(id) && nodes.get(id).parent == null && nodes.get(id).kids.length > 0);
|
|
87
|
+
const topSet = new Set(topIds);
|
|
88
|
+
// A top is drawn as a top and nowhere else, so it leaves its parent's kids.
|
|
89
|
+
for (const id of topIds) {
|
|
90
|
+
const parent = nodes.get(id).parent;
|
|
91
|
+
if (parent != null) nodes.get(parent).kids = nodes.get(parent).kids.filter((k) => k !== id);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Walk down from the tops; the first way in wins.
|
|
95
|
+
const walk = (id, tier, up) => {
|
|
96
|
+
const node = nodes.get(id);
|
|
97
|
+
node.tier = tier;
|
|
98
|
+
node.up = up;
|
|
99
|
+
node.group = "tree";
|
|
100
|
+
let total = 0;
|
|
101
|
+
for (const kid of node.kids) {
|
|
102
|
+
if (nodes.get(kid).group === "tree") continue;
|
|
103
|
+
walk(kid, tier + 1, id);
|
|
104
|
+
total += 1 + nodes.get(kid).total;
|
|
105
|
+
}
|
|
106
|
+
node.kids = node.kids.filter((k) => nodes.get(k).up === id && nodes.get(k).group === "tree");
|
|
107
|
+
node.total = total;
|
|
108
|
+
};
|
|
109
|
+
for (const id of topIds) walk(id, 0, null);
|
|
110
|
+
|
|
111
|
+
// Real items the tops do not reach.
|
|
112
|
+
const unparented = order.filter((id) => !nodes.get(id).ghost && nodes.get(id).group !== "tree");
|
|
113
|
+
for (const id of unparented) {
|
|
114
|
+
const node = nodes.get(id);
|
|
115
|
+
node.kids = [];
|
|
116
|
+
node.up = null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Drafts: a forest of their own, parents first.
|
|
120
|
+
const ghostRoots = [];
|
|
121
|
+
const ghostOrder = [];
|
|
122
|
+
for (const id of order) {
|
|
123
|
+
const node = nodes.get(id);
|
|
124
|
+
if (!node.ghost) continue;
|
|
125
|
+
node.group = "ghost";
|
|
126
|
+
node.up = node.parent != null && nodes.get(node.parent).ghost ? node.parent : null;
|
|
127
|
+
if (node.up == null) ghostRoots.push(id);
|
|
128
|
+
}
|
|
129
|
+
const lay = (id, depth) => {
|
|
130
|
+
ghostOrder.push({ id, depth });
|
|
131
|
+
const node = nodes.get(id);
|
|
132
|
+
node.tier = depth;
|
|
133
|
+
let total = 0;
|
|
134
|
+
for (const kid of node.kids) {
|
|
135
|
+
lay(kid, depth + 1);
|
|
136
|
+
total += 1 + nodes.get(kid).total;
|
|
137
|
+
}
|
|
138
|
+
node.total = total;
|
|
139
|
+
};
|
|
140
|
+
for (const id of ghostRoots) lay(id, 0);
|
|
141
|
+
|
|
142
|
+
return { nodes, order, tops: topIds, unparented, ghostRoots, ghostOrder, isTop: (id) => topSet.has(id) };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// A chain of parents that comes back to itself: the member that comes first
|
|
146
|
+
// in the list becomes parentless, so the rest hang under it.
|
|
147
|
+
function breakLoops(nodes, order) {
|
|
148
|
+
const place = new Map(order.map((id, i) => [id, i]));
|
|
149
|
+
const seen = new Map(); // id -> 1 on the walk now, 2 done
|
|
150
|
+
for (const start of order) {
|
|
151
|
+
if (seen.has(start)) continue;
|
|
152
|
+
const walked = [];
|
|
153
|
+
let cur = start;
|
|
154
|
+
while (cur != null && !seen.has(cur)) {
|
|
155
|
+
seen.set(cur, 1);
|
|
156
|
+
walked.push(cur);
|
|
157
|
+
cur = nodes.get(cur).parent;
|
|
158
|
+
}
|
|
159
|
+
if (cur != null && seen.get(cur) === 1) {
|
|
160
|
+
const loop = walked.slice(walked.indexOf(cur));
|
|
161
|
+
let first = loop[0];
|
|
162
|
+
for (const id of loop) if (place.get(id) < place.get(first)) first = id;
|
|
163
|
+
nodes.get(first).parent = null;
|
|
164
|
+
}
|
|
165
|
+
for (const id of walked) seen.set(id, 2);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---- the open path
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The ids that are open, one per tier: the first is a top, the second one
|
|
173
|
+
* of its kids, and so on. Whatever cannot be (an id that is not there, one
|
|
174
|
+
* with nothing under it, one that is not under the one before) ends the
|
|
175
|
+
* path there.
|
|
176
|
+
*/
|
|
177
|
+
export function openPath(model, requested) {
|
|
178
|
+
const out = [];
|
|
179
|
+
let level = model.tops;
|
|
180
|
+
for (const raw of Array.isArray(requested) ? requested : []) {
|
|
181
|
+
const id = idOf(raw);
|
|
182
|
+
if (id == null || !level.includes(id)) break;
|
|
183
|
+
const node = model.nodes.get(id);
|
|
184
|
+
if (!node.kids.length) break;
|
|
185
|
+
out.push(id);
|
|
186
|
+
level = node.kids;
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The path after pressing a card in the tree: the card opens (and what was
|
|
193
|
+
* open at its tier and below closes), or, if it was open, it closes with
|
|
194
|
+
* everything under it. Any other id leaves the path as it is.
|
|
195
|
+
*/
|
|
196
|
+
export function togglePath(model, path, id) {
|
|
197
|
+
const key = idOf(id);
|
|
198
|
+
const node = key == null ? null : model.nodes.get(key);
|
|
199
|
+
if (!node || node.group !== "tree" || !node.kids.length) return path;
|
|
200
|
+
const base = path.slice(0, node.tier);
|
|
201
|
+
return path[node.tier] === key ? base : [...base, key];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** The ids to open so that this one is on screen: its parents, top first. */
|
|
205
|
+
export function pathTo(model, id) {
|
|
206
|
+
const out = [];
|
|
207
|
+
let node = model.nodes.get(idOf(id));
|
|
208
|
+
if (!node || node.group !== "tree") return out;
|
|
209
|
+
while (node && node.up != null) {
|
|
210
|
+
out.unshift(node.up);
|
|
211
|
+
node = model.nodes.get(node.up);
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The tiers on screen for a path: [{ ids, openId }], the tops first. */
|
|
217
|
+
export function tiersOf(model, path) {
|
|
218
|
+
const tiers = [];
|
|
219
|
+
let ids = model.tops;
|
|
220
|
+
for (let i = 0; ids.length; i += 1) {
|
|
221
|
+
const openId = path[i] ?? null;
|
|
222
|
+
tiers.push({ ids, openId });
|
|
223
|
+
if (openId == null) break;
|
|
224
|
+
ids = model.nodes.get(openId).kids;
|
|
225
|
+
}
|
|
226
|
+
return tiers;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ---- the keys
|
|
230
|
+
|
|
231
|
+
/** The cards beside this one: same parent, same group, in order. */
|
|
232
|
+
export function siblingsOf(model, id) {
|
|
233
|
+
const node = model.nodes.get(idOf(id));
|
|
234
|
+
if (!node) return [];
|
|
235
|
+
if (node.group === "unparented") return model.unparented;
|
|
236
|
+
if (node.group === "ghost") return node.up == null ? model.ghostRoots : model.nodes.get(node.up).kids;
|
|
237
|
+
return node.up == null ? model.tops : model.nodes.get(node.up).kids;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Where an arrow key goes from a card: ← → the card beside it, ↓ its first
|
|
242
|
+
* kid, ↑ its parent, Home and End the first and last beside it. null when
|
|
243
|
+
* there is nowhere to go.
|
|
244
|
+
*/
|
|
245
|
+
export function neighbour(model, id, key) {
|
|
246
|
+
const node = model.nodes.get(idOf(id));
|
|
247
|
+
if (!node) return null;
|
|
248
|
+
const side = siblingsOf(model, node.id);
|
|
249
|
+
const at = side.indexOf(node.id);
|
|
250
|
+
switch (key) {
|
|
251
|
+
case "ArrowLeft":
|
|
252
|
+
return side[at - 1] ?? null;
|
|
253
|
+
case "ArrowRight":
|
|
254
|
+
return side[at + 1] ?? null;
|
|
255
|
+
case "Home":
|
|
256
|
+
return side[0] ?? null;
|
|
257
|
+
case "End":
|
|
258
|
+
return side[side.length - 1] ?? null;
|
|
259
|
+
case "ArrowDown":
|
|
260
|
+
return node.kids[0] ?? null;
|
|
261
|
+
case "ArrowUp":
|
|
262
|
+
return node.up ?? null;
|
|
263
|
+
default:
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ---- what a card says
|
|
269
|
+
|
|
270
|
+
/** "6 serving it": the count and the app's words for what is under a card. */
|
|
271
|
+
export function countText(count, label = "below") {
|
|
272
|
+
const text = typeof label === "function" ? label(count) : `${count} ${label}`;
|
|
273
|
+
return String(text);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** A bar on a card: { pct, share } for a value 0..1, or null (no bar). A draft has none. */
|
|
277
|
+
export function barOf(item) {
|
|
278
|
+
if (!item || item.ghost === true) return null;
|
|
279
|
+
const v = item.value;
|
|
280
|
+
if (v == null || v === "" || typeof v === "boolean") return null;
|
|
281
|
+
const n = Number(v);
|
|
282
|
+
if (!Number.isFinite(n)) return null;
|
|
283
|
+
const share = Math.min(1, Math.max(0, n));
|
|
284
|
+
return { share, pct: Math.round(share * 100) };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export const TONES = ["ink", "quiet", "info", "success", "warning", "danger"];
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The bar's colour: one of TONES. `auto` (or none) is ink, and success when
|
|
291
|
+
* the bar is full; a word that is not a tone is ink. A status only.
|
|
292
|
+
*/
|
|
293
|
+
export function toneOf(tone, share) {
|
|
294
|
+
if (TONES.includes(tone)) return tone;
|
|
295
|
+
return share != null && share >= 1 ? "success" : "ink";
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ---- the lines between tiers (percentages of the tier's width)
|
|
299
|
+
|
|
300
|
+
const round = (n) => Math.round(n * 1000) / 1000;
|
|
301
|
+
|
|
302
|
+
/** The middle of column `index` of `count` equal columns, in percent of the row. */
|
|
303
|
+
export function centreOf(index, count) {
|
|
304
|
+
return count > 0 ? round(((index + 0.5) / count) * 100) : 50;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* The elbow from a card to the row of its kids, as positions in percent:
|
|
309
|
+
* `parent` (where the line leaves the card), `from` and `to` (the bar across,
|
|
310
|
+
* from the leftmost to the rightmost of the parent and every kid), `kids`
|
|
311
|
+
* (where each kid's line drops).
|
|
312
|
+
*/
|
|
313
|
+
export function elbowOf(parentIndex, parentCount, kidCount) {
|
|
314
|
+
const parent = centreOf(parentIndex, parentCount);
|
|
315
|
+
const kids = Array.from({ length: kidCount }, (_, i) => centreOf(i, kidCount));
|
|
316
|
+
const all = [parent, ...kids];
|
|
317
|
+
return { parent, from: Math.min(...all), to: Math.max(...all), kids };
|
|
318
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# TimeGrid
|
|
2
|
+
|
|
3
|
+
Days side by side on an hour grid, with **the agent's proposals in pencil**.
|
|
4
|
+
|
|
5
|
+
What is already there is **ink**: an event in its kind's colour, a lock on the
|
|
6
|
+
ones another calendar owns. What the agent suggests is **pencil**: a hatched
|
|
7
|
+
card on a dashed edge, sitting on its hour, with a **tag** hanging off it that
|
|
8
|
+
quotes the words it came from ("Thursday at 3pm your time"). A person keeps a
|
|
9
|
+
block and it inks in: the same block, its colours fading from pencil to ink.
|
|
10
|
+
Keep a few at once and they ink in one by one, in the order of the week.
|
|
11
|
+
|
|
12
|
+
- **Keep all says what it will do before it does it:** "Keeps 7 of 8 in ink:
|
|
13
|
+
5 Focus, 2 Admin. Leaves "Offer letter" in pencil: it clashes with Standup
|
|
14
|
+
9:30."
|
|
15
|
+
- **A clash is hatched where two things overlap**, before anything is kept.
|
|
16
|
+
- **A goals strip** (`GoalStrip`) can sit above the days: goals in pencil until
|
|
17
|
+
kept, and a total against a target that turns red once it is over.
|
|
18
|
+
- Blocks are **events, not tasks**: a date, a start, an end. Drag one to move
|
|
19
|
+
it, pull its edge to resize (snapping to 15 minutes), drag across free
|
|
20
|
+
working hours to make one, drop an item in from a list, arrow keys move a
|
|
21
|
+
focused block, **J** and **K** walk the proposals that are waiting.
|
|
22
|
+
- Hours outside the working day are hatched but still there. Today has a now
|
|
23
|
+
line. Under 36rem of its own width it shows one day at a time under a strip
|
|
24
|
+
of days.
|
|
25
|
+
|
|
26
|
+
```jsx
|
|
27
|
+
import { TimeGrid, GoalStrip } from "../components/blocks/time-grid"; // a Makersclaw app ("./blocks/time-grid" from components/)
|
|
28
|
+
import { TimeGrid, GoalStrip } from "@/components/makersclaw/blocks/time-grid"; // a shadcn project
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
A client component. No other block is laid with it. The helpers (`planKeepAll`,
|
|
32
|
+
`clashesOf`, `loadOf`…) are plain functions and work on the server.
|
|
33
|
+
|
|
34
|
+
## Which one: TimeGrid or calendar-view
|
|
35
|
+
|
|
36
|
+
`calendar-view` is the **tasks** calendar: it is built on `task-model`, takes
|
|
37
|
+
tasks with `start` and `due`, and saves task changes. Use it for work that has
|
|
38
|
+
dates and lives in a task list, for a month, for an agenda, and for a booking
|
|
39
|
+
page's open slots. It does not hold events that are not tasks, and it has no
|
|
40
|
+
pencil.
|
|
41
|
+
|
|
42
|
+
`time-grid` is for **a person reviewing what an agent would put on their
|
|
43
|
+
week**: plain events plus proposals, kept or dropped one at a time or all at
|
|
44
|
+
once. It reads and saves nothing; every press goes out through a callback.
|
|
45
|
+
|
|
46
|
+
## Which hook feeds it
|
|
47
|
+
|
|
48
|
+
None. The events are the app's rows (a calendar mirrored into a table, blocks
|
|
49
|
+
a person made); the proposals are rows the agent wrote. Keep them in the app's
|
|
50
|
+
tables and pass them in. The block never reads or writes: a press goes out
|
|
51
|
+
through `onAction`, `onKeepAll` or `onChange`, the app does the write, and
|
|
52
|
+
hands back the changed rows. **A kept proposal keeps its id.** Either give the
|
|
53
|
+
same row `state: "kept"` (it stays a proposal until the week is committed), or
|
|
54
|
+
move the row into `events` under the same id: the block is the same element
|
|
55
|
+
either way, so it inks in instead of blinking out and back.
|
|
56
|
+
|
|
57
|
+
## Props
|
|
58
|
+
|
|
59
|
+
### The week
|
|
60
|
+
|
|
61
|
+
- `days` — the days shown, in order, `["2026-09-21", …]` (one to seven). An
|
|
62
|
+
item whose `date` is not one of them is not drawn.
|
|
63
|
+
- `today` — "YYYY-MM-DD", marks the day's header and column.
|
|
64
|
+
- `now` — "HH:MM" (or minutes) for the now line; leave it out and it follows
|
|
65
|
+
the viewer's clock after mount.
|
|
66
|
+
- `startHour` (5) · `endHour` (21) — the hours on the grid. Time outside is
|
|
67
|
+
not drawn; an item that crosses an edge is cut to it.
|
|
68
|
+
- `workStart` · `workEnd` — "08:30", "17:30": the working day. Hours outside
|
|
69
|
+
it are hatched (still there), and a new block can only be made inside it.
|
|
70
|
+
- `hourHeight` (40) — pixels per hour. `snap` (15) — minutes a drag snaps to.
|
|
71
|
+
- `height` — the hour grid's scrolling height, `"34rem"` (never `vh`), or
|
|
72
|
+
`"fill"`: the grid fills what is left of a bounded parent (a flex column
|
|
73
|
+
with a height: an `AppLayout` content in `contentMode="fill"`, a bounded
|
|
74
|
+
`Card`) and its hours scroll inside it, so no app measures the box. Leave
|
|
75
|
+
it out and the grid is as tall as its hours. `scrollTo` ("08:00") — where
|
|
76
|
+
it opens; default the working day's start.
|
|
77
|
+
- `locale` ("en-GB") — weekday names. `formatTime(minutes)` — how the grid
|
|
78
|
+
prints a time (default 24-hour, `9:30`).
|
|
79
|
+
- `corner` — a small label in the top-left (a time zone, "BST").
|
|
80
|
+
- `dayNotes` — `{ "2026-09-21": { text: "5.3 of 9 h", tone: "danger" } }`, a
|
|
81
|
+
number beside a day's header. `tone`: `neutral` · `primary` · `info` ·
|
|
82
|
+
`success` · `warning` · `danger`. `loadOf` and `hoursOf` add the minutes up.
|
|
83
|
+
- `renderDayHeader(day, { isToday, index, compact, selected })` — your own
|
|
84
|
+
header.
|
|
85
|
+
- `above` — a node drawn above the days: `<GoalStrip …/>`, or your own.
|
|
86
|
+
|
|
87
|
+
### What is drawn
|
|
88
|
+
|
|
89
|
+
- `events` — ink. `[{ id, date, start, end, title, kind?, locked?, done?,
|
|
90
|
+
allDay?, caption?, sub?, icon?, draggable? }]`. `start`/`end` are "09:30".
|
|
91
|
+
No `start` (or `allDay`) puts it in the all-day row. `locked` shows a lock
|
|
92
|
+
and the block cannot be moved (`explainBlock` says why). `caption` follows
|
|
93
|
+
the time ("Maya's calendar"); `sub` is a line under the title when the block
|
|
94
|
+
is tall; `icon` is a kit icon.
|
|
95
|
+
- `proposals` — pencil. The same fields, and:
|
|
96
|
+
- `state` — `"pencil"` (default) · `"kept"` (drawn in ink, still a proposal,
|
|
97
|
+
with **Back to pencil**) · `"failed"` ("Didn't add", solid red edge) ·
|
|
98
|
+
`"replaced"` (struck, faint) · `"was"` (a dotted marker where it used to
|
|
99
|
+
be, under everything).
|
|
100
|
+
- `unsure: true` — a dotted edge and "Unsure".
|
|
101
|
+
- `tag` — `{ quote, source }` (or a string, the quote): hangs off the block
|
|
102
|
+
when it is selected (`tags="all"` shows every one). Under the block from
|
|
103
|
+
the left in the first half of the days, from the right in the second
|
|
104
|
+
half, above it when there is no room below.
|
|
105
|
+
- `label` — the small line over the title; default the kind's label,
|
|
106
|
+
"Unsure", "Didn't add"… and for a clash "Clash · Standup 9:30".
|
|
107
|
+
- `note` — a line at the foot of a tall block ("pencilled in Mon 06:00").
|
|
108
|
+
- `kinds` — `{ focus: { label: "Focus", tone: "primary", solid: true },
|
|
109
|
+
meeting: { label: "Meeting", tone: "neutral", locked: true }, personal: {
|
|
110
|
+
label: "Personal", hue: 5 } }`. `tone` is `neutral` · `primary` · `info` ·
|
|
111
|
+
`success` · `warning` · `danger`; `hue` is `1`–`5` (a categorical colour).
|
|
112
|
+
Ink is the kind's colour on its edge and a faint wash; `solid` fills it with
|
|
113
|
+
the accent. `locked` draws a lock on every block of that kind. `icon` is
|
|
114
|
+
a kit icon.
|
|
115
|
+
- `pencilColor` — `"neutral"` (default; pencil has no colour of its own) or
|
|
116
|
+
`"kind"` (the dashed edge takes the kind's colour).
|
|
117
|
+
- `pencilClassName` — a class for pencil titles, to set them in the app's
|
|
118
|
+
display face. Layout and type only, never colour.
|
|
119
|
+
- `drafting` — `[{ date, start, end, label? }]`: the agent still pencilling
|
|
120
|
+
this in, as a pulsing outline.
|
|
121
|
+
- `draft` — `{ date, start, end, title }`: the block being made while its
|
|
122
|
+
editor is open.
|
|
123
|
+
- `tags` — `"selected"` (default) · `"all"` · `"none"`.
|
|
124
|
+
- `clashAmong` (false) — also hatch two waiting proposals that overlap each
|
|
125
|
+
other (by default only a proposal against ink or a kept one).
|
|
126
|
+
|
|
127
|
+
### What a press does
|
|
128
|
+
|
|
129
|
+
- `selectedId`, `defaultSelectedId`, `onSelect(id | null)` — the selected
|
|
130
|
+
block. Pass `selectedId` to control it (from the address, say); leave it out
|
|
131
|
+
and the block keeps it. A press on empty hours clears it; <kbd>Esc</kbd> too.
|
|
132
|
+
- `onOpen(item, element)` — a block was clicked (or Enter on it): open your
|
|
133
|
+
record. The block is selected as well.
|
|
134
|
+
- `onAction(item, "keep" | "dismiss" | "unkeep")` — `async`, returns `{ ok:
|
|
135
|
+
true }` or `{ ok: false, error? }`. Keep and Dismiss are drawn on every
|
|
136
|
+
waiting proposal (in the block when it is tall, on hover or selection when it
|
|
137
|
+
is short; the all-day row shows them on the selected one), **Back to
|
|
138
|
+
pencil** on a kept one. A failure puts a notice over the grid with Try
|
|
139
|
+
again. **Without `onAction` no buttons are drawn.** The buttons are small
|
|
140
|
+
because a block is: put the same two in your side panel, full size.
|
|
141
|
+
- `keepAll` — `true`, or `{ label?, skip?, primary? }`. Draws the bar over
|
|
142
|
+
the grid: the sentence from `planKeepAll` and a **Keep all (7)** button.
|
|
143
|
+
`skip` is what stays in pencil: `["clash"]` (default), `["clash", "unsure"]`,
|
|
144
|
+
or `[]`.
|
|
145
|
+
- `onKeepAll(plan)` — `async`, same return. `plan.keep` is the ids to keep,
|
|
146
|
+
`plan.leave` the ones left and why, `plan.words` the sentence.
|
|
147
|
+
- `onChange(item, change, { proposal })` — `async`, same return. A block was
|
|
148
|
+
moved or resized: `change` is `{ date, start, end, mode: "move" | "start" |
|
|
149
|
+
"end", from: { date, start, end } }`, times as "09:30". It shows at once; a
|
|
150
|
+
failure puts it back and says so. Pencil blocks can be moved too
|
|
151
|
+
(`proposal: true`). A locked block, a failed or replaced proposal, and an
|
|
152
|
+
item with `draggable: false` stay put. Without `onChange` nothing moves.
|
|
153
|
+
- `onCompose({ date, start, end })` — a press, or a drag across free working
|
|
154
|
+
hours, on an empty day. Open your editor (and pass `draft` meanwhile).
|
|
155
|
+
- `onDrop(item, { date, start, end })` — an item from a list was dropped on
|
|
156
|
+
an hour. Make the list item draggable with `{...timeGridDragProps({ id,
|
|
157
|
+
title, minutes: 45 })}`. Same return as `onChange`.
|
|
158
|
+
- `explainBlock(item)` — a node: why a locked block cannot be moved ("From
|
|
159
|
+
Google Calendar · change it there"), shown once where it is when someone
|
|
160
|
+
tries. `explainOffHours({ date, minute, before })` — the same for a press
|
|
161
|
+
outside the working day.
|
|
162
|
+
- `readOnly` — to read: no buttons, no drags, no compose, no Keep all.
|
|
163
|
+
- `keys` (true) — <kbd>J</kbd> and <kbd>K</kbd> walk the proposals that are
|
|
164
|
+
waiting (by day, then by time) and move to their day on a narrow container.
|
|
165
|
+
The region takes focus with <kbd>Tab</kbd>. They are left alone while someone
|
|
166
|
+
is typing in a field.
|
|
167
|
+
|
|
168
|
+
### States and words
|
|
169
|
+
|
|
170
|
+
- `status` — `ready` (default) · `loading` (grey blocks on the grid) · `failed`
|
|
171
|
+
(a notice with `error`, and Try again if you pass `onRetry`).
|
|
172
|
+
- `emptyText` — what to say over a grid with nothing on it; `false` says
|
|
173
|
+
nothing.
|
|
174
|
+
- `words` — the block's few words, to be changed: `allDay`, `keep`, `dismiss`,
|
|
175
|
+
`unkeep`, `keepAll`, `pencilled`, `unsure`, `failed`, `replaced`, `was`,
|
|
176
|
+
`clash`, `locked`, `drafting`, `newBlock`, `empty`, `retry`, `back`.
|
|
177
|
+
- `label` ("Week") — the region's accessible name. `className` — layout:
|
|
178
|
+
width, margin. Never colour.
|
|
179
|
+
|
|
180
|
+
### GoalStrip
|
|
181
|
+
|
|
182
|
+
```jsx
|
|
183
|
+
<GoalStrip
|
|
184
|
+
items={[{ id: "g1", title: "Pricing page live", state: "pencil", mark: "pencilled in" }, …]}
|
|
185
|
+
summary={{ label: "Focus", value: 11, of: 15, unit: "h", over: false, note: "all placed" }}
|
|
186
|
+
onSelect={(id) => …}
|
|
187
|
+
/>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`items`: `{ id, title, number?, state: "ink" | "pencil" | "done" | "empty",
|
|
191
|
+
meta?, mark? }`. A goal in pencil is hatched with a dashed edge and fades to
|
|
192
|
+
ink when its `state` changes. `summary`: a total against a target; `over: true`
|
|
193
|
+
turns it red and says "over". `empty`: a node for no goals at all.
|
|
194
|
+
`selectedId` / `onSelect(id)` make the goals buttons.
|
|
195
|
+
|
|
196
|
+
### Helpers
|
|
197
|
+
|
|
198
|
+
Plain functions (`import` them from the same folder):
|
|
199
|
+
|
|
200
|
+
- `planKeepAll(items, { clashes?, kinds?, skip?, fmt? })` — `{ keep, leave,
|
|
201
|
+
counts, total, words }`: what Keep all will do. The block calls it itself;
|
|
202
|
+
call it too when a button of yours (a "Commit the week") should say the same.
|
|
203
|
+
- `clashesOf(items, { among? })` — Map id → `[{ withId, title, s, e }]`.
|
|
204
|
+
`items` are what `readItem` returns.
|
|
205
|
+
- `readItem(raw, { days, lo, hi, snap, pencil })` — an item as the grid draws
|
|
206
|
+
it (minutes for `s`/`e`).
|
|
207
|
+
- `loadOf(items, date, where?)` — minutes of timed items on a day;
|
|
208
|
+
`hoursOf(minutes)` → "5.3"; `durationWords(minutes)` → "1 h 30 min".
|
|
209
|
+
- `lookOf(item)`, `isWaiting(item)`, `waitingOrder`, `walkProposal`,
|
|
210
|
+
`newlyInked`, `previewOf`, `tagPlacement`, `gridLanes`, `namesOf`, `toMin`,
|
|
211
|
+
`toHHMM`, `clock`.
|
|
212
|
+
|
|
213
|
+
## Example
|
|
214
|
+
|
|
215
|
+
A week the agent drafted on Monday, kept one block at a time or all at once:
|
|
216
|
+
|
|
217
|
+
```jsx
|
|
218
|
+
"use client";
|
|
219
|
+
import { useRows } from "../lib/data";
|
|
220
|
+
import { updateRow, deleteRow } from "../lib/actions";
|
|
221
|
+
import { GoalStrip, TimeGrid } from "../components/blocks/time-grid";
|
|
222
|
+
|
|
223
|
+
const KINDS = {
|
|
224
|
+
focus: { label: "Focus", tone: "primary", solid: true },
|
|
225
|
+
meeting: { label: "Meeting", tone: "neutral", locked: true },
|
|
226
|
+
admin: { label: "Admin", tone: "success" },
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export default function Week({ days }) {
|
|
230
|
+
const blocks = useRows({ source: "blocks", filter: { week: days[0] } });
|
|
231
|
+
const goals = useRows({ source: "goals", filter: { week: days[0] } });
|
|
232
|
+
const rows = blocks.rows ?? [];
|
|
233
|
+
const toItem = (b) => ({ id: b.id, date: b.day, start: b.starts, end: b.ends, title: b.title, kind: b.kind, note: b.reason });
|
|
234
|
+
const keep = (ids) => Promise.all(ids.map((id) => updateRow({ source: "blocks", id, patch: { state: "kept" } })));
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<TimeGrid
|
|
238
|
+
days={days}
|
|
239
|
+
today={days[3]}
|
|
240
|
+
workStart="08:30"
|
|
241
|
+
workEnd="17:30"
|
|
242
|
+
height="36rem"
|
|
243
|
+
kinds={KINDS}
|
|
244
|
+
status={blocks.loading ? "loading" : blocks.error ? "failed" : "ready"}
|
|
245
|
+
error={blocks.error}
|
|
246
|
+
events={rows.filter((b) => b.state === "committed" || b.kind === "meeting").map(toItem)}
|
|
247
|
+
proposals={rows.filter((b) => b.state === "pencil" || b.state === "kept").map((b) => ({ ...toItem(b), state: b.state }))}
|
|
248
|
+
keepAll
|
|
249
|
+
onKeepAll={async (plan) => (await keep(plan.keep), { ok: true })}
|
|
250
|
+
onAction={async (item, action) => {
|
|
251
|
+
if (action === "dismiss") return deleteRow({ source: "blocks", id: item.id });
|
|
252
|
+
return updateRow({ source: "blocks", id: item.id, patch: { state: action === "keep" ? "kept" : "pencil" } });
|
|
253
|
+
}}
|
|
254
|
+
onChange={(item, c) => updateRow({ source: "blocks", id: item.id, patch: { day: c.date, starts: c.start, ends: c.end } })}
|
|
255
|
+
above={<GoalStrip items={(goals.rows ?? []).map((g) => ({ id: g.id, title: g.title, state: g.kept ? "ink" : "pencil" }))} />}
|
|
256
|
+
/>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
262
|
+
|
|
263
|
+
Proposals on the hours they came for, with the sender's words:
|
|
264
|
+
|
|
265
|
+
```jsx
|
|
266
|
+
<TimeGrid
|
|
267
|
+
days={days}
|
|
268
|
+
pencilColor="kind"
|
|
269
|
+
kinds={{ meeting: { label: "Meeting", hue: 1 }, todo: { label: "To-do", hue: 4 } }}
|
|
270
|
+
events={calendar}
|
|
271
|
+
proposals={found.map((f) => ({
|
|
272
|
+
id: f.id, date: f.day, start: f.start, end: f.end, title: f.title, kind: f.kind,
|
|
273
|
+
unsure: f.sureness < 0.7,
|
|
274
|
+
tag: { quote: f.phrase, source: `${f.sender} · ${f.subject}` },
|
|
275
|
+
}))}
|
|
276
|
+
selectedId={openId}
|
|
277
|
+
onSelect={setOpenId}
|
|
278
|
+
onAction={(item, action) => decide(item.id, action)}
|
|
279
|
+
/>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## States
|
|
283
|
+
|
|
284
|
+
Drawn in `demo.jsx`: a planner's week with goals above, locked meetings, focus
|
|
285
|
+
in pencil, Keep, Dismiss, Keep all, a failing drag, a drop from a list;
|
|
286
|
+
proposals with a tag and a hatched clash, a "was" marker and a failed add;
|
|
287
|
+
320 px (one day at a time); seven days and five kinds; one event; very long
|
|
288
|
+
words; empty; loading; failed; read-only.
|
|
289
|
+
|
|
290
|
+
## What it will not do
|
|
291
|
+
|
|
292
|
+
- Recurring events, time zones, or blocks that cross midnight: a time is wall
|
|
293
|
+
time on one day.
|
|
294
|
+
- Fetch or save. Rows come in as props, saves go out through the callbacks.
|
|
295
|
+
- Decide what clashes beyond overlap: two blocks that touch end to start do
|
|
296
|
+
not clash, and all-day items never do. A rule such as "no meetings after 5"
|
|
297
|
+
is the app's; mark it in the proposal's `label`.
|
|
298
|
+
- Take the place of the record: a click goes out through `onOpen`; the form,
|
|
299
|
+
the Commit and the Undo are the page's.
|
|
300
|
+
- Show a month, an agenda or a booking page's open slots. That is
|
|
301
|
+
`calendar-view`.
|
|
302
|
+
|
|
303
|
+
A grid it cannot be is your own component in `components/` — not an edit to
|
|
304
|
+
this block. Its files are fixed: `npm run check` refuses a change, and
|
|
305
|
+
`makersclaw add time-grid` restores them.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "time-grid",
|
|
3
|
+
"title": "Time grid",
|
|
4
|
+
"description": "Days side by side on an hour grid, with the agent's proposals in pencil. What is already there is ink (events in their kind's colour, a lock on the ones another calendar owns); what the agent suggests is pencil: a hatched card on a dashed edge sitting on its hour, with a tag hanging off it that quotes the words it came from (\"Thursday at 3pm your time\"). Keep inks a block in (the same block, so its colours fade from pencil to ink; kept together they ink one by one), Dismiss drops it, Back to pencil takes a kept one back. \"Keep all\" says what it will do before it does it (\"Keeps 7 of 8 in ink: 5 Focus, 2 Admin. Leaves 1 in pencil: it clashes with Standup 9:30.\"). A clash is hatched where a proposal overlaps something already there, before anything is kept. A goals strip (GoalStrip) can sit above the days, goals in pencil until kept, with a total against a target that turns red once it is over. Blocks are events, not tasks: drag to move or resize (snapping to 15 minutes), drag across free working hours to make one, drop an item in from a list, arrow keys move a focused block, J and K walk the proposals that are waiting. Hours outside the working day are hatched but still there; a now line on today; one day at a time under a strip of days when the container is narrow. Loading, failed, empty and read-only are drawn.",
|
|
5
|
+
"useWhen": "A screen where a person reviews what an agent would put on their week: a weekly planner whose focus blocks are drafted in pencil and committed in ink, an inbox that finds meetings and deadlines in mail and shows each on its hour next to the real calendar with the sender's own words, a shift or room plan an assistant drafts, a day plan with proposals. Events are plain items with a date, a start and an end (not tasks). Not for tasks with start and due dates that live in a task list (calendar-view), for a month, for long timelines (gantt), or for a booking page's open slots (calendar-view).",
|
|
6
|
+
"tags": ["calendar", "week", "day", "hour grid", "time grid", "schedule", "planner", "pencil", "ink", "proposal", "suggestion", "draft", "keep", "keep all", "dismiss", "ghost", "tag", "clash", "conflict", "overlap", "goals", "focus time", "agent", "inbox to calendar", "weekly planner", "drag", "resize"],
|
|
7
|
+
"exports": ["TimeGrid", "GoalStrip", "TIME_GRID_MIME", "timeGridDragProps", "PENCIL_STATES", "clashesOf", "clock", "durationWords", "gridLanes", "hoursOf", "isWaiting", "loadOf", "lookOf", "namesOf", "newlyInked", "planKeepAll", "previewOf", "readItem", "tagPlacement", "toHHMM", "toMin", "walkProposal", "waitingOrder"],
|
|
8
|
+
"files": ["index.js", "time-grid.jsx", "grid-model.js"],
|
|
9
|
+
"requires": []
|
|
10
|
+
}
|