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,656 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// The builder: one automation on the canvas, with its header, its side
|
|
4
|
+
// panel and its outline view. Everything an app's Automations page needs
|
|
5
|
+
// to let a member open, edit, test and switch on an automation.
|
|
6
|
+
//
|
|
7
|
+
// It holds the working copy of the definition (an undo stack of pure
|
|
8
|
+
// edits from automation-model) and says what is wrong with it (the quick
|
|
9
|
+
// check); it knows nothing of the network. The screen around it saves,
|
|
10
|
+
// keeps, switches live and runs, and hands the answers back as callbacks
|
|
11
|
+
// that resolve the platform's answer: `{ ok: true }` or `{ ok: false,
|
|
12
|
+
// error, problems? }` (what useAutomation's save, keep, setLive and test
|
|
13
|
+
// resolve), or throw an Error with a message.
|
|
14
|
+
//
|
|
15
|
+
// The model of saving ("done is evidence"):
|
|
16
|
+
// Save a draft version; the live one keeps running as it was
|
|
17
|
+
// Test a DRY run of the draft (or live) version: it reads real data and
|
|
18
|
+
// changes nothing (nothing is sent, nothing is written)
|
|
19
|
+
// Keep the draft becomes the version that runs
|
|
20
|
+
// Live the switch: it fires on its trigger, or it does not
|
|
21
|
+
// A draft never goes live by itself. Keep and Live need a passed test of that
|
|
22
|
+
// version (the platform refuses with `needs_test` otherwise), unless the
|
|
23
|
+
// member deliberately skips it: the second, inline-confirmed press calls
|
|
24
|
+
// onKeep({ skipTest: true }) / onLive(on, { skipTest: true }). Never silently,
|
|
25
|
+
// and never by an agent.
|
|
26
|
+
//
|
|
27
|
+
// The draft is shared with the agent: it may save a newer version while a
|
|
28
|
+
// member has edits that are not saved. So the editor remembers the version
|
|
29
|
+
// its working copy began on (`baseVersion`) and sends THAT with every save,
|
|
30
|
+
// never "the newest I can see": the platform answers `changed` when the draft
|
|
31
|
+
// moved, and the editor says so in a strip (Look at the new one, or Keep
|
|
32
|
+
// mine and save on top of it on purpose). A save never buries a newer
|
|
33
|
+
// version by accident.
|
|
34
|
+
|
|
35
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
36
|
+
import { Button, Confirm, Menu, Switch, cx } from "../../ui";
|
|
37
|
+
import {
|
|
38
|
+
Chip,
|
|
39
|
+
RunPicker,
|
|
40
|
+
addStep,
|
|
41
|
+
agentDraftOf,
|
|
42
|
+
availableBefore,
|
|
43
|
+
canonicalJson,
|
|
44
|
+
changeLinesBetween,
|
|
45
|
+
checkAutomation,
|
|
46
|
+
creditsWords,
|
|
47
|
+
deleteWords,
|
|
48
|
+
duplicateStep,
|
|
49
|
+
findStep,
|
|
50
|
+
flaggedSteps,
|
|
51
|
+
issuesFor,
|
|
52
|
+
latestVersionOf,
|
|
53
|
+
newerVersionWords,
|
|
54
|
+
priceMap,
|
|
55
|
+
removeStep,
|
|
56
|
+
slotAfter,
|
|
57
|
+
slotAtEnd,
|
|
58
|
+
stepTitle,
|
|
59
|
+
updateStep,
|
|
60
|
+
} from "../automation-model";
|
|
61
|
+
import { FixProposal } from "../fix-note";
|
|
62
|
+
import { FlowEditor } from "../flow-editor";
|
|
63
|
+
import { FlowSettings, StepSettings } from "../step-settings";
|
|
64
|
+
import { TriggerPicker } from "../trigger-picker";
|
|
65
|
+
import { RunInputs } from "./run-inputs";
|
|
66
|
+
|
|
67
|
+
const MAX_UNDO = 60;
|
|
68
|
+
|
|
69
|
+
/** The version a successful save made: `{ ok, version }`, or the hook's `{ ok, data: { version } }`. */
|
|
70
|
+
function versionOf(r) {
|
|
71
|
+
const n = r?.version ?? r?.data?.version;
|
|
72
|
+
return Number.isInteger(n) ? n : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Words for a refusal the platform sent, or a thrown error. `latest` is the
|
|
76
|
+
* newest version the platform has, when it said (a `changed` refusal does). */
|
|
77
|
+
function said(r, fallback) {
|
|
78
|
+
if (r && r.ok === false) return { text: r.error || fallback, lines: Array.isArray(r.problems) ? r.problems : [], code: typeof r.code === "string" ? r.code : null, latest: Number.isInteger(r.latest) ? r.latest : null };
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The test chip for the version being looked at: the draft if there is one,
|
|
83
|
+
* else the live one. Null when the app gave none, or the version is the app's
|
|
84
|
+
* own shipped one (it needs no test). */
|
|
85
|
+
function testChip(tests) {
|
|
86
|
+
if (!tests) return null;
|
|
87
|
+
const v = tests.draft ?? tests.live ?? null;
|
|
88
|
+
if (!v || v.exempt) return null;
|
|
89
|
+
if (v.passed) {
|
|
90
|
+
const n = Number(v.last?.would ?? 0);
|
|
91
|
+
return { tone: "green", text: n > 0 ? `Tested · ${n} ${n === 1 ? "step" : "steps"} would run` : "Tested" };
|
|
92
|
+
}
|
|
93
|
+
const last = v.last?.status;
|
|
94
|
+
if (last && last !== "done") return { tone: "red", text: "Last test failed" };
|
|
95
|
+
return { tone: "amber", text: "Not tested" };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* automationId the saved automation's id, or null for one not saved yet
|
|
100
|
+
* status draft | on | off | needs_owner | "new"
|
|
101
|
+
* liveVersion, draftVersion the numbers, or null
|
|
102
|
+
* baseline the definition as last saved (or the starting point)
|
|
103
|
+
* versions [{ version, kind, note, created_at }]
|
|
104
|
+
* runs [{ id, status, created_at, is_test, credits }], newest first
|
|
105
|
+
* apps [{ id, name }], for an action in a team automation
|
|
106
|
+
* actions the app's declared actions, [{ name, description? }]
|
|
107
|
+
* ownedByApp the automation belongs to an app
|
|
108
|
+
* estimate what a run is expected to cost (GET automation's estimate)
|
|
109
|
+
* samples { [stepId]: a real output }, for the data picker
|
|
110
|
+
* runnable a Set of the kinds the platform runs
|
|
111
|
+
* backLabel, onBack
|
|
112
|
+
* onSave(def, { baseVersion }), onKeep(opts?), onLive(on, opts?), onTest(inputs), onOpenRun(runId)
|
|
113
|
+
* `baseVersion` is the version the working copy began on: send it to the platform as
|
|
114
|
+
* `expect_version`, never the newest one you can see. A save the platform refuses with
|
|
115
|
+
* the code "changed" (and `latest`, if it says) draws the conflict strip. A save that
|
|
116
|
+
* works may resolve `{ ok: true, version }` (or the hook's `{ ok: true, data: { version } }`): the editor then begins from that version.
|
|
117
|
+
* opts is `{ skipTest: true }` ONLY after the member pressed "Go live without a
|
|
118
|
+
* test" and then confirmed "Skip the test" in the note. Resolve like the others;
|
|
119
|
+
* a refusal keeps its `code` ("needs_test" shows the note with its two buttons).
|
|
120
|
+
* tests { live: TestView|null, draft: TestView|null } (useAutomation().tests), TestView =
|
|
121
|
+
* { version, passed, exempt, last: { run_id, status, ended_at, credits, would } | null };
|
|
122
|
+
* draws the test chip (Not tested · Tested · 3 steps would run · Last test failed).
|
|
123
|
+
* Without it, or for the app's own shipped version, no chip.
|
|
124
|
+
* onReset "Reset to the app's version", when it was edited from a shipped one
|
|
125
|
+
* tabs the Flow / Runs tabs, drawn in the header
|
|
126
|
+
* suggestions the changes the agent drafted from failed runs, waiting for a person
|
|
127
|
+
* [{ id, why, stale, patch }] (useAutomation().suggestions)
|
|
128
|
+
* onKeepSuggestion(id), onDropSuggestion(id) keep (a new version that runs) or drop one;
|
|
129
|
+
* resolve the platform's answer. Without them the strip is not drawn.
|
|
130
|
+
* triggers what can start it (useAutomations().triggers, the list door's
|
|
131
|
+
* `triggers`): the picker offers events, an app's actions, records,
|
|
132
|
+
* a web address and another automation. Without it: by hand and a schedule.
|
|
133
|
+
* selfKey this automation's key (it cannot follow itself)
|
|
134
|
+
* hook a webhook trigger's address once saved (useAutomation().hook)
|
|
135
|
+
* onRotateHook makes (or replaces) the webhook's secret; resolves { ok, hook: { secret } }
|
|
136
|
+
* compareTo { version, definition }: what "What changed" compares the agent's draft with
|
|
137
|
+
* (the version the member kept, usually the live one). Without it, no What changed.
|
|
138
|
+
* onDelete(automation) async: "Delete automation", in a menu at the end of the header (never beside Save),
|
|
139
|
+
* after a question that says what it does: Deletes "<name>". Its runs stay in history.
|
|
140
|
+
* (and, for a live one, that it is switched off). `automation` is `{ id, name, status }`.
|
|
141
|
+
* Resolves `{ ok: true }` (the screen leaves the editor and says what followed) or
|
|
142
|
+
* `{ ok: false, error, code? }`: the words are shown as they are. Only for a saved
|
|
143
|
+
* automation the member may edit; without it, no menu
|
|
144
|
+
* canDelete offer it at all (default true; hide it where the member may not)
|
|
145
|
+
* source the automation's source (member · agent · package · legacy). An app's own job and what an
|
|
146
|
+
* app shipped are not deleted from here: the item is disabled and points at the app's settings
|
|
147
|
+
* onFocus `{ selected, unsaved, baseVersion }` whenever any of the three changes: the step
|
|
148
|
+
* pressed (a step id, "trigger", "flow" or null), whether the working copy has edits
|
|
149
|
+
* that are not saved, and the version it began on. For a pane beside the editor
|
|
150
|
+
* (an agent) that needs to know what the member is looking at; it never re-renders it.
|
|
151
|
+
* canEdit false: read-only (default true)
|
|
152
|
+
*/
|
|
153
|
+
export function AutomationEditor({
|
|
154
|
+
automationId = null,
|
|
155
|
+
status = "new",
|
|
156
|
+
liveVersion = null,
|
|
157
|
+
draftVersion = null,
|
|
158
|
+
baseline,
|
|
159
|
+
versions = [],
|
|
160
|
+
runs = [],
|
|
161
|
+
apps = [],
|
|
162
|
+
actions,
|
|
163
|
+
ownedByApp = false,
|
|
164
|
+
estimate = null,
|
|
165
|
+
samples = null,
|
|
166
|
+
runnable,
|
|
167
|
+
backLabel = "Automations",
|
|
168
|
+
onBack,
|
|
169
|
+
onSave,
|
|
170
|
+
onKeep,
|
|
171
|
+
onLive,
|
|
172
|
+
onTest,
|
|
173
|
+
tests = null,
|
|
174
|
+
onOpenRun,
|
|
175
|
+
onReset,
|
|
176
|
+
tabs,
|
|
177
|
+
suggestions = [],
|
|
178
|
+
onKeepSuggestion,
|
|
179
|
+
onDropSuggestion,
|
|
180
|
+
triggers = null,
|
|
181
|
+
selfKey = null,
|
|
182
|
+
hook = null,
|
|
183
|
+
onRotateHook,
|
|
184
|
+
compareTo = null,
|
|
185
|
+
onFocus,
|
|
186
|
+
onDelete,
|
|
187
|
+
canDelete = true,
|
|
188
|
+
source = null,
|
|
189
|
+
canEdit = true,
|
|
190
|
+
className,
|
|
191
|
+
}) {
|
|
192
|
+
const [hist, setHist] = useState({ past: [], present: baseline, future: [] });
|
|
193
|
+
const def = hist.present;
|
|
194
|
+
// The version this was opened on moved (a suggestion was kept, another tab
|
|
195
|
+
// saved): if nothing here was changed since, open the new one.
|
|
196
|
+
const [seen, setSeen] = useState(baseline);
|
|
197
|
+
if (canonicalJson(seen) !== canonicalJson(baseline)) {
|
|
198
|
+
setSeen(baseline);
|
|
199
|
+
if (canonicalJson(hist.present) === canonicalJson(seen)) setHist({ past: [], present: baseline, future: [] });
|
|
200
|
+
}
|
|
201
|
+
// The version the working copy began on (`baseVersion`). It moves to the
|
|
202
|
+
// newest only while the copy is in step with the saved one: a copy with
|
|
203
|
+
// edits keeps the version it was made from, so a newer version (the agent's)
|
|
204
|
+
// is seen as newer, and a save names the base, not the newest.
|
|
205
|
+
const latest = useMemo(() => latestVersionOf({ versions, draftVersion, liveVersion }), [versions, draftVersion, liveVersion]);
|
|
206
|
+
const [begun, setBegun] = useState(latest);
|
|
207
|
+
// What the member decided about a newer version: a conflict the platform
|
|
208
|
+
// reported ({ latest }), the newest version they chose to keep theirs over,
|
|
209
|
+
// and what "Look at" replaced (so Undo can bring the edits, and their base, back).
|
|
210
|
+
const [conflict, setConflict] = useState(null);
|
|
211
|
+
const [keptOver, setKeptOver] = useState(null);
|
|
212
|
+
const looked = useRef(null);
|
|
213
|
+
const [showChanges, setShowChanges] = useState(false);
|
|
214
|
+
if (canonicalJson(def) === canonicalJson(baseline)) {
|
|
215
|
+
if (begun !== latest) setBegun(latest);
|
|
216
|
+
if (conflict) setConflict(null);
|
|
217
|
+
}
|
|
218
|
+
const [selected, setSelected] = useState(null);
|
|
219
|
+
const [reveal, setReveal] = useState(null);
|
|
220
|
+
const [busy, setBusy] = useState(null);
|
|
221
|
+
const [note, setNote] = useState(null);
|
|
222
|
+
const [asking, setAsking] = useState(false);
|
|
223
|
+
const [skipAsk, setSkipAsk] = useState(false);
|
|
224
|
+
const [deleting, setDeleting] = useState(false);
|
|
225
|
+
|
|
226
|
+
const commit = useCallback((next, keepSelection = true) => {
|
|
227
|
+
setHist((h) => (canonicalJson(h.present) === canonicalJson(next) ? h : { past: [...h.past, h.present].slice(-MAX_UNDO), present: next, future: [] }));
|
|
228
|
+
if (!keepSelection) setSelected(null);
|
|
229
|
+
setNote(null);
|
|
230
|
+
}, []);
|
|
231
|
+
const undo = () => {
|
|
232
|
+
if (hist.past.length === 0) return;
|
|
233
|
+
const back = hist.past[hist.past.length - 1];
|
|
234
|
+
setHist((h) => (h.past.length ? { past: h.past.slice(0, -1), present: h.past[h.past.length - 1], future: [h.present, ...h.future] } : h));
|
|
235
|
+
// Undoing "Look at" gives back the member's edits and the version they were made on.
|
|
236
|
+
const was = looked.current;
|
|
237
|
+
if (was && canonicalJson(back) === was.def) {
|
|
238
|
+
looked.current = null;
|
|
239
|
+
setBegun(was.begun);
|
|
240
|
+
setKeptOver(null);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
const redo = () => setHist((h) => (h.future.length ? { past: [...h.past, h.present], present: h.future[0], future: h.future.slice(1) } : h));
|
|
244
|
+
|
|
245
|
+
const isNew = automationId === null;
|
|
246
|
+
// Something not saved yet is always a change to save.
|
|
247
|
+
const edited = useMemo(() => canonicalJson(def) !== canonicalJson(baseline), [def, baseline]);
|
|
248
|
+
const dirty = isNew || edited;
|
|
249
|
+
const issues = useMemo(() => checkAutomation(def, runnable ? { runnable } : undefined), [def, runnable]);
|
|
250
|
+
const flagged = useMemo(() => flaggedSteps(issues), [issues]);
|
|
251
|
+
const prices = useMemo(() => priceMap(estimate), [estimate]);
|
|
252
|
+
const hasLive = liveVersion !== null && liveVersion !== undefined;
|
|
253
|
+
const canKeep = !isNew && !dirty && draftVersion !== null && draftVersion !== undefined && draftVersion !== liveVersion;
|
|
254
|
+
const live = status === "on";
|
|
255
|
+
|
|
256
|
+
// Tell a pane beside the editor what the member is on: never re-renders the editor.
|
|
257
|
+
const focusRef = useRef(onFocus);
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
focusRef.current = onFocus;
|
|
260
|
+
});
|
|
261
|
+
useEffect(() => {
|
|
262
|
+
focusRef.current?.({ selected, unsaved: edited, baseVersion: begun });
|
|
263
|
+
}, [selected, edited, begun]);
|
|
264
|
+
|
|
265
|
+
// A newer version arrived while the working copy has edits made on an older one.
|
|
266
|
+
const newerThanMine = !isNew && edited && begun !== null && latest !== null && latest > begun;
|
|
267
|
+
const sawNewer = conflict ? Math.max(latest ?? 0, conflict.latest ?? 0) : latest;
|
|
268
|
+
const strip = !isNew && edited && (conflict ? "conflict" : newerThanMine && keptOver !== latest ? "behind" : null);
|
|
269
|
+
const agentDraft = useMemo(() => (isNew ? null : agentDraftOf({ versions, draftVersion, liveVersion })), [isNew, versions, draftVersion, liveVersion]);
|
|
270
|
+
const changeLines = useMemo(() => (showChanges && compareTo?.definition ? changeLinesBetween(compareTo.definition, baseline) : []), [showChanges, compareTo, baseline]);
|
|
271
|
+
|
|
272
|
+
const inputsAsked = useMemo(() => Object.entries(def.inputs ?? {}).map(([name, spec]) => ({ name, label: spec.label })), [def.inputs]);
|
|
273
|
+
|
|
274
|
+
/* ---- editing ------------------------------------------------------- */
|
|
275
|
+
|
|
276
|
+
const insert = (slot, kind) => {
|
|
277
|
+
const r = addStep(def, slot, kind);
|
|
278
|
+
commit(r.def);
|
|
279
|
+
setSelected(r.id);
|
|
280
|
+
setReveal(r.id);
|
|
281
|
+
};
|
|
282
|
+
const addAtSelection = (kind) => {
|
|
283
|
+
const slot = (selected && selected !== "trigger" && selected !== "flow" ? slotAfter(def, selected) : null) ?? slotAtEnd(def);
|
|
284
|
+
insert(slot, kind);
|
|
285
|
+
};
|
|
286
|
+
const selectedStep = selected && selected !== "trigger" && selected !== "flow" ? findStep(def.steps, selected) : null;
|
|
287
|
+
|
|
288
|
+
/* ---- the server ---------------------------------------------------- */
|
|
289
|
+
|
|
290
|
+
// `again` says how to ask a second time, deliberately without the test, when
|
|
291
|
+
// the platform answers needs_test: { kind: "keep" } or { kind: "live", on }.
|
|
292
|
+
const run = async (kind, work, ok, again = null) => {
|
|
293
|
+
setBusy(kind);
|
|
294
|
+
setNote(null);
|
|
295
|
+
setSkipAsk(false);
|
|
296
|
+
try {
|
|
297
|
+
const r = await work();
|
|
298
|
+
const bad = said(r, "That did not work.");
|
|
299
|
+
if (bad && bad.code === "changed" && (kind === "save" || kind === "test")) {
|
|
300
|
+
// Someone (the agent, another tab) saved a newer version: the strip says so.
|
|
301
|
+
setConflict({ latest: bad.latest });
|
|
302
|
+
setKeptOver(null);
|
|
303
|
+
} else if (bad) setNote({ tone: "bad", text: bad.text, lines: bad.lines, code: bad.code, again: bad.code === "needs_test" ? again : null });
|
|
304
|
+
else {
|
|
305
|
+
if (kind === "save") {
|
|
306
|
+
setConflict(null);
|
|
307
|
+
if (versionOf(r) !== null) setBegun(versionOf(r));
|
|
308
|
+
}
|
|
309
|
+
if (ok) setNote({ tone: "ok", text: ok });
|
|
310
|
+
}
|
|
311
|
+
} catch (e) {
|
|
312
|
+
if (e?.code === "changed" && (kind === "save" || kind === "test")) {
|
|
313
|
+
setConflict({ latest: Number.isInteger(e.latest) ? e.latest : null });
|
|
314
|
+
setKeptOver(null);
|
|
315
|
+
} else setNote({ tone: "bad", text: e?.message || "That did not work.", lines: Array.isArray(e?.problems) ? e.problems : [] });
|
|
316
|
+
} finally {
|
|
317
|
+
setBusy(null);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const save = () => run("save", () => onSave?.(def, { baseVersion: begun }), "Saved as a draft.");
|
|
322
|
+
// "Save on top of vN": the member's deliberate press, naming the newest version as the base.
|
|
323
|
+
const saveOnTop = () => run("save", () => onSave?.(def, { baseVersion: sawNewer ?? begun }), "Saved as a draft.");
|
|
324
|
+
// Replace the working copy with the saved one, as one step Undo can take back.
|
|
325
|
+
const lookAtNewer = () => {
|
|
326
|
+
looked.current = { def: canonicalJson(def), begun };
|
|
327
|
+
commit(baseline, false);
|
|
328
|
+
setBegun(latest);
|
|
329
|
+
setConflict(null);
|
|
330
|
+
setKeptOver(null);
|
|
331
|
+
setShowChanges(false);
|
|
332
|
+
};
|
|
333
|
+
const test = async (values) => {
|
|
334
|
+
setAsking(false);
|
|
335
|
+
await run("test", async () => {
|
|
336
|
+
if (dirty) {
|
|
337
|
+
const r = await onSave?.(def, { baseVersion: begun });
|
|
338
|
+
if (r && r.ok === false) return r;
|
|
339
|
+
if (versionOf(r) !== null) setBegun(versionOf(r));
|
|
340
|
+
}
|
|
341
|
+
return onTest?.(values);
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
const goLive = (on, opts) => run("live", () => (opts ? onLive?.(on, opts) : onLive?.(on)), undefined, { kind: "live", on });
|
|
345
|
+
const keep = (opts) => run("keep", () => (opts ? onKeep?.(opts) : onKeep?.()), "Kept. This version runs now.", { kind: "keep" });
|
|
346
|
+
// The member's deliberate second press: the same callback again, with the skip.
|
|
347
|
+
const skipTheTest = () => {
|
|
348
|
+
const again = note?.again;
|
|
349
|
+
if (!again) return;
|
|
350
|
+
if (again.kind === "keep") void keep({ skipTest: true });
|
|
351
|
+
else void goLive(again.on, { skipTest: true });
|
|
352
|
+
};
|
|
353
|
+
// The member said yes to the question: the platform answers, and a refusal is shown as it is.
|
|
354
|
+
const confirmDelete = () => {
|
|
355
|
+
setDeleting(false);
|
|
356
|
+
return run("delete", () => onDelete?.({ id: automationId, name: baseline.name, status }));
|
|
357
|
+
};
|
|
358
|
+
const pressTest = () => {
|
|
359
|
+
if (Object.keys(def.inputs ?? {}).length > 0) setAsking(true);
|
|
360
|
+
else void test({});
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const onKeyDown = (e) => {
|
|
364
|
+
if (e.target.closest?.("input, textarea, select, [contenteditable]")) return;
|
|
365
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "z") {
|
|
366
|
+
e.preventDefault();
|
|
367
|
+
if (e.shiftKey) redo();
|
|
368
|
+
else undo();
|
|
369
|
+
} else if ((e.key === "Delete" || e.key === "Backspace") && selectedStep && canEdit) {
|
|
370
|
+
e.preventDefault();
|
|
371
|
+
commit(removeStep(def, selectedStep.id), false);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
/* ---- the panel ----------------------------------------------------- */
|
|
376
|
+
|
|
377
|
+
let panel = null;
|
|
378
|
+
if (selected === "trigger") {
|
|
379
|
+
panel = (
|
|
380
|
+
<TriggerPicker
|
|
381
|
+
trigger={def.trigger}
|
|
382
|
+
readOnly={!canEdit}
|
|
383
|
+
catalog={triggers}
|
|
384
|
+
ownedByApp={ownedByApp}
|
|
385
|
+
selfKey={selfKey ?? def.key ?? null}
|
|
386
|
+
hook={hook}
|
|
387
|
+
onRotateHook={onRotateHook}
|
|
388
|
+
onChange={(trigger) => commit({ ...def, trigger })}
|
|
389
|
+
onClose={() => setSelected(null)}
|
|
390
|
+
/>
|
|
391
|
+
);
|
|
392
|
+
} else if (selected === "flow") {
|
|
393
|
+
panel = <FlowSettings def={def} readOnly={!canEdit} versions={versions} liveVersion={liveVersion} onChange={commit} onClose={() => setSelected(null)} onReset={onReset} />;
|
|
394
|
+
} else if (selectedStep) {
|
|
395
|
+
panel = (
|
|
396
|
+
<StepSettings
|
|
397
|
+
key={selectedStep.id}
|
|
398
|
+
step={selectedStep}
|
|
399
|
+
available={{ ...availableBefore(def, selectedStep.id, stepTitle), inputs: inputsAsked }}
|
|
400
|
+
samples={samples}
|
|
401
|
+
apps={apps}
|
|
402
|
+
actions={actions}
|
|
403
|
+
ownedByApp={ownedByApp}
|
|
404
|
+
problems={issuesFor(issues, selectedStep.id)}
|
|
405
|
+
readOnly={!canEdit}
|
|
406
|
+
onChange={(patch) => commit(updateStep(def, selectedStep.id, patch))}
|
|
407
|
+
onRemove={() => commit(removeStep(def, selectedStep.id), false)}
|
|
408
|
+
onDuplicate={() => {
|
|
409
|
+
const d = duplicateStep(def, selectedStep.id);
|
|
410
|
+
if (d) {
|
|
411
|
+
commit(d.def);
|
|
412
|
+
setSelected(d.id);
|
|
413
|
+
setReveal(d.id);
|
|
414
|
+
}
|
|
415
|
+
}}
|
|
416
|
+
onClose={() => setSelected(null)}
|
|
417
|
+
/>
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const chip = isNew
|
|
422
|
+
? { tone: "grey", text: "New" }
|
|
423
|
+
: !hasLive
|
|
424
|
+
? { tone: "amber", text: "Draft" }
|
|
425
|
+
: canKeep
|
|
426
|
+
? { tone: "amber", text: "Draft ahead" }
|
|
427
|
+
: live
|
|
428
|
+
? { tone: "green", text: "Live" }
|
|
429
|
+
: { tone: "grey", text: "Off" };
|
|
430
|
+
|
|
431
|
+
const cost = estimate && !dirty ? creditsWords(estimate.credits) : null;
|
|
432
|
+
const testTitle = isNew
|
|
433
|
+
? "Create it first"
|
|
434
|
+
: issues.length
|
|
435
|
+
? "Fix what is marked first"
|
|
436
|
+
: estimate
|
|
437
|
+
? `Reads your real data; nothing is sent or changed. About ${creditsWords(estimate.credits)}, at most ${creditsWords(estimate.credits_max)}.`
|
|
438
|
+
: "Reads your real data; nothing is sent or changed";
|
|
439
|
+
const tested = testChip(tests);
|
|
440
|
+
|
|
441
|
+
return (
|
|
442
|
+
<div onKeyDown={onKeyDown} className={cx("@container flex min-h-0 min-w-0 flex-1 flex-col bg-background text-foreground", className)}>
|
|
443
|
+
<header className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1.5 border-b border-border px-3 py-2">
|
|
444
|
+
{onBack ? (
|
|
445
|
+
<Button size="sm" variant="quiet" icon="arrow-left" onClick={onBack}>
|
|
446
|
+
{backLabel}
|
|
447
|
+
</Button>
|
|
448
|
+
) : null}
|
|
449
|
+
<label className="min-w-[10rem] max-w-md flex-1">
|
|
450
|
+
<span className="sr-only">Name</span>
|
|
451
|
+
<input
|
|
452
|
+
value={def.name}
|
|
453
|
+
maxLength={120}
|
|
454
|
+
placeholder="Name this automation"
|
|
455
|
+
readOnly={!canEdit}
|
|
456
|
+
onChange={(e) => commit({ ...def, name: e.target.value })}
|
|
457
|
+
className="min-h-9 w-full rounded-md border border-transparent bg-transparent px-2 text-sm font-semibold outline-none hover:border-border focus-visible:border-input focus-visible:ring-2 focus-visible:ring-ring"
|
|
458
|
+
/>
|
|
459
|
+
</label>
|
|
460
|
+
<Chip tone={edited && !isNew ? "amber" : chip.tone}>{edited && !isNew ? "Unsaved changes" : chip.text}</Chip>
|
|
461
|
+
{tested ? <Chip tone={tested.tone}>{tested.text}</Chip> : null}
|
|
462
|
+
{tabs}
|
|
463
|
+
<span className="ml-auto" />
|
|
464
|
+
{!isNew && runs.length > 0 ? <RunPicker runs={runs} onPick={onOpenRun} /> : null}
|
|
465
|
+
{canEdit ? (
|
|
466
|
+
<>
|
|
467
|
+
<span title={hasLive ? undefined : "Keep a version first"}>
|
|
468
|
+
<Switch checked={live} disabled={!hasLive || busy !== null || dirty} label="Live" onChange={(e) => void goLive(e.target.checked)} />
|
|
469
|
+
</span>
|
|
470
|
+
<Button size="sm" variant="secondary" icon="play" disabled={isNew || busy !== null || issues.length > 0 || def.steps.length === 0} onClick={pressTest} title={testTitle}>
|
|
471
|
+
Test run
|
|
472
|
+
{cost ? <span className="font-normal text-muted-foreground">about {cost}</span> : null}
|
|
473
|
+
</Button>
|
|
474
|
+
{canKeep ? (
|
|
475
|
+
<Button size="sm" variant="secondary" disabled={busy !== null} onClick={() => void keep()}>
|
|
476
|
+
Keep this version
|
|
477
|
+
</Button>
|
|
478
|
+
) : null}
|
|
479
|
+
<Button size="sm" variant="primary" loading={busy === "save"} disabled={busy !== null || !dirty || issues.length > 0 || !def.name.trim()} onClick={() => void save()} title={issues.length ? "Fix what is marked first" : undefined}>
|
|
480
|
+
{isNew ? "Create" : "Save"}
|
|
481
|
+
</Button>
|
|
482
|
+
{!isNew && onDelete && canDelete ? (
|
|
483
|
+
<Menu
|
|
484
|
+
label="More"
|
|
485
|
+
items={
|
|
486
|
+
source === "legacy" || source === "package"
|
|
487
|
+
? [{ label: "Delete automation", icon: "trash", disabled: true, description: "Comes with the app. Switch it off, or remove it in the app's settings." }]
|
|
488
|
+
: [{ label: "Delete automation", icon: "trash", tone: "danger", onSelect: () => setDeleting(true) }]
|
|
489
|
+
}
|
|
490
|
+
/>
|
|
491
|
+
) : null}
|
|
492
|
+
</>
|
|
493
|
+
) : null}
|
|
494
|
+
</header>
|
|
495
|
+
|
|
496
|
+
{agentDraft ? (
|
|
497
|
+
<div role="status" className="border-b border-border bg-muted px-3 py-1.5 text-[13px] text-muted-foreground">
|
|
498
|
+
<span>
|
|
499
|
+
Drawn by the agent{agentDraft.note ? <> · from: {agentDraft.note}</> : null}
|
|
500
|
+
</span>
|
|
501
|
+
{compareTo?.definition ? (
|
|
502
|
+
<>
|
|
503
|
+
{" · "}
|
|
504
|
+
<button type="button" aria-expanded={showChanges} onClick={() => setShowChanges((v) => !v)} className="text-foreground underline underline-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
505
|
+
What changed
|
|
506
|
+
</button>
|
|
507
|
+
</>
|
|
508
|
+
) : null}
|
|
509
|
+
{showChanges && compareTo?.definition ? (
|
|
510
|
+
<div className="mt-1">
|
|
511
|
+
<p className="m-0 text-xs">Compared with {compareTo.version ? `v${compareTo.version}` : "the version you kept"}:</p>
|
|
512
|
+
{changeLines.length ? (
|
|
513
|
+
<ul className="m-0 mt-0.5 list-disc pl-5">
|
|
514
|
+
{changeLines.map((l) => (
|
|
515
|
+
<li key={l}>{l}</li>
|
|
516
|
+
))}
|
|
517
|
+
</ul>
|
|
518
|
+
) : (
|
|
519
|
+
<p className="m-0 mt-0.5">Nothing in the steps differs.</p>
|
|
520
|
+
)}
|
|
521
|
+
</div>
|
|
522
|
+
) : null}
|
|
523
|
+
</div>
|
|
524
|
+
) : null}
|
|
525
|
+
|
|
526
|
+
{strip && canEdit ? (
|
|
527
|
+
<div role="status" className="flex flex-wrap items-center gap-2 border-b border-warning/50 bg-warning/10 px-3 py-2 text-sm">
|
|
528
|
+
<span className="min-w-0 flex-1">{newerVersionWords(sawNewer, versions)}</span>
|
|
529
|
+
{strip === "conflict" ? (
|
|
530
|
+
<Button size="sm" variant="secondary" disabled={busy !== null} onClick={() => void saveOnTop()}>
|
|
531
|
+
Save on top of v{sawNewer}
|
|
532
|
+
</Button>
|
|
533
|
+
) : null}
|
|
534
|
+
<Button size="sm" variant="secondary" disabled={busy !== null} onClick={lookAtNewer}>
|
|
535
|
+
Look at v{sawNewer}
|
|
536
|
+
</Button>
|
|
537
|
+
{strip === "behind" ? (
|
|
538
|
+
<Button size="sm" variant="quiet" onClick={() => setKeptOver(latest)}>
|
|
539
|
+
Keep mine
|
|
540
|
+
</Button>
|
|
541
|
+
) : null}
|
|
542
|
+
</div>
|
|
543
|
+
) : null}
|
|
544
|
+
|
|
545
|
+
{note || (issues.length > 0 && canEdit) ? (
|
|
546
|
+
<div
|
|
547
|
+
role={note?.tone === "bad" ? "alert" : "status"}
|
|
548
|
+
className={cx("border-b px-3 py-2 text-sm", note?.tone === "bad" ? "border-destructive/50 bg-destructive/10" : note?.tone === "ok" ? "border-success/40 bg-success/10" : "border-warning/50 bg-warning/10")}
|
|
549
|
+
>
|
|
550
|
+
{note ? (
|
|
551
|
+
<>
|
|
552
|
+
<span>{note.text}</span>
|
|
553
|
+
{note.code === "needs_test" && note.again && canEdit ? (
|
|
554
|
+
skipAsk ? (
|
|
555
|
+
<div className="mt-2 flex flex-wrap items-center gap-2">
|
|
556
|
+
<span>Skip the test? It will run for real the first time it starts.</span>
|
|
557
|
+
<Button size="sm" variant="secondary" disabled={busy !== null} onClick={skipTheTest}>
|
|
558
|
+
Skip the test
|
|
559
|
+
</Button>
|
|
560
|
+
<Button size="sm" variant="quiet" onClick={() => setSkipAsk(false)}>
|
|
561
|
+
Cancel
|
|
562
|
+
</Button>
|
|
563
|
+
</div>
|
|
564
|
+
) : (
|
|
565
|
+
<div className="mt-2 flex flex-wrap items-center gap-2">
|
|
566
|
+
<Button size="sm" variant="secondary" icon="play" disabled={busy !== null || isNew || issues.length > 0 || def.steps.length === 0} onClick={pressTest}>
|
|
567
|
+
Run a test
|
|
568
|
+
</Button>
|
|
569
|
+
<Button size="sm" variant="quiet" disabled={busy !== null} onClick={() => setSkipAsk(true)}>
|
|
570
|
+
{note.again.kind === "keep" ? "Keep without a test" : "Go live without a test"}
|
|
571
|
+
</Button>
|
|
572
|
+
</div>
|
|
573
|
+
)
|
|
574
|
+
) : null}
|
|
575
|
+
{note.lines?.length ? (
|
|
576
|
+
<ul className="m-0 mt-1 list-disc pl-5 text-[13px]">
|
|
577
|
+
{note.lines.slice(0, 3).map((l) => (
|
|
578
|
+
<li key={l}>{l}</li>
|
|
579
|
+
))}
|
|
580
|
+
</ul>
|
|
581
|
+
) : null}
|
|
582
|
+
</>
|
|
583
|
+
) : (
|
|
584
|
+
<>
|
|
585
|
+
<span>{issues.length === 1 ? "One thing to fix before it can be saved." : `${issues.length} things to fix before it can be saved.`}</span>
|
|
586
|
+
<ul className="m-0 mt-1 list-disc pl-5 text-[13px]">
|
|
587
|
+
{issues.slice(0, 3).map((i) => (
|
|
588
|
+
<li key={i.path + i.message}>
|
|
589
|
+
{i.stepId ? (
|
|
590
|
+
<button type="button" onClick={() => setSelected(i.stepId)} className="text-left underline underline-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
591
|
+
{i.message}
|
|
592
|
+
</button>
|
|
593
|
+
) : (
|
|
594
|
+
i.message
|
|
595
|
+
)}
|
|
596
|
+
</li>
|
|
597
|
+
))}
|
|
598
|
+
</ul>
|
|
599
|
+
</>
|
|
600
|
+
)}
|
|
601
|
+
</div>
|
|
602
|
+
) : null}
|
|
603
|
+
|
|
604
|
+
{canEdit && suggestions.length > 0 && (onKeepSuggestion || onDropSuggestion) ? (
|
|
605
|
+
<div className="grid max-h-[40%] gap-2 overflow-auto border-b border-border px-3 py-2">
|
|
606
|
+
{suggestions.slice(0, 2).map((s) => (
|
|
607
|
+
<FixProposal
|
|
608
|
+
key={s.id}
|
|
609
|
+
proposal={s}
|
|
610
|
+
def={def}
|
|
611
|
+
blocked={dirty && !isNew ? "Save or undo your changes first, so what is kept is what you see." : null}
|
|
612
|
+
onKeep={onKeepSuggestion ? () => onKeepSuggestion(s.id) : undefined}
|
|
613
|
+
onDrop={onDropSuggestion ? () => onDropSuggestion(s.id) : undefined}
|
|
614
|
+
/>
|
|
615
|
+
))}
|
|
616
|
+
{suggestions.length > 2 ? <p className="m-0 text-xs text-muted-foreground">{suggestions.length - 2} more suggestions after these.</p> : null}
|
|
617
|
+
</div>
|
|
618
|
+
) : null}
|
|
619
|
+
|
|
620
|
+
<div className="relative flex min-h-0 flex-1">
|
|
621
|
+
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
|
622
|
+
<FlowEditor
|
|
623
|
+
def={def}
|
|
624
|
+
selectedId={selected}
|
|
625
|
+
onSelect={setSelected}
|
|
626
|
+
editable={canEdit}
|
|
627
|
+
onInsert={insert}
|
|
628
|
+
onAddAtSelection={addAtSelection}
|
|
629
|
+
flagged={flagged}
|
|
630
|
+
prices={prices}
|
|
631
|
+
fitKey={automationId ?? "new"}
|
|
632
|
+
revealId={reveal}
|
|
633
|
+
runnable={runnable}
|
|
634
|
+
undo={canEdit ? { canUndo: hist.past.length > 0, canRedo: hist.future.length > 0, onUndo: undo, onRedo: redo } : null}
|
|
635
|
+
onOpenFlow={() => setSelected("flow")}
|
|
636
|
+
/>
|
|
637
|
+
</div>
|
|
638
|
+
{panel ? (
|
|
639
|
+
<div className="absolute inset-x-0 bottom-0 z-10 flex max-h-[75%] flex-col overflow-hidden rounded-t-xl border-t border-border shadow-lg [&>aside]:min-h-0 [&>aside]:flex-1 @3xl:relative @3xl:inset-auto @3xl:max-h-none @3xl:w-[380px] @3xl:shrink-0 @3xl:rounded-none @3xl:border-l @3xl:border-t-0 @3xl:shadow-none">{panel}</div>
|
|
640
|
+
) : null}
|
|
641
|
+
</div>
|
|
642
|
+
|
|
643
|
+
<Confirm
|
|
644
|
+
open={deleting}
|
|
645
|
+
onClose={() => setDeleting(false)}
|
|
646
|
+
onConfirm={() => void confirmDelete()}
|
|
647
|
+
title="Delete this automation?"
|
|
648
|
+
body={deleteWords(baseline.name, status)}
|
|
649
|
+
confirmLabel="Delete"
|
|
650
|
+
tone="danger"
|
|
651
|
+
/>
|
|
652
|
+
|
|
653
|
+
{asking ? <RunInputs open inputs={def.inputs} onCancel={() => setAsking(false)} onRun={(v) => void test(v)} /> : null}
|
|
654
|
+
</div>
|
|
655
|
+
);
|
|
656
|
+
}
|