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,211 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// A shelf of starting points from the Library, in named columns, each
|
|
4
|
+
// with a Use (the app kit, D15; grown in the CRM, 0.7.0). Two ways in:
|
|
5
|
+
// `sections` you shaped, or `library` — what useLibraryItems() returns
|
|
6
|
+
// (lib/hooks.js) — with `onUse(item)`, which draws one column of the
|
|
7
|
+
// collection's items, each with its cover, and the hook's paging, preview
|
|
8
|
+
// and "not connected yet". Props in, callbacks out; the block never reads
|
|
9
|
+
// the Library itself. Tokens only.
|
|
10
|
+
|
|
11
|
+
import { useState } from "react";
|
|
12
|
+
import { Button, Icon, Image, Skeleton, cx } from "../../ui";
|
|
13
|
+
|
|
14
|
+
/** A Library item's format, as a word and a glyph. */
|
|
15
|
+
const FORMATS = {
|
|
16
|
+
pdf: { word: "PDF", icon: "file" },
|
|
17
|
+
image: { word: "Picture", icon: "image" },
|
|
18
|
+
html: { word: "Web page", icon: "globe" },
|
|
19
|
+
slides: { word: "Slides", icon: "layers" },
|
|
20
|
+
document: { word: "Document", icon: "file" },
|
|
21
|
+
sheet: { word: "Sheet", icon: "table" },
|
|
22
|
+
archive: { word: "Archive", icon: "archive" },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Library items (useLibraryItems' `items`) as shelf items. */
|
|
26
|
+
export function libraryShelfItems(items, { onUse, useLabel, once } = {}) {
|
|
27
|
+
return (items ?? []).filter(Boolean).map((item) => {
|
|
28
|
+
const f = FORMATS[item.format] ?? { word: null, icon: "file" };
|
|
29
|
+
return {
|
|
30
|
+
id: item.id,
|
|
31
|
+
label: item.title ?? "Untitled",
|
|
32
|
+
meta: item.locked ? "Paid plan" : f.word,
|
|
33
|
+
mark: item.preview_url ? (
|
|
34
|
+
<Image src={item.preview_url} alt="" ratio="square" rounded="sm" className="size-7 shrink-0" />
|
|
35
|
+
) : (
|
|
36
|
+
<Icon name={f.icon} size={14} className="shrink-0 text-muted-foreground" />
|
|
37
|
+
),
|
|
38
|
+
locked: Boolean(item.locked),
|
|
39
|
+
onUse: onUse && !item.locked ? () => onUse(item) : undefined,
|
|
40
|
+
useLabel,
|
|
41
|
+
once,
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ShelfItem({ item }) {
|
|
47
|
+
const [state, setState] = useState(item.used ? "used" : "idle");
|
|
48
|
+
const [error, setError] = useState(null);
|
|
49
|
+
async function use() {
|
|
50
|
+
if (!item.onUse || state === "busy") return;
|
|
51
|
+
setState("busy");
|
|
52
|
+
setError(null);
|
|
53
|
+
let r;
|
|
54
|
+
try {
|
|
55
|
+
r = await item.onUse();
|
|
56
|
+
} catch (e) {
|
|
57
|
+
r = { ok: false, error: e?.message ?? String(e) };
|
|
58
|
+
}
|
|
59
|
+
if (r && r.ok === false) {
|
|
60
|
+
setState("idle");
|
|
61
|
+
setError(r.error || "Couldn't use it. Try again.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
setState(item.once === false ? "idle" : "used");
|
|
65
|
+
}
|
|
66
|
+
const used = state === "used" || item.used;
|
|
67
|
+
return (
|
|
68
|
+
<li className="grid grid-cols-1 min-w-0 gap-0.5 border-b border-border py-1 last:border-b-0" data-shelf-item>
|
|
69
|
+
<div className="flex min-h-[30px] min-w-0 items-center gap-2">
|
|
70
|
+
{item.mark ?? null}
|
|
71
|
+
{/* The meta gives way, to an ellipsis, before the label does. It was
|
|
72
|
+
shrink-0, and a long meta ran the label down to nothing (UI
|
|
73
|
+
review X3). Neither may be wider than the row. */}
|
|
74
|
+
<span className="min-w-0 truncate" title={typeof item.label === "string" ? item.label : undefined} data-shelf-label>
|
|
75
|
+
{item.label}
|
|
76
|
+
</span>
|
|
77
|
+
{item.meta ? (
|
|
78
|
+
<span className="min-w-0 shrink-[4] truncate text-xs text-muted-foreground" title={typeof item.meta === "string" ? item.meta : undefined}>
|
|
79
|
+
{item.meta}
|
|
80
|
+
</span>
|
|
81
|
+
) : null}
|
|
82
|
+
<span className="ml-auto shrink-0">
|
|
83
|
+
{item.locked ? (
|
|
84
|
+
<Icon name="lock" size={12} className="text-muted-foreground" label="Comes with a paid plan" />
|
|
85
|
+
) : used ? (
|
|
86
|
+
<span className="inline-flex items-center gap-1 text-xs text-success">
|
|
87
|
+
<Icon name="check" size={12} />
|
|
88
|
+
{item.usedLabel ?? "Used"}
|
|
89
|
+
</span>
|
|
90
|
+
) : item.onUse ? (
|
|
91
|
+
<Button size="sm" variant="quiet" loading={state === "busy"} onClick={use} aria-label={`${item.useLabel ?? "Use"}: ${typeof item.label === "string" ? item.label : "this"}`}>
|
|
92
|
+
{item.useLabel ?? "Use"}
|
|
93
|
+
</Button>
|
|
94
|
+
) : null}
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
{item.description ? <p className="m-0 min-w-0 break-words pb-1 text-xs text-muted-foreground">{item.description}</p> : null}
|
|
98
|
+
{error ? (
|
|
99
|
+
<p role="alert" className="pb-1 text-xs text-destructive">
|
|
100
|
+
{error}
|
|
101
|
+
</p>
|
|
102
|
+
) : null}
|
|
103
|
+
</li>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** What a library read says under its items: loading, more, a preview, refusals. */
|
|
108
|
+
function LibraryFoot({ library, count }) {
|
|
109
|
+
const l = library ?? {};
|
|
110
|
+
if (l.unavailable) return <p className="text-[13px] text-muted-foreground">The Library is not connected yet.</p>;
|
|
111
|
+
if (l.error) return <p role="alert" className="text-[13px] text-destructive">{l.error}</p>;
|
|
112
|
+
const hidden = l.previewOnly && typeof l.total === "number" ? l.total - count : 0;
|
|
113
|
+
return (
|
|
114
|
+
<>
|
|
115
|
+
{hidden > 0 ? (
|
|
116
|
+
<p className="flex items-center gap-1.5 text-[13px] text-muted-foreground">
|
|
117
|
+
<Icon name="lock" size={12} className="shrink-0" />
|
|
118
|
+
{hidden} more come with a paid plan.
|
|
119
|
+
</p>
|
|
120
|
+
) : null}
|
|
121
|
+
{l.hasMore && l.loadMore ? (
|
|
122
|
+
<Button size="sm" variant="quiet" className="justify-self-start" loading={l.loadingMore} onClick={() => l.loadMore()}>
|
|
123
|
+
Show more
|
|
124
|
+
</Button>
|
|
125
|
+
) : null}
|
|
126
|
+
</>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The shelf.
|
|
132
|
+
*
|
|
133
|
+
* title "Start from a template"
|
|
134
|
+
* source "CRM starting points, from the Library"
|
|
135
|
+
* icon a kit Icon name before the title (default "layers")
|
|
136
|
+
* sections [{ key, title, items: [{ id, label, meta?, description?, mark?, onUse?, useLabel?,
|
|
137
|
+
* used?, usedLabel?, once?, locked? }] }]
|
|
138
|
+
* meta is ONE short fact ("6 stages", "PDF"); it truncates, and
|
|
139
|
+
* so does the label, so no row is ever wider than its box. A
|
|
140
|
+
* sentence is a `description`: it sits under the label and wraps.
|
|
141
|
+
*
|
|
142
|
+
* Or straight from the Library:
|
|
143
|
+
*
|
|
144
|
+
* library what useLibraryItems(id) returns ({ items, total,
|
|
145
|
+
* previewOnly, hasMore, loadMore, loading, loadingMore,
|
|
146
|
+
* error, unavailable })
|
|
147
|
+
* onUse(item) async → { ok } | { ok: false, error }: what Use does
|
|
148
|
+
* with a Library item (ask the agent to copy it in, open
|
|
149
|
+
* it as a draft); a locked item has no Use
|
|
150
|
+
* sectionTitle the column's title (default none)
|
|
151
|
+
* useLabel default "Use"; once — false: an item can be used again
|
|
152
|
+
* empty the line when the collection has nothing
|
|
153
|
+
*/
|
|
154
|
+
export function LibraryShelf({
|
|
155
|
+
title = "Start from a template",
|
|
156
|
+
source,
|
|
157
|
+
icon = "layers",
|
|
158
|
+
sections: given,
|
|
159
|
+
library,
|
|
160
|
+
onUse,
|
|
161
|
+
sectionTitle,
|
|
162
|
+
useLabel,
|
|
163
|
+
once,
|
|
164
|
+
empty = "Nothing on this shelf yet.",
|
|
165
|
+
className,
|
|
166
|
+
}) {
|
|
167
|
+
const fromLibrary = !given && Boolean(library);
|
|
168
|
+
const libItems = fromLibrary ? libraryShelfItems(library.items, { onUse, useLabel, once }) : [];
|
|
169
|
+
const sections = given ?? (fromLibrary && libItems.length ? [{ key: "library", title: sectionTitle, items: libItems }] : []);
|
|
170
|
+
const loading = fromLibrary && library.loading && !libItems.length;
|
|
171
|
+
return (
|
|
172
|
+
<section data-library-shelf aria-label={title} className={cx("grid grid-cols-1 min-w-0 gap-3", className)}>
|
|
173
|
+
<div className="flex flex-wrap items-center gap-2 text-[13px]">
|
|
174
|
+
<Icon name={icon} size={16} className="text-muted-foreground" />
|
|
175
|
+
<b className="font-semibold">{title}</b>
|
|
176
|
+
{source ? <span className="text-muted-foreground">· {source}</span> : null}
|
|
177
|
+
</div>
|
|
178
|
+
{loading ? (
|
|
179
|
+
<div aria-busy="true" aria-label="Loading" className="grid grid-cols-1 gap-2 rounded-[var(--radius)] border border-border px-3.5 py-3">
|
|
180
|
+
<Skeleton className="w-2/3" />
|
|
181
|
+
<Skeleton className="w-1/2" />
|
|
182
|
+
<Skeleton className="w-3/5" />
|
|
183
|
+
</div>
|
|
184
|
+
) : fromLibrary && !libItems.length && !library.unavailable && !library.error ? (
|
|
185
|
+
<p className="text-[13px] text-muted-foreground">{empty}</p>
|
|
186
|
+
) : null}
|
|
187
|
+
{sections.length ? (
|
|
188
|
+
<>
|
|
189
|
+
{/* Side by side by the shelf's own width, not the window's: an app on
|
|
190
|
+
the desk is often 800px with a rail beside it, where three columns
|
|
191
|
+
cut every name to its first letter (CRM 0.7.1). */}
|
|
192
|
+
<div className="@container min-w-0">
|
|
193
|
+
<div className="grid grid-cols-1 min-w-0 rounded-[var(--radius)] border border-border @3xl:grid-cols-[repeat(auto-fit,minmax(14rem,1fr))]">
|
|
194
|
+
{sections.map((s) => (
|
|
195
|
+
<div key={s.key ?? s.title} className="grid grid-cols-1 min-w-0 content-start gap-0.5 border-b border-border px-3.5 py-3 last:border-b-0 @3xl:border-b-0 @3xl:border-r @3xl:last:border-r-0">
|
|
196
|
+
{s.title ? <h3 className="text-xs font-semibold text-muted-foreground">{s.title}</h3> : null}
|
|
197
|
+
<ul className="m-0 grid grid-cols-1 list-none p-0 text-[13px]">
|
|
198
|
+
{s.items.map((it) => (
|
|
199
|
+
<ShelfItem key={it.id} item={it} />
|
|
200
|
+
))}
|
|
201
|
+
</ul>
|
|
202
|
+
</div>
|
|
203
|
+
))}
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</>
|
|
207
|
+
) : null}
|
|
208
|
+
{fromLibrary ? <LibraryFoot library={library} count={libItems.length} /> : null}
|
|
209
|
+
</section>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The layout rules this block ships for the store's probe (makersclaw-store
|
|
2
|
+
// scripts/probe.mjs --suite); see top-bar/probe-rules.js. Nothing imports this.
|
|
3
|
+
export default {
|
|
4
|
+
block: "library-shelf",
|
|
5
|
+
rules: [
|
|
6
|
+
{
|
|
7
|
+
id: "label-never-cut-to-nothing",
|
|
8
|
+
kind: "visible-text",
|
|
9
|
+
note: "UI review X3: the meta ran the label down to a letter",
|
|
10
|
+
scope: "[data-library-shelf]",
|
|
11
|
+
target: "[data-shelf-label], li > div > span.truncate:not(.text-xs)",
|
|
12
|
+
min: 3,
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# MarginNotes
|
|
2
|
+
|
|
3
|
+
Passages of text with notes in the margin, **level with the words they are
|
|
4
|
+
about**. On a wide container each passage is a row: the words on one side,
|
|
5
|
+
its notes on the other. On a narrow one (under about 42rem) the notes stack
|
|
6
|
+
under the passage they belong to.
|
|
7
|
+
|
|
8
|
+
A note is one of four kinds, each with its own tone and its own buttons:
|
|
9
|
+
|
|
10
|
+
- **source** — where the words came from: a title, a line of detail, the
|
|
11
|
+
words that were read, a link.
|
|
12
|
+
- **flag** — "No source. Check this line." with **It's true**, **Cut it**,
|
|
13
|
+
**Ask where it came from**.
|
|
14
|
+
- **cut** — the words struck through where they stand, the reason in the
|
|
15
|
+
margin, **Put back** (or a lock when nobody may put them back).
|
|
16
|
+
- **comment** — what someone thought of the line.
|
|
17
|
+
|
|
18
|
+
An app can change any of them or add its own kind.
|
|
19
|
+
|
|
20
|
+
The words a note is about (`quote`) are marked in the passage: dotted for a
|
|
21
|
+
source, wavy for a flag, struck for a cut, washed for a comment. **The
|
|
22
|
+
selected note and its passage are linked**: click a passage or a note, or
|
|
23
|
+
press <kbd>J</kbd> and <kbd>K</kbd> to walk the notes that still need a
|
|
24
|
+
person. A settled note folds to one line with what was decided and a
|
|
25
|
+
**Reopen**.
|
|
26
|
+
|
|
27
|
+
```jsx
|
|
28
|
+
import { MarginNotes } from "../components/blocks/margin-notes"; // a Makersclaw app ("./blocks/margin-notes" from components/)
|
|
29
|
+
import { MarginNotes } from "@/components/makersclaw/blocks/margin-notes"; // a shadcn project
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
A client component. `makersclaw add margin-notes` lays `agent-marks` with
|
|
33
|
+
it. The helpers (`walkNext`, `inlineMarks`, `countNotes`…) are plain
|
|
34
|
+
functions and work on the server.
|
|
35
|
+
|
|
36
|
+
## Which hook feeds it
|
|
37
|
+
|
|
38
|
+
None. The passages are the app's words (a draft, a transcript); the notes are
|
|
39
|
+
rows the agent or a person wrote beside them. Keep them in the app's tables
|
|
40
|
+
and pass them in. The block never reads or writes: a press goes out through
|
|
41
|
+
`onAction`, the app does the write, and hands back the changed notes.
|
|
42
|
+
|
|
43
|
+
## Props
|
|
44
|
+
|
|
45
|
+
- `passages` — the words, in reading order: `[{ id, text, lead?, label?,
|
|
46
|
+
level?, node? }]`. `text` is plain words. `lead` is a short figure before
|
|
47
|
+
them (a time `00:12`, a number); `label` a name above them (a speaker, a
|
|
48
|
+
section); `level` `2` · `3` · `4` draws the passage as a heading. `node`
|
|
49
|
+
(a React node) draws your own markup instead of `text`: a note's `quote`
|
|
50
|
+
cannot mark inside it (the card shows the quote instead), but a cut still
|
|
51
|
+
strikes it whole.
|
|
52
|
+
- `notes` — `[{ id, passage, kind, … }]`. `passage` is the id of the
|
|
53
|
+
passage it sits by; a note whose passage is not in the list is drawn
|
|
54
|
+
after the last row under `orphanTitle`. The rest:
|
|
55
|
+
- `kind` — `source` · `flag` · `cut` · `comment`, or one of your `kinds`.
|
|
56
|
+
- `title` — the note's heading ("v3.0.0", "No source. Check this line.").
|
|
57
|
+
- `meta` — a muted line under it ("tern-app/tern · shipped Tue 22 Sep").
|
|
58
|
+
- `text` — the note's words (a reason, a comment).
|
|
59
|
+
- `detail` — the proof: the words that were read, under a dashed rule.
|
|
60
|
+
- `quote` — the words **in the passage** the note is about. The first
|
|
61
|
+
place they appear (ignoring case) is marked. Without one a `cut` strikes
|
|
62
|
+
the whole passage; the other kinds mark nothing in the words.
|
|
63
|
+
- `by` — `"agent"` or `{ agent: true }` draws the agent's mark; any other
|
|
64
|
+
string or `{ name }` is shown as a name.
|
|
65
|
+
- `at` — when, in words ("12:31", "Tue 22 Sep").
|
|
66
|
+
- `href`, `hrefLabel` — "Open the source" (default) as a link that opens
|
|
67
|
+
in a new tab.
|
|
68
|
+
- `locked` — words for why nothing can be done to this note ("On the
|
|
69
|
+
never-list: margins"). A locked note shows a lock and no buttons.
|
|
70
|
+
- `state` — `"settled"` folds the note to a line: `answer` ("You said
|
|
71
|
+
it's true") and `at`, and the kind's **Reopen** if it has one. Settled
|
|
72
|
+
notes mark nothing in the words and are skipped by <kbd>J</kbd> and
|
|
73
|
+
<kbd>K</kbd>.
|
|
74
|
+
- `actions` — `[{ id, label, variant?, icon? }]`, replacing the kind's
|
|
75
|
+
buttons for this note.
|
|
76
|
+
- `label`, `tone`, `hue`, `mark` — this note's chip words, status tone
|
|
77
|
+
(`neutral` · `primary` · `info` · `success` · `warning` · `danger`),
|
|
78
|
+
categorical colour `1`–`5` (for a family of cuts: pricing, other people,
|
|
79
|
+
in confidence, length), and mark (`underline` · `wavy` · `strike` ·
|
|
80
|
+
`highlight` · `none`) over the kind's.
|
|
81
|
+
- `selected`, `defaultSelected`, `onSelect(id | null)` — the selected
|
|
82
|
+
note's id. Pass `selected` to control it (from the address, say); leave it
|
|
83
|
+
out and the block keeps it. <kbd>Esc</kbd> clears it.
|
|
84
|
+
- `onAction(note, actionId)` — `async`, returns `{ ok: true }` or
|
|
85
|
+
`{ ok: false, error? }`. Called when a button is pressed; the button shows
|
|
86
|
+
its spinner until it answers, and a failure prints its `error` under the
|
|
87
|
+
note, where it happened. A throw counts as a failure. A settled note's
|
|
88
|
+
Reopen calls it with `"reopen"`. **Without `onAction` no buttons are
|
|
89
|
+
drawn.**
|
|
90
|
+
- `kinds` — changes to the built-ins, or new kinds, by id: `{ flag: {
|
|
91
|
+
label: "Check" }, aside: { label: "Aside", tone: "primary", icon:
|
|
92
|
+
"message-circle", mark: "highlight", actions: [] } }`. Fields are `label`,
|
|
93
|
+
`icon` (a kit icon), `tone`, `mark`, `actions`, and `reopen` (`false`, or
|
|
94
|
+
`{ label }`). Each field you give replaces that one field.
|
|
95
|
+
- `side` — `right` (default) or `left`: which side of the words the margin
|
|
96
|
+
is on. Narrow, the notes are always under the words.
|
|
97
|
+
- `marginWidth` — `sm` (15rem) · `md` (18rem, default) · `lg` (22rem).
|
|
98
|
+
- `marginTitle` — a heading over the whole thing ("Where each paragraph
|
|
99
|
+
came from"), with the count line beside it.
|
|
100
|
+
- `showNotes` — `false` hides the margin and its marks: the words alone.
|
|
101
|
+
- `textSize` — `sm` · `md` (default) · `lg`.
|
|
102
|
+
- `status` — `ready` (default) · `loading` (rows of grey bars) · `failed`
|
|
103
|
+
(a notice with `error`, and Try again if you pass `onRetry`).
|
|
104
|
+
- `readOnly` — notes to read: no buttons, no Reopen.
|
|
105
|
+
- `keys` — <kbd>J</kbd> and <kbd>K</kbd> walk the notes (default `true`).
|
|
106
|
+
- `noNotes`, `orphanTitle`, `emptyTitle`, `emptyBody`, `countLabels` — the
|
|
107
|
+
block's few words, to be changed (`countLabels`: `{ open, settled }`).
|
|
108
|
+
- `label` — the region's accessible name.
|
|
109
|
+
- `className` — layout: width, margin, height. Never colour.
|
|
110
|
+
|
|
111
|
+
### Keys
|
|
112
|
+
|
|
113
|
+
On the region (it takes focus with Tab): <kbd>J</kbd> the next note that
|
|
114
|
+
still needs a person, <kbd>K</kbd> the one before, <kbd>Esc</kbd> clears the
|
|
115
|
+
selection. On a passage with notes: <kbd>Enter</kbd> or <kbd>Space</kbd>
|
|
116
|
+
selects its first note, again its next, after the last none. The walk moves
|
|
117
|
+
focus to the note, scrolls it into view (without animation when the person
|
|
118
|
+
has asked for less motion) and a hidden line says "Note 2 of 5". The keys
|
|
119
|
+
are left alone while someone is typing in a field.
|
|
120
|
+
|
|
121
|
+
### Helpers
|
|
122
|
+
|
|
123
|
+
Plain functions (`import` them from the same folder):
|
|
124
|
+
|
|
125
|
+
- `walkNext(notes, currentId, dir)` — the id J (`1`) or K (`-1`) goes to.
|
|
126
|
+
- `inlineMarks(text, notes, markOf)` — a passage's words cut into pieces
|
|
127
|
+
by the notes' quotes: `{ segments: [{ text, ids }], whole }`.
|
|
128
|
+
- `arrangeNotes(passages, notes)` — `{ byPassage, ordered, orphans }`.
|
|
129
|
+
- `countNotes(notes)`, `countWords(counts)` — `{ total, open, settled,
|
|
130
|
+
byKind }` and "3 open · 1 settled".
|
|
131
|
+
- `resolveKinds(changes)`, `noteLook(note, kinds)`, `actionsOf(note, kinds)`
|
|
132
|
+
— the kinds, and what a note looks like and offers.
|
|
133
|
+
- `MARGIN_KINDS` — the built-in kinds.
|
|
134
|
+
|
|
135
|
+
## Example
|
|
136
|
+
|
|
137
|
+
An issue with a source beside each paragraph, and a line the agent could
|
|
138
|
+
not source:
|
|
139
|
+
|
|
140
|
+
```jsx
|
|
141
|
+
"use client";
|
|
142
|
+
import { useRows } from "../lib/data";
|
|
143
|
+
import { updateRow } from "../lib/actions";
|
|
144
|
+
import { MarginNotes } from "../components/blocks/margin-notes";
|
|
145
|
+
|
|
146
|
+
export default function Issue({ issueId }) {
|
|
147
|
+
const paragraphs = useRows({ source: "issue_paragraphs", filter: { issue_id: issueId }, orderBy: "position asc" });
|
|
148
|
+
const notes = useRows({ source: "paragraph_notes", filter: { issue_id: issueId } });
|
|
149
|
+
if (paragraphs.loading || notes.loading) return <MarginNotes status="loading" />;
|
|
150
|
+
return (
|
|
151
|
+
<MarginNotes
|
|
152
|
+
marginTitle="Where each paragraph came from"
|
|
153
|
+
passages={(paragraphs.rows ?? []).map((p) => ({ id: p.id, text: p.body, level: p.is_heading ? 3 : undefined }))}
|
|
154
|
+
notes={(notes.rows ?? []).map((n) => ({
|
|
155
|
+
id: n.id,
|
|
156
|
+
passage: n.paragraph_id,
|
|
157
|
+
kind: n.kind, // "source" · "flag"
|
|
158
|
+
title: n.title,
|
|
159
|
+
meta: n.meta,
|
|
160
|
+
detail: n.excerpt,
|
|
161
|
+
quote: n.quote,
|
|
162
|
+
by: n.by_agent ? "agent" : undefined,
|
|
163
|
+
state: n.settled_at ? "settled" : undefined,
|
|
164
|
+
answer: n.answer,
|
|
165
|
+
}))}
|
|
166
|
+
onAction={(note, action) =>
|
|
167
|
+
updateRow({ source: "paragraph_notes", id: note.id, patch: { answer: action, settled_at: action === "reopen" ? null : new Date().toISOString() } })
|
|
168
|
+
}
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
175
|
+
|
|
176
|
+
A transcript with the cut words struck, a family colour per reason, and a
|
|
177
|
+
lock for what may never come back:
|
|
178
|
+
|
|
179
|
+
```jsx
|
|
180
|
+
<MarginNotes
|
|
181
|
+
side="left"
|
|
182
|
+
marginWidth="lg"
|
|
183
|
+
textSize="sm"
|
|
184
|
+
passages={lines.map((l) => ({ id: l.id, lead: l.clock, label: l.speaker, text: l.words }))}
|
|
185
|
+
notes={cuts.map((c) => ({
|
|
186
|
+
id: c.id,
|
|
187
|
+
passage: c.line_id,
|
|
188
|
+
kind: "cut",
|
|
189
|
+
hue: c.family, // 1..5, one colour per reason family
|
|
190
|
+
label: c.reason, // "Pricing", "A third party"…
|
|
191
|
+
text: c.note,
|
|
192
|
+
locked: c.on_never_list ? `On the never-list: ${c.on_never_list}.` : undefined,
|
|
193
|
+
state: c.put_back ? "settled" : undefined,
|
|
194
|
+
answer: "Put back",
|
|
195
|
+
}))}
|
|
196
|
+
onAction={(note, action) => putBack(note.id)}
|
|
197
|
+
/>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## States
|
|
201
|
+
|
|
202
|
+
Drawn in `demo.jsx`: sources and a flag that settles in place, cuts with
|
|
203
|
+
the margin on the left, 320 px (notes under the words), one passage, very
|
|
204
|
+
long words, passages with an empty margin, no passages, loading, failed,
|
|
205
|
+
read-only.
|
|
206
|
+
|
|
207
|
+
## What it will not do
|
|
208
|
+
|
|
209
|
+
- Edit the words. A passage is read; to change it, open it in your editor
|
|
210
|
+
(a flag's "Cut it" goes out through `onAction` and your app changes the
|
|
211
|
+
text).
|
|
212
|
+
- Mark the same quote twice in one passage, or a quote that spans two
|
|
213
|
+
passages: the first place it appears is marked, in the passage the note
|
|
214
|
+
is on.
|
|
215
|
+
- Put a note anywhere but beside its passage's row. A long note makes its
|
|
216
|
+
row taller.
|
|
217
|
+
- Keep the app's data. Settled, cut, put back: all come back in through
|
|
218
|
+
`notes`.
|
|
219
|
+
|
|
220
|
+
A margin it cannot be is your own component in `components/` — not an edit
|
|
221
|
+
to this block. Its files are fixed: `npm run check` refuses a change, and
|
|
222
|
+
`makersclaw add margin-notes` restores them.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "margin-notes",
|
|
3
|
+
"title": "MarginNotes",
|
|
4
|
+
"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).",
|
|
5
|
+
"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).",
|
|
6
|
+
"tags": ["margin", "notes", "marginalia", "sources", "citations", "provenance", "fact check", "flag", "claim", "check", "cut", "struck", "redact", "transcript", "minutes", "review", "comment", "proof", "annotation", "agent"],
|
|
7
|
+
"exports": ["MarginNotes", "MARGIN_KINDS", "TONES", "MARKS", "resolveKinds", "noteLook", "actionsOf", "isSettled", "arrangeNotes", "walkNext", "cycleNote", "findQuote", "inlineMarks", "countNotes", "countWords"],
|
|
8
|
+
"files": ["index.js", "margin-notes.jsx", "margin-model.js"],
|
|
9
|
+
"requires": ["agent-marks"]
|
|
10
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// MarginNotes in the states a screen meets — the gallery's and the smoke
|
|
4
|
+
// test's surface. Not installed into apps: `makersclaw add margin-notes`
|
|
5
|
+
// lays index.js, margin-notes.jsx and margin-model.js only.
|
|
6
|
+
// Sources an issue, a source beside each paragraph, one line with
|
|
7
|
+
// none to flag (It's true, Cut it, Ask where; settles in place)
|
|
8
|
+
// Cuts a transcript with the cut words struck and the reason in
|
|
9
|
+
// the margin on the left, four hues, a lock, Put back
|
|
10
|
+
// Narrow the same issue in a 320 px column: notes under the words
|
|
11
|
+
// One a single passage with a single note
|
|
12
|
+
// Long words and a note with no break to wrap at
|
|
13
|
+
// Empty no passages; and passages with no notes
|
|
14
|
+
// Loading, failed, read-only
|
|
15
|
+
import { useState } from "react";
|
|
16
|
+
import { Switch } from "../components/ui";
|
|
17
|
+
import { MarginNotes } from "../components/blocks/margin-notes";
|
|
18
|
+
|
|
19
|
+
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
20
|
+
|
|
21
|
+
const ISSUE = [
|
|
22
|
+
{ id: "p1", level: 3, text: "Offline mode, for the whole day" },
|
|
23
|
+
{ id: "p2", text: "At 6am the tech app downloads the day’s jobs, maps and forms. In a basement plant room or past the last mast, your techs open jobs and fill in forms as usual." },
|
|
24
|
+
{ id: "p3", text: "Search now finds a job by address, customer or part number, so nobody scrolls a list at the van door." },
|
|
25
|
+
{ id: "p4", text: "PS. Offline mode cut sync errors by 90% in the pilot." },
|
|
26
|
+
{ id: "p5", text: "You get this because you use Tern." },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const ISSUE_NOTES = [
|
|
30
|
+
{ id: "s1", passage: "p1", kind: "source", title: "v3.0.0", meta: "tern-app/tern-mobile · shipped Tue 22 Sep", detail: "Release v3.0.0 of the tech app", href: "https://example.com/releases/v3.0.0", hrefLabel: "Open the release", quote: "Offline mode" },
|
|
31
|
+
{ id: "s2", passage: "p2", kind: "source", title: "v3.0.0", meta: "tern-app/tern-mobile · shipped Tue 22 Sep", detail: "offline queue for jobs, maps and forms; background sync on reconnect", quote: "downloads the day’s jobs, maps and forms" },
|
|
32
|
+
{ id: "s3", passage: "p3", kind: "source", title: "PR 482", meta: "tern-app/tern · merged Wed 23 Sep · Leo Brandt", detail: "search: index part numbers on job lines; add address and customer fields", quote: "address, customer or part number" },
|
|
33
|
+
{ id: "s3b", passage: "p3", kind: "comment", by: "Maya", at: "12:31", text: "Say it as one sentence, not a list.", quote: "scrolls a list" },
|
|
34
|
+
{ id: "f1", passage: "p4", kind: "flag", by: "agent", title: "No source. Check this line.", text: "The PS claims a 90% drop in sync errors. Nothing in the log or the blog says so.", quote: "90% in the pilot" },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
function useNotes(start) {
|
|
38
|
+
const [notes, setNotes] = useState(start);
|
|
39
|
+
const [log, setLog] = useState("");
|
|
40
|
+
const onAction = async (note, id) => {
|
|
41
|
+
await wait(350);
|
|
42
|
+
if (note.id === "f1" && id === "ask") return { ok: false, error: "The mail did not go. Try again." };
|
|
43
|
+
setNotes((all) =>
|
|
44
|
+
all.map((n) => {
|
|
45
|
+
if (n.id !== note.id) return n;
|
|
46
|
+
if (id === "confirm") return { ...n, state: "settled", answer: "You said it’s true", at: "12:48" };
|
|
47
|
+
if (id === "cut") return { ...n, kind: "cut", title: "Cut by you", text: "No source for it.", quote: undefined, actions: undefined };
|
|
48
|
+
if (id === "putback") return { ...n, state: "settled", answer: "Put back", at: "12:50" };
|
|
49
|
+
if (id === "reopen") return { ...n, state: "open", answer: undefined };
|
|
50
|
+
return n;
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
setLog(`${id}: ${note.id}`);
|
|
54
|
+
return { ok: true };
|
|
55
|
+
};
|
|
56
|
+
return { notes, onAction, log };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const FAMILY = { 1: "Commercial", 2: "Other people", 3: "In confidence", 4: "Length" };
|
|
60
|
+
const CALL = [
|
|
61
|
+
{ id: "t1", lead: "00:04", label: "Leo Park", text: "Honestly, our margin on this account is basically gone." },
|
|
62
|
+
{ id: "t2", lead: "00:12", label: "Dana Whitfield", text: "Our last agency was useless with reporting, so this is already better." },
|
|
63
|
+
{ id: "t3", lead: "00:31", label: "Sam Ortiz", text: "We agreed the TikTok pilot runs in Q4, owner to be agreed." },
|
|
64
|
+
{ id: "t4", lead: "01:02", label: "Leo Park", text: "Ha, I do love a Friday afternoon call. Anyway." },
|
|
65
|
+
];
|
|
66
|
+
const CALL_NOTES = [
|
|
67
|
+
{ id: "c1", passage: "t1", kind: "cut", hue: 1, label: FAMILY[1], by: "agent", text: "Our margin; before the client joined.", locked: "On the never-list: margins. Nobody can put this back." },
|
|
68
|
+
{ id: "c2", passage: "t2", kind: "cut", hue: 2, label: FAMILY[2], by: "agent", text: "About their previous agency." },
|
|
69
|
+
{ id: "c3", passage: "t4", kind: "cut", hue: 4, label: FAMILY[4], by: "agent", text: "A joke; nothing decided." },
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const LONG = "Supercalifragilisticexpialidocious".repeat(4);
|
|
73
|
+
|
|
74
|
+
export default function MarginNotesDemo() {
|
|
75
|
+
const issue = useNotes(ISSUE_NOTES);
|
|
76
|
+
const narrow = useNotes(ISSUE_NOTES);
|
|
77
|
+
const call = useNotes(CALL_NOTES);
|
|
78
|
+
const [showNotes, setShowNotes] = useState(true);
|
|
79
|
+
const [picked, setPicked] = useState(null);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div className="grid gap-10 p-6">
|
|
83
|
+
<section className="grid gap-3">
|
|
84
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
85
|
+
<h3 className="m-0 text-base font-semibold">Sources and a flag</h3>
|
|
86
|
+
<Switch id="mn-show" label="Show the margin" checked={showNotes} onChange={(e) => setShowNotes(e.target.checked)} />
|
|
87
|
+
</div>
|
|
88
|
+
<MarginNotes passages={ISSUE} notes={issue.notes} onAction={issue.onAction} showNotes={showNotes} marginTitle="Where each paragraph came from" selected={picked} onSelect={setPicked} />
|
|
89
|
+
<p className="m-0 text-xs text-muted-foreground">{issue.log ? `Last: ${issue.log}` : "Press J or K, or click a paragraph."}</p>
|
|
90
|
+
</section>
|
|
91
|
+
|
|
92
|
+
<section className="grid gap-3">
|
|
93
|
+
<h3 className="m-0 text-base font-semibold">Cuts, struck where they were said (margin on the left)</h3>
|
|
94
|
+
<MarginNotes passages={CALL} notes={call.notes} onAction={call.onAction} side="left" marginWidth="lg" marginTitle="The call, with every cut" textSize="sm" />
|
|
95
|
+
</section>
|
|
96
|
+
|
|
97
|
+
<section className="grid gap-3">
|
|
98
|
+
<h3 className="m-0 text-base font-semibold">Narrow (320)</h3>
|
|
99
|
+
<div className="w-80 max-w-full">
|
|
100
|
+
<MarginNotes passages={ISSUE} notes={narrow.notes} onAction={narrow.onAction} defaultSelected="f1" />
|
|
101
|
+
</div>
|
|
102
|
+
</section>
|
|
103
|
+
|
|
104
|
+
<section className="grid gap-3">
|
|
105
|
+
<h3 className="m-0 text-base font-semibold">One passage, one note</h3>
|
|
106
|
+
<MarginNotes passages={[ISSUE[3]]} notes={[ISSUE_NOTES[4]]} keys={false} />
|
|
107
|
+
</section>
|
|
108
|
+
|
|
109
|
+
<section className="grid gap-3">
|
|
110
|
+
<h3 className="m-0 text-base font-semibold">Very long words</h3>
|
|
111
|
+
<MarginNotes
|
|
112
|
+
passages={[{ id: "l1", label: LONG, text: `${LONG} is the whole of this line, and it has no break to wrap at: ${LONG}.` }]}
|
|
113
|
+
notes={[{ id: "ln", passage: "l1", kind: "flag", title: LONG, text: `${LONG}`, detail: `${LONG} ${LONG}`, quote: LONG.slice(0, 20) }]}
|
|
114
|
+
onAction={async () => ({ ok: true })}
|
|
115
|
+
/>
|
|
116
|
+
</section>
|
|
117
|
+
|
|
118
|
+
<section className="grid gap-3">
|
|
119
|
+
<h3 className="m-0 text-base font-semibold">Passages, nothing in the margin</h3>
|
|
120
|
+
<MarginNotes passages={ISSUE.slice(0, 3)} notes={[]} marginTitle="Sources" />
|
|
121
|
+
</section>
|
|
122
|
+
|
|
123
|
+
<section className="grid gap-3">
|
|
124
|
+
<h3 className="m-0 text-base font-semibold">No passages</h3>
|
|
125
|
+
<MarginNotes passages={[]} notes={[]} emptyTitle="No issue drafted yet" emptyBody="When the agent drafts one, each paragraph appears here beside where it came from." />
|
|
126
|
+
</section>
|
|
127
|
+
|
|
128
|
+
<section className="grid gap-3">
|
|
129
|
+
<h3 className="m-0 text-base font-semibold">Loading</h3>
|
|
130
|
+
<MarginNotes status="loading" />
|
|
131
|
+
</section>
|
|
132
|
+
|
|
133
|
+
<section className="grid gap-3">
|
|
134
|
+
<h3 className="m-0 text-base font-semibold">Failed</h3>
|
|
135
|
+
<MarginNotes status="failed" error="The sources could not be read." onRetry={() => undefined} />
|
|
136
|
+
</section>
|
|
137
|
+
|
|
138
|
+
<section className="grid gap-3">
|
|
139
|
+
<h3 className="m-0 text-base font-semibold">Read-only</h3>
|
|
140
|
+
<MarginNotes passages={ISSUE} notes={ISSUE_NOTES} readOnly marginTitle="Where each paragraph came from" />
|
|
141
|
+
</section>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The MarginNotes block — `makersclaw add margin-notes`. Fixed: compose it
|
|
2
|
+
// through its props (README.md beside this file). The model's helpers are
|
|
3
|
+
// plain functions; they work on the server too.
|
|
4
|
+
export { MarginNotes } from "./margin-notes";
|
|
5
|
+
export {
|
|
6
|
+
MARGIN_KINDS,
|
|
7
|
+
TONES,
|
|
8
|
+
MARKS,
|
|
9
|
+
resolveKinds,
|
|
10
|
+
noteLook,
|
|
11
|
+
actionsOf,
|
|
12
|
+
isSettled,
|
|
13
|
+
arrangeNotes,
|
|
14
|
+
walkNext,
|
|
15
|
+
cycleNote,
|
|
16
|
+
findQuote,
|
|
17
|
+
inlineMarks,
|
|
18
|
+
countNotes,
|
|
19
|
+
countWords,
|
|
20
|
+
} from "./margin-model";
|