@agent-native/core 0.92.7 → 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 +12 -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/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/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/struct-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/notifications/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +2 -2
- 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
|
@@ -15,9 +15,14 @@ import { nanoid } from "nanoid";
|
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
|
|
17
17
|
import { getDb, schema } from "../server/db/index.js";
|
|
18
|
+
import {
|
|
19
|
+
mutateDesignData,
|
|
20
|
+
type DesignDataRecord,
|
|
21
|
+
} from "../server/lib/design-data-mutation.js";
|
|
18
22
|
import {
|
|
19
23
|
mergeCanvasFramePlacements,
|
|
20
24
|
parseCanvasFrameGeometryById,
|
|
25
|
+
type CanvasFrameGeometry,
|
|
21
26
|
type CanvasFramePlacement,
|
|
22
27
|
} from "../shared/canvas-frames.js";
|
|
23
28
|
import {
|
|
@@ -32,6 +37,9 @@ const routeInputSchema = z.object({
|
|
|
32
37
|
url: z.string().optional(),
|
|
33
38
|
title: z.string().optional(),
|
|
34
39
|
sourceFile: z.string().optional(),
|
|
40
|
+
sourceKind: z.enum(["react-router", "html", "manual"]).optional(),
|
|
41
|
+
screenshotUrl: z.string().optional(),
|
|
42
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
35
43
|
width: z.number().positive().optional(),
|
|
36
44
|
height: z.number().positive().optional(),
|
|
37
45
|
x: z.number().optional(),
|
|
@@ -63,12 +71,89 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
63
71
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
64
72
|
}
|
|
65
73
|
|
|
74
|
+
function parseDesignDataSnapshot(
|
|
75
|
+
designId: string,
|
|
76
|
+
value: string | null,
|
|
77
|
+
): DesignDataRecord {
|
|
78
|
+
if (value === null) return {};
|
|
79
|
+
try {
|
|
80
|
+
const parsed: unknown = JSON.parse(value);
|
|
81
|
+
if (isRecord(parsed)) return parsed;
|
|
82
|
+
} catch {
|
|
83
|
+
// The error below deliberately refuses to discard malformed legacy data.
|
|
84
|
+
}
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Design "${designId}" has invalid data JSON. Refusing to overwrite it; repair or restore the design data before retrying.`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function jsonValuesEqual(left: unknown, right: unknown): boolean {
|
|
91
|
+
if (Object.is(left, right)) return true;
|
|
92
|
+
try {
|
|
93
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface PlacementIntent {
|
|
100
|
+
fileId: string;
|
|
101
|
+
filename: string;
|
|
102
|
+
fallback: CanvasFramePlacement;
|
|
103
|
+
existedAtStart: boolean;
|
|
104
|
+
owns: {
|
|
105
|
+
x: boolean;
|
|
106
|
+
y: boolean;
|
|
107
|
+
width: boolean;
|
|
108
|
+
height: boolean;
|
|
109
|
+
z: boolean;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function placementAgainstLatest(
|
|
114
|
+
intent: PlacementIntent,
|
|
115
|
+
latest: CanvasFrameGeometry | undefined,
|
|
116
|
+
): CanvasFramePlacement {
|
|
117
|
+
const choose = (key: keyof PlacementIntent["owns"]): number | undefined =>
|
|
118
|
+
intent.owns[key]
|
|
119
|
+
? intent.fallback[key]
|
|
120
|
+
: (latest?.[key] ?? intent.fallback[key]);
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
fileId: intent.fileId,
|
|
124
|
+
filename: intent.filename,
|
|
125
|
+
x: choose("x"),
|
|
126
|
+
y: choose("y"),
|
|
127
|
+
width: choose("width"),
|
|
128
|
+
height: choose("height"),
|
|
129
|
+
z: choose("z"),
|
|
130
|
+
// add-localhost-screens never owns rotation. A concurrent/local canvas
|
|
131
|
+
// rotation therefore survives even when this action refreshes the route.
|
|
132
|
+
rotation: latest?.rotation,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
66
136
|
function normalizeBaseUrl(value: string): string {
|
|
67
137
|
const parsed = new URL(value);
|
|
138
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
139
|
+
throw new Error("devServerUrl must be an http(s) URL");
|
|
140
|
+
}
|
|
68
141
|
parsed.hash = "";
|
|
69
142
|
return parsed.toString().replace(/\/$/, "");
|
|
70
143
|
}
|
|
71
144
|
|
|
145
|
+
function isLoopbackHostname(hostname: string): boolean {
|
|
146
|
+
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
147
|
+
if (
|
|
148
|
+
normalized === "localhost" ||
|
|
149
|
+
normalized === "::1" ||
|
|
150
|
+
normalized === "0.0.0.0"
|
|
151
|
+
) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
return /^127(?:\.\d{1,3}){3}$/.test(normalized);
|
|
155
|
+
}
|
|
156
|
+
|
|
72
157
|
function withLocalhostProtocol(value: string): string {
|
|
73
158
|
const raw = value.trim();
|
|
74
159
|
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) return raw;
|
|
@@ -98,6 +183,25 @@ export function routeUrl(
|
|
|
98
183
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
99
184
|
throw new Error(`Localhost screen URL must be an http(s) URL: ${raw}`);
|
|
100
185
|
}
|
|
186
|
+
const base = new URL(baseUrl);
|
|
187
|
+
if (parsed.origin !== base.origin) {
|
|
188
|
+
const equivalentLoopbackOrigin =
|
|
189
|
+
parsed.protocol === base.protocol &&
|
|
190
|
+
parsed.port === base.port &&
|
|
191
|
+
isLoopbackHostname(parsed.hostname) &&
|
|
192
|
+
isLoopbackHostname(base.hostname);
|
|
193
|
+
if (!equivalentLoopbackOrigin) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`Localhost screen URL must stay on the connected dev server origin (${base.origin}): ${raw}`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
// localhost / 127.0.0.1 / ::1 aliases can point at the same loopback
|
|
199
|
+
// server, but the bridge enforces exact same-origin fetches. Canonicalize
|
|
200
|
+
// the alias to the registered dev-server origin so live edit does not fail
|
|
201
|
+
// later with an opaque bridge 400.
|
|
202
|
+
parsed.protocol = base.protocol;
|
|
203
|
+
parsed.host = base.host;
|
|
204
|
+
}
|
|
101
205
|
parsed.hash = "";
|
|
102
206
|
return parsed.toString();
|
|
103
207
|
}
|
|
@@ -168,6 +272,36 @@ function metadataNumber(
|
|
|
168
272
|
: undefined;
|
|
169
273
|
}
|
|
170
274
|
|
|
275
|
+
function metadataForFile(
|
|
276
|
+
fileId: string,
|
|
277
|
+
screenMetadata: Record<string, unknown>,
|
|
278
|
+
localhostScreens: Record<string, unknown>,
|
|
279
|
+
): Record<string, unknown> | undefined {
|
|
280
|
+
const primary = screenMetadata[fileId];
|
|
281
|
+
if (isRecord(primary)) return primary;
|
|
282
|
+
const legacy = localhostScreens[fileId];
|
|
283
|
+
return isRecord(legacy) ? legacy : undefined;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function metadataMatchesRoute(
|
|
287
|
+
metadata: Record<string, unknown> | undefined,
|
|
288
|
+
args: { connectionId: string; routeId: string; path: string; url: string },
|
|
289
|
+
): boolean {
|
|
290
|
+
if (!metadata || metadata.sourceType !== "localhost") return false;
|
|
291
|
+
if (
|
|
292
|
+
typeof metadata.connectionId === "string" &&
|
|
293
|
+
metadata.connectionId !== args.connectionId
|
|
294
|
+
) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
return (
|
|
298
|
+
metadata.routeId === args.routeId ||
|
|
299
|
+
metadata.url === args.url ||
|
|
300
|
+
metadata.previewUrl === args.url ||
|
|
301
|
+
metadata.path === args.path
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
171
305
|
export default defineAction({
|
|
172
306
|
description:
|
|
173
307
|
"Create or refresh URL-backed localhost screens in a design project. " +
|
|
@@ -289,6 +423,10 @@ export default defineAction({
|
|
|
289
423
|
routeId: route.id,
|
|
290
424
|
path: route.path,
|
|
291
425
|
title: route.title,
|
|
426
|
+
sourceFile: route.sourceFile,
|
|
427
|
+
sourceKind: route.sourceKind,
|
|
428
|
+
screenshotUrl: route.screenshotUrl,
|
|
429
|
+
metadata: route.metadata,
|
|
292
430
|
width:
|
|
293
431
|
typeof route.metadata?.width === "number"
|
|
294
432
|
? route.metadata.width
|
|
@@ -310,13 +448,19 @@ export default defineAction({
|
|
|
310
448
|
.from(schema.designs)
|
|
311
449
|
.where(eq(schema.designs.id, designId))
|
|
312
450
|
.limit(1);
|
|
313
|
-
|
|
451
|
+
if (!design) throw new Error(`Design "${designId}" not found.`);
|
|
452
|
+
// Fail before touching files/collab when a legacy row contains malformed
|
|
453
|
+
// data. SQL NULL is the one supported legacy empty-data sentinel.
|
|
454
|
+
const prevData = parseDesignDataSnapshot(designId, design.data);
|
|
314
455
|
const existingCanvasFrames = parseCanvasFrameGeometryById(
|
|
315
456
|
prevData.canvasFrames,
|
|
316
457
|
);
|
|
317
458
|
const existingMetadata = isRecord(prevData.screenMetadata)
|
|
318
459
|
? (prevData.screenMetadata as Record<string, unknown>)
|
|
319
460
|
: {};
|
|
461
|
+
const existingLocalhostScreens = isRecord(prevData.localhostScreens)
|
|
462
|
+
? (prevData.localhostScreens as Record<string, unknown>)
|
|
463
|
+
: {};
|
|
320
464
|
const existingFiles = await db
|
|
321
465
|
.select()
|
|
322
466
|
.from(schema.designFiles)
|
|
@@ -326,8 +470,9 @@ export default defineAction({
|
|
|
326
470
|
);
|
|
327
471
|
const usedFilenames = new Set(existingFiles.map((file) => file.filename));
|
|
328
472
|
const now = new Date().toISOString();
|
|
329
|
-
const
|
|
330
|
-
const
|
|
473
|
+
const layoutStartX = startX ?? 0;
|
|
474
|
+
const layoutStartY = startY ?? 0;
|
|
475
|
+
const layoutGap = gap ?? 160;
|
|
331
476
|
const savedScreens: Array<{
|
|
332
477
|
id: string;
|
|
333
478
|
filename: string;
|
|
@@ -336,10 +481,13 @@ export default defineAction({
|
|
|
336
481
|
url: string;
|
|
337
482
|
routeId: string;
|
|
338
483
|
sourceFile?: string;
|
|
484
|
+
sourceKind?: "react-router" | "html" | "manual";
|
|
485
|
+
screenshotUrl?: string;
|
|
486
|
+
routeMetadata?: Record<string, unknown>;
|
|
339
487
|
width: number;
|
|
340
488
|
height: number;
|
|
341
489
|
}> = [];
|
|
342
|
-
const
|
|
490
|
+
const placementIntents: PlacementIntent[] = [];
|
|
343
491
|
|
|
344
492
|
for (let index = 0; index < requestedRoutes.length; index += 1) {
|
|
345
493
|
const input = requestedRoutes[index]!;
|
|
@@ -360,19 +508,51 @@ export default defineAction({
|
|
|
360
508
|
const title =
|
|
361
509
|
input.title ?? manifestRoute?.title ?? titleFromRoutePath(path);
|
|
362
510
|
const sourceFile = input.sourceFile ?? manifestRoute?.sourceFile;
|
|
363
|
-
const
|
|
364
|
-
const
|
|
511
|
+
const sourceKind = input.sourceKind ?? manifestRoute?.sourceKind;
|
|
512
|
+
const screenshotUrl = input.screenshotUrl ?? manifestRoute?.screenshotUrl;
|
|
513
|
+
const routeMetadata = {
|
|
514
|
+
...(manifestRoute?.metadata ?? {}),
|
|
515
|
+
...(input.metadata ?? {}),
|
|
516
|
+
};
|
|
365
517
|
const basePreferredFilename = `localhost-${slugForPath(path)}.html`;
|
|
366
|
-
const
|
|
518
|
+
const routeMatchArgs = {
|
|
519
|
+
connectionId: connection.id,
|
|
520
|
+
routeId,
|
|
521
|
+
path,
|
|
522
|
+
url,
|
|
523
|
+
};
|
|
524
|
+
const routeCandidates = existingFiles.filter((file) =>
|
|
525
|
+
metadataMatchesRoute(
|
|
526
|
+
metadataForFile(file.id, existingMetadata, existingLocalhostScreens),
|
|
527
|
+
routeMatchArgs,
|
|
528
|
+
),
|
|
529
|
+
);
|
|
530
|
+
const filenameBase = existingByFilename.get(basePreferredFilename);
|
|
531
|
+
const existingBase =
|
|
532
|
+
filenameBase &&
|
|
533
|
+
metadataMatchesRoute(
|
|
534
|
+
metadataForFile(
|
|
535
|
+
filenameBase.id,
|
|
536
|
+
existingMetadata,
|
|
537
|
+
existingLocalhostScreens,
|
|
538
|
+
),
|
|
539
|
+
routeMatchArgs,
|
|
540
|
+
)
|
|
541
|
+
? filenameBase
|
|
542
|
+
: routeCandidates.find(
|
|
543
|
+
(candidate) => candidate.filename === basePreferredFilename,
|
|
544
|
+
);
|
|
367
545
|
const requestedViewportExplicitly =
|
|
368
546
|
input.width !== undefined ||
|
|
369
547
|
input.height !== undefined ||
|
|
370
548
|
defaultWidth !== undefined ||
|
|
371
549
|
defaultHeight !== undefined;
|
|
372
|
-
const existingBaseMetadata =
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
550
|
+
const existingBaseMetadata = existingBase
|
|
551
|
+
? metadataForFile(
|
|
552
|
+
existingBase.id,
|
|
553
|
+
existingMetadata,
|
|
554
|
+
existingLocalhostScreens,
|
|
555
|
+
)
|
|
376
556
|
: undefined;
|
|
377
557
|
const existingBaseFrame = existingBase
|
|
378
558
|
? existingCanvasFrames[existingBase.id]
|
|
@@ -383,20 +563,87 @@ export default defineAction({
|
|
|
383
563
|
const existingBaseHeight =
|
|
384
564
|
existingBaseFrame?.height ??
|
|
385
565
|
metadataNumber(existingBaseMetadata, "height");
|
|
566
|
+
const requestedWidth =
|
|
567
|
+
input.width ??
|
|
568
|
+
defaultWidth ??
|
|
569
|
+
existingBaseWidth ??
|
|
570
|
+
metadataNumber(routeMetadata, "width") ??
|
|
571
|
+
1280;
|
|
572
|
+
const requestedHeight =
|
|
573
|
+
input.height ??
|
|
574
|
+
defaultHeight ??
|
|
575
|
+
existingBaseHeight ??
|
|
576
|
+
metadataNumber(routeMetadata, "height") ??
|
|
577
|
+
900;
|
|
386
578
|
const viewportDiffersFromBase =
|
|
387
579
|
(typeof existingBaseWidth === "number" &&
|
|
388
|
-
existingBaseWidth !==
|
|
580
|
+
existingBaseWidth !== requestedWidth) ||
|
|
389
581
|
(typeof existingBaseHeight === "number" &&
|
|
390
|
-
existingBaseHeight !==
|
|
582
|
+
existingBaseHeight !== requestedHeight);
|
|
391
583
|
const preferredFilename =
|
|
392
584
|
existingBase && requestedViewportExplicitly && viewportDiffersFromBase
|
|
393
|
-
? viewportFilename(path,
|
|
585
|
+
? viewportFilename(path, requestedWidth, requestedHeight)
|
|
394
586
|
: basePreferredFilename;
|
|
395
|
-
const
|
|
587
|
+
const preferredExisting = existingByFilename.get(preferredFilename);
|
|
588
|
+
const matchingPreferredExisting =
|
|
589
|
+
preferredExisting &&
|
|
590
|
+
metadataMatchesRoute(
|
|
591
|
+
metadataForFile(
|
|
592
|
+
preferredExisting.id,
|
|
593
|
+
existingMetadata,
|
|
594
|
+
existingLocalhostScreens,
|
|
595
|
+
),
|
|
596
|
+
routeMatchArgs,
|
|
597
|
+
)
|
|
598
|
+
? preferredExisting
|
|
599
|
+
: undefined;
|
|
600
|
+
const existing =
|
|
601
|
+
matchingPreferredExisting ??
|
|
602
|
+
routeCandidates.find((candidate) => {
|
|
603
|
+
const frame = existingCanvasFrames[candidate.id];
|
|
604
|
+
const metadata = metadataForFile(
|
|
605
|
+
candidate.id,
|
|
606
|
+
existingMetadata,
|
|
607
|
+
existingLocalhostScreens,
|
|
608
|
+
);
|
|
609
|
+
const candidateWidth =
|
|
610
|
+
frame?.width ?? metadataNumber(metadata, "width");
|
|
611
|
+
const candidateHeight =
|
|
612
|
+
frame?.height ?? metadataNumber(metadata, "height");
|
|
613
|
+
return requestedViewportExplicitly
|
|
614
|
+
? candidateWidth === requestedWidth &&
|
|
615
|
+
candidateHeight === requestedHeight
|
|
616
|
+
: candidate === existingBase;
|
|
617
|
+
}) ??
|
|
618
|
+
(!requestedViewportExplicitly ? routeCandidates[0] : undefined);
|
|
396
619
|
const filename =
|
|
397
620
|
existing?.filename ??
|
|
398
621
|
uniqueFilename(path, usedFilenames, preferredFilename);
|
|
399
622
|
const fileId = existing?.id ?? nanoid();
|
|
623
|
+
const existingScreenMetadata = existing
|
|
624
|
+
? metadataForFile(
|
|
625
|
+
existing.id,
|
|
626
|
+
existingMetadata,
|
|
627
|
+
existingLocalhostScreens,
|
|
628
|
+
)
|
|
629
|
+
: undefined;
|
|
630
|
+
const existingFrame = existing
|
|
631
|
+
? existingCanvasFrames[existing.id]
|
|
632
|
+
: undefined;
|
|
633
|
+
const width =
|
|
634
|
+
input.width ??
|
|
635
|
+
defaultWidth ??
|
|
636
|
+
existingFrame?.width ??
|
|
637
|
+
metadataNumber(existingScreenMetadata, "width") ??
|
|
638
|
+
metadataNumber(routeMetadata, "width") ??
|
|
639
|
+
1280;
|
|
640
|
+
const height =
|
|
641
|
+
input.height ??
|
|
642
|
+
defaultHeight ??
|
|
643
|
+
existingFrame?.height ??
|
|
644
|
+
metadataNumber(existingScreenMetadata, "height") ??
|
|
645
|
+
metadataNumber(routeMetadata, "height") ??
|
|
646
|
+
900;
|
|
400
647
|
|
|
401
648
|
if (existing) {
|
|
402
649
|
await db
|
|
@@ -429,65 +676,253 @@ export default defineAction({
|
|
|
429
676
|
url,
|
|
430
677
|
routeId,
|
|
431
678
|
sourceFile,
|
|
679
|
+
sourceKind,
|
|
680
|
+
screenshotUrl,
|
|
681
|
+
routeMetadata,
|
|
432
682
|
width,
|
|
433
683
|
height,
|
|
434
684
|
});
|
|
435
|
-
|
|
685
|
+
const fallbackPlacement: CanvasFramePlacement = {
|
|
436
686
|
fileId,
|
|
437
687
|
filename,
|
|
438
|
-
x:
|
|
439
|
-
|
|
688
|
+
x:
|
|
689
|
+
input.x ??
|
|
690
|
+
existingFrame?.x ??
|
|
691
|
+
layoutStartX + index * (width + layoutGap),
|
|
692
|
+
y: input.y ?? existingFrame?.y ?? layoutStartY,
|
|
440
693
|
width,
|
|
441
694
|
height,
|
|
442
|
-
z: input.z ?? index,
|
|
695
|
+
z: input.z ?? existingFrame?.z ?? index,
|
|
696
|
+
};
|
|
697
|
+
placementIntents.push({
|
|
698
|
+
fileId,
|
|
699
|
+
filename,
|
|
700
|
+
fallback: fallbackPlacement,
|
|
701
|
+
existedAtStart: Boolean(existingFrame),
|
|
702
|
+
owns: {
|
|
703
|
+
x: input.x !== undefined,
|
|
704
|
+
y: input.y !== undefined,
|
|
705
|
+
width: input.width !== undefined || defaultWidth !== undefined,
|
|
706
|
+
height: input.height !== undefined || defaultHeight !== undefined,
|
|
707
|
+
z: input.z !== undefined,
|
|
708
|
+
},
|
|
443
709
|
});
|
|
444
710
|
}
|
|
445
711
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
placements,
|
|
449
|
-
resolveFileId: (placement) => placement.fileId,
|
|
450
|
-
});
|
|
451
|
-
const previousMetadata = isRecord(prevData.screenMetadata)
|
|
452
|
-
? { ...prevData.screenMetadata }
|
|
453
|
-
: {};
|
|
454
|
-
const previousLocalhostScreens = isRecord(prevData.localhostScreens)
|
|
455
|
-
? { ...prevData.localhostScreens }
|
|
456
|
-
: {};
|
|
457
|
-
for (const screen of savedScreens) {
|
|
458
|
-
const metadata = {
|
|
459
|
-
sourceType: "localhost",
|
|
460
|
-
previewState: "live",
|
|
461
|
-
title: screen.title,
|
|
462
|
-
width: screen.width,
|
|
463
|
-
height: screen.height,
|
|
464
|
-
url: screen.url,
|
|
465
|
-
previewUrl: screen.url,
|
|
466
|
-
connectionId: connection.id,
|
|
467
|
-
routeId: screen.routeId,
|
|
468
|
-
sourceFile: screen.sourceFile,
|
|
469
|
-
path: screen.path,
|
|
470
|
-
bridgeUrl: connection.bridgeUrl ?? undefined,
|
|
471
|
-
bridgeToken: connection.bridgeToken ?? undefined,
|
|
472
|
-
};
|
|
473
|
-
previousMetadata[screen.id] = metadata;
|
|
474
|
-
previousLocalhostScreens[screen.id] = metadata;
|
|
475
|
-
}
|
|
712
|
+
let lastOwnedMetadata = new Map<string, Record<string, unknown>>();
|
|
713
|
+
let lastOwnedFrameFields = new Map<string, Partial<CanvasFrameGeometry>>();
|
|
476
714
|
|
|
477
|
-
await
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
715
|
+
const { data: persistedData } = await mutateDesignData({
|
|
716
|
+
designId,
|
|
717
|
+
mutate: (currentData, { updatedAt }) => {
|
|
718
|
+
const latestFrames = parseCanvasFrameGeometryById(
|
|
719
|
+
currentData.canvasFrames,
|
|
720
|
+
);
|
|
721
|
+
const placements = placementIntents.map((intent) =>
|
|
722
|
+
placementAgainstLatest(intent, latestFrames[intent.fileId]),
|
|
723
|
+
);
|
|
724
|
+
const mergedFrames = mergeCanvasFramePlacements({
|
|
725
|
+
existing: currentData.canvasFrames,
|
|
726
|
+
placements,
|
|
727
|
+
resolveFileId: (placement) => placement.fileId,
|
|
728
|
+
});
|
|
729
|
+
const previousMetadata = isRecord(currentData.screenMetadata)
|
|
730
|
+
? { ...currentData.screenMetadata }
|
|
731
|
+
: {};
|
|
732
|
+
const previousLocalhostScreens = isRecord(currentData.localhostScreens)
|
|
733
|
+
? { ...currentData.localhostScreens }
|
|
734
|
+
: {};
|
|
735
|
+
const nextOwnedMetadata = new Map<string, Record<string, unknown>>();
|
|
736
|
+
const nextOwnedFrameFields = new Map<
|
|
737
|
+
string,
|
|
738
|
+
Partial<CanvasFrameGeometry>
|
|
739
|
+
>();
|
|
740
|
+
|
|
741
|
+
for (const screen of savedScreens) {
|
|
742
|
+
const currentMetadata = isRecord(previousMetadata[screen.id])
|
|
743
|
+
? (previousMetadata[screen.id] as Record<string, unknown>)
|
|
744
|
+
: {};
|
|
745
|
+
const currentLocalhostMetadata = isRecord(
|
|
746
|
+
previousLocalhostScreens[screen.id],
|
|
747
|
+
)
|
|
748
|
+
? (previousLocalhostScreens[screen.id] as Record<string, unknown>)
|
|
749
|
+
: {};
|
|
750
|
+
const frame = mergedFrames.canvasFrames[screen.id] ?? {};
|
|
751
|
+
const ownedMetadata: Record<string, unknown> = {
|
|
752
|
+
sourceType: "localhost",
|
|
753
|
+
previewState: "live",
|
|
754
|
+
title: screen.title,
|
|
755
|
+
width: frame.width ?? screen.width,
|
|
756
|
+
height: frame.height ?? screen.height,
|
|
757
|
+
url: screen.url,
|
|
758
|
+
previewUrl: screen.url,
|
|
759
|
+
connectionId: connection.id,
|
|
760
|
+
routeId: screen.routeId,
|
|
761
|
+
path: screen.path,
|
|
762
|
+
bridgeUrl: connection.bridgeUrl ?? undefined,
|
|
763
|
+
previewToken: connection.previewToken ?? undefined,
|
|
764
|
+
};
|
|
765
|
+
if (screen.sourceFile !== undefined) {
|
|
766
|
+
ownedMetadata.sourceFile = screen.sourceFile;
|
|
767
|
+
}
|
|
768
|
+
if (screen.sourceKind !== undefined) {
|
|
769
|
+
ownedMetadata.sourceKind = screen.sourceKind;
|
|
770
|
+
}
|
|
771
|
+
if (screen.screenshotUrl !== undefined) {
|
|
772
|
+
ownedMetadata.screenshotUrl = screen.screenshotUrl;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const mergedRouteMetadata = (
|
|
776
|
+
primary: Record<string, unknown>,
|
|
777
|
+
counterpart: Record<string, unknown>,
|
|
778
|
+
) => ({
|
|
779
|
+
...(isRecord(counterpart.routeMetadata)
|
|
780
|
+
? counterpart.routeMetadata
|
|
781
|
+
: {}),
|
|
782
|
+
...(isRecord(primary.routeMetadata) ? primary.routeMetadata : {}),
|
|
783
|
+
...(screen.routeMetadata ?? {}),
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
// Preserve independently written legacy and canonical metadata keys.
|
|
787
|
+
// Each map keeps its own value on an unrelated same-key conflict,
|
|
788
|
+
// while localhost-owned fields above intentionally converge.
|
|
789
|
+
previousMetadata[screen.id] = {
|
|
790
|
+
...currentLocalhostMetadata,
|
|
791
|
+
...currentMetadata,
|
|
792
|
+
...ownedMetadata,
|
|
793
|
+
routeMetadata: mergedRouteMetadata(
|
|
794
|
+
currentMetadata,
|
|
795
|
+
currentLocalhostMetadata,
|
|
796
|
+
),
|
|
797
|
+
};
|
|
798
|
+
previousLocalhostScreens[screen.id] = {
|
|
799
|
+
...currentMetadata,
|
|
800
|
+
...currentLocalhostMetadata,
|
|
801
|
+
...ownedMetadata,
|
|
802
|
+
routeMetadata: mergedRouteMetadata(
|
|
803
|
+
currentLocalhostMetadata,
|
|
804
|
+
currentMetadata,
|
|
805
|
+
),
|
|
806
|
+
};
|
|
807
|
+
nextOwnedMetadata.set(screen.id, {
|
|
808
|
+
...ownedMetadata,
|
|
809
|
+
routeMetadata: screen.routeMetadata ?? {},
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
const placementIntent = placementIntents.find(
|
|
813
|
+
(intent) => intent.fileId === screen.id,
|
|
814
|
+
);
|
|
815
|
+
const ownedFrameFields: Partial<CanvasFrameGeometry> = {};
|
|
816
|
+
if (placementIntent) {
|
|
817
|
+
for (const key of ["x", "y", "width", "height", "z"] as const) {
|
|
818
|
+
// A newly created screen owns its initial geometry. A refresh of
|
|
819
|
+
// an existing screen only owns fields explicitly supplied by the
|
|
820
|
+
// caller; current canvas movement/resizing wins for the rest.
|
|
821
|
+
if (
|
|
822
|
+
!placementIntent.existedAtStart ||
|
|
823
|
+
placementIntent.owns[key]
|
|
824
|
+
) {
|
|
825
|
+
ownedFrameFields[key] = frame[key];
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
nextOwnedFrameFields.set(screen.id, ownedFrameFields);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
lastOwnedMetadata = nextOwnedMetadata;
|
|
833
|
+
lastOwnedFrameFields = nextOwnedFrameFields;
|
|
834
|
+
return {
|
|
835
|
+
...currentData,
|
|
836
|
+
sourceType: "localhost",
|
|
482
837
|
sourceMode: "localhost",
|
|
838
|
+
connectionId: connection.id,
|
|
483
839
|
canvasFrames: mergedFrames.canvasFrames,
|
|
484
840
|
screenMetadata: previousMetadata,
|
|
485
841
|
localhostScreens: previousLocalhostScreens,
|
|
486
|
-
updatedAt
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
842
|
+
updatedAt,
|
|
843
|
+
};
|
|
844
|
+
},
|
|
845
|
+
isApplied: (data) => {
|
|
846
|
+
if (
|
|
847
|
+
data.sourceType !== "localhost" ||
|
|
848
|
+
data.sourceMode !== "localhost" ||
|
|
849
|
+
data.connectionId !== connection.id
|
|
850
|
+
) {
|
|
851
|
+
return false;
|
|
852
|
+
}
|
|
853
|
+
const frames = parseCanvasFrameGeometryById(data.canvasFrames);
|
|
854
|
+
const metadataById = isRecord(data.screenMetadata)
|
|
855
|
+
? data.screenMetadata
|
|
856
|
+
: {};
|
|
857
|
+
const localhostById = isRecord(data.localhostScreens)
|
|
858
|
+
? data.localhostScreens
|
|
859
|
+
: {};
|
|
860
|
+
|
|
861
|
+
for (const screen of savedScreens) {
|
|
862
|
+
const frame = frames[screen.id];
|
|
863
|
+
if (!frame) return false;
|
|
864
|
+
for (const [key, expected] of Object.entries(
|
|
865
|
+
lastOwnedFrameFields.get(screen.id) ?? {},
|
|
866
|
+
)) {
|
|
867
|
+
if (
|
|
868
|
+
!jsonValuesEqual(
|
|
869
|
+
frame[key as keyof CanvasFrameGeometry],
|
|
870
|
+
expected,
|
|
871
|
+
)
|
|
872
|
+
) {
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const expectedMetadata = lastOwnedMetadata.get(screen.id) ?? {};
|
|
878
|
+
for (const rawMetadata of [
|
|
879
|
+
metadataById[screen.id],
|
|
880
|
+
localhostById[screen.id],
|
|
881
|
+
]) {
|
|
882
|
+
if (!isRecord(rawMetadata)) return false;
|
|
883
|
+
for (const [key, expected] of Object.entries(expectedMetadata)) {
|
|
884
|
+
if (key === "routeMetadata") {
|
|
885
|
+
if (!isRecord(rawMetadata.routeMetadata)) return false;
|
|
886
|
+
for (const [routeKey, routeValue] of Object.entries(
|
|
887
|
+
expected as Record<string, unknown>,
|
|
888
|
+
)) {
|
|
889
|
+
if (
|
|
890
|
+
!jsonValuesEqual(
|
|
891
|
+
rawMetadata.routeMetadata[routeKey],
|
|
892
|
+
routeValue,
|
|
893
|
+
)
|
|
894
|
+
) {
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
} else if (!jsonValuesEqual(rawMetadata[key], expected)) {
|
|
899
|
+
return false;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return true;
|
|
905
|
+
},
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
const persistedFrames = parseCanvasFrameGeometryById(
|
|
909
|
+
persistedData.canvasFrames,
|
|
910
|
+
);
|
|
911
|
+
const resultScreens = savedScreens.map((screen) => ({
|
|
912
|
+
...screen,
|
|
913
|
+
width: persistedFrames[screen.id]?.width ?? screen.width,
|
|
914
|
+
height: persistedFrames[screen.id]?.height ?? screen.height,
|
|
915
|
+
}));
|
|
916
|
+
const placedFrames = placementIntents.map((intent) => ({
|
|
917
|
+
fileId: intent.fileId,
|
|
918
|
+
filename: intent.filename,
|
|
919
|
+
frame:
|
|
920
|
+
persistedFrames[intent.fileId] ??
|
|
921
|
+
parseCanvasFrameGeometryById({
|
|
922
|
+
[intent.fileId]: intent.fallback,
|
|
923
|
+
})[intent.fileId] ??
|
|
924
|
+
{},
|
|
925
|
+
}));
|
|
491
926
|
|
|
492
927
|
return {
|
|
493
928
|
designId,
|
|
@@ -495,8 +930,8 @@ export default defineAction({
|
|
|
495
930
|
devServerUrl,
|
|
496
931
|
bridgeUrl: connection.bridgeUrl ?? null,
|
|
497
932
|
screenCount: savedScreens.length,
|
|
498
|
-
screens:
|
|
499
|
-
placedFrames
|
|
933
|
+
screens: resultScreens,
|
|
934
|
+
placedFrames,
|
|
500
935
|
overview: true,
|
|
501
936
|
urlPath: `/design/${designId}`,
|
|
502
937
|
};
|