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,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "margin-notes",
|
|
4
|
+
"type": "registry:block",
|
|
5
|
+
"title": "MarginNotes",
|
|
6
|
+
"description": "Passages of text with notes in the margin, level with the words they are about (stacked under each passage when the container is narrow). Four kinds of note, each with its tone and its buttons: source (where the words came from: a title, a line of detail, the words read), flag (\"No source. Check this line.\" with It's true, Cut it, Ask where it came from), cut (the words struck through, with the reason, Put back, or a lock when nobody may put them back) and comment. The words a note is about are marked in the passage; the selected passage and its note are linked, and J and K walk the notes that still need a person. A settled note folds to one line with what was decided and a Reopen. Saves go out through one callback, onAction(note, actionId).",
|
|
7
|
+
"registryDependencies": [
|
|
8
|
+
"https://cdn.jsdelivr.net/npm/makersclaw@0.8.1/r/core.json",
|
|
9
|
+
"https://cdn.jsdelivr.net/npm/makersclaw@0.8.1/r/agent-marks.json"
|
|
10
|
+
],
|
|
11
|
+
"dependencies": [],
|
|
12
|
+
"files": [
|
|
13
|
+
{
|
|
14
|
+
"path": "registry/blocks/margin-notes/index.js",
|
|
15
|
+
"type": "registry:component",
|
|
16
|
+
"target": "@components/makersclaw/blocks/margin-notes/index.js",
|
|
17
|
+
"content": "// The MarginNotes block — `makersclaw add margin-notes`. Fixed: compose it\n// through its props (README.md beside this file). The model's helpers are\n// plain functions; they work on the server too.\nexport { MarginNotes } from \"./margin-notes\";\nexport {\n MARGIN_KINDS,\n TONES,\n MARKS,\n resolveKinds,\n noteLook,\n actionsOf,\n isSettled,\n arrangeNotes,\n walkNext,\n cycleNote,\n findQuote,\n inlineMarks,\n countNotes,\n countWords,\n} from \"./margin-model\";\n"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"path": "registry/blocks/margin-notes/margin-notes.jsx",
|
|
21
|
+
"type": "registry:component",
|
|
22
|
+
"target": "@components/makersclaw/blocks/margin-notes/margin-notes.jsx",
|
|
23
|
+
"content": "\"use client\";\n\n// Passages of text with notes in the margin, level with their words. Each\n// passage is a row: the words on one side, the notes about them on the\n// other (stacked under the words when the container is narrow). A note says\n// where the words came from (source), that they need a look (flag), that\n// they were cut and why (cut), or what someone thought (comment); an app\n// can add kinds. The words a note is about are marked in the passage, and\n// the selected note and its passage are linked: click either, or press J\n// and K to walk the notes that still need a person.\n//\n// Props in, callbacks out: the block never reads or writes data. A press on\n// a note's button calls onAction(note, actionId); the app does the work and\n// hands back changed notes. README.md beside this file is the contract.\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { Badge, Button, Empty, Icon, Kbd, Notice, Skeleton, cx } from \"../../ui\";\nimport { AgentMark } from \"../agent-marks\";\nimport { actionsOf, arrangeNotes, countNotes, countWords, cycleNote, inlineMarks, isSettled, noteLook, resolveKinds, walkNext } from \"./margin-model\";\n\n// Literal class names, so Tailwind sees every one of them.\nconst GRID = {\n right: {\n sm: \"@2xl:grid-cols-[minmax(0,1fr)_15rem]\",\n md: \"@2xl:grid-cols-[minmax(0,1fr)_18rem]\",\n lg: \"@2xl:grid-cols-[minmax(0,1fr)_22rem]\",\n },\n left: {\n sm: \"@2xl:grid-cols-[15rem_minmax(0,1fr)]\",\n md: \"@2xl:grid-cols-[18rem_minmax(0,1fr)]\",\n lg: \"@2xl:grid-cols-[22rem_minmax(0,1fr)]\",\n },\n};\n\nconst TEXT = { sm: \"text-sm leading-relaxed\", md: \"text-base leading-relaxed\", lg: \"text-lg leading-relaxed\" };\n\n// The coloured bar down a note's inline-start edge.\nconst BAR = {\n neutral: \"bg-muted-foreground\",\n primary: \"bg-primary\",\n info: \"bg-info\",\n success: \"bg-success\",\n warning: \"bg-warning\",\n danger: \"bg-destructive\",\n};\nconst BAR_HUE = { 1: \"bg-chart-1\", 2: \"bg-chart-2\", 3: \"bg-chart-3\", 4: \"bg-chart-4\", 5: \"bg-chart-5\" };\n\nconst DECO = {\n neutral: \"decoration-muted-foreground\",\n primary: \"decoration-primary\",\n info: \"decoration-info\",\n success: \"decoration-success\",\n warning: \"decoration-warning\",\n danger: \"decoration-destructive\",\n};\nconst DECO_HUE = { 1: \"decoration-chart-1\", 2: \"decoration-chart-2\", 3: \"decoration-chart-3\", 4: \"decoration-chart-4\", 5: \"decoration-chart-5\" };\n\nconst WASH = {\n neutral: \"bg-muted\",\n primary: \"bg-primary/15\",\n info: \"bg-info/15\",\n success: \"bg-success/15\",\n warning: \"bg-warning/15\",\n danger: \"bg-destructive/15\",\n};\nconst WASH_HUE = { 1: \"bg-chart-1/15\", 2: \"bg-chart-2/15\", 3: \"bg-chart-3/15\", 4: \"bg-chart-4/15\", 5: \"bg-chart-5/15\" };\n\nconst deco = (look) => (look.hue ? DECO_HUE[look.hue] : DECO[look.tone]);\nconst wash = (look) => (look.hue ? WASH_HUE[look.hue] : WASH[look.tone]);\n\nconst domId = (root, kind, id) => `${root}-${kind}-${String(id).replace(/[^a-zA-Z0-9_-]/g, \"_\")}`;\n\nconst reducedMotion = () => typeof window !== \"undefined\" && window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches;\n\n/** Words typed into, or a key meant for something else: J and K are left alone. */\nfunction isTyping(el) {\n if (!el || el.nodeType !== 1) return false;\n return Boolean(el.closest?.(\"input, textarea, select, [contenteditable=''], [contenteditable='true']\"));\n}\n\n/** The words of a passage, with the marks the notes make on them. */\nfunction Words({ passage, notes, kinds, selectedId, rootId, as: Tag = \"p\" }) {\n const looks = useMemo(() => new Map(notes.map((n) => [n.id, noteLook(n, kinds)])), [notes, kinds]);\n const { segments } = useMemo(() => inlineMarks(passage.text, notes, (n) => looks.get(n.id)?.mark ?? \"none\"), [passage.text, notes, looks]);\n if (passage.node != null) {\n const struck = notes.find((n) => !isSettled(n) && looks.get(n.id)?.mark === \"strike\");\n const look = struck ? looks.get(struck.id) : null;\n return <div className={cx(\"min-w-0 break-words\", look && cx(\"text-muted-foreground line-through decoration-2\", deco(look)))}>{passage.node}</div>;\n }\n return (\n <Tag className={cx(\"m-0 min-w-0 break-words\", Tag !== \"p\" && \"text-[1.05em] font-semibold leading-snug\")}>\n {segments.map((seg, i) => {\n if (!seg.ids.length) return <span key={i}>{seg.text}</span>;\n const id = seg.ids.includes(selectedId) ? selectedId : seg.ids[0];\n const look = looks.get(id);\n const on = seg.ids.includes(selectedId);\n const common = { \"data-note\": id, \"data-selected\": on ? \"\" : undefined, \"aria-describedby\": domId(rootId, \"note\", id) };\n if (look.mark === \"strike\")\n return (\n <del key={i} {...common} className={cx(\"rounded-sm text-muted-foreground line-through decoration-2\", deco(look), on && wash(look))}>\n <span className=\"sr-only\">{look.label}: </span>\n {seg.text}\n </del>\n );\n return (\n <mark\n key={i}\n {...common}\n className={cx(\n \"rounded-sm text-inherit\",\n look.mark === \"highlight\" || on ? wash(look) : \"bg-transparent\",\n look.mark === \"underline\" && \"underline decoration-dotted decoration-2 underline-offset-4\",\n look.mark === \"wavy\" && \"underline decoration-wavy decoration-1 underline-offset-4\",\n look.mark !== \"highlight\" && deco(look),\n )}\n >\n <span className=\"sr-only\">{look.label}: </span>\n {seg.text}\n </mark>\n );\n })}\n </Tag>\n );\n}\n\n/** One note. */\nfunction Note({ note, kinds, selected, busy, error, readOnly, canAct, onSelect, onPress, rootId, cardRef, quoteShown }) {\n const look = noteLook(note, kinds);\n const settled = isSettled(note);\n const actions = canAct ? actionsOf(note, kinds, { readOnly }) : [];\n const agent = note.by === \"agent\" || note.by?.agent === true;\n const byName = typeof note.by === \"string\" ? (note.by === \"agent\" ? \"\" : note.by) : (note.by?.name ?? \"\");\n const showReopen = settled && canAct && !readOnly && look.reopen;\n\n return (\n <article\n ref={cardRef}\n id={domId(rootId, \"note\", note.id)}\n tabIndex={-1}\n data-note={note.id}\n data-kind={note.kind}\n data-selected={selected ? \"\" : undefined}\n aria-current={selected ? \"true\" : undefined}\n aria-label={note.title && note.title !== look.label ? `${look.label}: ${note.title}` : look.label}\n onClick={() => onSelect(note.id)}\n className={cx(\n \"relative grid min-w-0 gap-1.5 overflow-hidden rounded-lg border border-border py-3 pe-3 ps-4 text-sm text-card-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n selected && \"ring-2 ring-ring\",\n settled ? \"bg-muted\" : \"bg-card\",\n )}\n >\n <span aria-hidden=\"true\" className={cx(\"absolute inset-y-0 start-0 w-1\", look.hue ? BAR_HUE[look.hue] : BAR[look.tone], settled && \"opacity-40\")} />\n {settled ? (\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n <Icon name=\"check\" size={14} className=\"shrink-0 text-success\" />\n <span className=\"text-[13px] font-medium\">{note.answer || \"Settled\"}</span>\n {note.at ? <span className=\"text-xs text-muted-foreground\">{note.at}</span> : null}\n {showReopen ? (\n <Button size=\"sm\" variant=\"link\" tone=\"muted\" loading={busy === \"reopen\"} aria-label={`${look.reopen.label ?? \"Reopen\"}: ${look.label}`} onClick={() => onPress(note, \"reopen\")}>\n {look.reopen.label ?? \"Reopen\"}\n </Button>\n ) : null}\n </div>\n ) : (\n <>\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n <Badge variant=\"soft\" tone={look.hue ? undefined : look.tone} hue={look.hue ?? undefined} className=\"gap-1\">\n <Icon name={look.icon} size={12} aria-hidden=\"true\" />\n {look.label}\n </Badge>\n {agent ? <AgentMark label=\"Raised by the agent\" /> : null}\n {byName ? <span className=\"text-xs text-muted-foreground\">{byName}</span> : null}\n {note.at ? <span className=\"text-xs tabular-nums text-muted-foreground\">{note.at}</span> : null}\n </div>\n {note.title ? <p className=\"m-0 font-medium\">{note.title}</p> : null}\n {note.meta ? <p className=\"m-0 text-xs text-muted-foreground\">{note.meta}</p> : null}\n {quoteShown && note.quote ? <p className=\"m-0 break-words text-[13px] italic text-muted-foreground\">{`“${note.quote}”`}</p> : null}\n {note.text ? <p className=\"m-0 break-words\">{note.text}</p> : null}\n {note.detail ? <p className=\"m-0 mt-0.5 break-words border-t border-dashed border-border pt-1.5 text-[13px] leading-snug text-muted-foreground\">{note.detail}</p> : null}\n {note.href ? (\n <a href={note.href} target=\"_blank\" rel=\"noreferrer\" className=\"inline-flex items-center gap-1 justify-self-start rounded-sm text-xs font-medium text-primary underline-offset-4 hover:underline focus-visible:ring-2 focus-visible:ring-ring\">\n {note.hrefLabel || \"Open the source\"}\n <Icon name=\"external\" size={12} aria-hidden=\"true\" />\n </a>\n ) : null}\n {note.locked ? (\n <p className=\"m-0 flex items-start gap-1.5 text-xs text-muted-foreground\">\n <Icon name=\"lock\" size={12} className=\"mt-0.5 shrink-0\" />\n <span className=\"min-w-0\">{note.locked}</span>\n </p>\n ) : null}\n {actions.length ? (\n <div className=\"flex flex-wrap items-center gap-1.5 pt-0.5\">\n {actions.map((a) => (\n <Button\n key={a.id}\n size=\"sm\"\n variant={a.variant || \"secondary\"}\n icon={a.icon}\n loading={busy === a.id}\n disabled={Boolean(busy) && busy !== a.id}\n aria-label={`${a.label}: ${look.label}${note.title ? `, ${note.title}` : \"\"}`}\n onClick={() => onPress(note, a.id)}\n >\n {a.label}\n </Button>\n ))}\n </div>\n ) : null}\n </>\n )}\n {error ? (\n <p role=\"alert\" className=\"m-0 text-xs text-destructive\">\n {error}\n </p>\n ) : null}\n </article>\n );\n}\n\n/** Rows of grey bars where the passages and their notes will be. */\nfunction Loading({ cols }) {\n return (\n <div role=\"status\" aria-label=\"Loading\" aria-busy=\"true\" className={cx(\"grid grid-cols-[minmax(0,1fr)] gap-x-6 gap-y-4\", cols)}>\n {[0, 1, 2].map((i) => (\n <div key={i} className=\"contents\">\n <div className=\"grid min-w-0 gap-2 py-2\">\n <Skeleton className=\"w-full\" />\n <Skeleton className=\"w-11/12\" />\n <Skeleton className=\"w-2/3\" />\n </div>\n <div className=\"grid min-w-0 content-start py-2\">\n <Skeleton shape=\"rect\" className=\"h-16 w-full\" />\n </div>\n </div>\n ))}\n </div>\n );\n}\n\n/**\n * passages [{ id, text | node, lead?, label?, level? }]: the words. `lead` is a\n * short figure before them (a time, a number); `label` a name above\n * (a speaker, a section); `level` 2 · 3 · 4 draws the text as a heading.\n * notes [{ id, passage, kind, title?, meta?, text?, detail?, quote?, by?, at?,\n * href?, hrefLabel?, locked?, state?, answer?, actions?, tone?, hue?,\n * mark?, label? }]\n * selected / defaultSelected / onSelect(id | null) the selected note\n * onAction(note, actionId) async → { ok } | { ok: false, error }\n * kinds changes to the built-in kinds, or new ones\n * side right (default) · left: which side of the words the margin is on\n * marginWidth sm · md (default) · lg\n * marginTitle the margin's heading\n * showNotes false hides the margin (default true)\n * textSize sm · md (default) · lg\n * status ready (default) · loading · failed, with `error` and onRetry\n * readOnly notes to read: no buttons\n * keys J and K walk the notes (default true)\n */\nexport function MarginNotes({\n passages = [],\n notes = [],\n selected,\n defaultSelected = null,\n onSelect,\n onAction,\n kinds: kindChanges,\n side = \"right\",\n marginWidth = \"md\",\n marginTitle,\n showNotes = true,\n textSize = \"md\",\n status = \"ready\",\n error,\n onRetry,\n readOnly = false,\n keys = true,\n noNotes = \"Nothing in the margin.\",\n orphanTitle = \"Not tied to a passage\",\n emptyTitle = \"Nothing to read yet\",\n emptyBody,\n countLabels,\n label = \"Passages with notes in the margin\",\n className,\n}) {\n const rootId = useRef(`mn${Math.random().toString(36).slice(2, 8)}`).current;\n const kinds = useMemo(() => resolveKinds(kindChanges), [kindChanges]);\n const { byPassage, ordered, orphans } = useMemo(() => arrangeNotes(passages, notes), [passages, notes]);\n const counts = useMemo(() => countNotes(notes), [notes]);\n\n const [inner, setInner] = useState(defaultSelected);\n const raw = selected === undefined ? inner : selected;\n const current = raw != null && ordered.some((n) => n.id === raw) ? raw : null;\n const position = current == null ? 0 : ordered.findIndex((n) => n.id === current) + 1;\n\n const [busy, setBusy] = useState({});\n const [errors, setErrors] = useState({});\n const cards = useRef(new Map());\n const rows = useRef(new Map());\n const frame = useRef(0);\n useEffect(() => () => cancelAnimationFrame(frame.current), []);\n\n const choose = (id, bring = false) => {\n if (selected === undefined) setInner(id);\n onSelect?.(id);\n if (bring && id != null) {\n cancelAnimationFrame(frame.current);\n frame.current = requestAnimationFrame(() => {\n cards.current.get(id)?.focus({ preventScroll: true });\n (rows.current.get(ordered.find((n) => n.id === id)?.passage) ?? cards.current.get(id))?.scrollIntoView({ block: \"nearest\", behavior: reducedMotion() ? \"auto\" : \"smooth\" });\n });\n }\n };\n\n async function press(note, actionId) {\n if (!onAction) return;\n const key = note.id;\n if (busy[key]) return;\n setBusy((b) => ({ ...b, [key]: actionId }));\n setErrors((e) => ({ ...e, [key]: null }));\n let r;\n try {\n r = await onAction(note, actionId);\n } catch (e) {\n r = { ok: false, error: e?.message ?? String(e) };\n }\n setBusy((b) => ({ ...b, [key]: null }));\n if (r && r.ok === false) setErrors((e) => ({ ...e, [key]: r.error || \"That did not work. Try again.\" }));\n }\n\n function onKeyDown(e) {\n if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.altKey || isTyping(e.target)) return;\n if (e.key === \"Escape\" && current != null) {\n e.preventDefault();\n choose(null);\n } else if (keys && showNotes && (e.key === \"j\" || e.key === \"J\" || e.key === \"k\" || e.key === \"K\")) {\n const next = walkNext(ordered, current, e.key.toLowerCase() === \"j\" ? 1 : -1);\n if (next == null) return;\n e.preventDefault();\n choose(next, true);\n }\n }\n\n const cols = showNotes ? (GRID[side] ?? GRID.right)[marginWidth] ?? GRID.right.md : \"\";\n const marginFirst = side === \"left\";\n const typeSize = TEXT[textSize] ?? TEXT.md;\n const summary = countWords(counts, countLabels);\n\n const noteList = (list) =>\n list.map((n) => (\n <Note\n key={n.id}\n note={n}\n kinds={kinds}\n selected={n.id === current}\n busy={busy[n.id]}\n error={errors[n.id]}\n readOnly={readOnly}\n canAct={Boolean(onAction)}\n onSelect={(id) => choose(id)}\n onPress={press}\n rootId={rootId}\n cardRef={(el) => (el ? cards.current.set(n.id, el) : cards.current.delete(n.id))}\n quoteShown={passages.find((p) => p.id === n.passage)?.node != null}\n />\n ));\n\n let body;\n if (status === \"loading\") body = <Loading cols={cols} />;\n else if (status === \"failed\")\n body = (\n <Notice tone=\"danger\" title=\"The notes did not load\" action={onRetry ? <Button size=\"sm\" icon=\"refresh\" onClick={onRetry}>Try again</Button> : null}>\n {error || \"Something went wrong. Try again.\"}\n </Notice>\n );\n else if (!passages.length) body = <Empty icon=\"message-square\" title={emptyTitle} body={emptyBody} />;\n else\n body = (\n <>\n <ol className=\"m-0 grid list-none gap-y-1 p-0\">\n {passages.map((p) => {\n const mine = byPassage.get(p.id) ?? [];\n const selectedHere = mine.some((n) => n.id === current);\n const Heading = p.level >= 2 && p.level <= 4 ? `h${p.level}` : null;\n const onPassage = mine.length ? () => choose(cycleNote(mine, current)) : undefined;\n return (\n <li\n key={p.id}\n ref={(el) => (el ? rows.current.set(p.id, el) : rows.current.delete(p.id))}\n data-passage={p.id}\n data-selected={selectedHere ? \"\" : undefined}\n className={cx(\"grid grid-cols-[minmax(0,1fr)] gap-x-6 gap-y-2 py-1.5\", cols)}\n >\n <div\n {...(mine.length\n ? {\n tabIndex: 0,\n onClick: onPassage,\n onKeyDown: (e) => {\n if ((e.key === \"Enter\" || e.key === \" \") && e.target === e.currentTarget) {\n e.preventDefault();\n onPassage();\n }\n },\n \"aria-label\": `${p.label ? `${p.label}. ` : \"\"}${mine.length} ${mine.length === 1 ? \"note\" : \"notes\"}. Press Enter to select.`,\n role: \"group\",\n }\n : {})}\n className={cx(\n \"flex min-w-0 gap-3 rounded-md px-2 py-1.5 outline-none\",\n typeSize,\n marginFirst && showNotes && \"@2xl:order-last\",\n mine.length && \"cursor-pointer focus-visible:ring-2 focus-visible:ring-ring\",\n selectedHere ? \"bg-accent\" : mine.length ? \"hover:bg-accent/50\" : \"\",\n )}\n >\n {p.lead ? <span className=\"w-12 shrink-0 pt-0.5 text-xs tabular-nums text-muted-foreground\">{p.lead}</span> : null}\n <div className=\"grid min-w-0 grow gap-0.5\">\n {p.label ? <span className=\"text-[13px] font-semibold leading-snug\">{p.label}</span> : null}\n <Words passage={p} notes={mine} kinds={kinds} selectedId={current} rootId={rootId} as={Heading ?? \"p\"} />\n </div>\n </div>\n {showNotes ? <div className={cx(\"grid min-w-0 content-start gap-2\", marginFirst && \"@2xl:order-first\")}>{noteList(mine)}</div> : null}\n </li>\n );\n })}\n </ol>\n {showNotes && orphans.length ? (\n <section aria-label={orphanTitle} className=\"mt-2 grid gap-2 border-t border-border pt-3\">\n <h3 className=\"m-0 text-xs font-medium uppercase tracking-wide text-muted-foreground\">{orphanTitle}</h3>\n <div className=\"grid grid-cols-[minmax(0,1fr)] gap-2 @2xl:grid-cols-2\">{noteList(orphans)}</div>\n </section>\n ) : null}\n </>\n );\n\n return (\n <section\n aria-label={label}\n tabIndex={0}\n onKeyDown={onKeyDown}\n className={cx(\"@container grid min-w-0 grid-cols-[minmax(0,1fr)] gap-3 rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-ring\", className)}\n >\n {showNotes && status === \"ready\" && passages.length > 0 && Boolean(marginTitle || summary || (keys && ordered.length > 0) || !notes.length) ? (\n <header className=\"flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1\">\n {marginTitle ? <h2 className=\"m-0 text-base font-semibold leading-tight\">{marginTitle}</h2> : null}\n <p className=\"m-0 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground\">\n {summary ? <span>{summary}</span> : !notes.length ? <span>{noNotes}</span> : null}\n {keys && ordered.length ? (\n <span className=\"hidden items-center gap-1 @2xl:inline-flex\">\n <Kbd>J</Kbd>\n <Kbd>K</Kbd>\n <span>next and previous note</span>\n </span>\n ) : null}\n </p>\n </header>\n ) : null}\n <p role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {current != null ? `Note ${position} of ${ordered.length}` : \"\"}\n </p>\n {body}\n </section>\n );\n}\n"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"path": "registry/blocks/margin-notes/margin-model.js",
|
|
27
|
+
"type": "registry:component",
|
|
28
|
+
"target": "@components/makersclaw/blocks/margin-notes/margin-model.js",
|
|
29
|
+
"content": "// The margin-notes block's pure model — no React, no DOM.\n//\n// A passage is a stretch of words with an id. A note belongs to one passage\n// (note.passage) and says something about it: where it came from, that it\n// needs checking, that it was cut, a comment. This file holds the rules the\n// component and its tests share: which kinds exist and how each looks and\n// acts, which notes sit by which passage, which words of a passage a note\n// marks, what J and K walk to, and the counts.\n//\n// README.md beside this file is the contract; this file is the proof.\n\n/** The tones a kind or a note may use (the kit's status tones). */\nexport const TONES = [\"neutral\", \"primary\", \"info\", \"success\", \"warning\", \"danger\"];\n\n/** How the words a note is about are drawn in the passage. */\nexport const MARKS = [\"underline\", \"wavy\", \"strike\", \"highlight\", \"none\"];\n\n/**\n * The four kinds a margin knows. An app may change any field of one, or add\n * its own kind, through the block's `kinds` prop.\n *\n * label the chip's words\n * icon a kit icon name\n * tone neutral · primary · info · success · warning · danger\n * mark underline · wavy · strike · highlight · none: the words in the\n * passage that the note is about\n * actions [{ id, label, variant?, icon? }]: the buttons on an open note\n * reopen the button on a settled note (false: none)\n */\nexport const MARGIN_KINDS = {\n source: { label: \"Source\", icon: \"link\", tone: \"neutral\", mark: \"underline\", actions: [], reopen: false },\n flag: {\n label: \"Check this line\",\n icon: \"flag\",\n tone: \"warning\",\n mark: \"wavy\",\n actions: [\n { id: \"confirm\", label: \"It’s true\", icon: \"check\", variant: \"secondary\" },\n { id: \"cut\", label: \"Cut it\", icon: \"x\", variant: \"secondary\" },\n { id: \"ask\", label: \"Ask where it came from\", variant: \"quiet\" },\n ],\n reopen: { label: \"Reopen\" },\n },\n cut: {\n label: \"Cut\",\n icon: \"eraser\",\n tone: \"danger\",\n mark: \"strike\",\n actions: [{ id: \"putback\", label: \"Put back\", icon: \"undo\", variant: \"secondary\" }],\n reopen: false,\n },\n comment: { label: \"Comment\", icon: \"message-square\", tone: \"info\", mark: \"highlight\", actions: [], reopen: false },\n};\n\nconst FALLBACK = { label: \"Note\", icon: \"message-circle\", tone: \"neutral\", mark: \"highlight\", actions: [], reopen: false };\n\n/** \"needs_review\" -> \"Needs review\". */\nexport function humanize(id) {\n const words = String(id ?? \"\").replace(/[_-]+/g, \" \").trim();\n return words ? words[0].toUpperCase() + words.slice(1) : \"\";\n}\n\n/** The built-in kinds with an app's changes laid over them, field by field. */\nexport function resolveKinds(overrides) {\n const out = {};\n for (const [id, def] of Object.entries(MARGIN_KINDS)) out[id] = { ...def };\n for (const [id, def] of Object.entries(overrides ?? {})) {\n if (!def || typeof def !== \"object\") continue;\n out[id] = { ...(out[id] ?? { ...FALLBACK, label: humanize(id) }), ...def };\n }\n return out;\n}\n\n/** What a note looks like and offers: its kind's definition with the note's own words over it. */\nexport function noteLook(note, kinds) {\n const kind = kinds?.[note?.kind] ?? { ...FALLBACK, label: humanize(note?.kind) || FALLBACK.label };\n const tone = TONES.includes(note?.tone) ? note.tone : TONES.includes(kind.tone) ? kind.tone : \"neutral\";\n const hue = Number.isInteger(note?.hue) && note.hue >= 1 && note.hue <= 5 ? note.hue : null;\n const mark = MARKS.includes(note?.mark) ? note.mark : MARKS.includes(kind.mark) ? kind.mark : \"highlight\";\n return {\n label: note?.label || kind.label,\n icon: kind.icon || FALLBACK.icon,\n tone,\n hue,\n mark,\n actions: Array.isArray(note?.actions) ? note.actions : kind.actions ?? [],\n reopen: kind.reopen,\n };\n}\n\n/** A note nobody needs to act on any more. */\nexport const isSettled = (note) => note?.state === \"settled\";\n\n/** The buttons an open note shows; none when it is locked, settled or read-only. */\nexport function actionsOf(note, kinds, { readOnly = false } = {}) {\n if (readOnly || isSettled(note) || note?.locked) return [];\n return noteLook(note, kinds).actions.filter((a) => a && a.id && a.label);\n}\n\n/**\n * Which notes sit by which passage.\n * byPassage Map: passage id -> its notes, in the order given\n * ordered every note, in the passages' order (a passage's notes keep their order)\n * orphans notes whose passage is not in the list\n */\nexport function arrangeNotes(passages, notes) {\n const byPassage = new Map();\n for (const p of passages ?? []) {\n if (!p || p.id == null || byPassage.has(p.id)) continue;\n byPassage.set(p.id, []);\n }\n const orphans = [];\n const seen = new Set();\n for (const n of notes ?? []) {\n if (!n || n.id == null || seen.has(n.id)) continue;\n seen.add(n.id);\n if (byPassage.has(n.passage)) byPassage.get(n.passage).push(n);\n else orphans.push(n);\n }\n const ordered = [];\n for (const list of byPassage.values()) ordered.push(...list);\n ordered.push(...orphans);\n return { byPassage, ordered, orphans };\n}\n\n/**\n * What J (dir 1) and K (dir -1) walk to: the next note that still needs a\n * person, after the selected one. With nothing selected, the first (or the\n * last, going back). At the end it stays where it is. Null when no note is\n * left to walk.\n */\nexport function walkNext(ordered, currentId, dir = 1) {\n const list = ordered ?? [];\n const step = dir < 0 ? -1 : 1;\n const open = (n) => !isSettled(n);\n if (!list.some(open)) return null;\n const at = list.findIndex((n) => n.id === currentId);\n if (at < 0) {\n const pick = step > 0 ? list.find(open) : [...list].reverse().find(open);\n return pick.id;\n }\n for (let i = at + step; i >= 0 && i < list.length; i += step) if (open(list[i])) return list[i].id;\n return currentId;\n}\n\n/** A press on a passage: its first note; pressed again, its next; after the last, none. */\nexport function cycleNote(passageNotes, currentId) {\n const list = passageNotes ?? [];\n if (!list.length) return null;\n const at = list.findIndex((n) => n.id === currentId);\n if (at < 0) return list[0].id;\n return list[at + 1]?.id ?? null;\n}\n\nconst escapeRe = (s) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\n/** Where a quote sits in a text, ignoring case: [start, end] or null. */\nexport function findQuote(text, quote) {\n const q = typeof quote === \"string\" ? quote.trim() : \"\";\n if (!q || typeof text !== \"string\") return null;\n const m = new RegExp(escapeRe(q).replace(/\\s+/g, \"\\\\s+\"), \"iu\").exec(text);\n return m ? [m.index, m.index + m[0].length] : null;\n}\n\n/**\n * The words of a passage, cut into pieces by the notes that mark them.\n *\n * segments [{ text, ids }]: in order, joined they are the text; ids are\n * the notes whose quote covers the piece (empty: plain words)\n * whole the first open note whose mark is \"strike\" and whose quote\n * is empty or not in the text: it strikes the whole passage\n * (then `segments` is that one piece)\n *\n * `markOf(note)` names how a note marks its words; a settled note marks\n * nothing (a cut that was put back reads as plain words again).\n */\nexport function inlineMarks(text, notes, markOf = () => \"highlight\") {\n const body = typeof text === \"string\" ? text : \"\";\n const ranges = [];\n let whole = null;\n for (const n of notes ?? []) {\n if (isSettled(n)) continue;\n const mark = markOf(n);\n if (mark === \"none\") continue;\n const at = findQuote(body, n.quote);\n if (at) ranges.push({ id: n.id, from: at[0], to: at[1] });\n else if (mark === \"strike\" && !whole) whole = n;\n }\n if (whole) return { segments: [{ text: body, ids: [whole.id] }], whole };\n if (!ranges.length) return { segments: [{ text: body, ids: [] }], whole: null };\n const cuts = [...new Set([0, body.length, ...ranges.flatMap((r) => [r.from, r.to])])].sort((a, b) => a - b);\n const segments = [];\n for (let i = 0; i < cuts.length - 1; i++) {\n const [a, b] = [cuts[i], cuts[i + 1]];\n if (a === b) continue;\n segments.push({ text: body.slice(a, b), ids: ranges.filter((r) => r.from <= a && r.to >= b).map((r) => r.id) });\n }\n return { segments, whole: null };\n}\n\n/** How many notes there are, how many still need a person, and by kind. */\nexport function countNotes(notes) {\n const out = { total: 0, open: 0, settled: 0, byKind: {} };\n for (const n of notes ?? []) {\n if (!n) continue;\n out.total += 1;\n if (isSettled(n)) out.settled += 1;\n else out.open += 1;\n const k = (out.byKind[n.kind] ??= { total: 0, open: 0 });\n k.total += 1;\n if (!isSettled(n)) k.open += 1;\n }\n return out;\n}\n\n/** \"3 open · 1 settled\" — the margin's count line. */\nexport function countWords(counts, words = {}) {\n const open = words.open ?? \"open\";\n const settled = words.settled ?? \"settled\";\n const parts = [];\n if (counts.open) parts.push(`${counts.open} ${open}`);\n if (counts.settled) parts.push(`${counts.settled} ${settled}`);\n return parts.join(\" · \");\n}\n"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"path": "registry/blocks/margin-notes/README.md",
|
|
33
|
+
"type": "registry:file",
|
|
34
|
+
"target": "@components/makersclaw/blocks/margin-notes/README.md",
|
|
35
|
+
"content": "# MarginNotes\n\nPassages of text with notes in the margin, **level with the words they are\nabout**. On a wide container each passage is a row: the words on one side,\nits notes on the other. On a narrow one (under about 42rem) the notes stack\nunder the passage they belong to.\n\nA note is one of four kinds, each with its own tone and its own buttons:\n\n- **source** — where the words came from: a title, a line of detail, the\n words that were read, a link.\n- **flag** — \"No source. Check this line.\" with **It's true**, **Cut it**,\n **Ask where it came from**.\n- **cut** — the words struck through where they stand, the reason in the\n margin, **Put back** (or a lock when nobody may put them back).\n- **comment** — what someone thought of the line.\n\nAn app can change any of them or add its own kind.\n\nThe words a note is about (`quote`) are marked in the passage: dotted for a\nsource, wavy for a flag, struck for a cut, washed for a comment. **The\nselected note and its passage are linked**: click a passage or a note, or\npress <kbd>J</kbd> and <kbd>K</kbd> to walk the notes that still need a\nperson. A settled note folds to one line with what was decided and a\n**Reopen**.\n\n```jsx\nimport { MarginNotes } from \"../components/blocks/margin-notes\"; // a Makersclaw app (\"./blocks/margin-notes\" from components/)\nimport { MarginNotes } from \"@/components/makersclaw/blocks/margin-notes\"; // a shadcn project\n```\n\nA client component. `makersclaw add margin-notes` lays `agent-marks` with\nit. The helpers (`walkNext`, `inlineMarks`, `countNotes`…) are plain\nfunctions and work on the server.\n\n## Which hook feeds it\n\nNone. The passages are the app's words (a draft, a transcript); the notes are\nrows the agent or a person wrote beside them. Keep them in the app's tables\nand pass them in. The block never reads or writes: a press goes out through\n`onAction`, the app does the write, and hands back the changed notes.\n\n## Props\n\n- `passages` — the words, in reading order: `[{ id, text, lead?, label?,\n level?, node? }]`. `text` is plain words. `lead` is a short figure before\n them (a time `00:12`, a number); `label` a name above them (a speaker, a\n section); `level` `2` · `3` · `4` draws the passage as a heading. `node`\n (a React node) draws your own markup instead of `text`: a note's `quote`\n cannot mark inside it (the card shows the quote instead), but a cut still\n strikes it whole.\n- `notes` — `[{ id, passage, kind, … }]`. `passage` is the id of the\n passage it sits by; a note whose passage is not in the list is drawn\n after the last row under `orphanTitle`. The rest:\n - `kind` — `source` · `flag` · `cut` · `comment`, or one of your `kinds`.\n - `title` — the note's heading (\"v3.0.0\", \"No source. Check this line.\").\n - `meta` — a muted line under it (\"tern-app/tern · shipped Tue 22 Sep\").\n - `text` — the note's words (a reason, a comment).\n - `detail` — the proof: the words that were read, under a dashed rule.\n - `quote` — the words **in the passage** the note is about. The first\n place they appear (ignoring case) is marked. Without one a `cut` strikes\n the whole passage; the other kinds mark nothing in the words.\n - `by` — `\"agent\"` or `{ agent: true }` draws the agent's mark; any other\n string or `{ name }` is shown as a name.\n - `at` — when, in words (\"12:31\", \"Tue 22 Sep\").\n - `href`, `hrefLabel` — \"Open the source\" (default) as a link that opens\n in a new tab.\n - `locked` — words for why nothing can be done to this note (\"On the\n never-list: margins\"). A locked note shows a lock and no buttons.\n - `state` — `\"settled\"` folds the note to a line: `answer` (\"You said\n it's true\") and `at`, and the kind's **Reopen** if it has one. Settled\n notes mark nothing in the words and are skipped by <kbd>J</kbd> and\n <kbd>K</kbd>.\n - `actions` — `[{ id, label, variant?, icon? }]`, replacing the kind's\n buttons for this note.\n - `label`, `tone`, `hue`, `mark` — this note's chip words, status tone\n (`neutral` · `primary` · `info` · `success` · `warning` · `danger`),\n categorical colour `1`–`5` (for a family of cuts: pricing, other people,\n in confidence, length), and mark (`underline` · `wavy` · `strike` ·\n `highlight` · `none`) over the kind's.\n- `selected`, `defaultSelected`, `onSelect(id | null)` — the selected\n note's id. Pass `selected` to control it (from the address, say); leave it\n out and the block keeps it. <kbd>Esc</kbd> clears it.\n- `onAction(note, actionId)` — `async`, returns `{ ok: true }` or\n `{ ok: false, error? }`. Called when a button is pressed; the button shows\n its spinner until it answers, and a failure prints its `error` under the\n note, where it happened. A throw counts as a failure. A settled note's\n Reopen calls it with `\"reopen\"`. **Without `onAction` no buttons are\n drawn.**\n- `kinds` — changes to the built-ins, or new kinds, by id: `{ flag: {\n label: \"Check\" }, aside: { label: \"Aside\", tone: \"primary\", icon:\n \"message-circle\", mark: \"highlight\", actions: [] } }`. Fields are `label`,\n `icon` (a kit icon), `tone`, `mark`, `actions`, and `reopen` (`false`, or\n `{ label }`). Each field you give replaces that one field.\n- `side` — `right` (default) or `left`: which side of the words the margin\n is on. Narrow, the notes are always under the words.\n- `marginWidth` — `sm` (15rem) · `md` (18rem, default) · `lg` (22rem).\n- `marginTitle` — a heading over the whole thing (\"Where each paragraph\n came from\"), with the count line beside it.\n- `showNotes` — `false` hides the margin and its marks: the words alone.\n- `textSize` — `sm` · `md` (default) · `lg`.\n- `status` — `ready` (default) · `loading` (rows of grey bars) · `failed`\n (a notice with `error`, and Try again if you pass `onRetry`).\n- `readOnly` — notes to read: no buttons, no Reopen.\n- `keys` — <kbd>J</kbd> and <kbd>K</kbd> walk the notes (default `true`).\n- `noNotes`, `orphanTitle`, `emptyTitle`, `emptyBody`, `countLabels` — the\n block's few words, to be changed (`countLabels`: `{ open, settled }`).\n- `label` — the region's accessible name.\n- `className` — layout: width, margin, height. Never colour.\n\n### Keys\n\nOn the region (it takes focus with Tab): <kbd>J</kbd> the next note that\nstill needs a person, <kbd>K</kbd> the one before, <kbd>Esc</kbd> clears the\nselection. On a passage with notes: <kbd>Enter</kbd> or <kbd>Space</kbd>\nselects its first note, again its next, after the last none. The walk moves\nfocus to the note, scrolls it into view (without animation when the person\nhas asked for less motion) and a hidden line says \"Note 2 of 5\". The keys\nare left alone while someone is typing in a field.\n\n### Helpers\n\nPlain functions (`import` them from the same folder):\n\n- `walkNext(notes, currentId, dir)` — the id J (`1`) or K (`-1`) goes to.\n- `inlineMarks(text, notes, markOf)` — a passage's words cut into pieces\n by the notes' quotes: `{ segments: [{ text, ids }], whole }`.\n- `arrangeNotes(passages, notes)` — `{ byPassage, ordered, orphans }`.\n- `countNotes(notes)`, `countWords(counts)` — `{ total, open, settled,\n byKind }` and \"3 open · 1 settled\".\n- `resolveKinds(changes)`, `noteLook(note, kinds)`, `actionsOf(note, kinds)`\n — the kinds, and what a note looks like and offers.\n- `MARGIN_KINDS` — the built-in kinds.\n\n## Example\n\nAn issue with a source beside each paragraph, and a line the agent could\nnot source:\n\n```jsx\n\"use client\";\nimport { useRows } from \"../lib/data\";\nimport { updateRow } from \"../lib/actions\";\nimport { MarginNotes } from \"../components/blocks/margin-notes\";\n\nexport default function Issue({ issueId }) {\n const paragraphs = useRows({ source: \"issue_paragraphs\", filter: { issue_id: issueId }, orderBy: \"position asc\" });\n const notes = useRows({ source: \"paragraph_notes\", filter: { issue_id: issueId } });\n if (paragraphs.loading || notes.loading) return <MarginNotes status=\"loading\" />;\n return (\n <MarginNotes\n marginTitle=\"Where each paragraph came from\"\n passages={(paragraphs.rows ?? []).map((p) => ({ id: p.id, text: p.body, level: p.is_heading ? 3 : undefined }))}\n notes={(notes.rows ?? []).map((n) => ({\n id: n.id,\n passage: n.paragraph_id,\n kind: n.kind, // \"source\" · \"flag\"\n title: n.title,\n meta: n.meta,\n detail: n.excerpt,\n quote: n.quote,\n by: n.by_agent ? \"agent\" : undefined,\n state: n.settled_at ? \"settled\" : undefined,\n answer: n.answer,\n }))}\n onAction={(note, action) =>\n updateRow({ source: \"paragraph_notes\", id: note.id, patch: { answer: action, settled_at: action === \"reopen\" ? null : new Date().toISOString() } })\n }\n />\n );\n}\n```\n\nIn a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.\n\nA transcript with the cut words struck, a family colour per reason, and a\nlock for what may never come back:\n\n```jsx\n<MarginNotes\n side=\"left\"\n marginWidth=\"lg\"\n textSize=\"sm\"\n passages={lines.map((l) => ({ id: l.id, lead: l.clock, label: l.speaker, text: l.words }))}\n notes={cuts.map((c) => ({\n id: c.id,\n passage: c.line_id,\n kind: \"cut\",\n hue: c.family, // 1..5, one colour per reason family\n label: c.reason, // \"Pricing\", \"A third party\"…\n text: c.note,\n locked: c.on_never_list ? `On the never-list: ${c.on_never_list}.` : undefined,\n state: c.put_back ? \"settled\" : undefined,\n answer: \"Put back\",\n }))}\n onAction={(note, action) => putBack(note.id)}\n/>\n```\n\n## States\n\nDrawn in `demo.jsx`: sources and a flag that settles in place, cuts with\nthe margin on the left, 320 px (notes under the words), one passage, very\nlong words, passages with an empty margin, no passages, loading, failed,\nread-only.\n\n## What it will not do\n\n- Edit the words. A passage is read; to change it, open it in your editor\n (a flag's \"Cut it\" goes out through `onAction` and your app changes the\n text).\n- Mark the same quote twice in one passage, or a quote that spans two\n passages: the first place it appears is marked, in the passage the note\n is on.\n- Put a note anywhere but beside its passage's row. A long note makes its\n row taller.\n- Keep the app's data. Settled, cut, put back: all come back in through\n `notes`.\n\nA margin it cannot be is your own component in `components/` — not an edit\nto this block. Its files are fixed: `npm run check` refuses a change, and\n`makersclaw add margin-notes` restores them.\n"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"categories": [
|
|
39
|
+
"margin",
|
|
40
|
+
"notes",
|
|
41
|
+
"marginalia",
|
|
42
|
+
"sources",
|
|
43
|
+
"citations",
|
|
44
|
+
"provenance",
|
|
45
|
+
"fact check",
|
|
46
|
+
"flag",
|
|
47
|
+
"claim",
|
|
48
|
+
"check",
|
|
49
|
+
"cut",
|
|
50
|
+
"struck",
|
|
51
|
+
"redact",
|
|
52
|
+
"transcript",
|
|
53
|
+
"minutes",
|
|
54
|
+
"review",
|
|
55
|
+
"comment",
|
|
56
|
+
"proof",
|
|
57
|
+
"annotation",
|
|
58
|
+
"agent"
|
|
59
|
+
],
|
|
60
|
+
"docs": "Installed to components/makersclaw/blocks/margin-notes. Import: import { MarginNotes, MARGIN_KINDS, TONES, MARKS, resolveKinds, noteLook, actionsOf, isSettled, arrangeNotes, walkNext, cycleNote, findQuote, inlineMarks, countNotes, countWords } from \"@/components/makersclaw/blocks/margin-notes\". Props and an example: components/makersclaw/blocks/margin-notes/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
|
|
61
|
+
"meta": {
|
|
62
|
+
"makersclaw": {
|
|
63
|
+
"version": "0.8.1",
|
|
64
|
+
"useWhen": "A piece of writing is read against what stands behind it: a newsletter issue with a source beside every paragraph, a blog post with the claims that need a person, minutes whose transcript shows what was cut and why, a review with a reviewer's comments beside the lines. Not a thread of comments with no text beside it (comment-box), not one note log for a record (record-notes), not suggested edits inside an editor (doc-editor), not a screenshot with pins (annotate)."
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
package/r/menubar.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"title": "Menubar",
|
|
6
6
|
"description": "A desktop app's menu bar — File, Edit, View, Help — each a menu of actions with shortcuts, checkboxes, radio groups and submenus; click to open, hover to switch, arrow keys along the bar; one menu button on a phone.",
|
|
7
7
|
"registryDependencies": [
|
|
8
|
-
"https://cdn.jsdelivr.net/npm/makersclaw@0.
|
|
8
|
+
"https://cdn.jsdelivr.net/npm/makersclaw@0.8.1/r/core.json"
|
|
9
9
|
],
|
|
10
10
|
"dependencies": [],
|
|
11
11
|
"files": [
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"docs": "Installed to components/makersclaw/blocks/menubar. Import: import { Menubar } from \"@/components/makersclaw/blocks/menubar\". Props and an example: components/makersclaw/blocks/menubar/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
|
|
46
46
|
"meta": {
|
|
47
47
|
"makersclaw": {
|
|
48
|
-
"version": "0.
|
|
48
|
+
"version": "0.8.1",
|
|
49
49
|
"useWhen": "A document-style tool with many commands grouped by kind: an editor, a canvas, a spreadsheet, an admin console that mimics a desktop app. Not for a site's navigation (a navigation menu) or one record's actions (a menu on the row)."
|
|
50
50
|
}
|
|
51
51
|
}
|
package/r/mind-map.json
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"title": "MindMap",
|
|
6
6
|
"description": "A mind map you can build without the mouse, on flow-canvas: a centre topic with main branches balanced on both sides, each branch a tidy auto-laid-out tree in its own chart hue. Tab adds a subtopic, Enter the next topic, typing renames, arrows walk the map, ⌘↑/⌘↓ fold a branch (a folded one shows how many it hides), ⌥↑/⌥↓ reorder, Shift+Tab moves up a level; drag a topic onto another to move its branch. Undo/redo, a colour per branch, an Outline view, copy a branch as an outline and paste an outline as topics, starter templates. The map is plain JSON (a tree of topics); saves are async callbacks answering { ok }.",
|
|
7
7
|
"registryDependencies": [
|
|
8
|
-
"https://cdn.jsdelivr.net/npm/makersclaw@0.
|
|
9
|
-
"https://cdn.jsdelivr.net/npm/makersclaw@0.
|
|
8
|
+
"https://cdn.jsdelivr.net/npm/makersclaw@0.8.1/r/core.json",
|
|
9
|
+
"https://cdn.jsdelivr.net/npm/makersclaw@0.8.1/r/flow-canvas.json"
|
|
10
10
|
],
|
|
11
11
|
"dependencies": [],
|
|
12
12
|
"files": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"path": "registry/blocks/mind-map/README.md",
|
|
39
39
|
"type": "registry:file",
|
|
40
40
|
"target": "@components/makersclaw/blocks/mind-map/README.md",
|
|
41
|
-
"content": "# MindMap\n\nA mind map you can build without touching the mouse. One centre topic,\nmain branches balanced on both sides of it, each branch a tidy tree in its\nown colour. **Tab** adds a subtopic, **Enter** the next topic, typing\nrenames, the arrows walk the map. The layout is automatic: topics never\nneed dragging into place, and the map re-balances as it grows.\n\nA bar on top holds undo and redo, the save state, the selected topic's\nactions (add, colour, fold, delete) and a Map / Outline switch. The\nOutline view is the map as a nested list — the reading of it for a screen\nreader, and a quick way to jump to a topic.\n\nIt is built on `flow-canvas` (which draws it, pans and zooms): this block\nowns the tree, the layout and the keys.\n\nA map opens fitted to its view, but never smaller than its words can be\nread: 79%, where its smallest words (14px) draw at the kit's smallest\ntext size (11px). A map too big for that — most maps on a phone — opens\nat 79% centred on its centre topic; ⇧1 still fits the whole of it.\n\n```jsx\nimport { MindMap } from \"../components/blocks/mind-map\"; // a Makersclaw app (\"./blocks/mind-map\" from components/)\nimport { MindMap } from \"@/components/makersclaw/blocks/mind-map\"; // a shadcn project\n```\n\n`MindMap` is a client component: render it from a `\"use client\"`\ncomponent. `makersclaw add mind-map` adds `flow-canvas` with it (and its\nnpm package, `@xyflow/react`: run `npm install` after). The tree helpers\n(`mindMapToText`, `outlineToMindMap`, `layoutMindMap`…) are plain\nfunctions and work on the server too.\n\n## The data\n\nA map is one tree of topics. Store it as it comes out of `onChange` (one\n`jsonb` column), and keep `mindMapToText(map)` beside it for search and\nagents.\n\n```js\n{\n id: \"root\", text: \"Relay 2.0 launch\",\n children: [\n { id: \"why\", text: \"Why now\", side: \"right\", children: [\n { id: \"why-1\", text: \"Churn up 3 pts\" },\n ] },\n { id: \"risks\", text: \"Risks\", side: \"left\", color: \"chart-4\", collapsed: true, children: [ … ] },\n ],\n}\n```\n\n- `id` — any string, unique in the map. `text` — the words (a newline is\n allowed). `children` — the subtopics, in order.\n- `collapsed: true` — the branch is folded; the map shows how many topics\n it hides.\n- `color` — a colour **token**, never a colour value: `\"chart-2\"` …\n `\"chart-5\"`, `\"chart-1\"` (the theme's own), `\"warning\"`, `\"success\"`,\n `\"info\"`, `\"destructive\"`. Subtopics inherit their branch's. A main\n branch with none takes the next of `colors`.\n- `side` — `\"left\"` or `\"right\"`, main branches only. Missing, the map\n splits the branches to balance the two sides; the first change pins\n them, so adding a branch never flips the others across the centre.\n- Anything else on a topic (`note`, your own keys) is kept as it is.\n\n`normalizeMap(anything)` cleans what it is given: missing or repeated ids,\n`title`/`label` for `text`, `topics`/`items` for `children`, an outline\nstring, or nothing (a fresh map). The component runs it on `value`.\n\n## Props\n\n- `value` — the map, controlled. A new `value` that is not what the map\n last sent through `onChange` replaces it (loading another record); that\n replacement can be undone, and when it is another map (a new centre, or\n other main branches) the view fits it, as it fits a first one. Pass\n back exactly what `onChange` gave you.\n- `defaultValue` — the starting map, uncontrolled. With neither, a centre\n topic called \"Central topic\" and the first-run card.\n- `onChange(map)` — the whole tree after a change settles (debounced by\n `changeDelay`; typing is saved as it goes). Selection, folding in\n read-only mode, panning and zooming never call it.\n- `onSave(map)` — `async`, returns `{ ok: true }` or `{ ok: false,\n error? }` (a throw counts as failed). Called right after `onChange`. The\n bar shows \"Saving…\", \"Saved\", or the error with Retry. A failure keeps\n the map as it is on screen (nothing typed is thrown away); Retry sends\n it again.\n- `changeDelay` — ms of quiet before `onChange` / `onSave` (default 400).\n- `readOnly` — `false` (default). `true`: no editing; the arrows still\n walk the map, Space folds and unfolds (not saved), the Outline view and\n copying work.\n- `colors` — the branch palette, token names (default `BRANCH_COLORS`:\n chart-2, 3, 4, 5, 1). The colour picker offers these plus warning,\n success, info and destructive.\n- `templates` — what the first-run card offers: `[{ id, label, icon?,\n description?, map }]`, `map` a tree or a function returning one (default\n `mindMapTemplates()`: Project plan, Brainstorm, Meeting notes). `[]` for\n none.\n- `placeholder` — what an empty topic shows while it is typed (default\n \"New topic\").\n- `height` — px or any CSS length, default `600`. A height class in\n `className` (`h-dvh`, `h-full`, `flex-1`) replaces it.\n- `label` — the map's accessible name (default \"Mind map\").\n- `onSelectionChange(topic | null)` — the selected topic changed (a side\n panel with its notes).\n- `className` — layout around it (margin, width, height). Never colour.\n\n## Keyboard\n\nWith a topic selected (click one, or press an arrow):\n\n| Key | Does |\n| --- | --- |\n| Tab | add a subtopic and type it |\n| Enter / Shift+Enter | add the next topic below / above |\n| Space, F2, or just type | rename |\n| Delete / Backspace | delete the topic and its branch (a toast offers Undo) |\n| ← → ↑ ↓ | walk the map: outwards to a child, inwards to the parent, up and down the column |\n| ⌥↑ / ⌥↓ | move the topic up or down among its siblings |\n| Shift+Tab | move the topic up a level |\n| ⌘↑ / ⌘↓ | fold / unfold the branch |\n| ⌘D | duplicate the branch |\n| ⌘C / ⌘X / ⌘V | copy or cut a branch as an outline; paste an outline (from notes, a doc, an agent) as subtopics |\n| ⌘Z / ⇧⌘Z | undo / redo |\n| Home | the centre |\n| Esc | clear the selection |\n| ⇧1 | fit the whole map to the screen, however small |\n| ? | the shortcuts sheet |\n\nWhile typing a topic: **Enter** finishes it and starts the next one (on\nan empty one it steps out a level, so a run of topics never needs the\nmouse), **Tab** finishes it and starts a subtopic (on an empty one it\nmoves in a level), **Backspace** on an empty topic removes it and goes\nback to the one before, **Esc** finishes. Shift+Enter is a new line.\n\nWith the mouse: click selects, double-click renames, the **+** at a\ntopic's end adds a subtopic, the chip at a branch's end folds it (folded,\nit shows the count it hides), and dragging a topic onto another moves its\nbranch there. A plain drag on the empty canvas pans; pinch or ⌘-scroll\nzooms. On a touch screen a drag always pans; tap selects, double-tap\nrenames, and the bar's buttons add.\n\n## The helpers\n\nPlain functions over a tree; each returns a new tree and leaves the old\none as it was.\n\n- Read: `findTopic(map, id)`, `pathTo(map, id)` → `[root, …, topic]`,\n `parentOf(map, id)`, `countTopics(topic)` (everything under it).\n- Change: `updateTopic(map, id, patch)`, `insertTopic(map, parentId,\n topic, index?)`, `insertSibling(map, id, topic, { before })`,\n `removeTopic(map, id)`, `moveTopic(map, id, parentId, index?)` (refused\n into its own branch), `shiftTopic(map, id, ±1)`, `indentTopic`,\n `outdentTopic`, `duplicateTopic(map, id)` → `{ root, id }`,\n `setAllCollapsed(map, on)`, `createTopic(text)`.\n- Text: `mindMapToText(map)` (an indented outline, for an agent or search),\n `mindMapToMarkdown(map)` (a heading and a nested list),\n `outlineToMindMap(text)` and `outlineToTopics(text)` (bullets, numbers,\n headings, tabs or spaces — what an agent writes).\n- Layout: `layoutMindMap(map, { measure?, colors? })` → `{ items, bounds }`\n (every visible topic's box, side, depth, colour and the line to its\n parent), `mindMapToBoard(map)` (the same as a flow-canvas board).\n- `mindMapTemplates()`, `BRANCH_COLORS`.\n\n## Example\n\n```jsx\n// surfaces/plan-map.jsx — client: one plan's map\n\"use client\";\nimport { useState } from \"react\";\nimport { useSearchParams } from \"next/navigation\";\nimport { useRow } from \"../lib/data\";\nimport { updateRow } from \"../lib/actions\";\nimport { MindMap, mindMapToText } from \"../components/blocks/mind-map\";\n\nexport default function PlanMapSurface() {\n const id = useSearchParams().get(\"row\");\n const { row: plan, loading } = useRow({ source: \"plans\", id });\n if (loading || !plan) return null;\n return <PlanMap key={plan.id} plan={plan} />;\n}\n\nfunction PlanMap({ plan }) {\n const [map, setMap] = useState(plan.map); // jsonb, may be null; read once per plan\n return (\n <MindMap\n value={map ?? undefined}\n defaultValue={{ text: plan.name }}\n onChange={setMap}\n onSave={(m) => updateRow({ source: \"plans\", id: plan.id, patch: { map: m, map_text: mindMapToText(m) } })}\n label={`${plan.name} map`}\n className=\"h-[calc(100dvh-8rem)]\"\n />\n );\n}\n```\n\nIn a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.\n\nAn agent's outline, straight onto a map:\n\n```jsx\nconst map = outlineToMindMap(`# Hiring plan\n- Roles\n - Founding engineer\n - Designer\n- Sourcing\n - Referrals\n - Communities`);\n<MindMap defaultValue={map} onSave={save} />\n```\n\n## What it will not do\n\n- Free placement: topics sit where the layout puts them (drag moves a\n branch to another topic, not to a spot). For free boards use\n `flow-canvas` or `whiteboard`.\n- Cross-links between branches, images or icons on topics.\n- Collaborate live: saves are last-write-wins.\n- Export an image.\n- Render the map on the server: the server shows it as text until the\n canvas mounts. For a read-only page that must be rich in the HTML,\n render `mindMapToMarkdown` yourself.\n\nA map it cannot be is your own component in `components/` — not an edit\nto this block. Its files are fixed: `npm run check` refuses a change, and\n`makersclaw add mind-map` restores them.\n"
|
|
41
|
+
"content": "# MindMap\n\nA mind map you can build without touching the mouse. One centre topic,\nmain branches balanced on both sides of it, each branch a tidy tree in its\nown colour. **Tab** adds a subtopic, **Enter** the next topic, typing\nrenames, the arrows walk the map. The layout is automatic: topics never\nneed dragging into place, and the map re-balances as it grows.\n\nA bar on top holds undo and redo, the save state, the selected topic's\nactions (add, colour, fold, delete) and a Map / Outline switch. The\nOutline view is the map as a nested list — the reading of it for a screen\nreader, and a quick way to jump to a topic.\n\nIt is built on `flow-canvas` (which draws it, pans and zooms): this block\nowns the tree, the layout and the keys.\n\nA map opens fitted to its view, but never smaller than its words can be\nread: 79%, where its smallest words (14px) draw at the kit's smallest\ntext size (11px). A map too big for that — most maps on a phone — opens\nat 79% centred on its centre topic; ⇧1 still fits the whole of it.\n\n```jsx\nimport { MindMap } from \"../components/blocks/mind-map\"; // a Makersclaw app (\"./blocks/mind-map\" from components/)\nimport { MindMap } from \"@/components/makersclaw/blocks/mind-map\"; // a shadcn project\n```\n\n`MindMap` is a client component: render it from a `\"use client\"`\ncomponent. `makersclaw add mind-map` adds `flow-canvas` with it (and its\nnpm package, `@xyflow/react`: run `npm install` after). The tree helpers\n(`mindMapToText`, `outlineToMindMap`, `layoutMindMap`…) are plain\nfunctions and work on the server too.\n\n## The data\n\nA map is one tree of topics. Store it as it comes out of `onChange` (one\n`jsonb` column), and keep `mindMapToText(map)` beside it for search and\nagents.\n\n```js\n{\n id: \"root\", text: \"Relay 2.0 launch\",\n children: [\n { id: \"why\", text: \"Why now\", side: \"right\", children: [\n { id: \"why-1\", text: \"Churn up 3 pts\" },\n ] },\n { id: \"risks\", text: \"Risks\", side: \"left\", color: \"chart-4\", collapsed: true, children: [ … ] },\n ],\n}\n```\n\n- `id` — any string, unique in the map. `text` — the words (a newline is\n allowed). `children` — the subtopics, in order.\n- `collapsed: true` — the branch is folded; the map shows how many topics\n it hides.\n- `color` — a colour **token**, never a colour value: `\"chart-2\"` …\n `\"chart-5\"`, `\"chart-1\"` (the theme's own), `\"warning\"`, `\"success\"`,\n `\"info\"`, `\"destructive\"`. Subtopics inherit their branch's. A main\n branch with none takes the next of `colors`.\n- `side` — `\"left\"` or `\"right\"`, main branches only. Missing, the map\n splits the branches to balance the two sides; the first change pins\n them, so adding a branch never flips the others across the centre.\n- Anything else on a topic (`note`, your own keys) is kept as it is.\n\n`normalizeMap(anything)` cleans what it is given: missing or repeated ids,\n`title`/`label` for `text`, `topics`/`items` for `children`, an outline\nstring, or nothing (a fresh map). The component runs it on `value`.\n\n## Props\n\n- `value` — the map, controlled. A new `value` that is not what the map\n last sent through `onChange` replaces it (loading another record); that\n replacement can be undone, and when it is another map (a new centre, or\n other main branches) the view fits it, as it fits a first one. Pass\n back exactly what `onChange` gave you.\n- `defaultValue` — the starting map, uncontrolled. With neither, a centre\n topic called \"Central topic\" and the first-run card.\n- `onChange(map)` — the whole tree after a change settles (debounced by\n `changeDelay`; typing is saved as it goes). Selection, folding in\n read-only mode, panning and zooming never call it.\n- `onSave(map)` — `async`, returns `{ ok: true }` or `{ ok: false,\n error? }` (a throw counts as failed). Called right after `onChange`. The\n bar shows \"Saving…\", \"Saved\", or the error with Retry. A failure keeps\n the map as it is on screen (nothing typed is thrown away); Retry sends\n it again.\n- `changeDelay` — ms of quiet before `onChange` / `onSave` (default 400).\n- `readOnly` — `false` (default). `true`: no editing; the arrows still\n walk the map, Space folds and unfolds (not saved), the Outline view and\n copying work.\n- `colors` — the branch palette, token names (default `BRANCH_COLORS`:\n chart-2, 3, 4, 5, 1). The colour picker offers these plus warning,\n success, info and destructive.\n- `templates` — what the first-run card offers: `[{ id, label, icon?,\n description?, map }]`, `map` a tree or a function returning one (default\n `mindMapTemplates()`: Project plan, Brainstorm, Meeting notes). `[]` for\n none.\n- `placeholder` — what an empty topic shows while it is typed (default\n \"New topic\").\n- `height` — px or any CSS length, default `600`. A height class in\n `className` (`h-full`, `min-h-0 flex-1`) replaces it: `min-h-0 flex-1`\n fills what the surface has left (the Example). Never `h-dvh` or a `vh`\n height, which `npm run check` refuses.\n- `label` — the map's accessible name (default \"Mind map\").\n- `onSelectionChange(topic | null)` — the selected topic changed (a side\n panel with its notes).\n- `className` — layout around it (margin, width, height). Never colour.\n\n## Keyboard\n\nWith a topic selected (click one, or press an arrow):\n\n| Key | Does |\n| --- | --- |\n| Tab | add a subtopic and type it |\n| Enter / Shift+Enter | add the next topic below / above |\n| Space, F2, or just type | rename |\n| Delete / Backspace | delete the topic and its branch (a toast offers Undo) |\n| ← → ↑ ↓ | walk the map: outwards to a child, inwards to the parent, up and down the column |\n| ⌥↑ / ⌥↓ | move the topic up or down among its siblings |\n| Shift+Tab | move the topic up a level |\n| ⌘↑ / ⌘↓ | fold / unfold the branch |\n| ⌘D | duplicate the branch |\n| ⌘C / ⌘X / ⌘V | copy or cut a branch as an outline; paste an outline (from notes, a doc, an agent) as subtopics |\n| ⌘Z / ⇧⌘Z | undo / redo |\n| Home | the centre |\n| Esc | clear the selection |\n| ⇧1 | fit the whole map to the screen, however small |\n| ? | the shortcuts sheet |\n\nWhile typing a topic: **Enter** finishes it and starts the next one (on\nan empty one it steps out a level, so a run of topics never needs the\nmouse), **Tab** finishes it and starts a subtopic (on an empty one it\nmoves in a level), **Backspace** on an empty topic removes it and goes\nback to the one before, **Esc** finishes. Shift+Enter is a new line.\n\nWith the mouse: click selects, double-click renames, the **+** at a\ntopic's end adds a subtopic, the chip at a branch's end folds it (folded,\nit shows the count it hides), and dragging a topic onto another moves its\nbranch there. A plain drag on the empty canvas pans; pinch or ⌘-scroll\nzooms. On a touch screen a drag always pans; tap selects, double-tap\nrenames, and the bar's buttons add.\n\n## The helpers\n\nPlain functions over a tree; each returns a new tree and leaves the old\none as it was.\n\n- Read: `findTopic(map, id)`, `pathTo(map, id)` → `[root, …, topic]`,\n `parentOf(map, id)`, `countTopics(topic)` (everything under it).\n- Change: `updateTopic(map, id, patch)`, `insertTopic(map, parentId,\n topic, index?)`, `insertSibling(map, id, topic, { before })`,\n `removeTopic(map, id)`, `moveTopic(map, id, parentId, index?)` (refused\n into its own branch), `shiftTopic(map, id, ±1)`, `indentTopic`,\n `outdentTopic`, `duplicateTopic(map, id)` → `{ root, id }`,\n `setAllCollapsed(map, on)`, `createTopic(text)`.\n- Text: `mindMapToText(map)` (an indented outline, for an agent or search),\n `mindMapToMarkdown(map)` (a heading and a nested list),\n `outlineToMindMap(text)` and `outlineToTopics(text)` (bullets, numbers,\n headings, tabs or spaces — what an agent writes).\n- Layout: `layoutMindMap(map, { measure?, colors? })` → `{ items, bounds }`\n (every visible topic's box, side, depth, colour and the line to its\n parent), `mindMapToBoard(map)` (the same as a flow-canvas board).\n- `mindMapTemplates()`, `BRANCH_COLORS`.\n\n## Example\n\n```jsx\n// surfaces/plan-map.jsx — client: one plan's map\n\"use client\";\nimport { useState } from \"react\";\nimport { useSearchParams } from \"next/navigation\";\nimport { useRow } from \"../lib/data\";\nimport { updateRow } from \"../lib/actions\";\nimport { MindMap, mindMapToText } from \"../components/blocks/mind-map\";\n\nexport default function PlanMapSurface() {\n const id = useSearchParams().get(\"row\");\n const { row: plan, loading } = useRow({ source: \"plans\", id });\n if (loading || !plan) return null;\n return <PlanMap key={plan.id} plan={plan} />;\n}\n\nfunction PlanMap({ plan }) {\n const [map, setMap] = useState(plan.map); // jsonb, may be null; read once per plan\n return (\n <MindMap\n value={map ?? undefined}\n defaultValue={{ text: plan.name }}\n onChange={setMap}\n onSave={(m) => updateRow({ source: \"plans\", id: plan.id, patch: { map: m, map_text: mindMapToText(m) } })}\n label={`${plan.name} map`}\n className=\"min-h-0 flex-1\"\n />\n );\n}\n```\n\nIn a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.\n\nAn agent's outline, straight onto a map:\n\n```jsx\nconst map = outlineToMindMap(`# Hiring plan\n- Roles\n - Founding engineer\n - Designer\n- Sourcing\n - Referrals\n - Communities`);\n<MindMap defaultValue={map} onSave={save} />\n```\n\n## What it will not do\n\n- Free placement: topics sit where the layout puts them (drag moves a\n branch to another topic, not to a spot). For free boards use\n `flow-canvas` or `whiteboard`.\n- Cross-links between branches, images or icons on topics.\n- Collaborate live: saves are last-write-wins.\n- Export an image.\n- Render the map on the server: the server shows it as text until the\n canvas mounts. For a read-only page that must be rich in the HTML,\n render `mindMapToMarkdown` yourself.\n\nA map it cannot be is your own component in `components/` — not an edit\nto this block. Its files are fixed: `npm run check` refuses a change, and\n`makersclaw add mind-map` restores them.\n"
|
|
42
42
|
}
|
|
43
43
|
],
|
|
44
44
|
"categories": [
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"docs": "Installed to components/makersclaw/blocks/mind-map. Import: import { MindMap, BRANCH_COLORS, normalizeMap, createTopic, findTopic, updateTopic, insertTopic, removeTopic, moveTopic, layoutMindMap, mindMapToBoard, mindMapToText, mindMapToMarkdown, outlineToMindMap, outlineToTopics, mindMapTemplates } from \"@/components/makersclaw/blocks/mind-map\". Props and an example: components/makersclaw/blocks/mind-map/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
|
|
61
61
|
"meta": {
|
|
62
62
|
"makersclaw": {
|
|
63
|
-
"version": "0.
|
|
63
|
+
"version": "0.8.1",
|
|
64
64
|
"useWhen": "A screen for thinking in a tree: brainstorming around one idea, planning a project, breaking a goal down, notes from a meeting, a topic outline an agent drafted. Not for free-form boards with stickies and arrows (flow-canvas, whiteboard), a people hierarchy (org-chart), or a process with steps (process-map)."
|
|
65
65
|
}
|
|
66
66
|
}
|
package/r/navigation-menu.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"title": "Navigation Menu",
|
|
6
6
|
"description": "A site's top nav: plain links, and triggers that open a mega-menu panel of links (icon, title, one-line description) with an optional featured card; hover with intent, click, arrow keys and Escape; a menu button and a sheet of collapsible groups on a phone.",
|
|
7
7
|
"registryDependencies": [
|
|
8
|
-
"https://cdn.jsdelivr.net/npm/makersclaw@0.
|
|
8
|
+
"https://cdn.jsdelivr.net/npm/makersclaw@0.8.1/r/core.json"
|
|
9
9
|
],
|
|
10
10
|
"dependencies": [],
|
|
11
11
|
"files": [
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"docs": "Installed to components/makersclaw/blocks/navigation-menu. Import: import { NavigationMenu } from \"@/components/makersclaw/blocks/navigation-menu\". Props and an example: components/makersclaw/blocks/navigation-menu/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
|
|
46
46
|
"meta": {
|
|
47
47
|
"makersclaw": {
|
|
48
|
-
"version": "0.
|
|
48
|
+
"version": "0.8.1",
|
|
49
49
|
"useWhen": "The header of a public-facing site or a product with many destinations: a marketing site, a docs hub, a customer portal. Not for an app's own sidebar or a row of tabs that switch views on one page."
|
|
50
50
|
}
|
|
51
51
|
}
|
package/r/needs-you.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "needs-you",
|
|
4
|
+
"type": "registry:block",
|
|
5
|
+
"title": "NeedsYou",
|
|
6
|
+
"description": "What waits on the member, in the app: NeedsYou is the queue of the agent's questions and yeses — a question is answered in place (QuestionBox: its options, and the member's own words where it welcomes them), a yes is the `approval` block's press frame (renderYes), and the rest open the desk (\"Waiting for your yes on the desk\" when the press frame cannot load, \"Connect on the desk\"); WaitingYes is the frozen-text card for that fallback — what goes out, frozen, then \"Waiting for your yes on the desk\", Open the desk and Take the ask back; AgentWorking is the strip while the agent works, with what it is on, Stop, and \"Stop keeps everything written so far\". Fed by useNeedsYou() (lib/hooks.js). Never an Approve in the app's own code: the yes is pressed in the Makersclaw-held frame; account sign-ins and money stay on the desk.",
|
|
7
|
+
"registryDependencies": [
|
|
8
|
+
"https://cdn.jsdelivr.net/npm/makersclaw@0.8.1/r/core.json"
|
|
9
|
+
],
|
|
10
|
+
"dependencies": [],
|
|
11
|
+
"files": [
|
|
12
|
+
{
|
|
13
|
+
"path": "registry/blocks/needs-you/index.js",
|
|
14
|
+
"type": "registry:component",
|
|
15
|
+
"target": "@components/makersclaw/blocks/needs-you/index.js",
|
|
16
|
+
"content": "// The NeedsYou block — `makersclaw add needs-you`. Fixed: compose it\n// through its props (README.md beside this file).\nexport { NeedsYou, QuestionBox, WaitingYes, AgentWorking } from \"./needs-you\";\n"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "registry/blocks/needs-you/needs-you.jsx",
|
|
20
|
+
"type": "registry:component",
|
|
21
|
+
"target": "@components/makersclaw/blocks/needs-you/needs-you.jsx",
|
|
22
|
+
"content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Button, Icon, Input, Skeleton, Spinner, cx } from \"../../ui\";\n\n// What waits on the member, in the app (the app kit, D15). Four pieces:\n//\n// NeedsYou the queue: the agent's questions and its yeses. A\n// question is answered in place (QuestionBox, 0.8.1, D19);\n// a yes is the `approval` block's press frame, given to\n// `renderYes` (D18); everything else opens the desk.\n// QuestionBox the agent's question answered where it was asked: its\n// options as one press each, and the member's own words\n// where the question welcomes them.\n// WaitingYes the frozen-text card: what goes out, frozen, then\n// \"Waiting for your yes on the desk\", Open the desk, and\n// Take the ask back (Blog Post Studio's design).\n// AgentWorking the strip while the agent works: what it is on, Stop,\n// and \"Stop keeps everything written so far\".\n//\n// Fed by useNeedsYou() (lib/hooks.js). An answer is words, so the app\n// takes it (D19). The YES is not: these blocks never draw an Approve — the\n// press is the Makersclaw-held frame of the `approval` block, which app\n// code cannot press (D18). The account sign-in and money stay on the\n// desk. WaitingYes is the card for when the press frame cannot load: it\n// says the yes waits on the desk and opens it. Props in, callbacks out.\n\nconst ISO = /^\\d{4}-\\d{2}-\\d{2}T/;\n\n/** \"11:20\" today, \"12 Sep\" another day; words as given. */\nfunction when(at) {\n if (at == null || at === \"\") return null;\n const d = at instanceof Date ? at : typeof at === \"string\" && ISO.test(at) ? new Date(at) : null;\n if (!d) return { words: String(at) };\n if (Number.isNaN(d.getTime())) return null;\n const today = d.toDateString() === new Date().toDateString();\n return {\n words: today ? d.toLocaleTimeString(\"en-GB\", { hour: \"2-digit\", minute: \"2-digit\" }) : d.toLocaleDateString(\"en-GB\", { day: \"numeric\", month: \"short\" }),\n iso: d.toISOString(),\n };\n}\n\n/** Each kind of need: its word, its icon, what its one press says. */\nconst KINDS = {\n question: { word: \"A question\", icon: \"circle-help\", head: \"Waiting for your answer on the desk\", press: \"Answer on the desk\" },\n yes: { word: \"Your yes\", icon: \"shield-check\", head: \"Waiting for your yes on the desk\", press: \"Open the desk\" },\n approval: { word: \"Your yes\", icon: \"shield-check\", head: \"Waiting for your yes on the desk\", press: \"Open the desk\" },\n spend: { word: \"Spending\", icon: \"credit-card\", head: \"Waiting for your yes to spend, on the desk\", press: \"Open the desk\" },\n limit: { word: \"Credits\", icon: \"credit-card\", head: \"Paused for credits — see the desk\", press: \"Open the desk\" },\n connect: { word: \"An account\", icon: \"link\", head: \"Waiting for you to connect an account on the desk\", press: \"Connect on the desk\" },\n};\nconst OTHER = { word: \"Needs you\", icon: \"bell\", head: \"Needs you on the desk\", press: \"Open the desk\" };\nconst kindOf = (k) => KINDS[k] ?? OTHER;\n\n/**\n * The agent's question, answered where it was asked (D19). Its options are\n * one press each (a lone question with options answers on the spot, as the\n * desk's form does), and where the question welcomes words there is a box\n * for them.\n *\n * options [{ id, label }] the question offers (none: words only)\n * free false when the question takes one of its options only\n * onAnswer({ optionId } | { text }) async → { ok } | { ok: false, error }\n * placeholder, sendLabel the box's words\n *\n * Fed by useNeedsYou().answer(item, …) or useAgentThread().answer(…). The\n * door checks it all again (the member who asked, one of the options, the\n * words not empty): a refusal is shown as it is.\n */\nexport function QuestionBox({ options = [], free = true, onAnswer, placeholder, sendLabel = \"Send answer\", className }) {\n const list = Array.isArray(options) ? options.filter((o) => o && o.id) : [];\n const [text, setText] = useState(\"\");\n const [busy, setBusy] = useState(null); // an option id, or \"text\"\n const [error, setError] = useState(null);\n const [sent, setSent] = useState(false);\n if (!onAnswer) return null;\n\n async function give(payload, which) {\n if (busy || sent) return;\n setBusy(which);\n setError(null);\n let r;\n try {\n r = await onAnswer(payload);\n } catch (e) {\n r = { ok: false, error: e?.message ?? String(e) };\n }\n setBusy(null);\n if (r && r.ok === false) setError(r.error || \"That did not go through. Try again.\");\n else setSent(true);\n }\n const words = text.trim();\n const hasBox = free !== false || list.length === 0;\n\n if (sent)\n return (\n <p role=\"status\" className={cx(\"flex items-center gap-2 text-sm text-success\", className)}>\n <Icon name=\"check\" size={14} className=\"shrink-0\" /> Sent. The agent is on it.\n </p>\n );\n return (\n <div className={cx(\"grid grid-cols-1 min-w-0 gap-2\", className)}>\n {list.length ? (\n <div className=\"flex min-w-0 flex-wrap gap-1.5\">\n {list.map((o) => (\n <Button key={o.id} size=\"sm\" variant=\"secondary\" loading={busy === o.id} disabled={Boolean(busy) && busy !== o.id} onClick={() => give({ optionId: o.id }, o.id)}>\n {o.label}\n </Button>\n ))}\n </div>\n ) : null}\n {hasBox ? (\n <form\n className=\"flex min-w-0 items-center gap-2\"\n onSubmit={(e) => {\n e.preventDefault();\n if (words) give({ text: words }, \"text\");\n }}\n >\n <Input\n aria-label={placeholder ?? (list.length ? \"Or answer in your own words\" : \"Your answer\")}\n placeholder={placeholder ?? (list.length ? \"Or answer in your own words…\" : \"Your answer…\")}\n value={text}\n disabled={Boolean(busy)}\n onChange={(e) => setText(e.target.value)}\n className=\"min-w-0 flex-1\"\n />\n <Button size=\"sm\" variant=\"primary\" type=\"submit\" loading={busy === \"text\"} disabled={!words || Boolean(busy)}>\n {sendLabel}\n </Button>\n </form>\n ) : null}\n {error ? (\n <p role=\"alert\" className=\"text-xs text-destructive\">\n {error}\n </p>\n ) : null}\n </div>\n );\n}\n\n/** One thing that waits. */\nfunction Need({ item, onOpen, onAnswer, renderYes }) {\n const k = kindOf(item.kind);\n const answerHere = item.via === \"answer\" && Boolean(onAnswer);\n const pressHere = item.via === \"press\" && typeof renderYes === \"function\";\n const t = when(item.at);\n const [opening, setOpening] = useState(false);\n async function open() {\n if (!onOpen || opening) return;\n setOpening(true);\n try {\n await onOpen(item);\n } finally {\n setOpening(false);\n }\n }\n return (\n <li\n className={cx(\n \"grid grid-cols-1 min-w-0 gap-2 border-b border-border px-3.5 py-3 last:border-b-0\",\n !answerHere && !pressHere && \"@lg:grid-cols-[minmax(0,1fr)_auto] @lg:items-start\",\n )}\n >\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <Icon name={k.icon} size={16} className=\"mt-0.5 shrink-0 text-warning\" />\n <div className=\"grid grid-cols-1 min-w-0 gap-0.5\">\n <p className=\"flex min-w-0 flex-wrap items-baseline gap-x-2 text-xs text-muted-foreground\">\n <span className=\"font-medium text-foreground\">{k.word}</span>\n {item.title ? <span className=\"min-w-0 break-words\">{item.title}</span> : null}\n {t ? <time dateTime={t.iso}>{t.words}</time> : null}\n </p>\n <p className=\"font-medium\">{answerHere ? \"A question for you\" : pressHere ? \"Waiting for your yes\" : k.head}</p>\n {item.text ? <p className={cx(\"break-words text-muted-foreground\", !answerHere && !pressHere && \"line-clamp-3\")}>{item.text}</p> : null}\n </div>\n </div>\n {answerHere ? (\n <QuestionBox\n options={item.options ?? []}\n free={item.free}\n onAnswer={(payload) => onAnswer(item, payload)}\n className=\"pl-[26px]\"\n />\n ) : pressHere ? (\n <div className=\"min-w-0 pl-[26px]\">{renderYes(item)}</div>\n ) : onOpen ? (\n <Button size=\"sm\" variant=\"secondary\" loading={opening} onClick={open} className=\"justify-self-start @lg:justify-self-end\">\n {k.press} <Icon name=\"external\" size={12} />\n </Button>\n ) : null}\n </li>\n );\n}\n\n/**\n * items what useNeedsYou() returns as `items`:\n * [{ id, request, kind, text, title, at, via, options,\n * free, needsAt }]; kind is question · yes · spend ·\n * connect (another word reads \"Needs you\"); `via` says how\n * the app can settle it (\"answer\", \"press\" or null: the desk)\n * onOpen(item) opens it on the desk (useNeedsYou's `open`); the press\n * of everything the app cannot settle itself\n * onAnswer(item, { optionId | text }) answers a question in place\n * (useNeedsYou's `answer`, D19): an item with via \"answer\"\n * shows its QuestionBox; without it, it opens the desk\n * renderYes(item) draws the yes of an item with via \"press\" (D18):\n * `<YesCard item={item} />`, a component of yours that\n * calls useApproval(item.id) and returns the `approval`\n * block; without it the item opens the desk\n * empty the line when nothing waits (default \"Nothing waits on\n * you here.\"); `false` draws nothing at all then\n * title the head (default \"Needs you\"); `null` for none\n * loading skeleton rows before the first read\n * unavailable a quiet \"not connected yet\" instead of the list\n */\nexport function NeedsYou({ items = [], onOpen, onAnswer, renderYes, empty = \"Nothing waits on you here.\", title = \"Needs you\", loading = false, unavailable = false, className }) {\n const list = (items ?? []).filter(Boolean);\n if (!list.length && empty === false && !loading) return null;\n return (\n <section aria-label={typeof title === \"string\" ? title : \"Needs you\"} className={cx(\"@container min-w-0 overflow-hidden rounded-lg border border-border bg-card text-sm text-card-foreground\", className)}>\n {title ? (\n <div className=\"flex min-w-0 items-center gap-2 border-b border-border px-3.5 py-2.5 font-semibold\">\n <span className=\"min-w-0 flex-1 truncate\">{title}</span>\n {list.length ? <span className=\"shrink-0 rounded-full bg-warning/15 px-2 text-xs font-medium text-warning\">{list.length}</span> : null}\n </div>\n ) : null}\n {unavailable ? (\n <p className=\"px-3.5 py-3 text-muted-foreground\">What waits on you is not connected yet.</p>\n ) : loading && !list.length ? (\n <div aria-busy=\"true\" aria-label=\"Loading\" className=\"grid grid-cols-1 gap-3 px-3.5 py-3\">\n <Skeleton className=\"w-3/4\" />\n <Skeleton className=\"w-1/2\" />\n </div>\n ) : !list.length ? (\n <p className=\"flex items-center gap-2 px-3.5 py-3 text-muted-foreground\">\n <Icon name=\"check\" size={14} className=\"shrink-0 text-success\" />\n {empty}\n </p>\n ) : (\n <ul className=\"m-0 grid grid-cols-1 list-none p-0\">\n {list.map((item, i) => (\n <Need key={item.id ?? i} item={item} onOpen={onOpen} onAnswer={onAnswer} renderYes={renderYes} />\n ))}\n </ul>\n )}\n </section>\n );\n}\n\n/** What WaitingYes says in each state. */\nconst YES = {\n ready: null,\n waiting: { icon: \"clock\", head: \"Waiting for your yes on the desk\", tone: \"text-primary\" },\n approved: { icon: \"check\", head: \"You said yes · the agent is on it\", tone: \"text-success\" },\n declined: { icon: \"x\", head: \"You said no on the desk. Nothing went out.\", tone: \"text-muted-foreground\" },\n taken: { icon: \"undo\", head: \"Taken back. Nothing went out.\", tone: \"text-muted-foreground\" },\n failed: { icon: \"warning\", head: \"It didn't go through\", tone: \"text-destructive\" },\n};\n\n/**\n * The frozen-text card: what goes out, frozen while the ask waits, and\n * where it stands. Never an Approve: the yes is the `approval` block's\n * press frame (D18), and this card is what shows when that frame cannot\n * load — \"Waiting for your yes on the desk\", with the desk one press away.\n *\n * title what it is (\"How to cover a sudden sick day\")\n * frozen what goes out, as it will go (text or a node)\n * kicker the line over it (default \"Ready to send\")\n * state ready · waiting (default) · approved · declined ·\n * taken · failed\n * body one sentence under the state, in the app's words\n * (default: \"The ask is on your desk. Say yes there, and\n * the agent does it.\")\n * error the words for `failed`\n * onOpen() opens the desk on this ask (useNeedsYou's `open`)\n * onTakeBack() async → { ok } | { ok: false, error }: stops the ask\n * while the agent has not acted (useNeedsYou's `takeBack`);\n * without it there is no Take back\n * takeBackLabel default \"Take the ask back\"\n */\nexport function WaitingYes({\n title,\n frozen,\n kicker = \"Ready to send\",\n state = \"waiting\",\n body = \"The ask is on your desk. Say yes there, and the agent does it.\",\n error,\n onOpen,\n onTakeBack,\n takeBackLabel = \"Take the ask back\",\n className,\n}) {\n const [taking, setTaking] = useState(false);\n const [takeError, setTakeError] = useState(null);\n const [taken, setTaken] = useState(false);\n const now = taken ? \"taken\" : state;\n const s = YES[now] ?? YES.waiting;\n\n async function takeBack() {\n if (!onTakeBack || taking) return;\n setTaking(true);\n setTakeError(null);\n let r;\n try {\n r = await onTakeBack();\n } catch (e) {\n r = { ok: false, error: e?.message ?? String(e) };\n }\n setTaking(false);\n if (r && r.ok === false) setTakeError(r.error || \"Couldn't take it back. Open the desk to stop it there.\");\n else setTaken(true);\n }\n\n return (\n <section\n aria-label={typeof title === \"string\" ? title : \"What goes out\"}\n className={cx(\"@container grid grid-cols-1 min-w-0 content-start gap-3 rounded-lg border border-border bg-card p-3.5 text-sm text-card-foreground\", className)}\n >\n <div className=\"grid grid-cols-1 min-w-0 gap-1\">\n {kicker ? (\n <p className=\"flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {now !== \"ready\" && now !== \"taken\" ? <Icon name=\"lock\" size={12} label=\"Frozen while it waits\" /> : null}\n {kicker}\n </p>\n ) : null}\n {title ? <p className=\"break-words text-[15px] font-semibold leading-snug\">{title}</p> : null}\n </div>\n {frozen != null && frozen !== \"\" ? (\n <div\n className={cx(\n \"min-w-0 whitespace-pre-wrap break-words rounded-[var(--radius)] border border-border px-3 py-2.5\",\n now === \"ready\" || now === \"taken\" ? \"bg-background\" : \"bg-muted text-muted-foreground\",\n )}\n >\n {frozen}\n </div>\n ) : null}\n\n {s ? (\n <div role=\"status\" className=\"grid grid-cols-1 min-w-0 gap-2 border-t border-border pt-3\">\n <p className={cx(\"flex min-w-0 items-center gap-2 font-medium\", now === \"waiting\" ? null : s.tone)}>\n <Icon name={s.icon} size={16} className={cx(\"shrink-0\", s.tone)} />\n <span className=\"min-w-0 break-words\">{s.head}</span>\n </p>\n {now === \"waiting\" && body ? <p className=\"text-muted-foreground\">{body}</p> : null}\n {now === \"failed\" && error ? <p className=\"text-destructive\">{error}</p> : null}\n {now === \"waiting\" ? (\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n {onOpen ? (\n <Button size=\"sm\" variant=\"primary\" onClick={onOpen}>\n Open the desk <Icon name=\"external\" size={12} />\n </Button>\n ) : null}\n {onTakeBack ? (\n <>\n <Button size=\"sm\" variant=\"quiet\" loading={taking} onClick={takeBack}>\n {takeBackLabel}\n </Button>\n <span className=\"text-xs text-muted-foreground\">while the agent hasn't acted</span>\n </>\n ) : null}\n </div>\n ) : now === \"failed\" && onOpen ? (\n <Button size=\"sm\" variant=\"secondary\" className=\"justify-self-start\" onClick={onOpen}>\n Open the desk <Icon name=\"external\" size={12} />\n </Button>\n ) : null}\n {takeError ? (\n <p role=\"alert\" className=\"text-xs text-destructive\">\n {takeError}\n </p>\n ) : null}\n </div>\n ) : null}\n </section>\n );\n}\n\n/**\n * The strip while the agent works on an ask: what it is on now, Stop,\n * and what Stop keeps.\n *\n * words what it is doing (\"Writing the draft\"); default \"Working on it\"\n * steps [{ label, status: running · done · failed }]: the running\n * step's label shows, with the count\n * onStop() stops it (useNeedsYou's `stop`, useAgentThread's `stop`);\n * may be async → { ok } | { ok: false, error }; without it\n * there is no Stop (the member may not stop it)\n * stopping true while a stop you track is out\n * stopHint default \"Stop keeps everything written so far\"; null for none\n */\nexport function AgentWorking({ words = \"Working on it\", steps, onStop, stopping = false, stopHint = \"Stop keeps everything written so far\", className }) {\n const list = Array.isArray(steps) ? steps : [];\n const running = list.filter((s) => s?.status === \"running\").at(-1);\n const done = list.filter((s) => s?.status === \"done\").length;\n // useNeedsYou's stop(request) answers { ok } | { ok: false, error }: the\n // strip spins while it is out and says why when it fails.\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState(null);\n async function stop() {\n if (!onStop || busy || stopping) return;\n setBusy(true);\n setError(null);\n let r;\n try {\n r = await onStop();\n } catch (e) {\n r = { ok: false, error: e?.message ?? String(e) };\n }\n setBusy(false);\n if (r && r.ok === false) setError(r.error || \"Couldn't stop it. Open the desk to stop it there.\");\n }\n return (\n <div role=\"status\" className={cx(\"@container flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1.5 rounded-lg border border-border bg-card px-3.5 py-2.5 text-sm text-card-foreground\", className)}>\n <Spinner size=\"sm\" label=\"Working\" className=\"shrink-0 text-primary\" />\n <div className=\"grid grid-cols-1 min-w-0 flex-1 basis-48 gap-0.5\">\n <span className=\"min-w-0 break-words font-medium\">{words}</span>\n {running || list.length ? (\n <span className=\"min-w-0 truncate text-xs text-muted-foreground\">\n {running ? running.label : null}\n {running && list.length > 1 ? \" · \" : null}\n {list.length > 1 ? `${done} of ${list.length} steps done` : null}\n </span>\n ) : null}\n </div>\n {onStop ? (\n <span className=\"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5\">\n {stopHint ? <span className=\"text-xs text-muted-foreground\">{stopHint}</span> : null}\n <Button size=\"sm\" variant=\"secondary\" loading={stopping || busy} onClick={stop}>\n <Icon name=\"stop\" size={12} /> Stop\n </Button>\n </span>\n ) : null}\n {error ? (\n <p role=\"alert\" className=\"basis-full text-xs text-destructive\">\n {error}\n </p>\n ) : null}\n </div>\n );\n}\n"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"path": "registry/blocks/needs-you/README.md",
|
|
26
|
+
"type": "registry:file",
|
|
27
|
+
"target": "@components/makersclaw/blocks/needs-you/README.md",
|
|
28
|
+
"content": "# NeedsYou\n\nWhat waits on the member, drawn inside the app. Four pieces:\n\n- **NeedsYou** — the queue. The agent's questions and its yeses on this\n app. A question is **answered in place** (`QuestionBox`); a yes is the\n `approval` block's press frame (`renderYes`); anything else the app\n cannot settle (connecting an account, spending money, several questions\n at once) opens the desk with one press.\n- **QuestionBox** — the agent's question, answered where it was asked: its\n options, one press each, and a box for the member's own words where the\n question welcomes them.\n- **WaitingYes** — the frozen-text card. What goes out, frozen while the\n ask waits, then \"Waiting for your yes on the desk\", **Open the desk**\n and **Take the ask back**. It is also what the `approval` block shows\n when its press frame cannot load.\n- **AgentWorking** — the strip while the agent works: what it is on now,\n **Stop**, and \"Stop keeps everything written so far\".\n\nAn answer is words, so the app takes it. The yes is not: the app's own\ncode never draws an Approve. The press is the Makersclaw-held frame of the\n`approval` block, which app code cannot script. Connecting an account and\nmoney stay on the desk.\n\n```jsx\nimport { NeedsYou, QuestionBox, WaitingYes, AgentWorking } from \"../components/blocks/needs-you\"; // a Makersclaw app (\"./blocks/needs-you\" from components/)\nimport { NeedsYou, QuestionBox, WaitingYes, AgentWorking } from \"@/components/makersclaw/blocks/needs-you\"; // a shadcn project\n```\n\nClient components. The Example is at the end.\n\n## Which hook feeds it\n\n```js\nconst needs = useNeedsYou(); // lib/hooks.js\n// → { items, working, open(item), answer(item, …), takeBack(item), stop(request), loading, error, unavailable, reload }\n```\n\nIt reads `GET _claw/requests`: only the member's own asks (`mine`).\n`items` are the asks that wait on them; `working` are their asks that\nrun now. Each item says how the app can settle it: `via` is `\"answer\"` (a\nquestion: `answer(item, { optionId | text })`, which is\n`POST _claw/requests/:id/answer`), `\"press\"` (the one ask's yes: the\n`approval` block, `useApproval(item.id)`) or `null` (the desk: `open`).\n`takeBack` and `stop` are `POST _claw/requests/:id/stop`.\n\n## Props\n\n### NeedsYou\n\n- `items` — `needs.items`: `[{ id, request, kind, text, title, at, via,\n options, free, needsAt }]`. `kind` is `question`, `yes` or `spend`\n (\"Waiting for your yes on the desk · Open the desk\" when the app cannot\n take it), `connect` (\"Connect on the desk\"); any other word reads \"Needs\n you on the desk\". `text` is the agent's words, `title` the ask's, `at`\n when (words, a `Date` or ISO).\n- `onOpen(item)` — `needs.open`. The press of everything the app cannot\n settle itself. Without it those rows have no press.\n- `onAnswer(item, { optionId | text })` — `needs.answer`. An item with\n `via: \"answer\"` then shows its `QuestionBox`; without it that item opens\n the desk, as before.\n- `renderYes(item)` — draws the yes of an item with `via: \"press\"`: a\n component of yours that calls `useApproval(item.id)` and returns the\n `approval` block. Without it that item opens the desk.\n- `empty` — the line when nothing waits. Default \"Nothing waits on you\n here.\" `false` draws nothing at all when the list is empty.\n- `title` — the head. Default \"Needs you\"; `null` for none.\n- `loading` — `needs.loading`: skeleton lines before the first read.\n- `unavailable` — `needs.unavailable`: a quiet \"not connected yet\".\n- `className` — layout around it. Never colour.\n\n### QuestionBox\n\n- `options` — `[{ id, label }]` the question offers (none: words only).\n A lone press answers on the spot, as the desk's form does.\n- `free` — `false` when the question takes one of its options only\n (default: words are welcome).\n- `onAnswer({ optionId } | { text })` — `needs.answer(item, …)` or\n `chat.answer(…)`; async, answers `{ ok }` or `{ ok: false, error }`. A\n refusal is shown as the door words it.\n- `placeholder`, `sendLabel`, `className`.\n\n### WaitingYes\n\n- `title` — what it is (\"How to cover a sudden sick day\").\n- `frozen` — what goes out, exactly as it will go (text or a node). It is\n drawn frozen (muted, with a lock) while the ask waits.\n- `kicker` — the line over it. Default \"Ready to send\".\n- `state` — `ready` (nothing asked yet: the text only, no state line) ·\n `waiting` (default) · `approved` (\"You said yes · the agent is on it\")\n · `declined` · `taken` · `failed` (with `error`).\n- `body` — one sentence under \"Waiting for your yes on the desk\", in the\n app's words: say what the agent does after the yes.\n- `onOpen()` — opens the desk on this ask (`() => needs.open(item)`).\n- `onTakeBack()` — async, answers `{ ok: true }` or `{ ok: false, error\n }` (`() => needs.takeBack(item)`). It stops the ask while the agent\n has not acted; the card then says \"Taken back. Nothing went out.\"\n Without it there is no Take back.\n- `takeBackLabel` — default \"Take the ask back\".\n\n### AgentWorking\n\n- `words` — what the agent is doing, in the app's words (\"Writing the\n draft\"). Default \"Working on it\".\n- `steps` — `[{ label, status }]` (`running` · `done` · `failed`): the\n running step shows, with \"2 of 6 steps done\".\n- `onStop()` — `() => needs.stop(request)` or `chat.stop`. It may be\n async and answer `{ ok }` or `{ ok: false, error }`: the button spins\n while it is out and a failure says why. Without it there is no Stop.\n- `stopping` — true while a stop you track yourself is out.\n- `stopHint` — default \"Stop keeps everything written so far\"; `null`\n for none.\n\n## Who may do what\n\n- Every member sees only their own asks: the door lists `mine`.\n- Stop and Take back: the asker, an owner, an admin, or the app's owner\n (`mayStop` on the request). Give `onStop`/`onTakeBack` only when the\n request says the member may; the door refuses anyone else anyway.\n- Only the member who asked answers a question or gives a yes; the door\n refuses everyone else (owners and admins too), and a question answered\n after it moved on (`needsAt`) is refused.\n- The yes: the `approval` block's press frame, never these blocks.\n- An account sign-in and money: the desk, always.\n\n## Never\n\n- **Never an Approve, a Yes, or a Send that gives the yes.** The yes is\n the `approval` block's frame; this block's only press toward it is Open\n the desk, when the frame cannot load.\n- Never answer the one ask (a yes) through `QuestionBox`: the door refuses\n it (`needs_a_press`).\n- Never let the frozen text be edited while the ask waits. To change it,\n take the ask back first.\n- Never show another member's asks here.\n\n## Example\n\nA post that waits for its yes, the strip while the agent works, and the\nqueue on the home screen.\n\n```jsx\n\"use client\";\nimport { useNeedsYou } from \"../lib/hooks\";\nimport { NeedsYou, WaitingYes, AgentWorking } from \"../components/blocks/needs-you\";\n\nexport function PostStatus({ post }) {\n const needs = useNeedsYou();\n const item = needs.items.find((i) => i.request?.id === post.request_id);\n const running = needs.working.find((r) => r.id === post.request_id);\n if (running) return <AgentWorking words=\"Putting the post on Webflow\" onStop={() => needs.stop(running)} />;\n return (\n <WaitingYes\n title={post.title}\n frozen={post.body}\n state={item ? \"waiting\" : \"ready\"}\n body=\"Say yes there, and the agent makes this one post live on Webflow, then adds its link here.\"\n onOpen={item ? () => needs.open(item) : undefined}\n onTakeBack={item ? () => needs.takeBack(item) : undefined}\n />\n );\n}\n\nexport function Waiting() {\n const needs = useNeedsYou();\n return (\n <NeedsYou\n items={needs.items}\n onOpen={needs.open}\n onAnswer={needs.answer}\n renderYes={(item) => <SendCard item={item} />} // an Approval card, see the approval block\n loading={needs.loading}\n unavailable={needs.unavailable}\n />\n );\n}\n```\n"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"categories": [
|
|
32
|
+
"needs you",
|
|
33
|
+
"approval",
|
|
34
|
+
"yes",
|
|
35
|
+
"waiting",
|
|
36
|
+
"inbox",
|
|
37
|
+
"queue",
|
|
38
|
+
"agent",
|
|
39
|
+
"stop",
|
|
40
|
+
"working",
|
|
41
|
+
"take back",
|
|
42
|
+
"send",
|
|
43
|
+
"desk",
|
|
44
|
+
"question",
|
|
45
|
+
"answer",
|
|
46
|
+
"press"
|
|
47
|
+
],
|
|
48
|
+
"docs": "Installed to components/makersclaw/blocks/needs-you. Import: import { NeedsYou, QuestionBox, WaitingYes, AgentWorking } from \"@/components/makersclaw/blocks/needs-you\". Props and an example: components/makersclaw/blocks/needs-you/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
|
|
49
|
+
"meta": {
|
|
50
|
+
"makersclaw": {
|
|
51
|
+
"version": "0.8.1",
|
|
52
|
+
"useWhen": "A home screen or a record that should say what the agent waits on and let the member settle it there (answer a question, give a yes before sending or spending), a screen where something is about to go out (a post, an email, a batch) and waits for the member's yes, or any place an ask is running and the member should be able to stop it. For the yes card itself use approval; for the conversation use agent-chat; for one job's progress use job-panel."
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
package/r/order-detail.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"title": "OrderDetail",
|
|
6
6
|
"description": "One placed order on a page: its number, status and date, the items, the delivery address and contact, a timeline from placed to delivered (Steps), and the totals, with Track order and Invoice actions. Loading, error and not-found states built in.",
|
|
7
7
|
"registryDependencies": [
|
|
8
|
-
"https://cdn.jsdelivr.net/npm/makersclaw@0.
|
|
8
|
+
"https://cdn.jsdelivr.net/npm/makersclaw@0.8.1/r/core.json"
|
|
9
9
|
],
|
|
10
10
|
"dependencies": [],
|
|
11
11
|
"files": [
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"docs": "Installed to components/makersclaw/blocks/order-detail. Import: import { OrderDetail } from \"@/components/makersclaw/blocks/order-detail\". Props and an example: components/makersclaw/blocks/order-detail/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
|
|
48
48
|
"meta": {
|
|
49
49
|
"makersclaw": {
|
|
50
|
-
"version": "0.
|
|
50
|
+
"version": "0.8.1",
|
|
51
51
|
"useWhen": "A buyer or an operator opens one order: \"where is my order\", an order confirmation page, a support view of a purchase. Not for a list of orders (data-table) or for a basket before paying (cart)."
|
|
52
52
|
}
|
|
53
53
|
}
|
package/r/org-chart.json
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"title": "OrgChart",
|
|
6
6
|
"description": "An org chart on flow-canvas: people laid out top down from a flat list (each names a manager), managers centred over their teams, a team with no managers in it as a compact column, every line drawn for you. Teams fold past the first levels behind a chip that says how many are hidden; search finds a person, unfolds the way to them, centres them and lights their chain of managers; a card opens a details sheet (reports to, direct reports, team size). Colour strips and a legend by department. Optional: drag a card onto another to change someone's manager (async { ok }, optimistic, with Undo). Keyboard throughout.",
|
|
7
7
|
"registryDependencies": [
|
|
8
|
-
"https://cdn.jsdelivr.net/npm/makersclaw@0.
|
|
9
|
-
"https://cdn.jsdelivr.net/npm/makersclaw@0.
|
|
8
|
+
"https://cdn.jsdelivr.net/npm/makersclaw@0.8.1/r/core.json",
|
|
9
|
+
"https://cdn.jsdelivr.net/npm/makersclaw@0.8.1/r/flow-canvas.json"
|
|
10
10
|
],
|
|
11
11
|
"dependencies": [],
|
|
12
12
|
"files": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"path": "registry/blocks/org-chart/README.md",
|
|
39
39
|
"type": "registry:file",
|
|
40
40
|
"target": "@components/makersclaw/blocks/org-chart/README.md",
|
|
41
|
-
"content": "# OrgChart\n\nWho reports to whom, top down. Give it people as a flat list — each row\nnames its manager — and it draws the tree: managers centred over their\nteams, a team with no managers in it as a compact column (twelve cards in\na row read worse than a list), every line in place.\n\nTeams past the first levels start folded; the chip under a manager says\nhow many report to them and, folded, how many are hidden. **Find a\nperson** (or press `/`) unfolds the way down to them, centres them and\nlights their chain of managers. A click selects a card; a second click\n(or Enter) opens a details sheet: who they report to, their direct\nreports, the team's size. Cards carry a colour strip by department, with\na legend on top.\n\nWith `onChangeManager`, drag a card onto another to move that person to a\nnew manager — shown at once, saved through your callback, undone if it\nfails, with an Undo in the toast.\n\nIt is built on `flow-canvas` (which draws it, pans and zooms): this block\nbuilds the tree, lays it out and owns the keys.\n\n```jsx\nimport { OrgChart } from \"../components/blocks/org-chart\"; // a Makersclaw app (\"./blocks/org-chart\" from components/)\nimport { OrgChart } from \"@/components/makersclaw/blocks/org-chart\"; // a shadcn project\n```\n\n`OrgChart` is a client component: render it from a `\"use client\"`\ncomponent. `makersclaw add org-chart` adds `flow-canvas` with it (and its\nnpm package, `@xyflow/react`: run `npm install` after). The helpers\n(`buildOrg`, `orgToText`…) are plain functions and work on the server too.\n\n## The data\n\nOne row per person, as a table holds them:\n\n```js\n[\n { id: \"p1\", name: \"Amara Okafor\", title: \"Chief Executive Officer\", department: \"Leadership\" },\n { id: \"p2\", name: \"Daniel Reyes\", title: \"VP Engineering\", department: \"Engineering\", managerId: \"p1\", avatar: \"https://…\", email: \"daniel@…\", location: \"Lisbon\" },\n]\n```\n\n- `id` — required, unique. `name`, `title` — the card's two lines.\n- `managerId` (or the field named by `managerKey`) — the manager's `id`.\n Missing, or naming someone not in the list: the person is the top of a\n tree (several tops sit side by side). A loop of managers is broken, and\n never hangs the chart.\n- `avatar` — an image URL; without one the card shows initials.\n- `department` (or the field named by `groupBy`) — the card's colour strip\n and the legend. Colours are the theme's chart hues, given in the order\n groups first appear.\n- `email`, `location` — shown in the details sheet. Any other field is\n kept and handed back to your callbacks.\n\n## Props\n\n- `people` — required, the rows above. An empty list shows a friendly\n empty state.\n- `managerKey` — the field naming the manager (default `\"managerId\"`).\n- `groupBy` — the field to colour by (default `\"department\"`), or\n `(person) => group`; `null` for no colours or legend.\n- `defaultDepth` — how many levels show before teams fold (default `3`:\n the top, their reports, and theirs).\n- `onChangeManager(person, managerId)` — `async`, return `{ ok: true }` or\n `{ ok: false, error? }` (a throw counts as failed). Turns on dragging a\n card onto another (with a mouse; on touch a drag always pans). The move\n shows at once; a failure puts the person back and says why. Update\n `people` when it succeeds (the chart keeps the move until you do).\n- `onOpenPerson(person)` — open your own view of a person (a record page,\n your own Sheet) instead of the built-in details sheet.\n- `renderDetails(person)` — more for the built-in details sheet, under the\n reports: kit parts (their open deals, a \"Message\" button).\n- `onSelectionChange(person | null)` — the selected card changed.\n- `height` — px or any CSS length, default `640`. A height class in\n `className` (`h-
|
|
41
|
+
"content": "# OrgChart\n\nWho reports to whom, top down. Give it people as a flat list — each row\nnames its manager — and it draws the tree: managers centred over their\nteams, a team with no managers in it as a compact column (twelve cards in\na row read worse than a list), every line in place.\n\nTeams past the first levels start folded; the chip under a manager says\nhow many report to them and, folded, how many are hidden. **Find a\nperson** (or press `/`) unfolds the way down to them, centres them and\nlights their chain of managers. A click selects a card; a second click\n(or Enter) opens a details sheet: who they report to, their direct\nreports, the team's size. Cards carry a colour strip by department, with\na legend on top.\n\nWith `onChangeManager`, drag a card onto another to move that person to a\nnew manager — shown at once, saved through your callback, undone if it\nfails, with an Undo in the toast.\n\nIt is built on `flow-canvas` (which draws it, pans and zooms): this block\nbuilds the tree, lays it out and owns the keys.\n\n```jsx\nimport { OrgChart } from \"../components/blocks/org-chart\"; // a Makersclaw app (\"./blocks/org-chart\" from components/)\nimport { OrgChart } from \"@/components/makersclaw/blocks/org-chart\"; // a shadcn project\n```\n\n`OrgChart` is a client component: render it from a `\"use client\"`\ncomponent. `makersclaw add org-chart` adds `flow-canvas` with it (and its\nnpm package, `@xyflow/react`: run `npm install` after). The helpers\n(`buildOrg`, `orgToText`…) are plain functions and work on the server too.\n\n## The data\n\nOne row per person, as a table holds them:\n\n```js\n[\n { id: \"p1\", name: \"Amara Okafor\", title: \"Chief Executive Officer\", department: \"Leadership\" },\n { id: \"p2\", name: \"Daniel Reyes\", title: \"VP Engineering\", department: \"Engineering\", managerId: \"p1\", avatar: \"https://…\", email: \"daniel@…\", location: \"Lisbon\" },\n]\n```\n\n- `id` — required, unique. `name`, `title` — the card's two lines.\n- `managerId` (or the field named by `managerKey`) — the manager's `id`.\n Missing, or naming someone not in the list: the person is the top of a\n tree (several tops sit side by side). A loop of managers is broken, and\n never hangs the chart.\n- `avatar` — an image URL; without one the card shows initials.\n- `department` (or the field named by `groupBy`) — the card's colour strip\n and the legend. Colours are the theme's chart hues, given in the order\n groups first appear.\n- `email`, `location` — shown in the details sheet. Any other field is\n kept and handed back to your callbacks.\n\n## Props\n\n- `people` — required, the rows above. An empty list shows a friendly\n empty state.\n- `managerKey` — the field naming the manager (default `\"managerId\"`).\n- `groupBy` — the field to colour by (default `\"department\"`), or\n `(person) => group`; `null` for no colours or legend.\n- `defaultDepth` — how many levels show before teams fold (default `3`:\n the top, their reports, and theirs).\n- `onChangeManager(person, managerId)` — `async`, return `{ ok: true }` or\n `{ ok: false, error? }` (a throw counts as failed). Turns on dragging a\n card onto another (with a mouse; on touch a drag always pans). The move\n shows at once; a failure puts the person back and says why. Update\n `people` when it succeeds (the chart keeps the move until you do).\n- `onOpenPerson(person)` — open your own view of a person (a record page,\n your own Sheet) instead of the built-in details sheet.\n- `renderDetails(person)` — more for the built-in details sheet, under the\n reports: kit parts (their open deals, a \"Message\" button).\n- `onSelectionChange(person | null)` — the selected card changed.\n- `height` — px or any CSS length, default `640`. A height class in\n `className` (`h-full`, `min-h-0 flex-1`) replaces it: `min-h-0 flex-1`\n fills what the surface has left (the Example). Never `h-dvh` or a `vh`\n height, which `npm run check` refuses.\n- `label` — the chart's accessible name (default \"Org chart\").\n- `className` — layout around it (margin, width, height). Never colour.\n\n## Keyboard and pointer\n\n- `/` finds a person. Arrows walk the chart: ↑ to the manager, ↓ into the\n team (unfolding it), ← → along the team (and across to the next team\n at the end of a row); in a column ↑ ↓ go up and down it.\n- Space folds or unfolds the selected person's team; Enter opens their\n details; Home goes to the top; Esc clears the selection; ⇧1 fits the\n chart to the screen.\n- A plain drag on the canvas pans, pinch or ⌘-scroll zooms, the chip under\n a manager folds their team. The zoom bar (bottom right) has fit, zoom\n and a list view for screen readers.\n\n## The helpers\n\n- `buildOrg(people, { managerKey })` → `{ roots, byId }`: each node\n `{ id, person, parentId, depth, children, total }` (`total`: everyone\n under them).\n- `chainOf(byId, id)` → the chain of managers, top first, ending with them.\n- `orgToText(people, { managerKey })` — one person a line, indented under\n their manager, \"Daniel Reyes — VP Engineering (10)\": for an agent or a\n search column.\n- `layoutOrg(roots, { collapsed(node), stack = true })` → `{ items,\n bounds }`: every visible card's box and the line up to its manager.\n- `groupHues(people, key)` → Map(group → hue 1..5), the colours the chart\n uses (for a Legend or Badge elsewhere).\n\n## Example\n\n```jsx\n// surfaces/team.jsx — client: the chart, read and rearranged\n\"use client\";\nimport { useRows, whenLanded } from \"../lib/data\";\nimport { updateRow } from \"../lib/actions\";\nimport { OrgChart } from \"../components/blocks/org-chart\";\n\nexport default function TeamChart() {\n const { rows: people, loading } = useRows({ source: \"employees\" });\n if (loading) return null;\n return (\n <OrgChart\n people={people}\n managerKey=\"manager_id\"\n onChangeManager={async (person, managerId) => {\n const res = await updateRow({ source: \"employees\", id: person.id, patch: { manager_id: managerId } });\n if (res.ok) await whenLanded(); // the people are read again: the chart draws the new manager\n return res;\n }}\n className=\"min-h-0 flex-1\"\n />\n );\n}\n```\n\nIn a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.\n\n## What it will not do\n\n- Show a person under two managers (dotted lines, matrix orgs): one\n manager each.\n- Edit a person's details: open your own form from `onOpenPerson` or put\n a button in `renderDetails`.\n- Export an image or print.\n- Render on the server: the server shows the people as text until the\n canvas mounts. For a read-only page that must be rich in the HTML,\n render `orgToText` yourself.\n\nA chart it cannot be is your own component in `components/` — not an edit\nto this block. Its files are fixed: `npm run check` refuses a change, and\n`makersclaw add org-chart` restores them.\n"
|
|
42
42
|
}
|
|
43
43
|
],
|
|
44
44
|
"categories": [
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"docs": "Installed to components/makersclaw/blocks/org-chart. Import: import { OrgChart, buildOrg, layoutOrg, chainOf, orgToText, groupHues } from \"@/components/makersclaw/blocks/org-chart\". Props and an example: components/makersclaw/blocks/org-chart/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
|
|
60
60
|
"meta": {
|
|
61
61
|
"makersclaw": {
|
|
62
|
-
"version": "0.
|
|
62
|
+
"version": "0.8.1",
|
|
63
63
|
"useWhen": "A screen that shows who reports to whom: a team or company directory's chart, an account's buying committee as a hierarchy, a reporting line to check before a reorg. Not for a free-form map of people and notes (account-map, flow-canvas), a plain list of people (data-table), or a tree of ideas (mind-map)."
|
|
64
64
|
}
|
|
65
65
|
}
|