@agent-native/core 0.92.6 → 0.92.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +18 -0
- package/corpus/core/docs/content/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/pure-agent-apps.mdx +22 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builtin.ts +1 -1
- package/corpus/core/src/agent/model-config.ts +30 -30
- package/corpus/core/src/agent/production-agent.ts +129 -7
- package/corpus/core/src/cli/design-connect.ts +438 -83
- package/corpus/core/src/cli/recap.ts +2 -2
- package/corpus/core/src/cli/sync-builder-starter-manifest.ts +1 -1
- package/corpus/core/src/client/AgentPanel.tsx +8 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +12 -7
- package/corpus/core/src/client/composer/TiptapComposer.tsx +1 -1
- package/corpus/core/src/client/index.ts +4 -0
- package/corpus/core/src/client/org/TeamPage.tsx +41 -26
- package/corpus/core/src/client/use-action.ts +113 -1
- package/corpus/core/src/db/client.ts +12 -2
- package/corpus/core/src/db/create-get-db.ts +4 -1
- package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
- package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
- package/corpus/core/src/server/transcribe-voice.ts +1 -1
- package/corpus/core/src/usage/store.ts +13 -4
- package/corpus/core/src/vite/client.ts +38 -1
- package/corpus/templates/analytics/AGENTS.md +3 -0
- package/corpus/templates/analytics/actions/get-data-program.ts +78 -0
- package/corpus/templates/analytics/actions/list-data-programs.ts +49 -0
- package/corpus/templates/analytics/app/global.css +11 -0
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +163 -17
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +20 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +96 -3
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +61 -4
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +40 -2
- package/corpus/templates/analytics/changelog/2026-07-09-analytics-no-longer-forgets-successful-data-queries-when-a-l.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-keep-consistent-spacing-when-they-stack-ver.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-tables-and-extensions-can-be-selected-and-d.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-long-running-analytics-requests-now-recover-automatically-wh.md +6 -0
- package/corpus/templates/analytics/server/db/index.ts +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +1 -7
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +102 -15
- package/corpus/templates/content/app/hooks/use-content-database.ts +52 -0
- package/corpus/templates/content/app/hooks/use-document-properties.ts +50 -6
- package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/content/app/i18n-data.ts +28 -0
- package/corpus/templates/content/changelog/2026-07-09-notion-conflict-warnings-no-longer-flash-during-normal-synce.md +6 -0
- package/corpus/templates/content/changelog/2026-07-09-property-menus-and-delete-confirmations-now-open-visibly-and.md +6 -0
- package/corpus/templates/content/server/lib/notion-sync.ts +130 -28
- package/corpus/templates/design/actions/add-breakpoint.ts +48 -68
- package/corpus/templates/design/actions/add-localhost-screens.ts +496 -61
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +2 -18
- package/corpus/templates/design/actions/apply-design-token-edit.ts +51 -82
- package/corpus/templates/design/actions/apply-tweaks.ts +63 -41
- package/corpus/templates/design/actions/capture-design-state.ts +2 -2
- package/corpus/templates/design/actions/connect-localhost.ts +58 -12
- package/corpus/templates/design/actions/create-component.ts +2 -18
- package/corpus/templates/design/actions/create-design-branch.ts +38 -18
- package/corpus/templates/design/actions/create-fusion-app.ts +15 -11
- package/corpus/templates/design/actions/delete-file.ts +19 -34
- package/corpus/templates/design/actions/deploy-design-preview.ts +59 -27
- package/corpus/templates/design/actions/deploy-fusion-app.ts +22 -15
- package/corpus/templates/design/actions/export-pdf.ts +2 -1
- package/corpus/templates/design/actions/export-zip.ts +9 -3
- package/corpus/templates/design/actions/generate-design.ts +107 -70
- package/corpus/templates/design/actions/get-component-details.ts +2 -18
- package/corpus/templates/design/actions/get-design-branch-diff.ts +2 -3
- package/corpus/templates/design/actions/get-design-surface-index.ts +2 -18
- package/corpus/templates/design/actions/get-design.ts +2 -1
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +7 -1
- package/corpus/templates/design/actions/import-design-tokens.ts +114 -76
- package/corpus/templates/design/actions/index-components.ts +2 -18
- package/corpus/templates/design/actions/list-design-source-capabilities.ts +23 -28
- package/corpus/templates/design/actions/list-local-files.ts +9 -2
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +82 -98
- package/corpus/templates/design/actions/open-component-source.ts +2 -18
- package/corpus/templates/design/actions/open-visual-edit.ts +19 -1
- package/corpus/templates/design/actions/present-design-variants.ts +70 -70
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +4 -14
- package/corpus/templates/design/actions/read-local-file.ts +9 -2
- package/corpus/templates/design/actions/remove-breakpoint.ts +40 -61
- package/corpus/templates/design/actions/rename-screen.ts +425 -0
- package/corpus/templates/design/actions/request-localhost-write-consent.ts +12 -2
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +9 -2
- package/corpus/templates/design/actions/sync-fusion-app.ts +56 -39
- package/corpus/templates/design/actions/update-design.ts +376 -61
- package/corpus/templates/design/actions/update-file.ts +455 -188
- package/corpus/templates/design/actions/write-local-file.ts +10 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +685 -608
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +43 -15
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +576 -2015
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +62 -14
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +263 -25
- package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +34 -0
- package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +40 -0
- package/corpus/templates/design/app/components/design/design-canvas/creation.ts +47 -0
- package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +27 -0
- package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +62 -0
- package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +137 -0
- package/corpus/templates/design/app/components/design/design-canvas/file-drop.ts +10 -0
- package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +23 -0
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +23 -0
- package/corpus/templates/design/app/components/design/design-canvas/iframe-pan.ts +177 -0
- package/corpus/templates/design/app/components/design/design-canvas/motion-types.ts +7 -0
- package/corpus/templates/design/app/components/design/design-canvas/pending-text-edit.ts +62 -0
- package/corpus/templates/design/app/components/design/index.ts +2 -2
- package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +100 -0
- package/corpus/templates/design/app/components/design/multi-screen/coordinate-transforms.ts +81 -0
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +23 -13
- package/corpus/templates/design/app/components/design/multi-screen/culling.ts +238 -19
- package/corpus/templates/design/app/components/design/multi-screen/draft-primitives.ts +424 -0
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +341 -0
- package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +52 -0
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +62 -0
- package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +283 -0
- package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +272 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +5 -1
- package/corpus/templates/design/app/components/design/multi-screen/wheel-gesture-state.ts +12 -0
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +325 -84
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +15 -1
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +119 -47
- package/corpus/templates/design/app/i18n/zh-TW.ts +9 -0
- package/corpus/templates/design/app/i18n-data.ts +223 -0
- package/corpus/templates/design/app/lib/design-save-outbox.ts +379 -0
- package/corpus/templates/design/app/lib/png-clipboard.ts +113 -0
- package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +229 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +918 -0
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +43 -0
- package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +336 -0
- package/corpus/templates/design/app/pages/design-editor/data-operations.ts +270 -0
- package/corpus/templates/design/app/pages/design-editor/dom-guards.ts +24 -0
- package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +11 -0
- package/corpus/templates/design/app/pages/design-editor/editor-session.ts +20 -0
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +408 -0
- package/corpus/templates/design/app/pages/design-editor/export-capture.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +93 -0
- package/corpus/templates/design/app/pages/design-editor/history.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +185 -0
- package/corpus/templates/design/app/pages/design-editor/motion-state.ts +309 -0
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +438 -0
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +510 -0
- package/corpus/templates/design/app/pages/design-editor/portable-style.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +337 -0
- package/corpus/templates/design/app/pages/design-editor/style-utils.ts +5 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +92 -0
- package/corpus/templates/design/app/pages/design-editor/tweak-save.ts +87 -0
- package/corpus/templates/design/app/pages/design-editor/types.ts +36 -0
- package/corpus/templates/design/app/routes/design.$id.tsx +11 -1
- package/corpus/templates/design/changelog/2026-07-09-design-edits-canvas-positions-tools-and-tab-context-now-pers.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-figma-style-shortcuts-drawing-annotations-and-cross-screen-d.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-hand-and-scale-tools-now-keep-their-active-toolbar-identity-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-large-multi-screen-designs-now-stay-responsive-during-long-c.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layer-drags-now-land-exactly-where-the-insertion-indicator-s.md +6 -0
- package/corpus/templates/design/server/db/index.ts +2 -52
- package/corpus/templates/design/server/db/schema.ts +16 -0
- package/corpus/templates/design/server/lib/design-data-access.ts +70 -0
- package/corpus/templates/design/server/lib/design-data-mutation.ts +249 -0
- package/corpus/templates/design/server/lib/fusion-screens.ts +89 -76
- package/corpus/templates/design/server/lib/import-design-files.ts +76 -35
- package/corpus/templates/design/server/lib/verify-write-grant.ts +7 -2
- package/corpus/templates/design/server/plugins/db.ts +19 -0
- package/corpus/templates/design/server/source-workspace.ts +12 -13
- package/corpus/templates/design/shared/resolve-tweaks.ts +15 -0
- package/corpus/templates/design/shared/screen-rename.ts +42 -0
- package/corpus/templates/design/shared/source-mode.ts +84 -4
- package/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builtin.js +1 -1
- package/dist/agent/engine/builtin.js.map +1 -1
- package/dist/agent/model-config.d.ts +9 -9
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +30 -30
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts +60 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +106 -7
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/design-connect.d.ts +30 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +359 -75
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/recap.js +2 -2
- package/dist/cli/recap.js.map +1 -1
- package/dist/cli/sync-builder-starter-manifest.js +1 -1
- package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +10 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/composer/TiptapComposer.js +1 -1
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +6 -5
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/use-action.d.ts +32 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +68 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +3 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +7 -2
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +2 -2
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.js +1 -1
- package/dist/server/transcribe-voice.js.map +1 -1
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +13 -4
- package/dist/usage/store.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +36 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
- package/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
- package/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
- package/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
- package/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
- package/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
- package/docs/content/pr-visual-recap.mdx +3 -3
- package/docs/content/pure-agent-apps.mdx +22 -0
- package/package.json +2 -2
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyVisualEdit,
|
|
3
|
+
type ApplyVisualEditResult,
|
|
4
|
+
} from "@shared/code-layer";
|
|
5
|
+
import {
|
|
6
|
+
duplicateStatePreviewRules,
|
|
7
|
+
type InteractionState,
|
|
8
|
+
upsertStateStyles,
|
|
9
|
+
} from "@shared/interaction-states";
|
|
10
|
+
import {
|
|
11
|
+
planBreakpointStyleWrite,
|
|
12
|
+
utilityStem,
|
|
13
|
+
} from "@shared/responsive-classes";
|
|
14
|
+
import {
|
|
15
|
+
normalizeDesignSourceType,
|
|
16
|
+
type DesignSourceType,
|
|
17
|
+
} from "@shared/source-mode";
|
|
18
|
+
|
|
19
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
20
|
+
|
|
21
|
+
import { camelStyleProperty } from "./style-utils";
|
|
22
|
+
|
|
23
|
+
export interface PendingVisualStyleEdit {
|
|
24
|
+
screenId: string;
|
|
25
|
+
filename: string;
|
|
26
|
+
screenName: string;
|
|
27
|
+
selector: string;
|
|
28
|
+
sourceId?: string | null;
|
|
29
|
+
tagName?: string | null;
|
|
30
|
+
classes: string[];
|
|
31
|
+
styles: Record<string, string>;
|
|
32
|
+
/**
|
|
33
|
+
* Inline style values to replay when the user discards the live preview.
|
|
34
|
+
* Missing authored inline values are stored as "" so the bridge removes the
|
|
35
|
+
* temporary inline style and lets the app's real CSS win again.
|
|
36
|
+
*/
|
|
37
|
+
originalStyles: Record<string, string>;
|
|
38
|
+
updatedAt: number;
|
|
39
|
+
/**
|
|
40
|
+
* §6.4 — breakpoint scope active when the edit was made. When present the
|
|
41
|
+
* edit must be applied as a width-scoped override (apply-visual-edit with
|
|
42
|
+
* `activeFrameWidthPx`), not a base write. `upperBoundPx` is the Framer
|
|
43
|
+
* cascade bound (just below the next-wider frame); null means the active
|
|
44
|
+
* frame was the widest context (base edit).
|
|
45
|
+
*/
|
|
46
|
+
breakpoint?: {
|
|
47
|
+
activeWidthPx: number;
|
|
48
|
+
upperBoundPx: number | null;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function pendingLiveTextEditKey(edit: PendingLiveTextEdit): string {
|
|
53
|
+
return `${edit.screenId}:${edit.sourceId?.trim() || edit.selector.trim()}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function mergePendingLiveNonStyleEdits(
|
|
57
|
+
edits: readonly PendingLiveNonStyleEdit[],
|
|
58
|
+
): PendingLiveNonStyleEdit[] {
|
|
59
|
+
const merged: PendingLiveNonStyleEdit[] = [];
|
|
60
|
+
for (const edit of edits) {
|
|
61
|
+
if (edit.kind === "structure") {
|
|
62
|
+
merged.push(edit);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const nextKey = pendingLiveTextEditKey(edit);
|
|
66
|
+
const index = merged.findIndex(
|
|
67
|
+
(candidate) =>
|
|
68
|
+
candidate.kind === "text" &&
|
|
69
|
+
pendingLiveTextEditKey(candidate) === nextKey,
|
|
70
|
+
);
|
|
71
|
+
if (index === -1) {
|
|
72
|
+
merged.push(edit);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const previous = merged[index] as PendingLiveTextEdit;
|
|
76
|
+
merged[index] = {
|
|
77
|
+
...previous,
|
|
78
|
+
...edit,
|
|
79
|
+
originalValue: previous.originalValue,
|
|
80
|
+
originalHtml: previous.originalHtml,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return merged;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function pendingLiveTextUndoRevertValue(
|
|
87
|
+
currentEdits: readonly PendingLiveNonStyleEdit[],
|
|
88
|
+
nextEdit: PendingLiveTextEdit,
|
|
89
|
+
): { value: string; html?: string } {
|
|
90
|
+
const currentForTarget = currentEdits.find(
|
|
91
|
+
(edit): edit is PendingLiveTextEdit =>
|
|
92
|
+
edit.kind === "text" &&
|
|
93
|
+
pendingLiveTextEditKey(edit) === pendingLiveTextEditKey(nextEdit),
|
|
94
|
+
);
|
|
95
|
+
return currentForTarget
|
|
96
|
+
? { value: currentForTarget.value, html: currentForTarget.html }
|
|
97
|
+
: { value: nextEdit.originalValue, html: nextEdit.originalHtml };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface PendingLiveTextEdit {
|
|
101
|
+
kind: "text";
|
|
102
|
+
screenId: string;
|
|
103
|
+
filename: string;
|
|
104
|
+
screenName: string;
|
|
105
|
+
selector: string;
|
|
106
|
+
sourceId?: string | null;
|
|
107
|
+
tagName?: string | null;
|
|
108
|
+
classes: string[];
|
|
109
|
+
value: string;
|
|
110
|
+
html?: string;
|
|
111
|
+
originalValue: string;
|
|
112
|
+
originalHtml?: string;
|
|
113
|
+
updatedAt: number;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface PendingLiveStructureEdit {
|
|
117
|
+
kind: "structure";
|
|
118
|
+
screenId: string;
|
|
119
|
+
filename: string;
|
|
120
|
+
screenName: string;
|
|
121
|
+
selector: string;
|
|
122
|
+
sourceId?: string | null;
|
|
123
|
+
anchorSelector: string;
|
|
124
|
+
anchorSourceId?: string | null;
|
|
125
|
+
placement: "before" | "after" | "inside";
|
|
126
|
+
requestId?: string;
|
|
127
|
+
updatedAt: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type PendingLiveNonStyleEdit =
|
|
131
|
+
| PendingLiveTextEdit
|
|
132
|
+
| PendingLiveStructureEdit;
|
|
133
|
+
export type PendingVisualStyleUndoEntry = {
|
|
134
|
+
edit: PendingVisualStyleEdit;
|
|
135
|
+
revertStyles: Record<string, string>;
|
|
136
|
+
};
|
|
137
|
+
export type PendingLiveTextUndoEntry = {
|
|
138
|
+
kind: "text";
|
|
139
|
+
edit: PendingLiveTextEdit;
|
|
140
|
+
revertValue: string;
|
|
141
|
+
revertHtml?: string;
|
|
142
|
+
};
|
|
143
|
+
export type PendingLiveStructureUndoEntry = {
|
|
144
|
+
kind: "structure";
|
|
145
|
+
edit: PendingLiveStructureEdit;
|
|
146
|
+
};
|
|
147
|
+
export type PendingLiveNonStyleUndoEntry =
|
|
148
|
+
| PendingLiveTextUndoEntry
|
|
149
|
+
| PendingLiveStructureUndoEntry;
|
|
150
|
+
|
|
151
|
+
function pendingVisualStyleEditKey(edit: PendingVisualStyleEdit): string {
|
|
152
|
+
return [
|
|
153
|
+
edit.screenId,
|
|
154
|
+
edit.sourceId?.trim() || edit.selector.trim() || "unknown",
|
|
155
|
+
].join("::");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function mergePendingVisualStyleEdit(
|
|
159
|
+
edits: readonly PendingVisualStyleEdit[],
|
|
160
|
+
nextEdit: PendingVisualStyleEdit,
|
|
161
|
+
): PendingVisualStyleEdit[] {
|
|
162
|
+
const nextKey = pendingVisualStyleEditKey(nextEdit);
|
|
163
|
+
let merged = false;
|
|
164
|
+
const next = edits.map((edit) => {
|
|
165
|
+
if (pendingVisualStyleEditKey(edit) !== nextKey) return edit;
|
|
166
|
+
merged = true;
|
|
167
|
+
return {
|
|
168
|
+
...edit,
|
|
169
|
+
...nextEdit,
|
|
170
|
+
classes: nextEdit.classes.length > 0 ? nextEdit.classes : edit.classes,
|
|
171
|
+
styles: { ...edit.styles, ...nextEdit.styles },
|
|
172
|
+
originalStyles: {
|
|
173
|
+
...nextEdit.originalStyles,
|
|
174
|
+
...edit.originalStyles,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
return merged ? next : [...edits, nextEdit];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function mergePendingVisualStyleEdits(
|
|
182
|
+
edits: readonly PendingVisualStyleEdit[],
|
|
183
|
+
): PendingVisualStyleEdit[] {
|
|
184
|
+
return edits.reduce<PendingVisualStyleEdit[]>(
|
|
185
|
+
(merged, edit) => mergePendingVisualStyleEdit(merged, edit),
|
|
186
|
+
[],
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function pendingVisualStyleUndoRevertStyles(
|
|
191
|
+
currentEdits: readonly PendingVisualStyleEdit[],
|
|
192
|
+
nextEdit: PendingVisualStyleEdit,
|
|
193
|
+
): Record<string, string> {
|
|
194
|
+
const currentForTarget = currentEdits.find(
|
|
195
|
+
(edit) =>
|
|
196
|
+
pendingVisualStyleEditKey(edit) === pendingVisualStyleEditKey(nextEdit),
|
|
197
|
+
);
|
|
198
|
+
return Object.fromEntries(
|
|
199
|
+
Object.keys(nextEdit.styles).map((property) => [
|
|
200
|
+
property,
|
|
201
|
+
currentForTarget?.styles[property] ??
|
|
202
|
+
nextEdit.originalStyles[property] ??
|
|
203
|
+
"",
|
|
204
|
+
]),
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function styleLookup(
|
|
209
|
+
styles: Record<string, string> | undefined,
|
|
210
|
+
property: string,
|
|
211
|
+
): string | undefined {
|
|
212
|
+
if (!styles) return undefined;
|
|
213
|
+
const camel = camelStyleProperty(property);
|
|
214
|
+
const kebab = property.replace(
|
|
215
|
+
/[A-Z]/g,
|
|
216
|
+
(match) => `-${match.toLowerCase()}`,
|
|
217
|
+
);
|
|
218
|
+
return styles[property] ?? styles[camel] ?? styles[kebab];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function originalStylesForPendingVisualEdit(
|
|
222
|
+
styles: Record<string, string>,
|
|
223
|
+
primaryInfo?: Pick<ElementInfo, "computedStyles" | "inlineStyles"> | null,
|
|
224
|
+
fallbackInfo?: Pick<ElementInfo, "computedStyles" | "inlineStyles"> | null,
|
|
225
|
+
): Record<string, string> {
|
|
226
|
+
const sourceInfo = primaryInfo ?? fallbackInfo ?? null;
|
|
227
|
+
const inlineStyles = sourceInfo?.inlineStyles;
|
|
228
|
+
const computedStyles = sourceInfo?.computedStyles;
|
|
229
|
+
return Object.fromEntries(
|
|
230
|
+
Object.keys(styles).map((property) => {
|
|
231
|
+
const inlineValue = styleLookup(inlineStyles, property);
|
|
232
|
+
if (inlineValue !== undefined) return [property, inlineValue];
|
|
233
|
+
if (inlineStyles) return [property, ""];
|
|
234
|
+
return [property, styleLookup(computedStyles, property) ?? ""];
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function buildPendingVisualStyleRevertPatches(
|
|
240
|
+
edits: readonly PendingVisualStyleEdit[],
|
|
241
|
+
): Array<{
|
|
242
|
+
screenId: string;
|
|
243
|
+
selector: string;
|
|
244
|
+
sourceId?: string | null;
|
|
245
|
+
styles: Record<string, string>;
|
|
246
|
+
}> {
|
|
247
|
+
return edits
|
|
248
|
+
.map((edit) => ({
|
|
249
|
+
screenId: edit.screenId,
|
|
250
|
+
selector: edit.selector,
|
|
251
|
+
sourceId: edit.sourceId,
|
|
252
|
+
styles: edit.originalStyles,
|
|
253
|
+
}))
|
|
254
|
+
.filter((patch) => Object.keys(patch.styles).length > 0);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function getPendingVisualStylePropertyCount(
|
|
258
|
+
edits: readonly PendingVisualStyleEdit[],
|
|
259
|
+
): number {
|
|
260
|
+
return edits.reduce(
|
|
261
|
+
(count, edit) => count + Object.keys(edit.styles).length,
|
|
262
|
+
0,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function shouldBlockPendingVisualStyleNavigation(args: {
|
|
267
|
+
hasPendingVisualStyleEdits: boolean;
|
|
268
|
+
currentPathname: string;
|
|
269
|
+
nextPathname: string;
|
|
270
|
+
}): boolean {
|
|
271
|
+
return (
|
|
272
|
+
args.hasPendingVisualStyleEdits &&
|
|
273
|
+
args.currentPathname !== args.nextPathname
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function formatPendingVisualStylePrompt(args: {
|
|
278
|
+
designId?: string | null;
|
|
279
|
+
designTitle?: string | null;
|
|
280
|
+
activeFileId?: string | null;
|
|
281
|
+
activeFilename?: string | null;
|
|
282
|
+
edits: readonly PendingVisualStyleEdit[];
|
|
283
|
+
liveEdits?: readonly PendingLiveNonStyleEdit[];
|
|
284
|
+
}): string {
|
|
285
|
+
const title = args.designTitle?.trim();
|
|
286
|
+
const editPayload = args.edits.map((edit) => ({
|
|
287
|
+
screenId: edit.screenId,
|
|
288
|
+
filename: edit.filename,
|
|
289
|
+
screenName: edit.screenName,
|
|
290
|
+
selector: edit.selector,
|
|
291
|
+
sourceId: edit.sourceId ?? null,
|
|
292
|
+
tagName: edit.tagName ?? null,
|
|
293
|
+
classes: edit.classes,
|
|
294
|
+
styles: edit.styles,
|
|
295
|
+
...(edit.breakpoint ? { breakpoint: edit.breakpoint } : {}),
|
|
296
|
+
}));
|
|
297
|
+
const hasBreakpointScopedEdits = args.edits.some(
|
|
298
|
+
(edit) => edit.breakpoint && edit.breakpoint.upperBoundPx !== null,
|
|
299
|
+
);
|
|
300
|
+
const liveEditPayload = (args.liveEdits ?? []).map((edit) => {
|
|
301
|
+
if (edit.kind === "text") {
|
|
302
|
+
return {
|
|
303
|
+
kind: edit.kind,
|
|
304
|
+
screenId: edit.screenId,
|
|
305
|
+
filename: edit.filename,
|
|
306
|
+
screenName: edit.screenName,
|
|
307
|
+
selector: edit.selector,
|
|
308
|
+
sourceId: edit.sourceId ?? null,
|
|
309
|
+
tagName: edit.tagName ?? null,
|
|
310
|
+
classes: edit.classes,
|
|
311
|
+
value: edit.value,
|
|
312
|
+
html: edit.html,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
kind: edit.kind,
|
|
317
|
+
screenId: edit.screenId,
|
|
318
|
+
filename: edit.filename,
|
|
319
|
+
screenName: edit.screenName,
|
|
320
|
+
selector: edit.selector,
|
|
321
|
+
sourceId: edit.sourceId ?? null,
|
|
322
|
+
anchorSelector: edit.anchorSelector,
|
|
323
|
+
anchorSourceId: edit.anchorSourceId ?? null,
|
|
324
|
+
placement: edit.placement,
|
|
325
|
+
};
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return [
|
|
329
|
+
`Apply these pending live visual edits${title ? ` to "${title}"` : ""}.`,
|
|
330
|
+
args.designId ? `Design id: "${args.designId}".` : "",
|
|
331
|
+
args.activeFileId
|
|
332
|
+
? `Active screen: "${args.activeFilename ?? args.activeFileId}" (${args.activeFileId}).`
|
|
333
|
+
: "",
|
|
334
|
+
"",
|
|
335
|
+
"Use the Design source tools to make the source match the current live canvas preview. Read each target screen, resolve source ids/selectors through the code-layer projection, then apply the style, text, and structure changes with focused source edits. Preserve layout, behavior, and unrelated styling.",
|
|
336
|
+
hasBreakpointScopedEdits
|
|
337
|
+
? "Edits that carry a `breakpoint` field were made while a narrower breakpoint frame was active: apply them as width-scoped overrides (apply-visual-edit with `activeFrameWidthPx` set to breakpoint.activeWidthPx), NOT as base writes — base values must keep rendering at wider viewports."
|
|
338
|
+
: "",
|
|
339
|
+
"",
|
|
340
|
+
"Pending style edits:",
|
|
341
|
+
JSON.stringify(editPayload, null, 2),
|
|
342
|
+
liveEditPayload.length > 0 ? "Pending text/structure edits:" : "",
|
|
343
|
+
liveEditPayload.length > 0 ? JSON.stringify(liveEditPayload, null, 2) : "",
|
|
344
|
+
]
|
|
345
|
+
.filter((line) => line !== "")
|
|
346
|
+
.join("\n");
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function resolveOverviewScreenSourceType(
|
|
350
|
+
screen:
|
|
351
|
+
| { sourceType?: unknown; bridgeUrl?: string | null }
|
|
352
|
+
| null
|
|
353
|
+
| undefined,
|
|
354
|
+
fallbackSourceType: DesignSourceType = "inline",
|
|
355
|
+
): DesignSourceType {
|
|
356
|
+
if (!screen) return fallbackSourceType;
|
|
357
|
+
return (
|
|
358
|
+
normalizeDesignSourceType(screen.sourceType) ??
|
|
359
|
+
(screen.bridgeUrl ? "localhost" : undefined) ??
|
|
360
|
+
fallbackSourceType
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function shouldShowPendingVisualStyleApply(args: {
|
|
365
|
+
edits: readonly PendingVisualStyleEdit[];
|
|
366
|
+
liveEdits?: readonly PendingLiveNonStyleEdit[];
|
|
367
|
+
screenSourceTypes: ReadonlyMap<string, unknown>;
|
|
368
|
+
fallbackSourceType?: unknown;
|
|
369
|
+
}): boolean {
|
|
370
|
+
const allEdits = [...args.edits, ...(args.liveEdits ?? [])];
|
|
371
|
+
return (
|
|
372
|
+
allEdits.length > 0 &&
|
|
373
|
+
allEdits.every(
|
|
374
|
+
(edit) =>
|
|
375
|
+
normalizeDesignSourceType(
|
|
376
|
+
args.screenSourceTypes.get(edit.screenId) ?? args.fallbackSourceType,
|
|
377
|
+
) === "localhost",
|
|
378
|
+
)
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* §6.4 — One scoped style write (Framer cascade). Routes a single
|
|
384
|
+
* (property, value) edit through the class-vs-media decision
|
|
385
|
+
* (`planBreakpointStyleWrite`) for the active breakpoint scope:
|
|
386
|
+
*
|
|
387
|
+
* - `upperBoundPx == null` (base editing): plain inline-style edit that
|
|
388
|
+
* cascades down to every narrower breakpoint unless overridden there.
|
|
389
|
+
* - Tailwind-utility value: width-scoped responsive class
|
|
390
|
+
* (`max-[<bound>px]:utility`), falling back to the media path if the
|
|
391
|
+
* class patch is rejected.
|
|
392
|
+
* - Raw CSS value: managed `@media (max-width: <bound>px)` rule in the
|
|
393
|
+
* `<style data-agent-native-breakpoints>` block.
|
|
394
|
+
*
|
|
395
|
+
* Scoped failures return the failing patch rather than silently mutating
|
|
396
|
+
* the base layer — callers surface `result.message`.
|
|
397
|
+
*/
|
|
398
|
+
export function applyScopedVisualStyleEdit(args: {
|
|
399
|
+
content: string;
|
|
400
|
+
target: { nodeId: string } | { selector: string };
|
|
401
|
+
property: string;
|
|
402
|
+
value: string;
|
|
403
|
+
upperBoundPx: number | null;
|
|
404
|
+
}): ApplyVisualEditResult {
|
|
405
|
+
const { content, target, property, value, upperBoundPx } = args;
|
|
406
|
+
const plan = planBreakpointStyleWrite({ property, value, upperBoundPx });
|
|
407
|
+
if (plan.mode === "class") {
|
|
408
|
+
const rcPatch = applyVisualEdit(content, {
|
|
409
|
+
kind: "responsive-class",
|
|
410
|
+
target,
|
|
411
|
+
// `prefix` is ignored when maxWidthPx is set (desktop-down scope).
|
|
412
|
+
prefix: "base",
|
|
413
|
+
maxWidthPx: plan.boundPx,
|
|
414
|
+
operation: "replace",
|
|
415
|
+
utility: plan.utility,
|
|
416
|
+
stem: utilityStem(plan.utility),
|
|
417
|
+
});
|
|
418
|
+
if (rcPatch.result.status === "applied") return rcPatch;
|
|
419
|
+
// Fall through to the media path so the edit still lands scoped.
|
|
420
|
+
}
|
|
421
|
+
if (
|
|
422
|
+
plan.mode !== "base" &&
|
|
423
|
+
upperBoundPx !== null &&
|
|
424
|
+
upperBoundPx !== undefined
|
|
425
|
+
) {
|
|
426
|
+
return applyVisualEdit(content, {
|
|
427
|
+
kind: "breakpoint-style",
|
|
428
|
+
target,
|
|
429
|
+
maxWidthPx: upperBoundPx,
|
|
430
|
+
property,
|
|
431
|
+
value,
|
|
432
|
+
operation: "set",
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
return applyVisualEdit(content, { kind: "style", target, property, value });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Pure decision behind commitVisualStyles' commit-or-fail outcome, extracted
|
|
440
|
+
* so the fail-loud contract is unit-testable:
|
|
441
|
+
*
|
|
442
|
+
* - scoped patch applied → its content wins;
|
|
443
|
+
* - scoped patch failed while a BREAKPOINT scope is active → hard error
|
|
444
|
+
* (the legacy selector fallback is a BASE write and would clobber every
|
|
445
|
+
* viewport width with a value the user meant to scope — §6.4);
|
|
446
|
+
* - scoped patch failed on BASE scope → the legacy selector-based
|
|
447
|
+
* inline-style fallback may stand in, but ONLY when it actually resolved
|
|
448
|
+
* (queryUniqueSelector demands exactly one match — never a guessy write);
|
|
449
|
+
* - nothing resolved → hard error. Callers MUST surface `error` loudly
|
|
450
|
+
* (toast), never swallow it: a silent no-op here leaves the inspector
|
|
451
|
+
* displaying a value that was never persisted.
|
|
452
|
+
*/
|
|
453
|
+
export function resolveVisualStyleCommitContent(args: {
|
|
454
|
+
scopedContent: string;
|
|
455
|
+
scopedFailure: string | null;
|
|
456
|
+
legacyFallbackContent: string | null;
|
|
457
|
+
breakpointScoped: boolean;
|
|
458
|
+
}): { content: string } | { error: string | null } {
|
|
459
|
+
if (!args.scopedFailure) return { content: args.scopedContent };
|
|
460
|
+
if (args.breakpointScoped) return { error: args.scopedFailure };
|
|
461
|
+
if (args.legacyFallbackContent)
|
|
462
|
+
return { content: args.legacyFallbackContent };
|
|
463
|
+
return { error: args.scopedFailure };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Interaction-states phase 2 — the pure content transform behind
|
|
468
|
+
* `commitInteractionStateStyles` (DesignEditor's useCallback wrapper, which
|
|
469
|
+
* only resolves `activeFile`/`selectedElement`/`canEditDesign` and calls
|
|
470
|
+
* `applyFileContentUpdate`). Extracted as a top-level function so it's
|
|
471
|
+
* unit-testable the same way `applyScopedVisualStyleEdit` is above.
|
|
472
|
+
*
|
|
473
|
+
* Writes every property in `styles` into the managed
|
|
474
|
+
* `[data-agent-native-node-id="<nodeId>"]:<state> { … }` rule
|
|
475
|
+
* (`upsertStateStyles`) and regenerates that rule's forced-preview twin
|
|
476
|
+
* (`duplicateStatePreviewRules`) in one pass, so a caller that folds the
|
|
477
|
+
* result into a single `applyFileContentUpdate`/history-recording call gets
|
|
478
|
+
* exactly one undo step for the whole commit — see
|
|
479
|
+
* `shared/interaction-states.ts`'s module doc for the twin-rule mechanism.
|
|
480
|
+
*/
|
|
481
|
+
export function applyInteractionStateStyleCommit(
|
|
482
|
+
content: string,
|
|
483
|
+
nodeId: string,
|
|
484
|
+
state: InteractionState,
|
|
485
|
+
styles: Record<string, string>,
|
|
486
|
+
): string {
|
|
487
|
+
const withStateStyles = upsertStateStyles(content, nodeId, state, styles);
|
|
488
|
+
return duplicateStatePreviewRules(withStateStyles);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Interaction-states phase 2 — the pure decision behind `statePreviewTarget`,
|
|
493
|
+
* the value DesignEditor forwards into both the single-screen and overview
|
|
494
|
+
* `DesignCanvas` instances' `statePreviewTarget` prop, which in turn drives
|
|
495
|
+
* the `state-preview` postMessage that sets/clears the bridge's
|
|
496
|
+
* `data-an-state-preview` attribute (see interaction-states.ts's "Forced-
|
|
497
|
+
* preview mechanism" doc comment for the full pipeline). Returns null
|
|
498
|
+
* whenever there's no active non-default interaction state OR no resolvable
|
|
499
|
+
* single-element screen/node target — both must be present for a preview to
|
|
500
|
+
* make sense, matching EditPanel's InteractionStatePanel only ever offering
|
|
501
|
+
* the state selector for a single selection with a stable node id.
|
|
502
|
+
*/
|
|
503
|
+
export function deriveStatePreviewTarget(
|
|
504
|
+
activeState: InteractionState | null,
|
|
505
|
+
screenId: string | null | undefined,
|
|
506
|
+
nodeId: string | null | undefined,
|
|
507
|
+
): { screenId: string; nodeId: string; state: InteractionState } | null {
|
|
508
|
+
if (!activeState || !screenId || !nodeId) return null;
|
|
509
|
+
return { screenId, nodeId, state: activeState };
|
|
510
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PortableStyleSnapshot,
|
|
3
|
+
PortableStyleSnapshotNode,
|
|
4
|
+
} from "@/components/design/types";
|
|
5
|
+
|
|
6
|
+
export function styleHost(element: Element): HTMLElement | SVGElement | null {
|
|
7
|
+
return element instanceof HTMLElement || element instanceof SVGElement
|
|
8
|
+
? element
|
|
9
|
+
: null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function elementAtPortableStylePath(
|
|
13
|
+
root: Element,
|
|
14
|
+
node: PortableStyleSnapshotNode,
|
|
15
|
+
): Element | null {
|
|
16
|
+
let current: Element | null = root;
|
|
17
|
+
for (const index of node.path) {
|
|
18
|
+
if (!current || !Number.isInteger(index) || index < 0) return null;
|
|
19
|
+
current = current.children.item(index);
|
|
20
|
+
}
|
|
21
|
+
return current;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const EDITOR_INTERNAL_CSS_VAR_PREFIXES = [
|
|
25
|
+
"--design-editor-",
|
|
26
|
+
"--agent-native-editor-chrome-",
|
|
27
|
+
"--agent-native-",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export function isEditorInternalCssVar(property: string): boolean {
|
|
31
|
+
return EDITOR_INTERNAL_CSS_VAR_PREFIXES.some((prefix) =>
|
|
32
|
+
property.startsWith(prefix),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function applyPortableStyles(
|
|
37
|
+
element: Element | null,
|
|
38
|
+
styles: Record<string, string>,
|
|
39
|
+
) {
|
|
40
|
+
if (!element) return;
|
|
41
|
+
const host = styleHost(element);
|
|
42
|
+
if (!host) return;
|
|
43
|
+
Object.entries(styles).forEach(([property, value]) => {
|
|
44
|
+
if (!value) return;
|
|
45
|
+
if (property.startsWith("--")) {
|
|
46
|
+
if (isEditorInternalCssVar(property)) return;
|
|
47
|
+
host.style.setProperty(property, value);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (property.includes("-")) {
|
|
51
|
+
host.style.setProperty(property, value);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
(host.style as any)[property] = value;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function sameStylesheetHead(
|
|
59
|
+
sourceHtml: string,
|
|
60
|
+
destHtml: string,
|
|
61
|
+
): boolean {
|
|
62
|
+
if (typeof window === "undefined") return false;
|
|
63
|
+
try {
|
|
64
|
+
const parser = new DOMParser();
|
|
65
|
+
const sourceHead = parser.parseFromString(sourceHtml, "text/html").head
|
|
66
|
+
?.innerHTML;
|
|
67
|
+
const destHead = parser.parseFromString(destHtml, "text/html").head
|
|
68
|
+
?.innerHTML;
|
|
69
|
+
return (
|
|
70
|
+
typeof sourceHead === "string" &&
|
|
71
|
+
typeof destHead === "string" &&
|
|
72
|
+
sourceHead === destHead
|
|
73
|
+
);
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function applyPortableStyleSnapshotToHtml(
|
|
80
|
+
content: string,
|
|
81
|
+
nodeAttrId: string,
|
|
82
|
+
snapshot?: PortableStyleSnapshot,
|
|
83
|
+
sourceContent?: string,
|
|
84
|
+
): string {
|
|
85
|
+
if (typeof window === "undefined" || !snapshot?.nodes?.length) {
|
|
86
|
+
return content;
|
|
87
|
+
}
|
|
88
|
+
if (sourceContent && sameStylesheetHead(sourceContent, content)) {
|
|
89
|
+
return content;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const doc = new DOMParser().parseFromString(content, "text/html");
|
|
93
|
+
const root = doc.querySelector(
|
|
94
|
+
`[data-agent-native-node-id="${CSS.escape(nodeAttrId)}"]`,
|
|
95
|
+
);
|
|
96
|
+
if (!root) return content;
|
|
97
|
+
let appliedAny = false;
|
|
98
|
+
snapshot.nodes.forEach((node) => {
|
|
99
|
+
const target = elementAtPortableStylePath(root, node);
|
|
100
|
+
if (!target) return;
|
|
101
|
+
const filteredEntries = Object.entries(node.styles).filter(
|
|
102
|
+
([property, value]) => value && !isEditorInternalCssVar(property),
|
|
103
|
+
);
|
|
104
|
+
if (filteredEntries.length === 0) return;
|
|
105
|
+
applyPortableStyles(target, Object.fromEntries(filteredEntries));
|
|
106
|
+
appliedAny = true;
|
|
107
|
+
});
|
|
108
|
+
if (appliedAny) {
|
|
109
|
+
root.setAttribute("data-agent-native-preserve-styles", "true");
|
|
110
|
+
}
|
|
111
|
+
return `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
|
|
112
|
+
} catch {
|
|
113
|
+
return content;
|
|
114
|
+
}
|
|
115
|
+
}
|