@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
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
|
|
2
|
+
import type { CodeLayerProjection } from "@shared/code-layer";
|
|
3
|
+
import type { DesignSourceType } from "@shared/source-mode";
|
|
4
|
+
|
|
5
|
+
import type { ScreenGeometrySelection } from "@/components/design/EditPanel";
|
|
6
|
+
import { getInitialFrameGeometry } from "@/components/design/multi-screen/frame-geometry";
|
|
7
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
8
|
+
import { prettyScreenName } from "@/lib/screen-names";
|
|
9
|
+
|
|
10
|
+
import type { DesignTool, EditorMode } from "./types";
|
|
11
|
+
|
|
12
|
+
// PF11: cache the FNV hash by content-string value. Two calls with an equal
|
|
13
|
+
// (===, i.e. SameValueZero) content string always hash to the same
|
|
14
|
+
// signature, so a plain value-keyed Map is a correct cache — no need for
|
|
15
|
+
// reference-identity tricks. Bounded LRU-ish eviction (drop oldest entry)
|
|
16
|
+
// keeps this from growing unboundedly across a long editing session with
|
|
17
|
+
// many distinct HTML revisions.
|
|
18
|
+
const CONTENT_SIGNATURE_CACHE_MAX = 200;
|
|
19
|
+
const contentSignatureCache = new Map<string, string>();
|
|
20
|
+
export function getContentSignature(content: string): string {
|
|
21
|
+
const cached = contentSignatureCache.get(content);
|
|
22
|
+
if (cached !== undefined) return cached;
|
|
23
|
+
let hash = 2166136261;
|
|
24
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
25
|
+
hash ^= content.charCodeAt(index);
|
|
26
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
27
|
+
}
|
|
28
|
+
const signature = `${content.length}:${hash.toString(36)}`;
|
|
29
|
+
if (contentSignatureCache.size >= CONTENT_SIGNATURE_CACHE_MAX) {
|
|
30
|
+
const oldestKey = contentSignatureCache.keys().next().value;
|
|
31
|
+
if (oldestKey !== undefined) contentSignatureCache.delete(oldestKey);
|
|
32
|
+
}
|
|
33
|
+
contentSignatureCache.set(content, signature);
|
|
34
|
+
return signature;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getOverviewScreenRuntimeReplacementKey({
|
|
38
|
+
screenId,
|
|
39
|
+
updatedAt,
|
|
40
|
+
content,
|
|
41
|
+
}: {
|
|
42
|
+
screenId: string;
|
|
43
|
+
updatedAt?: string | null;
|
|
44
|
+
content: string;
|
|
45
|
+
}) {
|
|
46
|
+
return [screenId, updatedAt ?? "", getContentSignature(content)].join(":");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function shouldUseOverviewRuntimeReplacement({
|
|
50
|
+
sourceType,
|
|
51
|
+
externalSnapshotHtml,
|
|
52
|
+
}: {
|
|
53
|
+
sourceType?: DesignSourceType | null;
|
|
54
|
+
externalSnapshotHtml?: string | null;
|
|
55
|
+
}) {
|
|
56
|
+
return sourceType === "inline" && !externalSnapshotHtml;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Only inline HTML screens may contribute a fresh client snapshot to the
|
|
61
|
+
* atomic screen-rename action. Localhost/fusion design_files rows intentionally
|
|
62
|
+
* store a route URL marker rather than the rendered preview HTML; sending that
|
|
63
|
+
* live snapshot as an override would silently convert the screen to inline
|
|
64
|
+
* source and break /visual-edit.
|
|
65
|
+
*/
|
|
66
|
+
export function shouldIncludeScreenRenameContentOverride(args: {
|
|
67
|
+
fileType: string;
|
|
68
|
+
sourceType: DesignSourceType;
|
|
69
|
+
persistedContent: string;
|
|
70
|
+
freshContent: string;
|
|
71
|
+
}): boolean {
|
|
72
|
+
return (
|
|
73
|
+
args.fileType.toLowerCase() === "html" &&
|
|
74
|
+
args.sourceType === "inline" &&
|
|
75
|
+
args.freshContent !== args.persistedContent
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function dedupeStringIds(ids: string[]): string[] {
|
|
80
|
+
return Array.from(new Set(ids.filter(Boolean)));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function sameStringIds(a: string[], b: string[]) {
|
|
84
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isScreenRootElementInfo(info: ElementInfo | null | undefined) {
|
|
88
|
+
const tagName = info?.tagName?.toUpperCase();
|
|
89
|
+
return tagName === "BODY" || tagName === "HTML";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function shouldMirrorSelectedElementToAgentChat(
|
|
93
|
+
info: ElementInfo | null | undefined,
|
|
94
|
+
): info is ElementInfo {
|
|
95
|
+
if (!info || isScreenRootElementInfo(info)) return false;
|
|
96
|
+
const text = info.textContent?.replace(/\s+/g, " ").trim();
|
|
97
|
+
if (!text) return true;
|
|
98
|
+
const labelText = text.replace(/^[^A-Za-z0-9]+/, "").toLowerCase();
|
|
99
|
+
return !labelText.startsWith("nothing here yet");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function shouldIgnoreOverviewLayerCreationEcho(args: {
|
|
103
|
+
pendingLayerId: string | null | undefined;
|
|
104
|
+
pendingScreenId: string | null | undefined;
|
|
105
|
+
screenId: string;
|
|
106
|
+
info?: ElementInfo | null;
|
|
107
|
+
event: "select" | "clear";
|
|
108
|
+
}) {
|
|
109
|
+
if (!args.pendingLayerId) return false;
|
|
110
|
+
if (args.pendingScreenId && args.pendingScreenId !== args.screenId) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
return args.event === "clear" || isScreenRootElementInfo(args.info);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getSelectedScreenIdsForEditorState(args: {
|
|
117
|
+
activeFileId: string | null | undefined;
|
|
118
|
+
overviewSelectedScreenIds: string[];
|
|
119
|
+
viewMode: "single" | "overview";
|
|
120
|
+
}) {
|
|
121
|
+
const { activeFileId, overviewSelectedScreenIds, viewMode } = args;
|
|
122
|
+
if (viewMode === "overview") {
|
|
123
|
+
return overviewSelectedScreenIds.length
|
|
124
|
+
? overviewSelectedScreenIds
|
|
125
|
+
: activeFileId
|
|
126
|
+
? [activeFileId]
|
|
127
|
+
: [];
|
|
128
|
+
}
|
|
129
|
+
return activeFileId ? [activeFileId] : [];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function resolveAvailableActiveFileId(args: {
|
|
133
|
+
activeFileId: string | null | undefined;
|
|
134
|
+
availableFileIds: Iterable<string>;
|
|
135
|
+
defaultFileId: string | null | undefined;
|
|
136
|
+
}): string | null {
|
|
137
|
+
const availableFileIds = new Set(args.availableFileIds);
|
|
138
|
+
if (args.activeFileId && availableFileIds.has(args.activeFileId)) {
|
|
139
|
+
return args.activeFileId;
|
|
140
|
+
}
|
|
141
|
+
return args.defaultFileId && availableFileIds.has(args.defaultFileId)
|
|
142
|
+
? args.defaultFileId
|
|
143
|
+
: null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function getSelectedScreenGeometryForInspector(args: {
|
|
147
|
+
selectedInspectorElementCount: number;
|
|
148
|
+
selectedScreenIds: string[];
|
|
149
|
+
overviewScreens: Array<{
|
|
150
|
+
id: string;
|
|
151
|
+
filename: string;
|
|
152
|
+
title?: string;
|
|
153
|
+
width?: number;
|
|
154
|
+
height?: number;
|
|
155
|
+
}>;
|
|
156
|
+
canvasFrameGeometryById: CanvasFrameGeometryById;
|
|
157
|
+
}): ScreenGeometrySelection | null {
|
|
158
|
+
if (args.selectedInspectorElementCount > 0) return null;
|
|
159
|
+
if (args.selectedScreenIds.length !== 1) return null;
|
|
160
|
+
const screenId = args.selectedScreenIds[0];
|
|
161
|
+
if (!screenId) return null;
|
|
162
|
+
const screenIndex = args.overviewScreens.findIndex(
|
|
163
|
+
(screen) => screen.id === screenId,
|
|
164
|
+
);
|
|
165
|
+
if (screenIndex < 0) return null;
|
|
166
|
+
const screen = args.overviewScreens[screenIndex];
|
|
167
|
+
if (!screen) return null;
|
|
168
|
+
const fallbackGeometry = getInitialFrameGeometry(screenIndex, {
|
|
169
|
+
width: screen.width ?? 1280,
|
|
170
|
+
height: screen.height ?? 2560,
|
|
171
|
+
});
|
|
172
|
+
const persistedGeometry = args.canvasFrameGeometryById[screenId] ?? {};
|
|
173
|
+
const geometry = {
|
|
174
|
+
...fallbackGeometry,
|
|
175
|
+
...persistedGeometry,
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
id: screen.id,
|
|
179
|
+
title: screen.title ?? prettyScreenName(screen.filename),
|
|
180
|
+
x: geometry.x,
|
|
181
|
+
y: geometry.y,
|
|
182
|
+
width: geometry.width,
|
|
183
|
+
height: geometry.height,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function fileIdFromLayerSelectionId(
|
|
188
|
+
layerId: string,
|
|
189
|
+
fileIds: Set<string>,
|
|
190
|
+
): string | null {
|
|
191
|
+
const normalized = layerId.startsWith("code:")
|
|
192
|
+
? layerId.slice("code:".length)
|
|
193
|
+
: layerId;
|
|
194
|
+
return fileIds.has(normalized) ? normalized : null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function getOverviewScreenIdsFromLayerSelection(args: {
|
|
198
|
+
fileIds: string[];
|
|
199
|
+
layerIds: string[];
|
|
200
|
+
}) {
|
|
201
|
+
const fileIds = new Set(args.fileIds);
|
|
202
|
+
const seen = new Set<string>();
|
|
203
|
+
const selectedScreenIds: string[] = [];
|
|
204
|
+
args.layerIds.forEach((layerId) => {
|
|
205
|
+
const fileId = fileIdFromLayerSelectionId(layerId, fileIds);
|
|
206
|
+
if (!fileId || seen.has(fileId)) return;
|
|
207
|
+
seen.add(fileId);
|
|
208
|
+
selectedScreenIds.push(fileId);
|
|
209
|
+
});
|
|
210
|
+
return selectedScreenIds;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function getOverviewEnterTarget(args: {
|
|
214
|
+
activeFileId: string | null | undefined;
|
|
215
|
+
overviewSelectedScreenIds: string[];
|
|
216
|
+
}) {
|
|
217
|
+
const { activeFileId, overviewSelectedScreenIds } = args;
|
|
218
|
+
if (overviewSelectedScreenIds.length === 0) {
|
|
219
|
+
return activeFileId ?? null;
|
|
220
|
+
}
|
|
221
|
+
if (activeFileId && overviewSelectedScreenIds.includes(activeFileId)) {
|
|
222
|
+
return activeFileId;
|
|
223
|
+
}
|
|
224
|
+
return (
|
|
225
|
+
overviewSelectedScreenIds[overviewSelectedScreenIds.length - 1] ?? null
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function getSidebarCodeLayerSelectionState(args: {
|
|
230
|
+
currentViewMode: "single" | "overview";
|
|
231
|
+
ownerFileId?: string | null;
|
|
232
|
+
overviewSelectedScreenIds: string[];
|
|
233
|
+
screenFileIds?: string[];
|
|
234
|
+
}) {
|
|
235
|
+
const {
|
|
236
|
+
currentViewMode,
|
|
237
|
+
ownerFileId,
|
|
238
|
+
overviewSelectedScreenIds,
|
|
239
|
+
screenFileIds,
|
|
240
|
+
} = args;
|
|
241
|
+
const ownerScreenId =
|
|
242
|
+
ownerFileId && (!screenFileIds || screenFileIds.includes(ownerFileId))
|
|
243
|
+
? ownerFileId
|
|
244
|
+
: null;
|
|
245
|
+
return {
|
|
246
|
+
viewMode: currentViewMode,
|
|
247
|
+
overviewSelectedScreenIds:
|
|
248
|
+
currentViewMode === "overview" && ownerScreenId
|
|
249
|
+
? [ownerScreenId]
|
|
250
|
+
: currentViewMode === "overview" && ownerFileId
|
|
251
|
+
? []
|
|
252
|
+
: overviewSelectedScreenIds,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function shouldLimitEditorChromeUntilContentReady(args: {
|
|
257
|
+
fileCount: number;
|
|
258
|
+
hasActiveCanvasContent: boolean;
|
|
259
|
+
generating: boolean;
|
|
260
|
+
pendingGenerationActive: boolean;
|
|
261
|
+
}) {
|
|
262
|
+
const {
|
|
263
|
+
fileCount,
|
|
264
|
+
generating,
|
|
265
|
+
hasActiveCanvasContent,
|
|
266
|
+
pendingGenerationActive,
|
|
267
|
+
} = args;
|
|
268
|
+
return (
|
|
269
|
+
(fileCount === 0 || !hasActiveCanvasContent) &&
|
|
270
|
+
(generating || pendingGenerationActive)
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function shouldEscapeToOverview(args: {
|
|
275
|
+
activeTool: DesignTool;
|
|
276
|
+
drawMode: boolean;
|
|
277
|
+
mode: EditorMode;
|
|
278
|
+
pinMode: boolean;
|
|
279
|
+
selectedElement: ElementInfo | null;
|
|
280
|
+
viewMode: "single" | "overview";
|
|
281
|
+
}) {
|
|
282
|
+
const { activeTool, drawMode, mode, pinMode, selectedElement, viewMode } =
|
|
283
|
+
args;
|
|
284
|
+
return (
|
|
285
|
+
viewMode === "single" &&
|
|
286
|
+
!selectedElement &&
|
|
287
|
+
!drawMode &&
|
|
288
|
+
!pinMode &&
|
|
289
|
+
mode === "edit" &&
|
|
290
|
+
activeTool === "move"
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* B5-1: an empty-canvas click (a marquee/hit-test that resolved to zero
|
|
296
|
+
* elements) must deselect ANY current selection kind — a selected
|
|
297
|
+
* overview screen frame AND a selected element inside a screen (set via the
|
|
298
|
+
* iframe bridge). `handleLayerMarqueeSelectionChange` already clears the
|
|
299
|
+
* host-side `selectedElement` state when nothing was hit and the gesture
|
|
300
|
+
* isn't additive; this helper decides whether it must ALSO signal the
|
|
301
|
+
* iframe/bridge overlays to clear their own selection highlight
|
|
302
|
+
* (`overviewClearSelectionRequest`) — otherwise a previously-selected
|
|
303
|
+
* in-screen element keeps showing its selection chrome inside the iframe
|
|
304
|
+
* even though the host's `selectedElement` is already null. True whenever
|
|
305
|
+
* the resolved hit-set is empty and the gesture isn't additive (an additive
|
|
306
|
+
* click/shift-click on empty space is a no-op, matching Escape and the
|
|
307
|
+
* MultiScreenCanvas-level `shouldClearSelectionOnEmptyCanvasClick`).
|
|
308
|
+
*
|
|
309
|
+
* Exported for unit testing.
|
|
310
|
+
*/
|
|
311
|
+
export function shouldClearBridgeSelectionOnEmptyMarquee(args: {
|
|
312
|
+
resolvedCount: number;
|
|
313
|
+
additive: boolean | undefined;
|
|
314
|
+
}): boolean {
|
|
315
|
+
return args.resolvedCount === 0 && !args.additive;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Build the set of all node ids (both projection ids and data-agent-native-node-id
|
|
320
|
+
* attribute values) that exist in the given projection. Used by handleGroupSelection
|
|
321
|
+
* and handleUngroupSelection to filter selectedLayerIdsState to the active file's
|
|
322
|
+
* nodes before passing them to wrapNodes / unwrap, preventing cross-file stale ids
|
|
323
|
+
* from causing spurious "conflict" errors.
|
|
324
|
+
*
|
|
325
|
+
* Exported for unit testing.
|
|
326
|
+
*/
|
|
327
|
+
export function buildActiveFileNodeIdSet(
|
|
328
|
+
projection: CodeLayerProjection,
|
|
329
|
+
): Set<string> {
|
|
330
|
+
const ids = new Set<string>();
|
|
331
|
+
for (const n of projection.nodes) {
|
|
332
|
+
ids.add(n.id);
|
|
333
|
+
const attrId = n.dataAttributes["data-agent-native-node-id"];
|
|
334
|
+
if (attrId) ids.add(attrId);
|
|
335
|
+
}
|
|
336
|
+
return ids;
|
|
337
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { CreationTool } from "@/components/design/design-canvas/creation";
|
|
2
|
+
|
|
3
|
+
import type { DesignTool, EditorMode } from "./types";
|
|
4
|
+
|
|
5
|
+
export const MOVE_GROUP_TOOL_PRESENTATIONS = {
|
|
6
|
+
move: {
|
|
7
|
+
labelKey: "designEditor.tools.move",
|
|
8
|
+
shortcut: "V",
|
|
9
|
+
},
|
|
10
|
+
hand: {
|
|
11
|
+
labelKey: "designEditor.tools.hand",
|
|
12
|
+
shortcut: "H",
|
|
13
|
+
},
|
|
14
|
+
scale: {
|
|
15
|
+
labelKey: "designEditor.tools.scale",
|
|
16
|
+
shortcut: "K",
|
|
17
|
+
},
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
export type MoveGroupTool = keyof typeof MOVE_GROUP_TOOL_PRESENTATIONS;
|
|
21
|
+
|
|
22
|
+
export function getMoveGroupToolPresentation(activeTool: DesignTool) {
|
|
23
|
+
const moveGroupTool: MoveGroupTool =
|
|
24
|
+
activeTool === "hand" || activeTool === "scale" ? activeTool : "move";
|
|
25
|
+
return {
|
|
26
|
+
tool: moveGroupTool,
|
|
27
|
+
...MOVE_GROUP_TOOL_PRESENTATIONS[moveGroupTool],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DESIGN_EDITOR_TOOLS = new Set<DesignTool>([
|
|
32
|
+
"move",
|
|
33
|
+
"frame",
|
|
34
|
+
"rect",
|
|
35
|
+
"line",
|
|
36
|
+
"arrow",
|
|
37
|
+
"ellipse",
|
|
38
|
+
"polygon",
|
|
39
|
+
"star",
|
|
40
|
+
"text",
|
|
41
|
+
"pen",
|
|
42
|
+
"hand",
|
|
43
|
+
"comment",
|
|
44
|
+
"draw",
|
|
45
|
+
"scale",
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
export function normalizeDesignTool(value: unknown): DesignTool | null {
|
|
49
|
+
return typeof value === "string" &&
|
|
50
|
+
DESIGN_EDITOR_TOOLS.has(value as DesignTool)
|
|
51
|
+
? (value as DesignTool)
|
|
52
|
+
: null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isSingleScreenAnnotationTool(tool: DesignTool): boolean {
|
|
56
|
+
return tool === "draw" || tool === "comment";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getDesignToolActivationState(tool: DesignTool): {
|
|
60
|
+
mode: EditorMode;
|
|
61
|
+
drawMode: boolean;
|
|
62
|
+
pinMode: boolean;
|
|
63
|
+
} {
|
|
64
|
+
if (tool === "draw") {
|
|
65
|
+
return { mode: "annotate", drawMode: true, pinMode: false };
|
|
66
|
+
}
|
|
67
|
+
if (tool === "comment") {
|
|
68
|
+
return { mode: "annotate", drawMode: false, pinMode: true };
|
|
69
|
+
}
|
|
70
|
+
return { mode: "edit", drawMode: false, pinMode: false };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getSingleScreenCreationTool(args: {
|
|
74
|
+
activeTool: DesignTool;
|
|
75
|
+
viewMode: "single" | "overview";
|
|
76
|
+
hasActiveFile: boolean;
|
|
77
|
+
}): CreationTool | null {
|
|
78
|
+
if (args.viewMode !== "single" || !args.hasActiveFile) return null;
|
|
79
|
+
switch (args.activeTool) {
|
|
80
|
+
case "rect":
|
|
81
|
+
return "rectangle";
|
|
82
|
+
case "ellipse":
|
|
83
|
+
case "line":
|
|
84
|
+
case "arrow":
|
|
85
|
+
case "text":
|
|
86
|
+
case "pen":
|
|
87
|
+
case "frame":
|
|
88
|
+
return args.activeTool;
|
|
89
|
+
default:
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { TweakSelections } from "@shared/resolve-tweaks";
|
|
2
|
+
|
|
3
|
+
export interface PendingTweakSave {
|
|
4
|
+
selections: TweakSelections;
|
|
5
|
+
revision: number;
|
|
6
|
+
expectedSelectionsHash: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createQueuedTweakSave(
|
|
10
|
+
selections: TweakSelections,
|
|
11
|
+
revision: number,
|
|
12
|
+
confirmedSelectionsHash: string,
|
|
13
|
+
existingDebouncedSave: PendingTweakSave | null,
|
|
14
|
+
): PendingTweakSave {
|
|
15
|
+
return {
|
|
16
|
+
selections,
|
|
17
|
+
revision,
|
|
18
|
+
// Multiple knob ticks inside one debounce window are one full-snapshot
|
|
19
|
+
// edit and must retain the base observed by the first tick.
|
|
20
|
+
expectedSelectionsHash:
|
|
21
|
+
existingDebouncedSave?.expectedSelectionsHash ?? confirmedSelectionsHash,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function rebaseTweakSaveForSend(
|
|
26
|
+
pending: PendingTweakSave,
|
|
27
|
+
confirmedSelectionsHash: string,
|
|
28
|
+
): PendingTweakSave {
|
|
29
|
+
return {
|
|
30
|
+
...pending,
|
|
31
|
+
// Saves are serialized. Resolve the base only when this request reaches
|
|
32
|
+
// the front of the chain so it follows a verified predecessor success,
|
|
33
|
+
// but not a predecessor that failed.
|
|
34
|
+
expectedSelectionsHash: confirmedSelectionsHash,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function retainLatestFailedTweakSave(
|
|
39
|
+
queued: PendingTweakSave | null,
|
|
40
|
+
failed: PendingTweakSave,
|
|
41
|
+
): PendingTweakSave {
|
|
42
|
+
return queued && queued.revision > failed.revision ? queued : failed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function clearCompletedTweakSave(
|
|
46
|
+
queued: PendingTweakSave | null,
|
|
47
|
+
completedRevision: number,
|
|
48
|
+
): PendingTweakSave | null {
|
|
49
|
+
return queued?.revision === completedRevision ? null : queued;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type TweakSaveKeepaliveAttempt =
|
|
53
|
+
| { accepted: true; completion: Promise<unknown> }
|
|
54
|
+
| { accepted: false; completion: null };
|
|
55
|
+
|
|
56
|
+
export async function sendJournaledTweakSaveKeepalive(options: {
|
|
57
|
+
journal: () => Promise<boolean>;
|
|
58
|
+
send: () => TweakSaveKeepaliveAttempt;
|
|
59
|
+
acknowledge: () => Promise<unknown>;
|
|
60
|
+
}): Promise<boolean> {
|
|
61
|
+
// Pagehide may freeze the document at any await boundary. Establish the
|
|
62
|
+
// durable retry entry first so either the keepalive finishes or a later
|
|
63
|
+
// editor session can replay the exact same operation.
|
|
64
|
+
if (!(await options.journal())) return false;
|
|
65
|
+
const attempt = options.send();
|
|
66
|
+
if (!attempt.accepted) return false;
|
|
67
|
+
await attempt.completion;
|
|
68
|
+
await options.acknowledge();
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type TweakSaveFailureKind =
|
|
73
|
+
| "conflict"
|
|
74
|
+
| "durable-retry"
|
|
75
|
+
| "tab-memory-only";
|
|
76
|
+
|
|
77
|
+
export function classifyTweakSaveFailure(
|
|
78
|
+
error: unknown,
|
|
79
|
+
journaled: boolean,
|
|
80
|
+
): TweakSaveFailureKind {
|
|
81
|
+
const status =
|
|
82
|
+
error && typeof error === "object" && "status" in error
|
|
83
|
+
? (error as { status?: unknown }).status
|
|
84
|
+
: undefined;
|
|
85
|
+
if (status === 409) return "conflict";
|
|
86
|
+
return journaled ? "durable-retry" : "tab-memory-only";
|
|
87
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type EditorMode = "annotate" | "edit" | "interact";
|
|
2
|
+
|
|
3
|
+
export type DesignTool =
|
|
4
|
+
| "move"
|
|
5
|
+
| "frame"
|
|
6
|
+
| "rect"
|
|
7
|
+
| "line"
|
|
8
|
+
| "arrow"
|
|
9
|
+
| "ellipse"
|
|
10
|
+
| "polygon"
|
|
11
|
+
| "star"
|
|
12
|
+
| "text"
|
|
13
|
+
| "pen"
|
|
14
|
+
| "hand"
|
|
15
|
+
| "comment"
|
|
16
|
+
| "draw"
|
|
17
|
+
| "scale";
|
|
18
|
+
|
|
19
|
+
export type ShapeTool =
|
|
20
|
+
| "rect"
|
|
21
|
+
| "line"
|
|
22
|
+
| "arrow"
|
|
23
|
+
| "ellipse"
|
|
24
|
+
| "polygon"
|
|
25
|
+
| "star";
|
|
26
|
+
|
|
27
|
+
export type DesignLeftPanel =
|
|
28
|
+
| "file"
|
|
29
|
+
| "agent"
|
|
30
|
+
| "assets"
|
|
31
|
+
| "tools"
|
|
32
|
+
| "tokens"
|
|
33
|
+
| "import"
|
|
34
|
+
| "code";
|
|
35
|
+
|
|
36
|
+
export const SHOW_DESIGN_CODE_LEFT_PANEL = true;
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
export { default } from "../pages/DesignEditor";
|
|
2
1
|
import { messagesByLocale } from "@/i18n-data";
|
|
3
2
|
|
|
3
|
+
import DesignEditorRoute from "../pages/DesignEditor";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Keep the route module itself as a React Fast Refresh boundary. A bare
|
|
7
|
+
* re-export does not register a local component, so an invalidated editor
|
|
8
|
+
* dependency can otherwise propagate into a full-page reload.
|
|
9
|
+
*/
|
|
10
|
+
export default function DesignRoute() {
|
|
11
|
+
return <DesignEditorRoute />;
|
|
12
|
+
}
|
|
13
|
+
|
|
4
14
|
export function meta() {
|
|
5
15
|
return [{ title: messagesByLocale["en-US"].routeTitles.designEditor }];
|
|
6
16
|
}
|
|
@@ -5,61 +5,12 @@ import {
|
|
|
5
5
|
DESIGN_AGENT_CONTEXT_ENDPOINT,
|
|
6
6
|
DESIGN_AGENT_RESOURCE_KIND,
|
|
7
7
|
} from "../../shared/agent-readable.js";
|
|
8
|
+
import { publicDesignAccessRole } from "../lib/design-data-access.js";
|
|
8
9
|
import * as schema from "./schema.js";
|
|
9
10
|
|
|
10
11
|
export const getDb = createGetDb(schema);
|
|
11
12
|
export { schema };
|
|
12
13
|
|
|
13
|
-
function isLocalDevRuntime(): boolean {
|
|
14
|
-
return (
|
|
15
|
-
process.env.NODE_ENV !== "production" &&
|
|
16
|
-
!process.env.NETLIFY &&
|
|
17
|
-
!process.env.VERCEL &&
|
|
18
|
-
!process.env.AWS_LAMBDA_FUNCTION_NAME &&
|
|
19
|
-
!process.env.CF_PAGES
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function isLoopbackHttpUrl(value: unknown): boolean {
|
|
24
|
-
if (typeof value !== "string" || !value.trim()) return false;
|
|
25
|
-
try {
|
|
26
|
-
const url = new URL(value);
|
|
27
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
28
|
-
const host = url.hostname.toLowerCase();
|
|
29
|
-
return (
|
|
30
|
-
host === "localhost" ||
|
|
31
|
-
host === "::1" ||
|
|
32
|
-
host === "[::1]" ||
|
|
33
|
-
/^127(?:\.\d{1,3}){3}$/.test(host)
|
|
34
|
-
);
|
|
35
|
-
} catch {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function isLoopbackLocalhostDesign(design: any): boolean {
|
|
41
|
-
if (!isLocalDevRuntime()) return false;
|
|
42
|
-
let data: any;
|
|
43
|
-
try {
|
|
44
|
-
data = typeof design?.data === "string" ? JSON.parse(design.data) : null;
|
|
45
|
-
} catch {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
if (!data || data.sourceMode !== "localhost") return false;
|
|
49
|
-
|
|
50
|
-
const metadataEntries = Object.values(
|
|
51
|
-
data.localhostScreens ?? data.screenMetadata ?? {},
|
|
52
|
-
) as any[];
|
|
53
|
-
if (metadataEntries.length === 0) return false;
|
|
54
|
-
|
|
55
|
-
return metadataEntries.every((entry) => {
|
|
56
|
-
if (!entry || entry.sourceType !== "localhost") return false;
|
|
57
|
-
const previewUrl = entry.previewUrl ?? entry.url;
|
|
58
|
-
if (!isLoopbackHttpUrl(previewUrl)) return false;
|
|
59
|
-
return !entry.bridgeUrl || isLoopbackHttpUrl(entry.bridgeUrl);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
14
|
registerShareableResource({
|
|
64
15
|
type: "design",
|
|
65
16
|
resourceTable: schema.designs,
|
|
@@ -72,8 +23,7 @@ registerShareableResource({
|
|
|
72
23
|
getContextPath: () => DESIGN_AGENT_CONTEXT_ENDPOINT,
|
|
73
24
|
},
|
|
74
25
|
getDb,
|
|
75
|
-
publicAccessRole:
|
|
76
|
-
isLoopbackLocalhostDesign(design) ? "editor" : "viewer",
|
|
26
|
+
publicAccessRole: publicDesignAccessRole,
|
|
77
27
|
});
|
|
78
28
|
|
|
79
29
|
registerShareableResource({
|