makersclaw 0.8.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 +9 -3
- 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 +3 -3
- 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 +2 -2
- package/r/chat-pane.json +84 -0
- package/r/chat.json +2 -2
- 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 +13 -7
- 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 +2 -2
- package/r/flow-editor.json +79 -0
- package/r/footer.json +2 -2
- 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 +3 -3
- 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 +3 -3
- package/r/pace-bar.json +69 -0
- package/r/page-builder.json +2 -2
- package/r/payment-methods.json +2 -2
- package/r/pdf-document.json +2 -2
- package/r/pdf-viewer.json +2 -2
- 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 +4086 -1512
- 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 +2 -2
- package/r/signature-pad.json +2 -2
- package/r/sketch-canvas.json +2 -2
- 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 +3 -3
- 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/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/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-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/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/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/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/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/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/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/install.mjs +77 -16
- package/src/registry.mjs +3 -0
- package/templates/next/browser/lib/data.js +6 -1
- package/templates/next/files/README.md +627 -24
- 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 +968 -9
- 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 +2 -1
- package/templates/next/files/components/ui/layout.jsx +77 -2
- 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 +241 -5
- package/templates/next/files/lib/door.js +92 -0
- package/templates/next/files/lib/hooks.js +1627 -8
- package/templates/next/files/lib/platform.js +529 -21
- package/templates/next/template.json +13 -3
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# ActivityLog
|
|
2
|
+
|
|
3
|
+
Who did what, to what, when. Newest first, one row per event on a
|
|
4
|
+
vertical timeline: the author's face, "**Anna Lee** edited **deals**",
|
|
5
|
+
the line's detail under it in muted words, a kind dot and the time.
|
|
6
|
+
|
|
7
|
+
The agent is an author like a person, with its own mark (the diamond from
|
|
8
|
+
`agent-marks`) where a face would be:
|
|
9
|
+
|
|
10
|
+
- "Agent, for Anna Lee" — the agent wrote it on Anna's ask;
|
|
11
|
+
- "Agent, on a run" — a scheduled job wrote it.
|
|
12
|
+
|
|
13
|
+
With a record, it is that record's story. Grown in the CRM (0.7.0) as
|
|
14
|
+
`activity-feed`; the premium registry owns that name, so here it is
|
|
15
|
+
`activity-log`, and `ActivityFeed` stays exported as an alias.
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
import { ActivityLog } from "../components/blocks/activity-log"; // a Makersclaw app ("./blocks/activity-log" from components/)
|
|
19
|
+
import { ActivityLog } from "@/components/makersclaw/blocks/activity-log"; // a shadcn project
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A client component. It requires `agent-marks` (laid with it). The
|
|
23
|
+
Example is at the end.
|
|
24
|
+
|
|
25
|
+
## Which hook feeds it
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
const { rows, loading, unavailable } = useActivity({ row: deal.id }); // lib/hooks.js
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`useActivity({ row, surface, since, limit, agent })` reads `GET
|
|
32
|
+
_claw/data/activity`: the lines the data door wrote for every change it
|
|
33
|
+
took, `{ id, at, action, surface, row_ref, detail, data, by }`. `row`
|
|
34
|
+
keeps one record's lines; `agent: true` keeps only the agent's. `by` is
|
|
35
|
+
`user:<email>`, `agent:<email>` (the agent, on that member's ask) or
|
|
36
|
+
`agent:job` (a scheduled run). `action` is the door's word: `added`,
|
|
37
|
+
`edited`, `moved`, `removed`, `confirmed`, `merged`, `linked`,
|
|
38
|
+
`unlinked`, `listed`, `unlisted`.
|
|
39
|
+
|
|
40
|
+
## Props
|
|
41
|
+
|
|
42
|
+
- `rows` — `useActivity()`'s rows. The block words them (below).
|
|
43
|
+
- `people` — `{ "anna@acme.com": "Anna Lee" }`: names for the emails in
|
|
44
|
+
`by`. Without a name, the email shows.
|
|
45
|
+
- `formatTime(at)` — words for a line's time. Default "just now", "5 min
|
|
46
|
+
ago", "3 h ago", "yesterday", "12 Sep".
|
|
47
|
+
- `describe(row)` — part of an item laid over what the block makes of a
|
|
48
|
+
row: an `href` to the record, an `object` name, a `tone`.
|
|
49
|
+
- `items` — instead of `rows`: items you shaped yourself, `[{ id,
|
|
50
|
+
actorName, actorAvatar?, agent?, verb, object, objectHref?, detail?,
|
|
51
|
+
tone, time, timestamp?, href?, onClick? }]`. `agent: true` draws the
|
|
52
|
+
agent's mark. `tone` is `success` · `info` · `warning` · `danger` ·
|
|
53
|
+
`neutral`.
|
|
54
|
+
- `title` — a head; draws the log inside a `Card`. `subtitle` — a muted
|
|
55
|
+
line under it.
|
|
56
|
+
- `subject` — a pill after the title naming whose story this is.
|
|
57
|
+
- `loading` — skeleton rows instead of items.
|
|
58
|
+
- `onItemClick(item)` — every row without its own `href`, `onClick` or
|
|
59
|
+
`objectHref` becomes a button calling it.
|
|
60
|
+
- `emptyTitle` — default "No activity yet". `emptyBody` — a muted line
|
|
61
|
+
under it.
|
|
62
|
+
- `label` — the list's name for a screen reader. Default the title, or
|
|
63
|
+
"Activity".
|
|
64
|
+
- `leading` — `avatar` (default) · `icon`: one subject's journey, each
|
|
65
|
+
row its kind's icon in a tinted circle, a title with the date, no
|
|
66
|
+
author column.
|
|
67
|
+
- `kinds` — `{ [kind]: { icon, tone } }` for `leading="icon"`; a row's
|
|
68
|
+
kind is its `action`.
|
|
69
|
+
- `className` — layout around it. Never colour.
|
|
70
|
+
|
|
71
|
+
`activityItems(rows, { people, formatTime, describe })`,
|
|
72
|
+
`activityAuthor(by, people)` and `activityTime(at)` are exported for a
|
|
73
|
+
screen that words lines itself.
|
|
74
|
+
|
|
75
|
+
## Who may do what
|
|
76
|
+
|
|
77
|
+
Any member who can open the app reads every line. That is why a line
|
|
78
|
+
never holds a field's value: only the action, the surface, the record's
|
|
79
|
+
id and one sentence of detail. The log has no controls of its own.
|
|
80
|
+
|
|
81
|
+
## Never
|
|
82
|
+
|
|
83
|
+
- Never put a field's value in a line's `detail` for this log to show.
|
|
84
|
+
- Never draw the agent with a person's avatar, or a person with the
|
|
85
|
+
agent's mark.
|
|
86
|
+
- Never use it for one ask's changes with an Undo: that is
|
|
87
|
+
`agent-changes`.
|
|
88
|
+
|
|
89
|
+
## Example
|
|
90
|
+
|
|
91
|
+
A record's story, beside the record.
|
|
92
|
+
|
|
93
|
+
```jsx
|
|
94
|
+
"use client";
|
|
95
|
+
import { useActivity } from "../lib/hooks";
|
|
96
|
+
import { ActivityLog } from "../components/blocks/activity-log";
|
|
97
|
+
|
|
98
|
+
export default function DealStory({ deal, people }) {
|
|
99
|
+
const { rows, loading, unavailable } = useActivity({ row: deal.id });
|
|
100
|
+
if (unavailable) return <p className="text-sm text-muted-foreground">The story is not connected yet.</p>;
|
|
101
|
+
return (
|
|
102
|
+
<ActivityLog
|
|
103
|
+
title="Story"
|
|
104
|
+
subject={deal.name}
|
|
105
|
+
rows={rows}
|
|
106
|
+
people={people}
|
|
107
|
+
loading={loading}
|
|
108
|
+
emptyBody="Changes to this deal show here, by people and by the agent."
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Avatar, Badge, Button, Card, Empty, Icon, Item, ItemGroup, Skeleton, StatusDot, cx } from "../../ui";
|
|
4
|
+
import { AgentMark } from "../agent-marks";
|
|
5
|
+
|
|
6
|
+
// ActivityLog — who did what, to what, when; newest first (the app kit,
|
|
7
|
+
// D15; grown as the CRM's activity-feed, 0.7.0). One row per event: the
|
|
8
|
+
// author's face on a vertical timeline line, "<b>Actor</b> verb
|
|
9
|
+
// <b>Object</b>" with a muted detail under it, and a kind dot and the time
|
|
10
|
+
// on the right. The agent is an author like a person, with its own mark:
|
|
11
|
+
// "Agent, for anna@acme.com", "Agent, on a run".
|
|
12
|
+
//
|
|
13
|
+
// Two ways in. `items` are rows you shaped yourself (the caller formats
|
|
14
|
+
// the time and picks the tone). `rows` are the app's activity lines as
|
|
15
|
+
// useActivity() returns them (lib/hooks.js) — { id, at, action, surface,
|
|
16
|
+
// row_ref, detail, by } — and the block words them: `by` user:<email> is
|
|
17
|
+
// the person (named through `people`), agent:<email> is "Agent, for
|
|
18
|
+
// <them>", agent:job is "Agent, on a run".
|
|
19
|
+
//
|
|
20
|
+
// leading="icon" is one subject's journey instead: each row leads with its
|
|
21
|
+
// kind's icon in a circle tinted by its tone, a title with the short date
|
|
22
|
+
// beside it, one muted line under it, no actor and no trailing column; the
|
|
23
|
+
// header can name the subject in a pill.
|
|
24
|
+
|
|
25
|
+
const TONES = ["success", "info", "warning", "danger", "neutral"];
|
|
26
|
+
const LOADING_ROWS = 4;
|
|
27
|
+
|
|
28
|
+
// The circle behind a kind's icon: a wash of its tone, the glyph in it.
|
|
29
|
+
const DISC = {
|
|
30
|
+
success: "bg-success/15 text-success",
|
|
31
|
+
info: "bg-info/15 text-info",
|
|
32
|
+
warning: "bg-warning/15 text-warning",
|
|
33
|
+
danger: "bg-destructive/15 text-destructive",
|
|
34
|
+
primary: "bg-primary/15 text-primary",
|
|
35
|
+
neutral: "bg-muted text-muted-foreground",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// The icon and tone of a journey row: its own, else its kind's.
|
|
39
|
+
function kindOf(item, kinds) {
|
|
40
|
+
const k = (item.kind != null && kinds?.[item.kind]) || {};
|
|
41
|
+
const tone = item.tone ?? k.tone;
|
|
42
|
+
return { icon: item.icon ?? k.icon ?? "circle", tone: DISC[tone] ? tone : "neutral" };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The timeline segment behind the avatar column: from the row's middle
|
|
46
|
+
* down on the first row, top to middle on the last, full height between.
|
|
47
|
+
* left-7 is the avatar's centre (Item's px-3 + half of a 32px avatar). */
|
|
48
|
+
function Line({ first, last, icon }) {
|
|
49
|
+
if (first && last) return null;
|
|
50
|
+
if (icon)
|
|
51
|
+
// The journey's circles sit on the title line, not the row's middle:
|
|
52
|
+
// their centre is 26px down (py-3.5 + half of a 24px circle), left-6.
|
|
53
|
+
return (
|
|
54
|
+
<span
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
className={cx("absolute left-6 w-px -translate-x-1/2 bg-border", first ? "top-[26px]" : "top-0", last ? "h-[26px]" : "bottom-0")}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
return (
|
|
60
|
+
<span
|
|
61
|
+
aria-hidden="true"
|
|
62
|
+
className={cx("absolute left-7 w-px -translate-x-1/2 bg-border", first ? "top-1/2" : "top-0", last ? "bottom-1/2" : "bottom-0")}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function Disc({ icon, tone }) {
|
|
68
|
+
return (
|
|
69
|
+
// raw: an icon in a circle washed with a status tone; Item's own `icon`
|
|
70
|
+
// square is always muted, and no kit part tints a glyph's backdrop.
|
|
71
|
+
<span aria-hidden="true" className={cx("relative flex size-6 shrink-0 items-center justify-center rounded-full", DISC[tone])}>
|
|
72
|
+
<Icon name={icon} size={13} />
|
|
73
|
+
</span>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function JourneyRow({ item, first, last, kinds, onItemClick }) {
|
|
78
|
+
const { icon, tone } = kindOf(item, kinds);
|
|
79
|
+
const onClick = item.onClick ?? (onItemClick && !item.href ? () => onItemClick(item) : undefined);
|
|
80
|
+
return (
|
|
81
|
+
// raw: a positioned wrapper so the timeline line runs behind the
|
|
82
|
+
// circles across the whole row; no kit part draws a timeline.
|
|
83
|
+
<div className="relative">
|
|
84
|
+
<Line first={first} last={last} icon />
|
|
85
|
+
<Item
|
|
86
|
+
className="py-3.5 rounded-none [&]:items-start"
|
|
87
|
+
media={<Disc icon={icon} tone={tone} />}
|
|
88
|
+
title={
|
|
89
|
+
<>
|
|
90
|
+
{item.title ?? item.object}
|
|
91
|
+
{item.time ? (
|
|
92
|
+
<>
|
|
93
|
+
{" "}
|
|
94
|
+
<time dateTime={item.timestamp} title={item.timestamp || undefined} data-nums className="ml-1 text-xs font-normal text-muted-foreground">
|
|
95
|
+
{item.time}
|
|
96
|
+
</time>
|
|
97
|
+
</>
|
|
98
|
+
) : null}
|
|
99
|
+
</>
|
|
100
|
+
}
|
|
101
|
+
description={item.detail || undefined}
|
|
102
|
+
href={item.href}
|
|
103
|
+
onClick={item.href ? undefined : onClick}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function Sentence({ item, linkObject }) {
|
|
110
|
+
return (
|
|
111
|
+
<>
|
|
112
|
+
<span>{item.actorName}</span> <span className="font-normal text-muted-foreground">{item.verb}</span>{" "}
|
|
113
|
+
{linkObject ? (
|
|
114
|
+
// The kit's inline text link, in the sentence's own size and weight;
|
|
115
|
+
// a row that is not a target of its own can still link its object.
|
|
116
|
+
<Button variant="link" size="inherit" href={item.objectHref} className="relative z-10">
|
|
117
|
+
{item.object}
|
|
118
|
+
</Button>
|
|
119
|
+
) : (
|
|
120
|
+
<span>{item.object}</span>
|
|
121
|
+
)}
|
|
122
|
+
</>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function Row({ item, first, last, onItemClick }) {
|
|
127
|
+
const tone = TONES.includes(item.tone) ? item.tone : "neutral";
|
|
128
|
+
const onClick = item.onClick ?? (onItemClick && !item.href && !item.objectHref ? () => onItemClick(item) : undefined);
|
|
129
|
+
const linkObject = Boolean(item.objectHref) && !item.href && !onClick;
|
|
130
|
+
return (
|
|
131
|
+
// raw: a positioned wrapper so the timeline line can run behind the
|
|
132
|
+
// Item's avatar across the whole row; no kit part draws a timeline.
|
|
133
|
+
<div className="relative">
|
|
134
|
+
<Line first={first} last={last} />
|
|
135
|
+
<Item
|
|
136
|
+
size="default"
|
|
137
|
+
className="py-3.5 rounded-none hover:bg-muted/50"
|
|
138
|
+
media={
|
|
139
|
+
item.agent ? (
|
|
140
|
+
// raw: the agent's mark in the avatar's circle, so the timeline
|
|
141
|
+
// line runs behind it like a face.
|
|
142
|
+
<span className="relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted">
|
|
143
|
+
<AgentMark size="md" />
|
|
144
|
+
</span>
|
|
145
|
+
) : (
|
|
146
|
+
<Avatar name={item.actorName} src={item.actorAvatar} size="sm" />
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
title={<Sentence item={item} linkObject={linkObject} />}
|
|
150
|
+
description={item.detail || undefined}
|
|
151
|
+
href={item.href}
|
|
152
|
+
onClick={item.href ? undefined : onClick}
|
|
153
|
+
meta={
|
|
154
|
+
<span className="inline-flex items-center gap-2 whitespace-nowrap text-sm">
|
|
155
|
+
<StatusDot tone={tone} size="sm" />
|
|
156
|
+
<time dateTime={item.timestamp} title={item.timestamp || undefined}>
|
|
157
|
+
{item.time}
|
|
158
|
+
</time>
|
|
159
|
+
</span>
|
|
160
|
+
}
|
|
161
|
+
/>
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function SkeletonRow({ first, last, icon }) {
|
|
167
|
+
return (
|
|
168
|
+
<div className="relative">
|
|
169
|
+
<Line first={first} last={last} icon={icon} />
|
|
170
|
+
<Item
|
|
171
|
+
className={cx("py-3.5 rounded-none", icon && "[&]:items-start")}
|
|
172
|
+
media={<Skeleton shape="circle" className={icon ? "size-6" : "size-8"} />}
|
|
173
|
+
title={<Skeleton className="w-48 max-w-full" />}
|
|
174
|
+
description={<Skeleton className="mt-1.5 w-32 max-w-full" />}
|
|
175
|
+
meta={icon ? undefined : <Skeleton className="w-16" />}
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const ISO = /^\d{4}-\d{2}-\d{2}T/;
|
|
182
|
+
|
|
183
|
+
/** "just now", "5 min ago", "3 h ago", "yesterday", "12 Sep". */
|
|
184
|
+
export function activityTime(at, now = new Date()) {
|
|
185
|
+
const d = at instanceof Date ? at : typeof at === "string" && (ISO.test(at) || /^\d{4}-\d{2}-\d{2}/.test(at)) ? new Date(at) : null;
|
|
186
|
+
if (!d || Number.isNaN(d.getTime())) return at == null ? "" : String(at);
|
|
187
|
+
const s = Math.round((now.getTime() - d.getTime()) / 1000);
|
|
188
|
+
if (s < 60) return "just now";
|
|
189
|
+
if (s < 3600) return `${Math.floor(s / 60)} min ago`;
|
|
190
|
+
if (s < 86400 && d.toDateString() === now.toDateString()) return `${Math.floor(s / 3600)} h ago`;
|
|
191
|
+
const y = new Date(now);
|
|
192
|
+
y.setDate(y.getDate() - 1);
|
|
193
|
+
if (d.toDateString() === y.toDateString()) return "yesterday";
|
|
194
|
+
return d.toLocaleDateString("en-GB", { day: "numeric", month: "short", ...(d.getFullYear() === now.getFullYear() ? {} : { year: "numeric" }) });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** A line's tone from its action word (the door's: added, edited, …). */
|
|
198
|
+
const ACTION_TONE = { added: "success", created: "success", confirmed: "success", linked: "info", listed: "info", edited: "info", moved: "info", merged: "warning", removed: "danger", erased: "danger", unlinked: "neutral", unlisted: "neutral" };
|
|
199
|
+
|
|
200
|
+
/** Who a `by` names, in words: { name, agent, email }. */
|
|
201
|
+
export function activityAuthor(by, people) {
|
|
202
|
+
const raw = String(by ?? "");
|
|
203
|
+
const named = (email) => (email && people && (people[email] ?? people[email.toLowerCase()])) || email;
|
|
204
|
+
if (raw === "agent:job" || raw === "agent:run") return { name: "Agent, on a run", agent: true };
|
|
205
|
+
if (raw.startsWith("agent:")) {
|
|
206
|
+
const email = raw.slice(6);
|
|
207
|
+
return { name: email ? `Agent, for ${named(email)}` : "Agent", agent: true, email };
|
|
208
|
+
}
|
|
209
|
+
if (raw.startsWith("user:")) {
|
|
210
|
+
const who = raw.slice(5);
|
|
211
|
+
if (!who || who === "unknown") return { name: "Someone", agent: false };
|
|
212
|
+
return { name: named(who), agent: false, email: who.includes("@") ? who : undefined };
|
|
213
|
+
}
|
|
214
|
+
return { name: raw || "Someone", agent: false };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The app's activity lines (useActivity's `rows`) as items:
|
|
219
|
+
*
|
|
220
|
+
* rows [{ id, at, action, surface, row_ref, detail, data, by }]
|
|
221
|
+
* people { "anna@acme.com": "Anna Lee" }: names for emails
|
|
222
|
+
* formatTime (at) → words; default activityTime ("5 min ago")
|
|
223
|
+
* describe (row) → part of an item, laid over what this makes
|
|
224
|
+
* (an href, an object's name, a tone)
|
|
225
|
+
*/
|
|
226
|
+
export function activityItems(rows, { people, formatTime = activityTime, describe } = {}) {
|
|
227
|
+
return (rows ?? []).filter(Boolean).map((row, i) => {
|
|
228
|
+
const who = activityAuthor(row.by, people);
|
|
229
|
+
const item = {
|
|
230
|
+
id: row.id ?? `${row.at ?? ""}-${i}`,
|
|
231
|
+
actorName: who.name,
|
|
232
|
+
agent: who.agent,
|
|
233
|
+
verb: row.action ?? "changed",
|
|
234
|
+
object: row.surface || "a record",
|
|
235
|
+
detail: row.detail || undefined,
|
|
236
|
+
tone: ACTION_TONE[row.action] ?? "neutral",
|
|
237
|
+
kind: row.action,
|
|
238
|
+
// A journey row's title is the sentence; its detail line, the detail.
|
|
239
|
+
title: [who.name, row.action ?? "changed", row.surface].filter(Boolean).join(" "),
|
|
240
|
+
time: formatTime(row.at),
|
|
241
|
+
timestamp: typeof row.at === "string" ? row.at : row.at instanceof Date ? row.at.toISOString() : undefined,
|
|
242
|
+
};
|
|
243
|
+
return describe ? { ...item, ...describe(row) } : item;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* items [{ id, actorName, actorAvatar?, agent?, verb, object,
|
|
249
|
+
* objectHref?, detail?, tone, time, timestamp?, href?,
|
|
250
|
+
* onClick? }]; agent: true draws the agent's mark
|
|
251
|
+
* rows instead of items: useActivity()'s rows, worded by
|
|
252
|
+
* activityItems (with `people`, `formatTime`, `describe`)
|
|
253
|
+
* title optional header, draws the feed inside a Card
|
|
254
|
+
* subtitle a muted line under the title (drawn only with a title)
|
|
255
|
+
* loading skeleton rows instead of items
|
|
256
|
+
* onItemClick (item) → void; every row without its own href, onClick
|
|
257
|
+
* or objectHref becomes a button calling it
|
|
258
|
+
* emptyTitle default "No activity yet"
|
|
259
|
+
* emptyBody a muted line under it
|
|
260
|
+
* label the list's name for a screen reader; default title or "Activity"
|
|
261
|
+
* leading avatar (default: the actor's face, the sentence, a dot and
|
|
262
|
+
* the time on the right) · icon (a journey: the kind's icon
|
|
263
|
+
* in a tinted circle, title + date, no rules between rows)
|
|
264
|
+
* kinds { [kind]: { icon, tone } } — leading="icon" looks up a
|
|
265
|
+
* row's `kind` here; a row's own `icon`/`tone` win
|
|
266
|
+
* subject a pill after the title naming whose history this is
|
|
267
|
+
*/
|
|
268
|
+
export function ActivityLog({
|
|
269
|
+
items,
|
|
270
|
+
rows,
|
|
271
|
+
people,
|
|
272
|
+
formatTime,
|
|
273
|
+
describe,
|
|
274
|
+
title,
|
|
275
|
+
subtitle,
|
|
276
|
+
loading = false,
|
|
277
|
+
onItemClick,
|
|
278
|
+
emptyTitle = "No activity yet",
|
|
279
|
+
emptyBody,
|
|
280
|
+
label,
|
|
281
|
+
leading = "avatar",
|
|
282
|
+
kinds,
|
|
283
|
+
subject,
|
|
284
|
+
className,
|
|
285
|
+
}) {
|
|
286
|
+
const journey = leading === "icon";
|
|
287
|
+
const list = (items ?? (rows ? activityItems(rows, { people, formatTime, describe }) : [])).filter(Boolean);
|
|
288
|
+
const named = label ?? (typeof title === "string" ? title : "Activity");
|
|
289
|
+
const framed = Boolean(title);
|
|
290
|
+
|
|
291
|
+
let body;
|
|
292
|
+
if (loading) {
|
|
293
|
+
body = (
|
|
294
|
+
<ItemGroup variant={framed ? "plain" : "outline"} aria-label={`${named}, loading`}>
|
|
295
|
+
{Array.from({ length: LOADING_ROWS }, (_, i) => (
|
|
296
|
+
<SkeletonRow key={i} first={i === 0} last={i === LOADING_ROWS - 1} icon={journey} />
|
|
297
|
+
))}
|
|
298
|
+
</ItemGroup>
|
|
299
|
+
);
|
|
300
|
+
} else if (list.length === 0) {
|
|
301
|
+
body = <Empty icon="clock" title={emptyTitle} body={emptyBody} className={framed ? "m-4" : undefined} />;
|
|
302
|
+
} else {
|
|
303
|
+
body = (
|
|
304
|
+
<ItemGroup
|
|
305
|
+
variant={framed ? "plain" : "outline"}
|
|
306
|
+
aria-label={named}
|
|
307
|
+
// A journey is one line of events, no rules between them.
|
|
308
|
+
className={journey ? "py-1 [&>li]:border-t-0" : undefined}
|
|
309
|
+
>
|
|
310
|
+
{list.map((item, i) =>
|
|
311
|
+
journey ? (
|
|
312
|
+
<JourneyRow key={item.id ?? i} item={item} first={i === 0} last={i === list.length - 1} kinds={kinds} onItemClick={onItemClick} />
|
|
313
|
+
) : (
|
|
314
|
+
<Row key={item.id ?? i} item={item} first={i === 0} last={i === list.length - 1} onItemClick={onItemClick} />
|
|
315
|
+
),
|
|
316
|
+
)}
|
|
317
|
+
</ItemGroup>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (!framed) return <div className={className}>{body}</div>;
|
|
322
|
+
return (
|
|
323
|
+
<Card
|
|
324
|
+
title={
|
|
325
|
+
subject ? (
|
|
326
|
+
<span className="flex flex-wrap items-center gap-x-2 gap-y-1">
|
|
327
|
+
<span>{title}</span>
|
|
328
|
+
{typeof subject === "string" ? (
|
|
329
|
+
<Badge variant="soft" className="font-normal">
|
|
330
|
+
{subject}
|
|
331
|
+
</Badge>
|
|
332
|
+
) : (
|
|
333
|
+
subject
|
|
334
|
+
)}
|
|
335
|
+
</span>
|
|
336
|
+
) : (
|
|
337
|
+
title
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
description={subtitle} className={cx("overflow-hidden", className)}>
|
|
341
|
+
{/* Edge to edge under the header, as the rows' rules are. */}
|
|
342
|
+
<div aria-busy={loading || undefined} className="-mx-4 -mb-4 border-t border-border">
|
|
343
|
+
{body}
|
|
344
|
+
</div>
|
|
345
|
+
</Card>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** The CRM's name for it (0.7.0), kept so a screen written against the
|
|
350
|
+
* CRM's activity-feed moves without a rename. */
|
|
351
|
+
export const ActivityFeed = ActivityLog;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "activity-log",
|
|
3
|
+
"title": "ActivityLog",
|
|
4
|
+
"description": "Who did what, to what, when, newest first: one row per event on a vertical timeline, \"Anna Lee edited deals\" with the line's detail under it, a kind dot and the time. The agent is an author like a person, with its own mark: \"Agent, for anna@acme.com\", \"Agent, on a run\". It takes the app's activity lines as useActivity() returns them (lib/hooks.js; with a row, one record's timeline) and words them itself, or rows you shaped. leading=\"icon\" draws one subject's journey instead.",
|
|
5
|
+
"useWhen": "A record's story (what happened to this deal, and who did it), an app's recent changes on a home screen, a review of what the agent did this week next to what people did. Not for one job's own steps (job-panel), one ask's changes with an undo (agent-changes), or a sortable audit table (data-table).",
|
|
6
|
+
"tags": [
|
|
7
|
+
"activity",
|
|
8
|
+
"timeline",
|
|
9
|
+
"history",
|
|
10
|
+
"audit",
|
|
11
|
+
"log",
|
|
12
|
+
"feed",
|
|
13
|
+
"events",
|
|
14
|
+
"who did what",
|
|
15
|
+
"record story",
|
|
16
|
+
"recent",
|
|
17
|
+
"changes"
|
|
18
|
+
],
|
|
19
|
+
"exports": [
|
|
20
|
+
"ActivityLog",
|
|
21
|
+
"ActivityFeed",
|
|
22
|
+
"activityItems",
|
|
23
|
+
"activityAuthor",
|
|
24
|
+
"activityTime"
|
|
25
|
+
],
|
|
26
|
+
"files": [
|
|
27
|
+
"index.js",
|
|
28
|
+
"activity-log.jsx"
|
|
29
|
+
],
|
|
30
|
+
"requires": [
|
|
31
|
+
"agent-marks"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// ActivityLog over fake lines shaped as useActivity() returns them, and in
|
|
4
|
+
// its other states — the gallery's and the smoke test's surface. Not
|
|
5
|
+
// installed into apps.
|
|
6
|
+
import { ActivityLog } from "../components/blocks/activity-log";
|
|
7
|
+
|
|
8
|
+
const ago = (min) => new Date(Date.now() - min * 60 * 1000).toISOString();
|
|
9
|
+
|
|
10
|
+
const ROWS = [
|
|
11
|
+
{ id: "a1", at: ago(2), action: "moved", surface: "deals", row_ref: "d1", detail: "Pinewood to Won", by: "agent:anna@acme.com" },
|
|
12
|
+
{ id: "a2", at: ago(14), action: "edited", surface: "people", row_ref: "p4", detail: "Sofia Reyes: job title", by: "user:owen@acme.com" },
|
|
13
|
+
{ id: "a3", at: ago(95), action: "added", surface: "deals", row_ref: "d9", detail: "Crumb & Co: 22 shops", by: "agent:job" },
|
|
14
|
+
{ id: "a4", at: ago(60 * 26), action: "removed", surface: "notes", row_ref: "n2", detail: null, by: "user:unknown" },
|
|
15
|
+
{
|
|
16
|
+
id: "a5",
|
|
17
|
+
at: ago(60 * 24 * 6),
|
|
18
|
+
action: "merged",
|
|
19
|
+
surface: "companies",
|
|
20
|
+
row_ref: "c3",
|
|
21
|
+
detail: "Harborview Hospitality Group and Harborview Hospitality Group Ltd (a duplicate found in last week's import)",
|
|
22
|
+
by: "user:priya.nair@acme.com",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const PEOPLE = { "anna@acme.com": "Anna Lee", "owen@acme.com": "Owen Park" };
|
|
27
|
+
|
|
28
|
+
export default function ActivityLogDemo() {
|
|
29
|
+
return (
|
|
30
|
+
<div className="grid gap-6 p-6">
|
|
31
|
+
<ActivityLog title="Story" subject="Pinewood" rows={ROWS} people={PEOPLE} />
|
|
32
|
+
<div className="grid gap-4 [grid-template-columns:repeat(auto-fit,minmax(min(100%,20rem),1fr))]">
|
|
33
|
+
<ActivityLog title="Loading" loading />
|
|
34
|
+
<ActivityLog title="Empty" rows={[]} emptyBody="Changes show here, by people and by the agent." />
|
|
35
|
+
<ActivityLog
|
|
36
|
+
title="A journey"
|
|
37
|
+
leading="icon"
|
|
38
|
+
rows={ROWS.slice(0, 3)}
|
|
39
|
+
people={PEOPLE}
|
|
40
|
+
kinds={{ moved: { icon: "arrow-right", tone: "info" }, edited: { icon: "pencil", tone: "neutral" }, added: { icon: "plus", tone: "success" } }}
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
<div className="w-80 max-w-full">
|
|
44
|
+
<ActivityLog rows={ROWS} people={PEOPLE} label="Narrow" />
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// The ActivityLog block — `makersclaw add activity-log`. Fixed: compose it
|
|
2
|
+
// through its props (README.md beside this file). ActivityFeed is the
|
|
3
|
+
// CRM's name for the same block.
|
|
4
|
+
export { ActivityLog, ActivityFeed, activityItems, activityAuthor, activityTime } from "./activity-log";
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# AgentChanges
|
|
2
|
+
|
|
3
|
+
The receipt of one ask. After the agent changes many things at once, the
|
|
4
|
+
member sees:
|
|
5
|
+
|
|
6
|
+
- what it did, in one line: "Sorted 46 stickies into 6 groups";
|
|
7
|
+
- who asked and when: "By the agent, on Priya's ask · 11:20";
|
|
8
|
+
- each change, folded after five ("Show 41 more");
|
|
9
|
+
- **Undo this ask**, which takes all of them back in one press.
|
|
10
|
+
|
|
11
|
+
Undone, the receipt stays, struck through, and says so in words.
|
|
12
|
+
|
|
13
|
+
The block draws; **the app does the undo**. Only the app knows what an
|
|
14
|
+
ask drew (the whiteboard's stickies, the map's branches, the plan's
|
|
15
|
+
bars), so Undo calls your own declared undo action.
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
import { AgentChanges } from "../components/blocks/agent-changes"; // a Makersclaw app ("./blocks/agent-changes" from components/)
|
|
19
|
+
import { AgentChanges } from "@/components/makersclaw/blocks/agent-changes"; // a shadcn project
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
A client component. It requires `agent-marks` (laid with it).
|
|
23
|
+
|
|
24
|
+
## Which hook feeds it
|
|
25
|
+
|
|
26
|
+
The changes come from what the ask reported, or from the activity lines
|
|
27
|
+
the agent wrote since it started:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
const { rows } = useActivity({ agent: true, since: ask.started_at }); // lib/hooks.js
|
|
31
|
+
const changes = rows.map((r) => ({ id: r.id, label: r.detail ?? `${r.action} ${r.surface}` }));
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Undo is the app's own declared action (`runAction`, lib/actions): the
|
|
35
|
+
same write path the agent uses, with the same door rules.
|
|
36
|
+
|
|
37
|
+
## Props
|
|
38
|
+
|
|
39
|
+
- `summary` — what the ask did, one line, in the app's words.
|
|
40
|
+
- `changes` — `[{ id?, label, detail? }]`, each change. Folded after five.
|
|
41
|
+
- `by` — who and when: `Byline`'s props (`{ by, ask, at }`, e.g. `{ ask:
|
|
42
|
+
"Priya", at: "11:20" }`), or your own words.
|
|
43
|
+
- `onUndo()` — async, answers `{ ok: true }` or `{ ok: false, error }` (a
|
|
44
|
+
throw counts as failed). The button spins while it runs; a failure says
|
|
45
|
+
why under it. Without `onUndo` there is no Undo.
|
|
46
|
+
- `undone` — `true` when your data says the ask was undone already.
|
|
47
|
+
- `undoLabel` — default "Undo this ask".
|
|
48
|
+
- `undoneWords` — default "Undone. Everything this ask changed is back as
|
|
49
|
+
it was."
|
|
50
|
+
- `label` — the region's name for a reader. Default the summary.
|
|
51
|
+
- `className` — layout around it. Never colour.
|
|
52
|
+
|
|
53
|
+
## Who may do what
|
|
54
|
+
|
|
55
|
+
Give `onUndo` to the member who asked, and to an owner or admin; your
|
|
56
|
+
declared undo action refuses anyone else anyway. Everyone else sees the
|
|
57
|
+
receipt without the button.
|
|
58
|
+
|
|
59
|
+
## Never
|
|
60
|
+
|
|
61
|
+
- Never undo by deleting what looks new: undo exactly what this ask
|
|
62
|
+
wrote, through your declared action.
|
|
63
|
+
- Never hide the receipt after an undo; it stays, struck, so the member
|
|
64
|
+
knows what happened.
|
|
65
|
+
- Never use it for proposals the member has not kept yet: that is
|
|
66
|
+
`suggestion`.
|
|
67
|
+
|
|
68
|
+
## Example
|
|
69
|
+
|
|
70
|
+
The receipt under a whiteboard after the agent sorted it.
|
|
71
|
+
|
|
72
|
+
```jsx
|
|
73
|
+
"use client";
|
|
74
|
+
import { runAction } from "../lib/actions";
|
|
75
|
+
import { AgentChanges } from "../components/blocks/agent-changes";
|
|
76
|
+
|
|
77
|
+
export function SortReceipt({ ask, moves, reload }) {
|
|
78
|
+
return (
|
|
79
|
+
<AgentChanges
|
|
80
|
+
summary={`Sorted ${moves.length} stickies into ${ask.groups} groups`}
|
|
81
|
+
by={{ ask: ask.asked_by_name, at: ask.ended_at }}
|
|
82
|
+
changes={moves.map((m) => ({ id: m.id, label: m.sticky, detail: `to ${m.group}` }))}
|
|
83
|
+
undone={ask.undone}
|
|
84
|
+
onUndo={async () => {
|
|
85
|
+
const r = await runAction({ name: "undo_ask", args: { ask: ask.id } });
|
|
86
|
+
if (r.ok) reload();
|
|
87
|
+
return r;
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|