@agent-native/core 0.92.6 → 0.92.8
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +18 -0
- package/corpus/core/docs/content/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/pure-agent-apps.mdx +22 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builtin.ts +1 -1
- package/corpus/core/src/agent/model-config.ts +30 -30
- package/corpus/core/src/agent/production-agent.ts +129 -7
- package/corpus/core/src/cli/design-connect.ts +438 -83
- package/corpus/core/src/cli/recap.ts +2 -2
- package/corpus/core/src/cli/sync-builder-starter-manifest.ts +1 -1
- package/corpus/core/src/client/AgentPanel.tsx +8 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +12 -7
- package/corpus/core/src/client/composer/TiptapComposer.tsx +1 -1
- package/corpus/core/src/client/index.ts +4 -0
- package/corpus/core/src/client/org/TeamPage.tsx +41 -26
- package/corpus/core/src/client/use-action.ts +113 -1
- package/corpus/core/src/db/client.ts +12 -2
- package/corpus/core/src/db/create-get-db.ts +4 -1
- package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
- package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
- package/corpus/core/src/server/transcribe-voice.ts +1 -1
- package/corpus/core/src/usage/store.ts +13 -4
- package/corpus/core/src/vite/client.ts +38 -1
- package/corpus/templates/analytics/AGENTS.md +3 -0
- package/corpus/templates/analytics/actions/get-data-program.ts +78 -0
- package/corpus/templates/analytics/actions/list-data-programs.ts +49 -0
- package/corpus/templates/analytics/app/global.css +11 -0
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +163 -17
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +20 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +96 -3
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +61 -4
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +40 -2
- package/corpus/templates/analytics/changelog/2026-07-09-analytics-no-longer-forgets-successful-data-queries-when-a-l.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-keep-consistent-spacing-when-they-stack-ver.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-tables-and-extensions-can-be-selected-and-d.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-long-running-analytics-requests-now-recover-automatically-wh.md +6 -0
- package/corpus/templates/analytics/server/db/index.ts +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +1 -7
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +102 -15
- package/corpus/templates/content/app/hooks/use-content-database.ts +52 -0
- package/corpus/templates/content/app/hooks/use-document-properties.ts +50 -6
- package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/content/app/i18n-data.ts +28 -0
- package/corpus/templates/content/changelog/2026-07-09-notion-conflict-warnings-no-longer-flash-during-normal-synce.md +6 -0
- package/corpus/templates/content/changelog/2026-07-09-property-menus-and-delete-confirmations-now-open-visibly-and.md +6 -0
- package/corpus/templates/content/server/lib/notion-sync.ts +130 -28
- package/corpus/templates/design/actions/add-breakpoint.ts +48 -68
- package/corpus/templates/design/actions/add-localhost-screens.ts +496 -61
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +2 -18
- package/corpus/templates/design/actions/apply-design-token-edit.ts +51 -82
- package/corpus/templates/design/actions/apply-tweaks.ts +63 -41
- package/corpus/templates/design/actions/capture-design-state.ts +2 -2
- package/corpus/templates/design/actions/connect-localhost.ts +58 -12
- package/corpus/templates/design/actions/create-component.ts +2 -18
- package/corpus/templates/design/actions/create-design-branch.ts +38 -18
- package/corpus/templates/design/actions/create-fusion-app.ts +15 -11
- package/corpus/templates/design/actions/delete-file.ts +19 -34
- package/corpus/templates/design/actions/deploy-design-preview.ts +59 -27
- package/corpus/templates/design/actions/deploy-fusion-app.ts +22 -15
- package/corpus/templates/design/actions/export-pdf.ts +2 -1
- package/corpus/templates/design/actions/export-zip.ts +9 -3
- package/corpus/templates/design/actions/generate-design.ts +107 -70
- package/corpus/templates/design/actions/get-component-details.ts +2 -18
- package/corpus/templates/design/actions/get-design-branch-diff.ts +2 -3
- package/corpus/templates/design/actions/get-design-surface-index.ts +2 -18
- package/corpus/templates/design/actions/get-design.ts +2 -1
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +7 -1
- package/corpus/templates/design/actions/import-design-tokens.ts +114 -76
- package/corpus/templates/design/actions/index-components.ts +2 -18
- package/corpus/templates/design/actions/list-design-source-capabilities.ts +23 -28
- package/corpus/templates/design/actions/list-local-files.ts +9 -2
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +82 -98
- package/corpus/templates/design/actions/open-component-source.ts +2 -18
- package/corpus/templates/design/actions/open-visual-edit.ts +19 -1
- package/corpus/templates/design/actions/present-design-variants.ts +70 -70
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +4 -14
- package/corpus/templates/design/actions/read-local-file.ts +9 -2
- package/corpus/templates/design/actions/remove-breakpoint.ts +40 -61
- package/corpus/templates/design/actions/rename-screen.ts +425 -0
- package/corpus/templates/design/actions/request-localhost-write-consent.ts +12 -2
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +9 -2
- package/corpus/templates/design/actions/sync-fusion-app.ts +56 -39
- package/corpus/templates/design/actions/update-design.ts +376 -61
- package/corpus/templates/design/actions/update-file.ts +455 -188
- package/corpus/templates/design/actions/write-local-file.ts +10 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +685 -608
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +43 -15
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +576 -2015
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +62 -14
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +263 -25
- package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +34 -0
- package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +40 -0
- package/corpus/templates/design/app/components/design/design-canvas/creation.ts +47 -0
- package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +27 -0
- package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +62 -0
- package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +137 -0
- package/corpus/templates/design/app/components/design/design-canvas/file-drop.ts +10 -0
- package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +23 -0
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +23 -0
- package/corpus/templates/design/app/components/design/design-canvas/iframe-pan.ts +177 -0
- package/corpus/templates/design/app/components/design/design-canvas/motion-types.ts +7 -0
- package/corpus/templates/design/app/components/design/design-canvas/pending-text-edit.ts +62 -0
- package/corpus/templates/design/app/components/design/index.ts +2 -2
- package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +100 -0
- package/corpus/templates/design/app/components/design/multi-screen/coordinate-transforms.ts +81 -0
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +23 -13
- package/corpus/templates/design/app/components/design/multi-screen/culling.ts +238 -19
- package/corpus/templates/design/app/components/design/multi-screen/draft-primitives.ts +424 -0
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +341 -0
- package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +52 -0
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +62 -0
- package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +283 -0
- package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +272 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +5 -1
- package/corpus/templates/design/app/components/design/multi-screen/wheel-gesture-state.ts +12 -0
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +325 -84
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +15 -1
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +119 -47
- package/corpus/templates/design/app/i18n/zh-TW.ts +9 -0
- package/corpus/templates/design/app/i18n-data.ts +223 -0
- package/corpus/templates/design/app/lib/design-save-outbox.ts +379 -0
- package/corpus/templates/design/app/lib/png-clipboard.ts +113 -0
- package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +229 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +918 -0
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +43 -0
- package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +336 -0
- package/corpus/templates/design/app/pages/design-editor/data-operations.ts +270 -0
- package/corpus/templates/design/app/pages/design-editor/dom-guards.ts +24 -0
- package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +11 -0
- package/corpus/templates/design/app/pages/design-editor/editor-session.ts +20 -0
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +408 -0
- package/corpus/templates/design/app/pages/design-editor/export-capture.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +93 -0
- package/corpus/templates/design/app/pages/design-editor/history.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +185 -0
- package/corpus/templates/design/app/pages/design-editor/motion-state.ts +309 -0
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +438 -0
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +510 -0
- package/corpus/templates/design/app/pages/design-editor/portable-style.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +337 -0
- package/corpus/templates/design/app/pages/design-editor/style-utils.ts +5 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +92 -0
- package/corpus/templates/design/app/pages/design-editor/tweak-save.ts +87 -0
- package/corpus/templates/design/app/pages/design-editor/types.ts +36 -0
- package/corpus/templates/design/app/routes/design.$id.tsx +11 -1
- package/corpus/templates/design/changelog/2026-07-09-design-edits-canvas-positions-tools-and-tab-context-now-pers.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-figma-style-shortcuts-drawing-annotations-and-cross-screen-d.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-hand-and-scale-tools-now-keep-their-active-toolbar-identity-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-large-multi-screen-designs-now-stay-responsive-during-long-c.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layer-drags-now-land-exactly-where-the-insertion-indicator-s.md +6 -0
- package/corpus/templates/design/server/db/index.ts +2 -52
- package/corpus/templates/design/server/db/schema.ts +16 -0
- package/corpus/templates/design/server/lib/design-data-access.ts +70 -0
- package/corpus/templates/design/server/lib/design-data-mutation.ts +249 -0
- package/corpus/templates/design/server/lib/fusion-screens.ts +89 -76
- package/corpus/templates/design/server/lib/import-design-files.ts +76 -35
- package/corpus/templates/design/server/lib/verify-write-grant.ts +7 -2
- package/corpus/templates/design/server/plugins/db.ts +19 -0
- package/corpus/templates/design/server/source-workspace.ts +12 -13
- package/corpus/templates/design/shared/resolve-tweaks.ts +15 -0
- package/corpus/templates/design/shared/screen-rename.ts +42 -0
- package/corpus/templates/design/shared/source-mode.ts +84 -4
- package/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builtin.js +1 -1
- package/dist/agent/engine/builtin.js.map +1 -1
- package/dist/agent/model-config.d.ts +9 -9
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +30 -30
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts +60 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +106 -7
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/design-connect.d.ts +30 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +359 -75
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/recap.js +2 -2
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/sync-builder-starter-manifest.js +1 -1
- package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +10 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/composer/TiptapComposer.js +1 -1
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +6 -5
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/use-action.d.ts +32 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +68 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +3 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +7 -2
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +2 -2
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.js +1 -1
- package/dist/server/transcribe-voice.js.map +1 -1
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +13 -4
- package/dist/usage/store.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +36 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
- package/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
- package/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
- package/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
- package/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
- package/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
- package/docs/content/pr-visual-recap.mdx +3 -3
- package/docs/content/pure-agent-apps.mdx +22 -0
- package/package.json +2 -2
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
applyText,
|
|
4
|
+
hasCollabState,
|
|
5
|
+
seedFromText,
|
|
6
|
+
} from "@agent-native/core/collab";
|
|
7
|
+
import { isPostgres } from "@agent-native/core/db";
|
|
8
|
+
import { accessFilter, assertAccess } from "@agent-native/core/sharing";
|
|
9
|
+
import { and, eq, isNull, sql } from "drizzle-orm";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
13
|
+
import { isProbablyHtmlDocumentContent } from "../shared/html-content.js";
|
|
14
|
+
import {
|
|
15
|
+
renameFilenamePreservingExtension,
|
|
16
|
+
replaceDataScreenReferences,
|
|
17
|
+
} from "../shared/screen-rename.js";
|
|
18
|
+
import { sourceContentHash } from "../shared/source-workspace.js";
|
|
19
|
+
|
|
20
|
+
const MAX_RENAME_ATTEMPTS = 5;
|
|
21
|
+
const MAX_CONTENT_OVERRIDE_BYTES = 2_000_000;
|
|
22
|
+
const MAX_TOTAL_CONTENT_OVERRIDE_BYTES = 4_000_000;
|
|
23
|
+
const renameLocks = new Map<string, Promise<unknown>>();
|
|
24
|
+
|
|
25
|
+
class ScreenRenameConflictError extends Error {
|
|
26
|
+
constructor(message = "Screen content changed during rename. Please retry.") {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "ScreenRenameConflictError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function withScreenRenameLock<T>(
|
|
33
|
+
designId: string,
|
|
34
|
+
callback: () => Promise<T>,
|
|
35
|
+
): Promise<T> {
|
|
36
|
+
const previous = renameLocks.get(designId) ?? Promise.resolve();
|
|
37
|
+
const next = previous.then(callback, callback);
|
|
38
|
+
renameLocks.set(designId, next);
|
|
39
|
+
const cleanup = () => {
|
|
40
|
+
if (renameLocks.get(designId) === next) renameLocks.delete(designId);
|
|
41
|
+
};
|
|
42
|
+
next.then(cleanup, cleanup);
|
|
43
|
+
return next;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function nextUpdatedAt(current: string | null, now: Date): string {
|
|
47
|
+
const currentMs = current ? Date.parse(current) : Number.NaN;
|
|
48
|
+
const nextMs = Number.isFinite(currentMs)
|
|
49
|
+
? Math.max(now.getTime(), currentMs + 1)
|
|
50
|
+
: now.getTime();
|
|
51
|
+
return new Date(nextMs).toISOString();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isRetryableTransactionConflict(error: unknown): boolean {
|
|
55
|
+
if (error instanceof ScreenRenameConflictError) return true;
|
|
56
|
+
const rawCode =
|
|
57
|
+
error && typeof error === "object" && "code" in error
|
|
58
|
+
? (error as { code?: unknown }).code
|
|
59
|
+
: undefined;
|
|
60
|
+
const code =
|
|
61
|
+
typeof rawCode === "string" || typeof rawCode === "number"
|
|
62
|
+
? String(rawCode)
|
|
63
|
+
: "";
|
|
64
|
+
return (
|
|
65
|
+
code === "SQLITE_BUSY" ||
|
|
66
|
+
code === "SQLITE_LOCKED" ||
|
|
67
|
+
code === "40001" ||
|
|
68
|
+
code === "40P01"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function assertValidFilename(filename: string): void {
|
|
73
|
+
let containsControlCharacter = false;
|
|
74
|
+
for (let index = 0; index < filename.length; index += 1) {
|
|
75
|
+
const codeUnit = filename.charCodeAt(index);
|
|
76
|
+
if (codeUnit <= 0x1f || codeUnit === 0x7f) {
|
|
77
|
+
containsControlCharacter = true;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (
|
|
82
|
+
!filename ||
|
|
83
|
+
filename.length > 255 ||
|
|
84
|
+
filename === "." ||
|
|
85
|
+
filename === ".." ||
|
|
86
|
+
filename.includes("..") ||
|
|
87
|
+
filename.includes("/") ||
|
|
88
|
+
filename.includes("\\") ||
|
|
89
|
+
containsControlCharacter
|
|
90
|
+
) {
|
|
91
|
+
throw new Error("Invalid filename: use a plain name without path segments");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const contentOverrideSchema = z.object({
|
|
96
|
+
fileId: z.string().min(1).max(256),
|
|
97
|
+
content: z.string().max(MAX_CONTENT_OVERRIDE_BYTES),
|
|
98
|
+
expectedVersionHash: z.string().min(1).max(256),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
type RenamedFileResult = {
|
|
102
|
+
id: string;
|
|
103
|
+
filename: string;
|
|
104
|
+
content: string;
|
|
105
|
+
updatedAt: string;
|
|
106
|
+
contentChanged: boolean;
|
|
107
|
+
referenceRewritten: boolean;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default defineAction({
|
|
111
|
+
description:
|
|
112
|
+
"Atomically rename one Design screen and rewrite exact data-screen links in every HTML screen.",
|
|
113
|
+
agentTool: false,
|
|
114
|
+
schema: z
|
|
115
|
+
.object({
|
|
116
|
+
id: z.string().min(1).max(256).describe("design_files.id to rename"),
|
|
117
|
+
name: z
|
|
118
|
+
.string()
|
|
119
|
+
.max(255)
|
|
120
|
+
.describe(
|
|
121
|
+
"New screen display name; the existing extension is preserved",
|
|
122
|
+
),
|
|
123
|
+
requestSource: z
|
|
124
|
+
.string()
|
|
125
|
+
.min(1)
|
|
126
|
+
.max(256)
|
|
127
|
+
.optional()
|
|
128
|
+
.describe(
|
|
129
|
+
"Stable browser tab id used for collaboration echo suppression",
|
|
130
|
+
),
|
|
131
|
+
contentOverrides: z
|
|
132
|
+
.array(contentOverrideSchema)
|
|
133
|
+
.max(100)
|
|
134
|
+
.optional()
|
|
135
|
+
.default([])
|
|
136
|
+
.describe(
|
|
137
|
+
"Fresh unsaved HTML snapshots from this client, guarded by their persisted base hashes.",
|
|
138
|
+
),
|
|
139
|
+
})
|
|
140
|
+
.superRefine((value, ctx) => {
|
|
141
|
+
const encoder = new TextEncoder();
|
|
142
|
+
const totalBytes = value.contentOverrides.reduce(
|
|
143
|
+
(sum, override) => sum + encoder.encode(override.content).byteLength,
|
|
144
|
+
0,
|
|
145
|
+
);
|
|
146
|
+
if (totalBytes > MAX_TOTAL_CONTENT_OVERRIDE_BYTES) {
|
|
147
|
+
ctx.addIssue({
|
|
148
|
+
code: "custom",
|
|
149
|
+
message: "Screen rename content overrides exceed the 4 MB limit.",
|
|
150
|
+
path: ["contentOverrides"],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}),
|
|
154
|
+
run: async ({ id, name, requestSource, contentOverrides }) => {
|
|
155
|
+
const db = getDb();
|
|
156
|
+
|
|
157
|
+
const [scopedFile] = await db
|
|
158
|
+
.select({
|
|
159
|
+
id: schema.designFiles.id,
|
|
160
|
+
designId: schema.designFiles.designId,
|
|
161
|
+
})
|
|
162
|
+
.from(schema.designFiles)
|
|
163
|
+
.innerJoin(
|
|
164
|
+
schema.designs,
|
|
165
|
+
eq(schema.designFiles.designId, schema.designs.id),
|
|
166
|
+
)
|
|
167
|
+
.where(
|
|
168
|
+
and(
|
|
169
|
+
eq(schema.designFiles.id, id),
|
|
170
|
+
accessFilter(schema.designs, schema.designShares),
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
.limit(1);
|
|
174
|
+
if (!scopedFile) throw new Error(`Screen not found: ${id}`);
|
|
175
|
+
|
|
176
|
+
await assertAccess("design", scopedFile.designId, "editor");
|
|
177
|
+
|
|
178
|
+
const overrideIds = new Set<string>();
|
|
179
|
+
for (const override of contentOverrides) {
|
|
180
|
+
if (overrideIds.has(override.fileId)) {
|
|
181
|
+
throw new Error(`Duplicate content override: ${override.fileId}`);
|
|
182
|
+
}
|
|
183
|
+
overrideIds.add(override.fileId);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const result = await withScreenRenameLock(scopedFile.designId, async () => {
|
|
187
|
+
for (let attempt = 0; attempt < MAX_RENAME_ATTEMPTS; attempt += 1) {
|
|
188
|
+
try {
|
|
189
|
+
return await db.transaction(async (tx) => {
|
|
190
|
+
if (isPostgres()) {
|
|
191
|
+
await (
|
|
192
|
+
tx as unknown as {
|
|
193
|
+
execute: (query: unknown) => Promise<unknown>;
|
|
194
|
+
}
|
|
195
|
+
).execute(
|
|
196
|
+
sql`LOCK TABLE design_files IN SHARE ROW EXCLUSIVE MODE`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const [design] = await tx
|
|
201
|
+
.select({ updatedAt: schema.designs.updatedAt })
|
|
202
|
+
.from(schema.designs)
|
|
203
|
+
.where(eq(schema.designs.id, scopedFile.designId))
|
|
204
|
+
.limit(1);
|
|
205
|
+
if (!design) {
|
|
206
|
+
throw new Error(`Design not found: ${scopedFile.designId}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const currentFiles = await tx
|
|
210
|
+
.select({
|
|
211
|
+
id: schema.designFiles.id,
|
|
212
|
+
designId: schema.designFiles.designId,
|
|
213
|
+
filename: schema.designFiles.filename,
|
|
214
|
+
content: schema.designFiles.content,
|
|
215
|
+
fileType: schema.designFiles.fileType,
|
|
216
|
+
updatedAt: schema.designFiles.updatedAt,
|
|
217
|
+
})
|
|
218
|
+
.from(schema.designFiles)
|
|
219
|
+
.where(eq(schema.designFiles.designId, scopedFile.designId));
|
|
220
|
+
const target = currentFiles.find((file) => file.id === id);
|
|
221
|
+
if (!target) throw new Error(`Screen not found: ${id}`);
|
|
222
|
+
if (target.fileType !== "html") {
|
|
223
|
+
throw new Error(`File is not a renameable HTML screen: ${id}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const nextFilename = renameFilenamePreservingExtension(
|
|
227
|
+
target.filename,
|
|
228
|
+
name,
|
|
229
|
+
);
|
|
230
|
+
assertValidFilename(nextFilename);
|
|
231
|
+
const collision = currentFiles.find(
|
|
232
|
+
(file) => file.id !== id && file.filename === nextFilename,
|
|
233
|
+
);
|
|
234
|
+
if (collision) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`File "${nextFilename}" already exists in design ${scopedFile.designId}`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const fileById = new Map(
|
|
241
|
+
currentFiles.map((file) => [file.id, file]),
|
|
242
|
+
);
|
|
243
|
+
const overrides = new Map(
|
|
244
|
+
contentOverrides.map((override) => [override.fileId, override]),
|
|
245
|
+
);
|
|
246
|
+
for (const override of contentOverrides) {
|
|
247
|
+
const file = fileById.get(override.fileId);
|
|
248
|
+
if (!file) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`Content override does not belong to this design: ${override.fileId}`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
if (file.fileType !== "html") {
|
|
254
|
+
throw new Error(
|
|
255
|
+
`Content overrides are only supported for HTML screens: ${override.fileId}`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
if (
|
|
259
|
+
file.content !== override.content &&
|
|
260
|
+
!isProbablyHtmlDocumentContent(file.content)
|
|
261
|
+
) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`URL-backed screen "${file.filename}" cannot be replaced with inline HTML during rename.`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
const persistedHash = sourceContentHash(file.content);
|
|
267
|
+
if (
|
|
268
|
+
file.content !== override.content &&
|
|
269
|
+
persistedHash !== override.expectedVersionHash
|
|
270
|
+
) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`Screen "${file.filename}" changed before the rename. Refresh and retry.`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const now = new Date();
|
|
278
|
+
const updatedFiles: RenamedFileResult[] = [];
|
|
279
|
+
const orderedFiles = [
|
|
280
|
+
target,
|
|
281
|
+
...currentFiles.filter((file) => file.id !== target.id),
|
|
282
|
+
];
|
|
283
|
+
for (const file of orderedFiles) {
|
|
284
|
+
const override = overrides.get(file.id);
|
|
285
|
+
const baseContent = override?.content ?? file.content;
|
|
286
|
+
const nextContent =
|
|
287
|
+
file.fileType === "html"
|
|
288
|
+
? replaceDataScreenReferences(
|
|
289
|
+
baseContent,
|
|
290
|
+
target.filename,
|
|
291
|
+
nextFilename,
|
|
292
|
+
)
|
|
293
|
+
: baseContent;
|
|
294
|
+
const contentChanged = nextContent !== file.content;
|
|
295
|
+
const referenceRewritten = nextContent !== baseContent;
|
|
296
|
+
const filenameChanged =
|
|
297
|
+
file.id === id && nextFilename !== file.filename;
|
|
298
|
+
if (!contentChanged && !filenameChanged) continue;
|
|
299
|
+
|
|
300
|
+
const updatedAt = nextUpdatedAt(file.updatedAt, now);
|
|
301
|
+
const updates: Record<string, unknown> = { updatedAt };
|
|
302
|
+
if (filenameChanged) updates.filename = nextFilename;
|
|
303
|
+
if (contentChanged) {
|
|
304
|
+
updates.content = nextContent;
|
|
305
|
+
// This atomic server write starts a new content lineage. A
|
|
306
|
+
// late browser save may not use pre-rename revision metadata
|
|
307
|
+
// as proof that no intervening writer changed the document.
|
|
308
|
+
updates.contentOperationSource = null;
|
|
309
|
+
updates.contentOperationRevision = null;
|
|
310
|
+
updates.contentOperationResultHash = null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
await tx
|
|
314
|
+
.update(schema.designFiles)
|
|
315
|
+
.set(updates)
|
|
316
|
+
.where(
|
|
317
|
+
and(
|
|
318
|
+
eq(schema.designFiles.id, file.id),
|
|
319
|
+
eq(schema.designFiles.designId, scopedFile.designId),
|
|
320
|
+
eq(schema.designFiles.filename, file.filename),
|
|
321
|
+
eq(schema.designFiles.content, file.content),
|
|
322
|
+
file.updatedAt === null
|
|
323
|
+
? isNull(schema.designFiles.updatedAt)
|
|
324
|
+
: eq(schema.designFiles.updatedAt, file.updatedAt),
|
|
325
|
+
),
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
const [confirmed] = await tx
|
|
329
|
+
.select({
|
|
330
|
+
filename: schema.designFiles.filename,
|
|
331
|
+
content: schema.designFiles.content,
|
|
332
|
+
updatedAt: schema.designFiles.updatedAt,
|
|
333
|
+
})
|
|
334
|
+
.from(schema.designFiles)
|
|
335
|
+
.where(eq(schema.designFiles.id, file.id))
|
|
336
|
+
.limit(1);
|
|
337
|
+
if (
|
|
338
|
+
!confirmed ||
|
|
339
|
+
confirmed.filename !==
|
|
340
|
+
(filenameChanged ? nextFilename : file.filename) ||
|
|
341
|
+
confirmed.content !== nextContent ||
|
|
342
|
+
confirmed.updatedAt !== updatedAt
|
|
343
|
+
) {
|
|
344
|
+
throw new ScreenRenameConflictError();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
updatedFiles.push({
|
|
348
|
+
id: file.id,
|
|
349
|
+
filename: filenameChanged ? nextFilename : file.filename,
|
|
350
|
+
content: nextContent,
|
|
351
|
+
updatedAt,
|
|
352
|
+
contentChanged,
|
|
353
|
+
referenceRewritten,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const designUpdatedAt = nextUpdatedAt(design.updatedAt, now);
|
|
358
|
+
await tx
|
|
359
|
+
.update(schema.designs)
|
|
360
|
+
.set({ updatedAt: designUpdatedAt })
|
|
361
|
+
.where(eq(schema.designs.id, scopedFile.designId));
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
id,
|
|
365
|
+
designId: scopedFile.designId,
|
|
366
|
+
previousFilename: target.filename,
|
|
367
|
+
filename: nextFilename,
|
|
368
|
+
renamed: nextFilename !== target.filename,
|
|
369
|
+
updatedAt: designUpdatedAt,
|
|
370
|
+
files: updatedFiles,
|
|
371
|
+
rewrittenFileIds: updatedFiles
|
|
372
|
+
.filter((file) => file.referenceRewritten)
|
|
373
|
+
.map((file) => file.id),
|
|
374
|
+
};
|
|
375
|
+
});
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (
|
|
378
|
+
!isRetryableTransactionConflict(error) ||
|
|
379
|
+
attempt === MAX_RENAME_ATTEMPTS - 1
|
|
380
|
+
) {
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
throw new ScreenRenameConflictError(
|
|
386
|
+
"Screen content kept changing during rename. Please retry.",
|
|
387
|
+
);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// SQL is the atomic durable source of truth. Reconcile the same committed
|
|
391
|
+
// snapshots through the existing diff-based Yjs primitive after commit so
|
|
392
|
+
// open peers update without replacing their document or undoing unrelated
|
|
393
|
+
// CRDT operations. If that transport is unavailable, the file/design
|
|
394
|
+
// updatedAt bump and normal get-design invalidation remain the durable
|
|
395
|
+
// reconciliation fallback; never report the committed transaction as a
|
|
396
|
+
// rollback after it has succeeded.
|
|
397
|
+
const collabReconcilePending: string[] = [];
|
|
398
|
+
await Promise.all(
|
|
399
|
+
result.files
|
|
400
|
+
.filter((file) => file.contentChanged)
|
|
401
|
+
.map(async (file) => {
|
|
402
|
+
try {
|
|
403
|
+
if (await hasCollabState(file.id)) {
|
|
404
|
+
await applyText(
|
|
405
|
+
file.id,
|
|
406
|
+
file.content,
|
|
407
|
+
"content",
|
|
408
|
+
requestSource ?? "server",
|
|
409
|
+
);
|
|
410
|
+
} else {
|
|
411
|
+
await seedFromText(file.id, file.content);
|
|
412
|
+
}
|
|
413
|
+
} catch (error) {
|
|
414
|
+
collabReconcilePending.push(file.id);
|
|
415
|
+
console.warn(
|
|
416
|
+
`[design] screen rename committed but collab reconcile is pending for ${file.id}:`,
|
|
417
|
+
error,
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
}),
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
return { ...result, collabReconcilePending };
|
|
424
|
+
},
|
|
425
|
+
});
|
|
@@ -3,9 +3,12 @@ import {
|
|
|
3
3
|
readAppStateForCurrentTab,
|
|
4
4
|
writeAppState,
|
|
5
5
|
} from "@agent-native/core/application-state";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getRequestOrgId,
|
|
8
|
+
getRequestUserEmail,
|
|
9
|
+
} from "@agent-native/core/server/request-context";
|
|
7
10
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
8
|
-
import { and, eq } from "drizzle-orm";
|
|
11
|
+
import { and, eq, isNull } from "drizzle-orm";
|
|
9
12
|
import { z } from "zod";
|
|
10
13
|
|
|
11
14
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -40,6 +43,7 @@ export default defineAction({
|
|
|
40
43
|
|
|
41
44
|
const ownerEmail = getRequestUserEmail();
|
|
42
45
|
if (!ownerEmail) throw new Error("no authenticated user");
|
|
46
|
+
const orgId = getRequestOrgId() ?? null;
|
|
43
47
|
|
|
44
48
|
const db = getDb();
|
|
45
49
|
const [connection] = await db
|
|
@@ -52,6 +56,9 @@ export default defineAction({
|
|
|
52
56
|
and(
|
|
53
57
|
eq(schema.designLocalhostConnections.id, connectionId),
|
|
54
58
|
eq(schema.designLocalhostConnections.ownerEmail, ownerEmail),
|
|
59
|
+
orgId
|
|
60
|
+
? eq(schema.designLocalhostConnections.orgId, orgId)
|
|
61
|
+
: isNull(schema.designLocalhostConnections.orgId),
|
|
55
62
|
),
|
|
56
63
|
)
|
|
57
64
|
.limit(1);
|
|
@@ -85,6 +92,9 @@ export default defineAction({
|
|
|
85
92
|
eq(schema.designLocalhostWriteGrants.designId, designId),
|
|
86
93
|
eq(schema.designLocalhostWriteGrants.connectionId, connectionId),
|
|
87
94
|
eq(schema.designLocalhostWriteGrants.ownerEmail, ownerEmail),
|
|
95
|
+
orgId
|
|
96
|
+
? eq(schema.designLocalhostWriteGrants.orgId, orgId)
|
|
97
|
+
: isNull(schema.designLocalhostWriteGrants.orgId),
|
|
88
98
|
),
|
|
89
99
|
)
|
|
90
100
|
.limit(1);
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server/request-context";
|
|
3
6
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
4
|
-
import { and, eq } from "drizzle-orm";
|
|
7
|
+
import { and, eq, isNull } from "drizzle-orm";
|
|
5
8
|
import { z } from "zod";
|
|
6
9
|
|
|
7
10
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -22,6 +25,7 @@ export default defineAction({
|
|
|
22
25
|
|
|
23
26
|
const ownerEmail = getRequestUserEmail();
|
|
24
27
|
if (!ownerEmail) throw new Error("no authenticated user");
|
|
28
|
+
const orgId = getRequestOrgId() ?? null;
|
|
25
29
|
|
|
26
30
|
const db = getDb();
|
|
27
31
|
|
|
@@ -33,6 +37,9 @@ export default defineAction({
|
|
|
33
37
|
eq(schema.designLocalhostWriteGrants.designId, designId),
|
|
34
38
|
eq(schema.designLocalhostWriteGrants.connectionId, connectionId),
|
|
35
39
|
eq(schema.designLocalhostWriteGrants.ownerEmail, ownerEmail),
|
|
40
|
+
orgId
|
|
41
|
+
? eq(schema.designLocalhostWriteGrants.orgId, orgId)
|
|
42
|
+
: isNull(schema.designLocalhostWriteGrants.orgId),
|
|
36
43
|
);
|
|
37
44
|
|
|
38
45
|
const [grant] = await db
|
|
@@ -17,6 +17,7 @@ import { z } from "zod";
|
|
|
17
17
|
|
|
18
18
|
import { getDb, schema } from "../server/db/index.js";
|
|
19
19
|
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
20
|
+
import { mutateDesignData } from "../server/lib/design-data-mutation.js";
|
|
20
21
|
import {
|
|
21
22
|
DEFAULT_FUSION_SCREEN_HEIGHT,
|
|
22
23
|
DEFAULT_FUSION_SCREEN_WIDTH,
|
|
@@ -90,40 +91,55 @@ export default defineAction({
|
|
|
90
91
|
timeoutMs: 20_000,
|
|
91
92
|
});
|
|
92
93
|
|
|
93
|
-
const db = getDb();
|
|
94
94
|
const now = new Date().toISOString();
|
|
95
95
|
|
|
96
96
|
if (result.status === "error") {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
const statusMessage = result.message ?? "Container provisioning failed";
|
|
98
|
+
await mutateDesignData({
|
|
99
|
+
designId,
|
|
100
|
+
mutate: (current) =>
|
|
101
|
+
writeFusionApp(current, {
|
|
102
|
+
...(readFusionApp(current) ?? fusionApp),
|
|
103
|
+
status: "error",
|
|
104
|
+
statusMessage,
|
|
105
|
+
updatedAt: now,
|
|
106
|
+
}),
|
|
107
|
+
isApplied: (current) => {
|
|
108
|
+
const persisted = readFusionApp(current);
|
|
109
|
+
return (
|
|
110
|
+
persisted?.status === "error" &&
|
|
111
|
+
persisted.statusMessage === statusMessage
|
|
112
|
+
);
|
|
113
|
+
},
|
|
102
114
|
});
|
|
103
|
-
await db
|
|
104
|
-
.update(schema.designs)
|
|
105
|
-
.set({ data: JSON.stringify(nextData), updatedAt: now })
|
|
106
|
-
.where(eq(schema.designs.id, designId));
|
|
107
115
|
return {
|
|
108
116
|
status: "error" as const,
|
|
109
|
-
message:
|
|
117
|
+
message: statusMessage,
|
|
110
118
|
};
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
if (result.status === "provisioning") {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
const statusMessage = result.message ?? "Container is still starting";
|
|
123
|
+
await mutateDesignData({
|
|
124
|
+
designId,
|
|
125
|
+
mutate: (current) =>
|
|
126
|
+
writeFusionApp(current, {
|
|
127
|
+
...(readFusionApp(current) ?? fusionApp),
|
|
128
|
+
status: "building",
|
|
129
|
+
statusMessage,
|
|
130
|
+
updatedAt: now,
|
|
131
|
+
}),
|
|
132
|
+
isApplied: (current) => {
|
|
133
|
+
const persisted = readFusionApp(current);
|
|
134
|
+
return (
|
|
135
|
+
persisted?.status === "building" &&
|
|
136
|
+
persisted.statusMessage === statusMessage
|
|
137
|
+
);
|
|
138
|
+
},
|
|
119
139
|
});
|
|
120
|
-
await db
|
|
121
|
-
.update(schema.designs)
|
|
122
|
-
.set({ data: JSON.stringify(nextData), updatedAt: now })
|
|
123
|
-
.where(eq(schema.designs.id, designId));
|
|
124
140
|
return {
|
|
125
141
|
status: "building" as const,
|
|
126
|
-
message:
|
|
142
|
+
message: statusMessage,
|
|
127
143
|
};
|
|
128
144
|
}
|
|
129
145
|
|
|
@@ -159,25 +175,26 @@ export default defineAction({
|
|
|
159
175
|
height: DEFAULT_FUSION_SCREEN_HEIGHT,
|
|
160
176
|
});
|
|
161
177
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
const statusMessage = result.message ?? "Container is ready";
|
|
179
|
+
await mutateDesignData({
|
|
180
|
+
designId,
|
|
181
|
+
mutate: (current) =>
|
|
182
|
+
writeFusionApp(current, {
|
|
183
|
+
...(readFusionApp(current) ?? fusionApp),
|
|
184
|
+
previewUrl,
|
|
185
|
+
status: "ready",
|
|
186
|
+
statusMessage,
|
|
187
|
+
updatedAt: now,
|
|
188
|
+
}),
|
|
189
|
+
isApplied: (current) => {
|
|
190
|
+
const persisted = readFusionApp(current);
|
|
191
|
+
return (
|
|
192
|
+
persisted?.previewUrl === previewUrl &&
|
|
193
|
+
persisted.status === "ready" &&
|
|
194
|
+
persisted.statusMessage === statusMessage
|
|
195
|
+
);
|
|
196
|
+
},
|
|
176
197
|
});
|
|
177
|
-
await db
|
|
178
|
-
.update(schema.designs)
|
|
179
|
-
.set({ data: JSON.stringify(nextData), updatedAt: now })
|
|
180
|
-
.where(eq(schema.designs.id, designId));
|
|
181
198
|
|
|
182
199
|
const pendingEditCount = await countPendingEdits(design);
|
|
183
200
|
|