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,465 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// Passages of text with notes in the margin, level with their words. Each
|
|
4
|
+
// passage is a row: the words on one side, the notes about them on the
|
|
5
|
+
// other (stacked under the words when the container is narrow). A note says
|
|
6
|
+
// where the words came from (source), that they need a look (flag), that
|
|
7
|
+
// they were cut and why (cut), or what someone thought (comment); an app
|
|
8
|
+
// can add kinds. The words a note is about are marked in the passage, and
|
|
9
|
+
// the selected note and its passage are linked: click either, or press J
|
|
10
|
+
// and K to walk the notes that still need a person.
|
|
11
|
+
//
|
|
12
|
+
// Props in, callbacks out: the block never reads or writes data. A press on
|
|
13
|
+
// a note's button calls onAction(note, actionId); the app does the work and
|
|
14
|
+
// hands back changed notes. README.md beside this file is the contract.
|
|
15
|
+
|
|
16
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
17
|
+
import { Badge, Button, Empty, Icon, Kbd, Notice, Skeleton, cx } from "../../ui";
|
|
18
|
+
import { AgentMark } from "../agent-marks";
|
|
19
|
+
import { actionsOf, arrangeNotes, countNotes, countWords, cycleNote, inlineMarks, isSettled, noteLook, resolveKinds, walkNext } from "./margin-model";
|
|
20
|
+
|
|
21
|
+
// Literal class names, so Tailwind sees every one of them.
|
|
22
|
+
const GRID = {
|
|
23
|
+
right: {
|
|
24
|
+
sm: "@2xl:grid-cols-[minmax(0,1fr)_15rem]",
|
|
25
|
+
md: "@2xl:grid-cols-[minmax(0,1fr)_18rem]",
|
|
26
|
+
lg: "@2xl:grid-cols-[minmax(0,1fr)_22rem]",
|
|
27
|
+
},
|
|
28
|
+
left: {
|
|
29
|
+
sm: "@2xl:grid-cols-[15rem_minmax(0,1fr)]",
|
|
30
|
+
md: "@2xl:grid-cols-[18rem_minmax(0,1fr)]",
|
|
31
|
+
lg: "@2xl:grid-cols-[22rem_minmax(0,1fr)]",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const TEXT = { sm: "text-sm leading-relaxed", md: "text-base leading-relaxed", lg: "text-lg leading-relaxed" };
|
|
36
|
+
|
|
37
|
+
// The coloured bar down a note's inline-start edge.
|
|
38
|
+
const BAR = {
|
|
39
|
+
neutral: "bg-muted-foreground",
|
|
40
|
+
primary: "bg-primary",
|
|
41
|
+
info: "bg-info",
|
|
42
|
+
success: "bg-success",
|
|
43
|
+
warning: "bg-warning",
|
|
44
|
+
danger: "bg-destructive",
|
|
45
|
+
};
|
|
46
|
+
const BAR_HUE = { 1: "bg-chart-1", 2: "bg-chart-2", 3: "bg-chart-3", 4: "bg-chart-4", 5: "bg-chart-5" };
|
|
47
|
+
|
|
48
|
+
const DECO = {
|
|
49
|
+
neutral: "decoration-muted-foreground",
|
|
50
|
+
primary: "decoration-primary",
|
|
51
|
+
info: "decoration-info",
|
|
52
|
+
success: "decoration-success",
|
|
53
|
+
warning: "decoration-warning",
|
|
54
|
+
danger: "decoration-destructive",
|
|
55
|
+
};
|
|
56
|
+
const DECO_HUE = { 1: "decoration-chart-1", 2: "decoration-chart-2", 3: "decoration-chart-3", 4: "decoration-chart-4", 5: "decoration-chart-5" };
|
|
57
|
+
|
|
58
|
+
const WASH = {
|
|
59
|
+
neutral: "bg-muted",
|
|
60
|
+
primary: "bg-primary/15",
|
|
61
|
+
info: "bg-info/15",
|
|
62
|
+
success: "bg-success/15",
|
|
63
|
+
warning: "bg-warning/15",
|
|
64
|
+
danger: "bg-destructive/15",
|
|
65
|
+
};
|
|
66
|
+
const 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" };
|
|
67
|
+
|
|
68
|
+
const deco = (look) => (look.hue ? DECO_HUE[look.hue] : DECO[look.tone]);
|
|
69
|
+
const wash = (look) => (look.hue ? WASH_HUE[look.hue] : WASH[look.tone]);
|
|
70
|
+
|
|
71
|
+
const domId = (root, kind, id) => `${root}-${kind}-${String(id).replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
72
|
+
|
|
73
|
+
const reducedMotion = () => typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
74
|
+
|
|
75
|
+
/** Words typed into, or a key meant for something else: J and K are left alone. */
|
|
76
|
+
function isTyping(el) {
|
|
77
|
+
if (!el || el.nodeType !== 1) return false;
|
|
78
|
+
return Boolean(el.closest?.("input, textarea, select, [contenteditable=''], [contenteditable='true']"));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The words of a passage, with the marks the notes make on them. */
|
|
82
|
+
function Words({ passage, notes, kinds, selectedId, rootId, as: Tag = "p" }) {
|
|
83
|
+
const looks = useMemo(() => new Map(notes.map((n) => [n.id, noteLook(n, kinds)])), [notes, kinds]);
|
|
84
|
+
const { segments } = useMemo(() => inlineMarks(passage.text, notes, (n) => looks.get(n.id)?.mark ?? "none"), [passage.text, notes, looks]);
|
|
85
|
+
if (passage.node != null) {
|
|
86
|
+
const struck = notes.find((n) => !isSettled(n) && looks.get(n.id)?.mark === "strike");
|
|
87
|
+
const look = struck ? looks.get(struck.id) : null;
|
|
88
|
+
return <div className={cx("min-w-0 break-words", look && cx("text-muted-foreground line-through decoration-2", deco(look)))}>{passage.node}</div>;
|
|
89
|
+
}
|
|
90
|
+
return (
|
|
91
|
+
<Tag className={cx("m-0 min-w-0 break-words", Tag !== "p" && "text-[1.05em] font-semibold leading-snug")}>
|
|
92
|
+
{segments.map((seg, i) => {
|
|
93
|
+
if (!seg.ids.length) return <span key={i}>{seg.text}</span>;
|
|
94
|
+
const id = seg.ids.includes(selectedId) ? selectedId : seg.ids[0];
|
|
95
|
+
const look = looks.get(id);
|
|
96
|
+
const on = seg.ids.includes(selectedId);
|
|
97
|
+
const common = { "data-note": id, "data-selected": on ? "" : undefined, "aria-describedby": domId(rootId, "note", id) };
|
|
98
|
+
if (look.mark === "strike")
|
|
99
|
+
return (
|
|
100
|
+
<del key={i} {...common} className={cx("rounded-sm text-muted-foreground line-through decoration-2", deco(look), on && wash(look))}>
|
|
101
|
+
<span className="sr-only">{look.label}: </span>
|
|
102
|
+
{seg.text}
|
|
103
|
+
</del>
|
|
104
|
+
);
|
|
105
|
+
return (
|
|
106
|
+
<mark
|
|
107
|
+
key={i}
|
|
108
|
+
{...common}
|
|
109
|
+
className={cx(
|
|
110
|
+
"rounded-sm text-inherit",
|
|
111
|
+
look.mark === "highlight" || on ? wash(look) : "bg-transparent",
|
|
112
|
+
look.mark === "underline" && "underline decoration-dotted decoration-2 underline-offset-4",
|
|
113
|
+
look.mark === "wavy" && "underline decoration-wavy decoration-1 underline-offset-4",
|
|
114
|
+
look.mark !== "highlight" && deco(look),
|
|
115
|
+
)}
|
|
116
|
+
>
|
|
117
|
+
<span className="sr-only">{look.label}: </span>
|
|
118
|
+
{seg.text}
|
|
119
|
+
</mark>
|
|
120
|
+
);
|
|
121
|
+
})}
|
|
122
|
+
</Tag>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** One note. */
|
|
127
|
+
function Note({ note, kinds, selected, busy, error, readOnly, canAct, onSelect, onPress, rootId, cardRef, quoteShown }) {
|
|
128
|
+
const look = noteLook(note, kinds);
|
|
129
|
+
const settled = isSettled(note);
|
|
130
|
+
const actions = canAct ? actionsOf(note, kinds, { readOnly }) : [];
|
|
131
|
+
const agent = note.by === "agent" || note.by?.agent === true;
|
|
132
|
+
const byName = typeof note.by === "string" ? (note.by === "agent" ? "" : note.by) : (note.by?.name ?? "");
|
|
133
|
+
const showReopen = settled && canAct && !readOnly && look.reopen;
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<article
|
|
137
|
+
ref={cardRef}
|
|
138
|
+
id={domId(rootId, "note", note.id)}
|
|
139
|
+
tabIndex={-1}
|
|
140
|
+
data-note={note.id}
|
|
141
|
+
data-kind={note.kind}
|
|
142
|
+
data-selected={selected ? "" : undefined}
|
|
143
|
+
aria-current={selected ? "true" : undefined}
|
|
144
|
+
aria-label={note.title && note.title !== look.label ? `${look.label}: ${note.title}` : look.label}
|
|
145
|
+
onClick={() => onSelect(note.id)}
|
|
146
|
+
className={cx(
|
|
147
|
+
"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",
|
|
148
|
+
selected && "ring-2 ring-ring",
|
|
149
|
+
settled ? "bg-muted" : "bg-card",
|
|
150
|
+
)}
|
|
151
|
+
>
|
|
152
|
+
<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")} />
|
|
153
|
+
{settled ? (
|
|
154
|
+
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
|
|
155
|
+
<Icon name="check" size={14} className="shrink-0 text-success" />
|
|
156
|
+
<span className="text-[13px] font-medium">{note.answer || "Settled"}</span>
|
|
157
|
+
{note.at ? <span className="text-xs text-muted-foreground">{note.at}</span> : null}
|
|
158
|
+
{showReopen ? (
|
|
159
|
+
<Button size="sm" variant="link" tone="muted" loading={busy === "reopen"} aria-label={`${look.reopen.label ?? "Reopen"}: ${look.label}`} onClick={() => onPress(note, "reopen")}>
|
|
160
|
+
{look.reopen.label ?? "Reopen"}
|
|
161
|
+
</Button>
|
|
162
|
+
) : null}
|
|
163
|
+
</div>
|
|
164
|
+
) : (
|
|
165
|
+
<>
|
|
166
|
+
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
|
|
167
|
+
<Badge variant="soft" tone={look.hue ? undefined : look.tone} hue={look.hue ?? undefined} className="gap-1">
|
|
168
|
+
<Icon name={look.icon} size={12} aria-hidden="true" />
|
|
169
|
+
{look.label}
|
|
170
|
+
</Badge>
|
|
171
|
+
{agent ? <AgentMark label="Raised by the agent" /> : null}
|
|
172
|
+
{byName ? <span className="text-xs text-muted-foreground">{byName}</span> : null}
|
|
173
|
+
{note.at ? <span className="text-xs tabular-nums text-muted-foreground">{note.at}</span> : null}
|
|
174
|
+
</div>
|
|
175
|
+
{note.title ? <p className="m-0 font-medium">{note.title}</p> : null}
|
|
176
|
+
{note.meta ? <p className="m-0 text-xs text-muted-foreground">{note.meta}</p> : null}
|
|
177
|
+
{quoteShown && note.quote ? <p className="m-0 break-words text-[13px] italic text-muted-foreground">{`“${note.quote}”`}</p> : null}
|
|
178
|
+
{note.text ? <p className="m-0 break-words">{note.text}</p> : null}
|
|
179
|
+
{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}
|
|
180
|
+
{note.href ? (
|
|
181
|
+
<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">
|
|
182
|
+
{note.hrefLabel || "Open the source"}
|
|
183
|
+
<Icon name="external" size={12} aria-hidden="true" />
|
|
184
|
+
</a>
|
|
185
|
+
) : null}
|
|
186
|
+
{note.locked ? (
|
|
187
|
+
<p className="m-0 flex items-start gap-1.5 text-xs text-muted-foreground">
|
|
188
|
+
<Icon name="lock" size={12} className="mt-0.5 shrink-0" />
|
|
189
|
+
<span className="min-w-0">{note.locked}</span>
|
|
190
|
+
</p>
|
|
191
|
+
) : null}
|
|
192
|
+
{actions.length ? (
|
|
193
|
+
<div className="flex flex-wrap items-center gap-1.5 pt-0.5">
|
|
194
|
+
{actions.map((a) => (
|
|
195
|
+
<Button
|
|
196
|
+
key={a.id}
|
|
197
|
+
size="sm"
|
|
198
|
+
variant={a.variant || "secondary"}
|
|
199
|
+
icon={a.icon}
|
|
200
|
+
loading={busy === a.id}
|
|
201
|
+
disabled={Boolean(busy) && busy !== a.id}
|
|
202
|
+
aria-label={`${a.label}: ${look.label}${note.title ? `, ${note.title}` : ""}`}
|
|
203
|
+
onClick={() => onPress(note, a.id)}
|
|
204
|
+
>
|
|
205
|
+
{a.label}
|
|
206
|
+
</Button>
|
|
207
|
+
))}
|
|
208
|
+
</div>
|
|
209
|
+
) : null}
|
|
210
|
+
</>
|
|
211
|
+
)}
|
|
212
|
+
{error ? (
|
|
213
|
+
<p role="alert" className="m-0 text-xs text-destructive">
|
|
214
|
+
{error}
|
|
215
|
+
</p>
|
|
216
|
+
) : null}
|
|
217
|
+
</article>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Rows of grey bars where the passages and their notes will be. */
|
|
222
|
+
function Loading({ cols }) {
|
|
223
|
+
return (
|
|
224
|
+
<div role="status" aria-label="Loading" aria-busy="true" className={cx("grid grid-cols-[minmax(0,1fr)] gap-x-6 gap-y-4", cols)}>
|
|
225
|
+
{[0, 1, 2].map((i) => (
|
|
226
|
+
<div key={i} className="contents">
|
|
227
|
+
<div className="grid min-w-0 gap-2 py-2">
|
|
228
|
+
<Skeleton className="w-full" />
|
|
229
|
+
<Skeleton className="w-11/12" />
|
|
230
|
+
<Skeleton className="w-2/3" />
|
|
231
|
+
</div>
|
|
232
|
+
<div className="grid min-w-0 content-start py-2">
|
|
233
|
+
<Skeleton shape="rect" className="h-16 w-full" />
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
))}
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* passages [{ id, text | node, lead?, label?, level? }]: the words. `lead` is a
|
|
243
|
+
* short figure before them (a time, a number); `label` a name above
|
|
244
|
+
* (a speaker, a section); `level` 2 · 3 · 4 draws the text as a heading.
|
|
245
|
+
* notes [{ id, passage, kind, title?, meta?, text?, detail?, quote?, by?, at?,
|
|
246
|
+
* href?, hrefLabel?, locked?, state?, answer?, actions?, tone?, hue?,
|
|
247
|
+
* mark?, label? }]
|
|
248
|
+
* selected / defaultSelected / onSelect(id | null) the selected note
|
|
249
|
+
* onAction(note, actionId) async → { ok } | { ok: false, error }
|
|
250
|
+
* kinds changes to the built-in kinds, or new ones
|
|
251
|
+
* side right (default) · left: which side of the words the margin is on
|
|
252
|
+
* marginWidth sm · md (default) · lg
|
|
253
|
+
* marginTitle the margin's heading
|
|
254
|
+
* showNotes false hides the margin (default true)
|
|
255
|
+
* textSize sm · md (default) · lg
|
|
256
|
+
* status ready (default) · loading · failed, with `error` and onRetry
|
|
257
|
+
* readOnly notes to read: no buttons
|
|
258
|
+
* keys J and K walk the notes (default true)
|
|
259
|
+
*/
|
|
260
|
+
export function MarginNotes({
|
|
261
|
+
passages = [],
|
|
262
|
+
notes = [],
|
|
263
|
+
selected,
|
|
264
|
+
defaultSelected = null,
|
|
265
|
+
onSelect,
|
|
266
|
+
onAction,
|
|
267
|
+
kinds: kindChanges,
|
|
268
|
+
side = "right",
|
|
269
|
+
marginWidth = "md",
|
|
270
|
+
marginTitle,
|
|
271
|
+
showNotes = true,
|
|
272
|
+
textSize = "md",
|
|
273
|
+
status = "ready",
|
|
274
|
+
error,
|
|
275
|
+
onRetry,
|
|
276
|
+
readOnly = false,
|
|
277
|
+
keys = true,
|
|
278
|
+
noNotes = "Nothing in the margin.",
|
|
279
|
+
orphanTitle = "Not tied to a passage",
|
|
280
|
+
emptyTitle = "Nothing to read yet",
|
|
281
|
+
emptyBody,
|
|
282
|
+
countLabels,
|
|
283
|
+
label = "Passages with notes in the margin",
|
|
284
|
+
className,
|
|
285
|
+
}) {
|
|
286
|
+
const rootId = useRef(`mn${Math.random().toString(36).slice(2, 8)}`).current;
|
|
287
|
+
const kinds = useMemo(() => resolveKinds(kindChanges), [kindChanges]);
|
|
288
|
+
const { byPassage, ordered, orphans } = useMemo(() => arrangeNotes(passages, notes), [passages, notes]);
|
|
289
|
+
const counts = useMemo(() => countNotes(notes), [notes]);
|
|
290
|
+
|
|
291
|
+
const [inner, setInner] = useState(defaultSelected);
|
|
292
|
+
const raw = selected === undefined ? inner : selected;
|
|
293
|
+
const current = raw != null && ordered.some((n) => n.id === raw) ? raw : null;
|
|
294
|
+
const position = current == null ? 0 : ordered.findIndex((n) => n.id === current) + 1;
|
|
295
|
+
|
|
296
|
+
const [busy, setBusy] = useState({});
|
|
297
|
+
const [errors, setErrors] = useState({});
|
|
298
|
+
const cards = useRef(new Map());
|
|
299
|
+
const rows = useRef(new Map());
|
|
300
|
+
const frame = useRef(0);
|
|
301
|
+
useEffect(() => () => cancelAnimationFrame(frame.current), []);
|
|
302
|
+
|
|
303
|
+
const choose = (id, bring = false) => {
|
|
304
|
+
if (selected === undefined) setInner(id);
|
|
305
|
+
onSelect?.(id);
|
|
306
|
+
if (bring && id != null) {
|
|
307
|
+
cancelAnimationFrame(frame.current);
|
|
308
|
+
frame.current = requestAnimationFrame(() => {
|
|
309
|
+
cards.current.get(id)?.focus({ preventScroll: true });
|
|
310
|
+
(rows.current.get(ordered.find((n) => n.id === id)?.passage) ?? cards.current.get(id))?.scrollIntoView({ block: "nearest", behavior: reducedMotion() ? "auto" : "smooth" });
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
async function press(note, actionId) {
|
|
316
|
+
if (!onAction) return;
|
|
317
|
+
const key = note.id;
|
|
318
|
+
if (busy[key]) return;
|
|
319
|
+
setBusy((b) => ({ ...b, [key]: actionId }));
|
|
320
|
+
setErrors((e) => ({ ...e, [key]: null }));
|
|
321
|
+
let r;
|
|
322
|
+
try {
|
|
323
|
+
r = await onAction(note, actionId);
|
|
324
|
+
} catch (e) {
|
|
325
|
+
r = { ok: false, error: e?.message ?? String(e) };
|
|
326
|
+
}
|
|
327
|
+
setBusy((b) => ({ ...b, [key]: null }));
|
|
328
|
+
if (r && r.ok === false) setErrors((e) => ({ ...e, [key]: r.error || "That did not work. Try again." }));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function onKeyDown(e) {
|
|
332
|
+
if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.altKey || isTyping(e.target)) return;
|
|
333
|
+
if (e.key === "Escape" && current != null) {
|
|
334
|
+
e.preventDefault();
|
|
335
|
+
choose(null);
|
|
336
|
+
} else if (keys && showNotes && (e.key === "j" || e.key === "J" || e.key === "k" || e.key === "K")) {
|
|
337
|
+
const next = walkNext(ordered, current, e.key.toLowerCase() === "j" ? 1 : -1);
|
|
338
|
+
if (next == null) return;
|
|
339
|
+
e.preventDefault();
|
|
340
|
+
choose(next, true);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const cols = showNotes ? (GRID[side] ?? GRID.right)[marginWidth] ?? GRID.right.md : "";
|
|
345
|
+
const marginFirst = side === "left";
|
|
346
|
+
const typeSize = TEXT[textSize] ?? TEXT.md;
|
|
347
|
+
const summary = countWords(counts, countLabels);
|
|
348
|
+
|
|
349
|
+
const noteList = (list) =>
|
|
350
|
+
list.map((n) => (
|
|
351
|
+
<Note
|
|
352
|
+
key={n.id}
|
|
353
|
+
note={n}
|
|
354
|
+
kinds={kinds}
|
|
355
|
+
selected={n.id === current}
|
|
356
|
+
busy={busy[n.id]}
|
|
357
|
+
error={errors[n.id]}
|
|
358
|
+
readOnly={readOnly}
|
|
359
|
+
canAct={Boolean(onAction)}
|
|
360
|
+
onSelect={(id) => choose(id)}
|
|
361
|
+
onPress={press}
|
|
362
|
+
rootId={rootId}
|
|
363
|
+
cardRef={(el) => (el ? cards.current.set(n.id, el) : cards.current.delete(n.id))}
|
|
364
|
+
quoteShown={passages.find((p) => p.id === n.passage)?.node != null}
|
|
365
|
+
/>
|
|
366
|
+
));
|
|
367
|
+
|
|
368
|
+
let body;
|
|
369
|
+
if (status === "loading") body = <Loading cols={cols} />;
|
|
370
|
+
else if (status === "failed")
|
|
371
|
+
body = (
|
|
372
|
+
<Notice tone="danger" title="The notes did not load" action={onRetry ? <Button size="sm" icon="refresh" onClick={onRetry}>Try again</Button> : null}>
|
|
373
|
+
{error || "Something went wrong. Try again."}
|
|
374
|
+
</Notice>
|
|
375
|
+
);
|
|
376
|
+
else if (!passages.length) body = <Empty icon="message-square" title={emptyTitle} body={emptyBody} />;
|
|
377
|
+
else
|
|
378
|
+
body = (
|
|
379
|
+
<>
|
|
380
|
+
<ol className="m-0 grid list-none gap-y-1 p-0">
|
|
381
|
+
{passages.map((p) => {
|
|
382
|
+
const mine = byPassage.get(p.id) ?? [];
|
|
383
|
+
const selectedHere = mine.some((n) => n.id === current);
|
|
384
|
+
const Heading = p.level >= 2 && p.level <= 4 ? `h${p.level}` : null;
|
|
385
|
+
const onPassage = mine.length ? () => choose(cycleNote(mine, current)) : undefined;
|
|
386
|
+
return (
|
|
387
|
+
<li
|
|
388
|
+
key={p.id}
|
|
389
|
+
ref={(el) => (el ? rows.current.set(p.id, el) : rows.current.delete(p.id))}
|
|
390
|
+
data-passage={p.id}
|
|
391
|
+
data-selected={selectedHere ? "" : undefined}
|
|
392
|
+
className={cx("grid grid-cols-[minmax(0,1fr)] gap-x-6 gap-y-2 py-1.5", cols)}
|
|
393
|
+
>
|
|
394
|
+
<div
|
|
395
|
+
{...(mine.length
|
|
396
|
+
? {
|
|
397
|
+
tabIndex: 0,
|
|
398
|
+
onClick: onPassage,
|
|
399
|
+
onKeyDown: (e) => {
|
|
400
|
+
if ((e.key === "Enter" || e.key === " ") && e.target === e.currentTarget) {
|
|
401
|
+
e.preventDefault();
|
|
402
|
+
onPassage();
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
"aria-label": `${p.label ? `${p.label}. ` : ""}${mine.length} ${mine.length === 1 ? "note" : "notes"}. Press Enter to select.`,
|
|
406
|
+
role: "group",
|
|
407
|
+
}
|
|
408
|
+
: {})}
|
|
409
|
+
className={cx(
|
|
410
|
+
"flex min-w-0 gap-3 rounded-md px-2 py-1.5 outline-none",
|
|
411
|
+
typeSize,
|
|
412
|
+
marginFirst && showNotes && "@2xl:order-last",
|
|
413
|
+
mine.length && "cursor-pointer focus-visible:ring-2 focus-visible:ring-ring",
|
|
414
|
+
selectedHere ? "bg-accent" : mine.length ? "hover:bg-accent/50" : "",
|
|
415
|
+
)}
|
|
416
|
+
>
|
|
417
|
+
{p.lead ? <span className="w-12 shrink-0 pt-0.5 text-xs tabular-nums text-muted-foreground">{p.lead}</span> : null}
|
|
418
|
+
<div className="grid min-w-0 grow gap-0.5">
|
|
419
|
+
{p.label ? <span className="text-[13px] font-semibold leading-snug">{p.label}</span> : null}
|
|
420
|
+
<Words passage={p} notes={mine} kinds={kinds} selectedId={current} rootId={rootId} as={Heading ?? "p"} />
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
{showNotes ? <div className={cx("grid min-w-0 content-start gap-2", marginFirst && "@2xl:order-first")}>{noteList(mine)}</div> : null}
|
|
424
|
+
</li>
|
|
425
|
+
);
|
|
426
|
+
})}
|
|
427
|
+
</ol>
|
|
428
|
+
{showNotes && orphans.length ? (
|
|
429
|
+
<section aria-label={orphanTitle} className="mt-2 grid gap-2 border-t border-border pt-3">
|
|
430
|
+
<h3 className="m-0 text-xs font-medium uppercase tracking-wide text-muted-foreground">{orphanTitle}</h3>
|
|
431
|
+
<div className="grid grid-cols-[minmax(0,1fr)] gap-2 @2xl:grid-cols-2">{noteList(orphans)}</div>
|
|
432
|
+
</section>
|
|
433
|
+
) : null}
|
|
434
|
+
</>
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
return (
|
|
438
|
+
<section
|
|
439
|
+
aria-label={label}
|
|
440
|
+
tabIndex={0}
|
|
441
|
+
onKeyDown={onKeyDown}
|
|
442
|
+
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)}
|
|
443
|
+
>
|
|
444
|
+
{showNotes && status === "ready" && passages.length > 0 && Boolean(marginTitle || summary || (keys && ordered.length > 0) || !notes.length) ? (
|
|
445
|
+
<header className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
|
|
446
|
+
{marginTitle ? <h2 className="m-0 text-base font-semibold leading-tight">{marginTitle}</h2> : null}
|
|
447
|
+
<p className="m-0 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
|
|
448
|
+
{summary ? <span>{summary}</span> : !notes.length ? <span>{noNotes}</span> : null}
|
|
449
|
+
{keys && ordered.length ? (
|
|
450
|
+
<span className="hidden items-center gap-1 @2xl:inline-flex">
|
|
451
|
+
<Kbd>J</Kbd>
|
|
452
|
+
<Kbd>K</Kbd>
|
|
453
|
+
<span>next and previous note</span>
|
|
454
|
+
</span>
|
|
455
|
+
) : null}
|
|
456
|
+
</p>
|
|
457
|
+
</header>
|
|
458
|
+
) : null}
|
|
459
|
+
<p role="status" aria-live="polite" className="sr-only">
|
|
460
|
+
{current != null ? `Note ${position} of ${ordered.length}` : ""}
|
|
461
|
+
</p>
|
|
462
|
+
{body}
|
|
463
|
+
</section>
|
|
464
|
+
);
|
|
465
|
+
}
|
|
@@ -96,7 +96,9 @@ string, or nothing (a fresh map). The component runs it on `value`.
|
|
|
96
96
|
- `placeholder` — what an empty topic shows while it is typed (default
|
|
97
97
|
"New topic").
|
|
98
98
|
- `height` — px or any CSS length, default `600`. A height class in
|
|
99
|
-
`className` (`h-
|
|
99
|
+
`className` (`h-full`, `min-h-0 flex-1`) replaces it: `min-h-0 flex-1`
|
|
100
|
+
fills what the surface has left (the Example). Never `h-dvh` or a `vh`
|
|
101
|
+
height, which `npm run check` refuses.
|
|
100
102
|
- `label` — the map's accessible name (default "Mind map").
|
|
101
103
|
- `onSelectionChange(topic | null)` — the selected topic changed (a side
|
|
102
104
|
panel with its notes).
|
|
@@ -186,7 +188,7 @@ function PlanMap({ plan }) {
|
|
|
186
188
|
onChange={setMap}
|
|
187
189
|
onSave={(m) => updateRow({ source: "plans", id: plan.id, patch: { map: m, map_text: mindMapToText(m) } })}
|
|
188
190
|
label={`${plan.name} map`}
|
|
189
|
-
className="h-
|
|
191
|
+
className="min-h-0 flex-1"
|
|
190
192
|
/>
|
|
191
193
|
);
|
|
192
194
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# NeedsYou
|
|
2
|
+
|
|
3
|
+
What waits on the member, drawn inside the app. Four pieces:
|
|
4
|
+
|
|
5
|
+
- **NeedsYou** — the queue. The agent's questions and its yeses on this
|
|
6
|
+
app. A question is **answered in place** (`QuestionBox`); a yes is the
|
|
7
|
+
`approval` block's press frame (`renderYes`); anything else the app
|
|
8
|
+
cannot settle (connecting an account, spending money, several questions
|
|
9
|
+
at once) opens the desk with one press.
|
|
10
|
+
- **QuestionBox** — the agent's question, answered where it was asked: its
|
|
11
|
+
options, one press each, and a box for the member's own words where the
|
|
12
|
+
question welcomes them.
|
|
13
|
+
- **WaitingYes** — the frozen-text card. What goes out, frozen while the
|
|
14
|
+
ask waits, then "Waiting for your yes on the desk", **Open the desk**
|
|
15
|
+
and **Take the ask back**. It is also what the `approval` block shows
|
|
16
|
+
when its press frame cannot load.
|
|
17
|
+
- **AgentWorking** — the strip while the agent works: what it is on now,
|
|
18
|
+
**Stop**, and "Stop keeps everything written so far".
|
|
19
|
+
|
|
20
|
+
An answer is words, so the app takes it. The yes is not: the app's own
|
|
21
|
+
code never draws an Approve. The press is the Makersclaw-held frame of the
|
|
22
|
+
`approval` block, which app code cannot script. Connecting an account and
|
|
23
|
+
money stay on the desk.
|
|
24
|
+
|
|
25
|
+
```jsx
|
|
26
|
+
import { NeedsYou, QuestionBox, WaitingYes, AgentWorking } from "../components/blocks/needs-you"; // a Makersclaw app ("./blocks/needs-you" from components/)
|
|
27
|
+
import { NeedsYou, QuestionBox, WaitingYes, AgentWorking } from "@/components/makersclaw/blocks/needs-you"; // a shadcn project
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Client components. The Example is at the end.
|
|
31
|
+
|
|
32
|
+
## Which hook feeds it
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
const needs = useNeedsYou(); // lib/hooks.js
|
|
36
|
+
// → { items, working, open(item), answer(item, …), takeBack(item), stop(request), loading, error, unavailable, reload }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
It reads `GET _claw/requests`: only the member's own asks (`mine`).
|
|
40
|
+
`items` are the asks that wait on them; `working` are their asks that
|
|
41
|
+
run now. Each item says how the app can settle it: `via` is `"answer"` (a
|
|
42
|
+
question: `answer(item, { optionId | text })`, which is
|
|
43
|
+
`POST _claw/requests/:id/answer`), `"press"` (the one ask's yes: the
|
|
44
|
+
`approval` block, `useApproval(item.id)`) or `null` (the desk: `open`).
|
|
45
|
+
`takeBack` and `stop` are `POST _claw/requests/:id/stop`.
|
|
46
|
+
|
|
47
|
+
## Props
|
|
48
|
+
|
|
49
|
+
### NeedsYou
|
|
50
|
+
|
|
51
|
+
- `items` — `needs.items`: `[{ id, request, kind, text, title, at, via,
|
|
52
|
+
options, free, needsAt }]`. `kind` is `question`, `yes` or `spend`
|
|
53
|
+
("Waiting for your yes on the desk · Open the desk" when the app cannot
|
|
54
|
+
take it), `connect` ("Connect on the desk"); any other word reads "Needs
|
|
55
|
+
you on the desk". `text` is the agent's words, `title` the ask's, `at`
|
|
56
|
+
when (words, a `Date` or ISO).
|
|
57
|
+
- `onOpen(item)` — `needs.open`. The press of everything the app cannot
|
|
58
|
+
settle itself. Without it those rows have no press.
|
|
59
|
+
- `onAnswer(item, { optionId | text })` — `needs.answer`. An item with
|
|
60
|
+
`via: "answer"` then shows its `QuestionBox`; without it that item opens
|
|
61
|
+
the desk, as before.
|
|
62
|
+
- `renderYes(item)` — draws the yes of an item with `via: "press"`: a
|
|
63
|
+
component of yours that calls `useApproval(item.id)` and returns the
|
|
64
|
+
`approval` block. Without it that item opens the desk.
|
|
65
|
+
- `empty` — the line when nothing waits. Default "Nothing waits on you
|
|
66
|
+
here." `false` draws nothing at all when the list is empty.
|
|
67
|
+
- `title` — the head. Default "Needs you"; `null` for none.
|
|
68
|
+
- `loading` — `needs.loading`: skeleton lines before the first read.
|
|
69
|
+
- `unavailable` — `needs.unavailable`: a quiet "not connected yet".
|
|
70
|
+
- `className` — layout around it. Never colour.
|
|
71
|
+
|
|
72
|
+
### QuestionBox
|
|
73
|
+
|
|
74
|
+
- `options` — `[{ id, label }]` the question offers (none: words only).
|
|
75
|
+
A lone press answers on the spot, as the desk's form does.
|
|
76
|
+
- `free` — `false` when the question takes one of its options only
|
|
77
|
+
(default: words are welcome).
|
|
78
|
+
- `onAnswer({ optionId } | { text })` — `needs.answer(item, …)` or
|
|
79
|
+
`chat.answer(…)`; async, answers `{ ok }` or `{ ok: false, error }`. A
|
|
80
|
+
refusal is shown as the door words it.
|
|
81
|
+
- `placeholder`, `sendLabel`, `className`.
|
|
82
|
+
|
|
83
|
+
### WaitingYes
|
|
84
|
+
|
|
85
|
+
- `title` — what it is ("How to cover a sudden sick day").
|
|
86
|
+
- `frozen` — what goes out, exactly as it will go (text or a node). It is
|
|
87
|
+
drawn frozen (muted, with a lock) while the ask waits.
|
|
88
|
+
- `kicker` — the line over it. Default "Ready to send".
|
|
89
|
+
- `state` — `ready` (nothing asked yet: the text only, no state line) ·
|
|
90
|
+
`waiting` (default) · `approved` ("You said yes · the agent is on it")
|
|
91
|
+
· `declined` · `taken` · `failed` (with `error`).
|
|
92
|
+
- `body` — one sentence under "Waiting for your yes on the desk", in the
|
|
93
|
+
app's words: say what the agent does after the yes.
|
|
94
|
+
- `onOpen()` — opens the desk on this ask (`() => needs.open(item)`).
|
|
95
|
+
- `onTakeBack()` — async, answers `{ ok: true }` or `{ ok: false, error
|
|
96
|
+
}` (`() => needs.takeBack(item)`). It stops the ask while the agent
|
|
97
|
+
has not acted; the card then says "Taken back. Nothing went out."
|
|
98
|
+
Without it there is no Take back.
|
|
99
|
+
- `takeBackLabel` — default "Take the ask back".
|
|
100
|
+
|
|
101
|
+
### AgentWorking
|
|
102
|
+
|
|
103
|
+
- `words` — what the agent is doing, in the app's words ("Writing the
|
|
104
|
+
draft"). Default "Working on it".
|
|
105
|
+
- `steps` — `[{ label, status }]` (`running` · `done` · `failed`): the
|
|
106
|
+
running step shows, with "2 of 6 steps done".
|
|
107
|
+
- `onStop()` — `() => needs.stop(request)` or `chat.stop`. It may be
|
|
108
|
+
async and answer `{ ok }` or `{ ok: false, error }`: the button spins
|
|
109
|
+
while it is out and a failure says why. Without it there is no Stop.
|
|
110
|
+
- `stopping` — true while a stop you track yourself is out.
|
|
111
|
+
- `stopHint` — default "Stop keeps everything written so far"; `null`
|
|
112
|
+
for none.
|
|
113
|
+
|
|
114
|
+
## Who may do what
|
|
115
|
+
|
|
116
|
+
- Every member sees only their own asks: the door lists `mine`.
|
|
117
|
+
- Stop and Take back: the asker, an owner, an admin, or the app's owner
|
|
118
|
+
(`mayStop` on the request). Give `onStop`/`onTakeBack` only when the
|
|
119
|
+
request says the member may; the door refuses anyone else anyway.
|
|
120
|
+
- Only the member who asked answers a question or gives a yes; the door
|
|
121
|
+
refuses everyone else (owners and admins too), and a question answered
|
|
122
|
+
after it moved on (`needsAt`) is refused.
|
|
123
|
+
- The yes: the `approval` block's press frame, never these blocks.
|
|
124
|
+
- An account sign-in and money: the desk, always.
|
|
125
|
+
|
|
126
|
+
## Never
|
|
127
|
+
|
|
128
|
+
- **Never an Approve, a Yes, or a Send that gives the yes.** The yes is
|
|
129
|
+
the `approval` block's frame; this block's only press toward it is Open
|
|
130
|
+
the desk, when the frame cannot load.
|
|
131
|
+
- Never answer the one ask (a yes) through `QuestionBox`: the door refuses
|
|
132
|
+
it (`needs_a_press`).
|
|
133
|
+
- Never let the frozen text be edited while the ask waits. To change it,
|
|
134
|
+
take the ask back first.
|
|
135
|
+
- Never show another member's asks here.
|
|
136
|
+
|
|
137
|
+
## Example
|
|
138
|
+
|
|
139
|
+
A post that waits for its yes, the strip while the agent works, and the
|
|
140
|
+
queue on the home screen.
|
|
141
|
+
|
|
142
|
+
```jsx
|
|
143
|
+
"use client";
|
|
144
|
+
import { useNeedsYou } from "../lib/hooks";
|
|
145
|
+
import { NeedsYou, WaitingYes, AgentWorking } from "../components/blocks/needs-you";
|
|
146
|
+
|
|
147
|
+
export function PostStatus({ post }) {
|
|
148
|
+
const needs = useNeedsYou();
|
|
149
|
+
const item = needs.items.find((i) => i.request?.id === post.request_id);
|
|
150
|
+
const running = needs.working.find((r) => r.id === post.request_id);
|
|
151
|
+
if (running) return <AgentWorking words="Putting the post on Webflow" onStop={() => needs.stop(running)} />;
|
|
152
|
+
return (
|
|
153
|
+
<WaitingYes
|
|
154
|
+
title={post.title}
|
|
155
|
+
frozen={post.body}
|
|
156
|
+
state={item ? "waiting" : "ready"}
|
|
157
|
+
body="Say yes there, and the agent makes this one post live on Webflow, then adds its link here."
|
|
158
|
+
onOpen={item ? () => needs.open(item) : undefined}
|
|
159
|
+
onTakeBack={item ? () => needs.takeBack(item) : undefined}
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function Waiting() {
|
|
165
|
+
const needs = useNeedsYou();
|
|
166
|
+
return (
|
|
167
|
+
<NeedsYou
|
|
168
|
+
items={needs.items}
|
|
169
|
+
onOpen={needs.open}
|
|
170
|
+
onAnswer={needs.answer}
|
|
171
|
+
renderYes={(item) => <SendCard item={item} />} // an Approval card, see the approval block
|
|
172
|
+
loading={needs.loading}
|
|
173
|
+
unavailable={needs.unavailable}
|
|
174
|
+
/>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
```
|