@agent-native/core 0.163.4 → 0.164.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
  3. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
  4. package/corpus/templates/analytics/netlify.toml +2 -0
  5. package/corpus/templates/assets/netlify.toml +2 -0
  6. package/corpus/templates/brain/netlify.toml +2 -0
  7. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
  8. package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
  9. package/corpus/templates/calendar/netlify.toml +2 -0
  10. package/corpus/templates/chat/netlify.toml +2 -0
  11. package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
  12. package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
  13. package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
  14. package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
  15. package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
  16. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
  17. package/corpus/templates/clips/app/routes/_index.tsx +10 -2
  18. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
  19. package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
  20. package/corpus/templates/clips/netlify.toml +2 -0
  21. package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
  22. package/corpus/templates/content/netlify.toml +2 -0
  23. package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
  24. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
  25. package/corpus/templates/design/AGENTS.md +3 -1
  26. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
  27. package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
  28. package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
  29. package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
  30. package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
  31. package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
  32. package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
  33. package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
  34. package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
  35. package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
  36. package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
  37. package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
  38. package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
  39. package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
  40. package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
  41. package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
  42. package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
  43. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
  44. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
  45. package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
  46. package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
  47. package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
  48. package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
  49. package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
  50. package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
  51. package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
  52. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
  53. package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
  54. package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
  55. package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
  56. package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
  57. package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
  58. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
  59. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
  60. package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
  61. package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
  62. package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
  63. package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
  64. package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
  65. package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
  66. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
  67. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
  68. package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
  69. package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
  70. package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
  71. package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
  72. package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
  73. package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
  74. package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
  75. package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
  76. package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
  77. package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
  78. package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
  79. package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
  80. package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
  81. package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
  82. package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
  83. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
  84. package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
  85. package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
  86. package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
  87. package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
  88. package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
  89. package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
  90. package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
  91. package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
  92. package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
  93. package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
  94. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
  95. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
  96. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
  97. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
  98. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
  99. package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
  100. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
  101. package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
  102. package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
  103. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
  104. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
  105. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
  106. package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
  107. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
  108. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
  109. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
  110. package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
  111. package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
  112. package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
  113. package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
  114. package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
  115. package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
  116. package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
  117. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
  118. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
  119. package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
  120. package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
  121. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
  122. package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
  123. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
  124. package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
  125. package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
  126. package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
  127. package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
  128. package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
  129. package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
  130. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
  131. package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
  132. package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
  133. package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
  134. package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
  135. package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
  136. package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
  137. package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
  138. package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
  139. package/corpus/templates/design/netlify.toml +2 -0
  140. package/corpus/templates/design/shared/board-file.ts +2 -1
  141. package/corpus/templates/dispatch/netlify.toml +2 -0
  142. package/corpus/templates/forms/netlify.toml +2 -0
  143. package/corpus/templates/mail/netlify.toml +2 -0
  144. package/corpus/templates/plan/netlify.toml +2 -0
  145. package/corpus/templates/slides/netlify.toml +2 -0
  146. package/dist/agent/engine/builder-engine.d.ts +6 -4
  147. package/dist/agent/engine/builder-engine.js +91 -26
  148. package/dist/agent/engine/registry.js +16 -0
  149. package/dist/cli/code-agent-agent-tools.d.ts +2 -0
  150. package/dist/cli/code-agent-agent-tools.js +188 -0
  151. package/dist/cli/code-agent-collaboration.d.ts +34 -0
  152. package/dist/cli/code-agent-collaboration.js +104 -0
  153. package/dist/cli/code-agent-executor.js +6 -0
  154. package/dist/cli/code-agent-schedules.d.ts +54 -0
  155. package/dist/cli/code-agent-schedules.js +198 -0
  156. package/dist/client/AgentPanel.js +1 -1
  157. package/dist/client/ConnectBuilderCard.js +5 -2
  158. package/dist/client/EnvironmentBadge.js +6 -5
  159. package/dist/client/chat/run-recovery.js +3 -2
  160. package/dist/client/chat/tool-call-display.js +2 -3
  161. package/dist/client/components/CodeRequiredDialog.js +4 -2
  162. package/dist/client/i18n.js +5 -4
  163. package/dist/client/settings/SettingsPanel.js +1 -1
  164. package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
  165. package/dist/client/settings/useBuilderStatus.d.ts +6 -1
  166. package/dist/client/settings/useBuilderStatus.js +22 -33
  167. package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
  168. package/dist/code-agents/index.d.ts +2 -0
  169. package/dist/code-agents/index.js +2 -0
  170. package/dist/collab/routes.d.ts +1 -1
  171. package/dist/localization/shared.d.ts +2 -0
  172. package/dist/localization/shared.js +5 -0
  173. package/dist/mcp-client/oauth-client.d.ts +1 -0
  174. package/dist/mcp-client/oauth-client.js +50 -1
  175. package/dist/observability/routes.d.ts +1 -1
  176. package/dist/progress/routes.d.ts +1 -1
  177. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  178. package/dist/resources/handlers.d.ts +1 -1
  179. package/dist/server/auth-marketing-locales.d.ts +7 -0
  180. package/dist/server/auth-marketing-locales.js +1322 -0
  181. package/dist/server/auth-marketing.d.ts +1 -0
  182. package/dist/server/auth-marketing.js +35 -6
  183. package/dist/server/auth.js +4 -17
  184. package/dist/server/builder-browser.d.ts +22 -0
  185. package/dist/server/builder-browser.js +77 -1
  186. package/dist/server/builder-oauth.d.ts +50 -0
  187. package/dist/server/builder-oauth.js +263 -0
  188. package/dist/server/core-routes-plugin.d.ts +11 -0
  189. package/dist/server/core-routes-plugin.js +291 -415
  190. package/dist/server/credential-provider.d.ts +1 -1
  191. package/dist/server/credential-provider.js +1 -1
  192. package/dist/server/csrf.js +2 -2
  193. package/dist/server/onboarding-html.js +52 -120
  194. package/dist/settings/store.d.ts +9 -0
  195. package/dist/settings/store.js +19 -0
  196. package/dist/templates/chat/netlify.toml +2 -0
  197. package/docs/content/deployment.mdx +2 -0
  198. package/docs/content/multi-app-workspace.mdx +1 -1
  199. package/package.json +2 -2
  200. package/src/templates/chat/netlify.toml +2 -0
@@ -0,0 +1,56 @@
1
+ export interface DesignBreakpointOption {
2
+ id: string;
3
+ label: string;
4
+ widthPx: number;
5
+ }
6
+
7
+ /**
8
+ * Reads `designs.data.breakpointSet` (§6.4) into the sorted option list the
9
+ * breakpoint controls render. Unlabelled widths fall back to a size bucket.
10
+ */
11
+ export function deriveDesignBreakpoints(
12
+ designDataJson: Record<string, unknown>,
13
+ ): DesignBreakpointOption[] {
14
+ try {
15
+ const raw = (designDataJson as Record<string, unknown>)?.breakpointSet;
16
+ if (
17
+ raw &&
18
+ typeof raw === "object" &&
19
+ !Array.isArray(raw) &&
20
+ Array.isArray((raw as Record<string, unknown>).breakpoints)
21
+ ) {
22
+ const parsed = (
23
+ raw as {
24
+ breakpoints: Array<{
25
+ id?: unknown;
26
+ widthPx?: unknown;
27
+ label?: unknown;
28
+ }>;
29
+ }
30
+ ).breakpoints
31
+ .filter(
32
+ (bp) =>
33
+ typeof bp?.id === "string" &&
34
+ typeof bp?.widthPx === "number" &&
35
+ Number.isFinite(bp.widthPx),
36
+ )
37
+ .map((bp) => ({
38
+ id: bp.id as string,
39
+ widthPx: bp.widthPx as number,
40
+ label:
41
+ typeof bp.label === "string" && bp.label.trim()
42
+ ? (bp.label as string)
43
+ : (bp.widthPx as number) >= 1024
44
+ ? "Desktop"
45
+ : (bp.widthPx as number) >= 600
46
+ ? "Tablet"
47
+ : "Mobile",
48
+ }));
49
+ return parsed.sort((a, b) => a.widthPx - b.widthPx);
50
+ }
51
+ // coercion-ok: an unreadable breakpointSet means "none configured", which the empty list already expresses to the caller.
52
+ } catch {
53
+ // ignore malformed design data
54
+ }
55
+ return [];
56
+ }
@@ -0,0 +1,146 @@
1
+ import { isBoardFile } from "@shared/board-file";
2
+
3
+ import { normalizedDesignFileType } from "../canvas-primitive-insert";
4
+ import { getDesignDataRecord } from "../design-data-geometry-utils";
5
+ import type { DesignFile } from "../types";
6
+
7
+ export interface OverviewScreen {
8
+ id: string;
9
+ filename: string;
10
+ content: string;
11
+ updatedAt: string;
12
+ sourceType?: string;
13
+ source?: string;
14
+ sourceFile?: string;
15
+ connectionId?: string;
16
+ lod?: string;
17
+ previewState?: string;
18
+ status?: string;
19
+ title?: string;
20
+ layoutGroupId?: string;
21
+ width?: number;
22
+ height?: number;
23
+ heightPinned: boolean;
24
+ url?: string;
25
+ previewUrl?: string;
26
+ bridgeUrl?: string;
27
+ previewToken?: string;
28
+ breakpointWidths?: number[];
29
+ activeBreakpointWidth?: number;
30
+ }
31
+
32
+ export interface DeriveOverviewScreensArgs {
33
+ designDataJson: Record<string, unknown>;
34
+ files: DesignFile[];
35
+ activeBreakpointWidthState: number | undefined;
36
+ breakpointFramesHidden: boolean;
37
+ /**
38
+ * Heights pinned in this session but not yet round-tripped through
39
+ * `designs.data`. Without them the content-fit pass regrows a screen the
40
+ * user just sized.
41
+ */
42
+ locallyPinnedHeightIds: ReadonlySet<string>;
43
+ }
44
+
45
+ export function deriveOverviewScreens({
46
+ designDataJson,
47
+ files,
48
+ activeBreakpointWidthState,
49
+ breakpointFramesHidden,
50
+ locallyPinnedHeightIds,
51
+ }: DeriveOverviewScreensArgs): OverviewScreen[] {
52
+ const metadataByFileId = getDesignDataRecord(
53
+ designDataJson,
54
+ "screenMetadata",
55
+ );
56
+ // §6.4 — breakpoint set stored in designs.data.breakpointSet as a
57
+ // BreakpointSet { id, breakpoints: BreakpointDefinition[] }.
58
+ // Each BreakpointDefinition has { id, label, widthPx, prefix }.
59
+ const breakpointSet = (() => {
60
+ try {
61
+ const raw = (designDataJson as Record<string, unknown>)?.breakpointSet;
62
+ if (
63
+ raw &&
64
+ typeof raw === "object" &&
65
+ !Array.isArray(raw) &&
66
+ Array.isArray((raw as Record<string, unknown>).breakpoints)
67
+ ) {
68
+ return raw as {
69
+ id: string;
70
+ breakpoints: Array<{
71
+ id: string;
72
+ widthPx: number;
73
+ label?: string;
74
+ prefix?: string;
75
+ }>;
76
+ };
77
+ }
78
+ // coercion-ok: an unreadable breakpointSet means "none configured", which the undefined return already expresses to the caller.
79
+ } catch {
80
+ // ignore
81
+ }
82
+ return undefined;
83
+ })();
84
+ const bpWidths =
85
+ !breakpointFramesHidden &&
86
+ breakpointSet &&
87
+ breakpointSet.breakpoints.length > 0
88
+ ? breakpointSet.breakpoints.map((bp) => bp.widthPx)
89
+ : undefined;
90
+
91
+ // Exclude the board file — it is rendered by its own DesignCanvas instance
92
+ // in MultiScreenCanvas and must not appear as a screen frame. Support files
93
+ // such as CSS are editable files, not visual screens.
94
+ return files
95
+ .filter(
96
+ (file) =>
97
+ normalizedDesignFileType(file.fileType) === "html" &&
98
+ !isBoardFile(file.filename),
99
+ )
100
+ .map((file) => {
101
+ const metadata = getDesignDataRecord(metadataByFileId, file.id);
102
+ const stringValue = (key: string) =>
103
+ typeof metadata[key] === "string"
104
+ ? (metadata[key] as string)
105
+ : undefined;
106
+ const numberValue = (key: string) =>
107
+ typeof metadata[key] === "number" && Number.isFinite(metadata[key])
108
+ ? (metadata[key] as number)
109
+ : undefined;
110
+ return {
111
+ id: file.id,
112
+ filename: file.filename,
113
+ content: file.content,
114
+ updatedAt: file.updatedAt,
115
+ sourceType: stringValue("sourceType"),
116
+ source: stringValue("source"),
117
+ sourceFile: stringValue("sourceFile"),
118
+ connectionId: stringValue("connectionId"),
119
+ lod: stringValue("lod"),
120
+ previewState: stringValue("previewState"),
121
+ status: stringValue("status"),
122
+ title: stringValue("title"),
123
+ layoutGroupId: stringValue("variantSetId"),
124
+ width: numberValue("width"),
125
+ height: numberValue("height"),
126
+ // Without this the pin never reaches the canvas and the content-fit
127
+ // pass grows a deliberately-sized screen straight back.
128
+ heightPinned:
129
+ metadata.heightPinned === true || locallyPinnedHeightIds.has(file.id),
130
+ url: stringValue("url"),
131
+ previewUrl: stringValue("previewUrl"),
132
+ bridgeUrl: stringValue("bridgeUrl"),
133
+ previewToken: stringValue("previewToken"),
134
+ // Breakpoint preview widths (§6.4). When non-empty, MultiScreenCanvas
135
+ // renders one iframe per width to the right of the primary frame.
136
+ breakpointWidths: bpWidths,
137
+ // Active breakpoint width tracked in component state; shared across all
138
+ // screens (a design has one active breakpoint set at a time in v1).
139
+ activeBreakpointWidth: bpWidths?.includes(
140
+ activeBreakpointWidthState ?? -1,
141
+ )
142
+ ? activeBreakpointWidthState
143
+ : undefined,
144
+ };
145
+ });
146
+ }
@@ -0,0 +1,326 @@
1
+ import {
2
+ tryCallActionKeepalive,
3
+ useReconciledState,
4
+ } from "@agent-native/core/client/hooks";
5
+ import type { TweakDefinition } from "@shared/api";
6
+ import {
7
+ resolveTweaksToCssVars,
8
+ tweakSelectionsHash,
9
+ type TweakSelections,
10
+ } from "@shared/resolve-tweaks";
11
+ import type { QueryClient } from "@tanstack/react-query";
12
+ import {
13
+ useCallback,
14
+ useEffect,
15
+ useLayoutEffect,
16
+ useMemo,
17
+ useRef,
18
+ useState,
19
+ type Dispatch,
20
+ type RefObject,
21
+ type SetStateAction,
22
+ } from "react";
23
+
24
+ import {
25
+ createDesignSaveOutboxEntry,
26
+ journalDesignSaveOutboxEntry,
27
+ type DesignSaveOutboxEntry,
28
+ } from "@/lib/design-save-outbox";
29
+ import {
30
+ runPersistTweakSave,
31
+ type PersistTweakSaveArgs,
32
+ } from "@/pages/design-editor/commands/persist-tweak-save";
33
+ import {
34
+ areTweakSelectionsEqual,
35
+ buildAuthoritativeTweakSelections,
36
+ } from "@/pages/design-editor/design-data-geometry-utils";
37
+ import {
38
+ createQueuedTweakSave,
39
+ sendJournaledTweakSaveKeepalive,
40
+ type PendingTweakSave,
41
+ } from "@/pages/design-editor/tweak-save";
42
+
43
+ interface UseTweaksArgs {
44
+ acknowledgeOutboxEntry: PersistTweakSaveArgs["acknowledgeOutboxEntry"];
45
+ applyTweaksAsync: PersistTweakSaveArgs["applyTweaksAsync"];
46
+ canEditDesign: boolean;
47
+ canEditDesignRef: RefObject<boolean>;
48
+ design: { data?: string | null } | null;
49
+ designSaveActorScope: string;
50
+ designSaveOperationSourceRef: RefObject<string>;
51
+ id: string | undefined;
52
+ queryClient: QueryClient;
53
+ t: PersistTweakSaveArgs["t"];
54
+ warnChangesWillRetry: () => void;
55
+ }
56
+
57
+ export interface UseTweaksResult {
58
+ cssVarValues: Record<string, string>;
59
+ flushPendingTweakSave: () => void;
60
+ handleTweakChange: (
61
+ tweakId: string,
62
+ value: string | number | boolean,
63
+ ) => void;
64
+ setTweakSelections: Dispatch<SetStateAction<TweakSelections>>;
65
+ tweakSelections: TweakSelections;
66
+ tweaks: TweakDefinition[];
67
+ }
68
+
69
+ /**
70
+ * Owns the tweak (visual-knob) domain: the parsed definitions, the reconciled
71
+ * selection state, the CSS-var projection for the canvas iframe, and the
72
+ * debounced save queue that persists selections into designs.data.
73
+ *
74
+ * Every dependency array below is byte-identical to the inline version this
75
+ * replaced. The keepalive/unload effect must stay in this hook so the 600 ms
76
+ * debounce is flushed before the keyed editor instance unmounts.
77
+ */
78
+ export function useTweaks({
79
+ acknowledgeOutboxEntry,
80
+ applyTweaksAsync,
81
+ canEditDesign,
82
+ canEditDesignRef,
83
+ design,
84
+ designSaveActorScope,
85
+ designSaveOperationSourceRef,
86
+ id,
87
+ queryClient,
88
+ t,
89
+ warnChangesWillRetry,
90
+ }: UseTweaksArgs): UseTweaksResult {
91
+ const [tweakSaveActive, setTweakSaveActive] = useState(false);
92
+ const pendingTweakSaveRef = useRef<PendingTweakSave | null>(null);
93
+ const tweakSaveTimerRef = useRef<number | null>(null);
94
+ const tweakSaveRevisionRef = useRef(0);
95
+ const tweakSaveChainRef = useRef<Promise<void>>(Promise.resolve());
96
+ const tweakSaveInFlightRef = useRef(false);
97
+ const confirmedTweakSelectionsHashRef = useRef(tweakSelectionsHash({}));
98
+ const journalTweakOutboxEntry = useCallback(
99
+ async (entry: DesignSaveOutboxEntry) => {
100
+ try {
101
+ await journalDesignSaveOutboxEntry(entry);
102
+ return true;
103
+ } catch {
104
+ // coercion-ok: `false` is the typed "not journaled" result, never
105
+ // mistakable for success. Do not claim an offline retry exists when
106
+ // IndexedDB itself failed — the mutation path uses this to tell
107
+ // durable retries from edits that must stay in this tab's memory.
108
+ return false;
109
+ }
110
+ },
111
+ [],
112
+ );
113
+ const createTweakSaveOutboxEntry = useCallback(
114
+ (pending: PendingTweakSave) => {
115
+ if (!id) return null;
116
+ return createDesignSaveOutboxEntry({
117
+ designId: id ?? "",
118
+ actorScope: designSaveActorScope,
119
+ actionName: "apply-tweaks",
120
+ resourceId: id,
121
+ operationSource: designSaveOperationSourceRef.current,
122
+ operationRevision: pending.revision,
123
+ payload: {
124
+ designId: id,
125
+ selections: pending.selections,
126
+ expectedSelectionsHash: pending.expectedSelectionsHash,
127
+ },
128
+ });
129
+ },
130
+ [designSaveActorScope, id],
131
+ );
132
+ const persistTweakSave = useCallback(
133
+ (pending: PendingTweakSave) =>
134
+ runPersistTweakSave(
135
+ {
136
+ acknowledgeOutboxEntry,
137
+ applyTweaksAsync,
138
+ canEditDesignRef,
139
+ confirmedTweakSelectionsHashRef,
140
+ createTweakSaveOutboxEntry,
141
+ id,
142
+ journalTweakOutboxEntry,
143
+ pendingTweakSaveRef,
144
+ queryClient,
145
+ setTweakSaveActive,
146
+ t,
147
+ tweakSaveChainRef,
148
+ tweakSaveInFlightRef,
149
+ tweakSaveRevisionRef,
150
+ warnChangesWillRetry,
151
+ },
152
+ pending,
153
+ ),
154
+ [
155
+ acknowledgeOutboxEntry,
156
+ applyTweaksAsync,
157
+ createTweakSaveOutboxEntry,
158
+ id,
159
+ journalTweakOutboxEntry,
160
+ queryClient,
161
+ t,
162
+ warnChangesWillRetry,
163
+ ],
164
+ );
165
+ const flushPendingTweakSave = useCallback(() => {
166
+ if (tweakSaveTimerRef.current !== null) {
167
+ window.clearTimeout(tweakSaveTimerRef.current);
168
+ tweakSaveTimerRef.current = null;
169
+ }
170
+ const pending = pendingTweakSaveRef.current;
171
+ pendingTweakSaveRef.current = null;
172
+ if (pending) persistTweakSave(pending);
173
+ }, [persistTweakSave]);
174
+ const queueTweakSave = useCallback(
175
+ (selections: TweakSelections) => {
176
+ if (!id || !canEditDesignRef.current) return;
177
+ const revision = tweakSaveRevisionRef.current + 1;
178
+ tweakSaveRevisionRef.current = revision;
179
+ setTweakSaveActive(true);
180
+ const pending = createQueuedTweakSave(
181
+ selections,
182
+ revision,
183
+ confirmedTweakSelectionsHashRef.current,
184
+ pendingTweakSaveRef.current,
185
+ );
186
+ pendingTweakSaveRef.current = pending;
187
+ const entry = createTweakSaveOutboxEntry(pending);
188
+ if (entry) void journalTweakOutboxEntry(entry);
189
+ if (tweakSaveTimerRef.current !== null) {
190
+ window.clearTimeout(tweakSaveTimerRef.current);
191
+ }
192
+ tweakSaveTimerRef.current = window.setTimeout(flushPendingTweakSave, 600);
193
+ },
194
+ [
195
+ createTweakSaveOutboxEntry,
196
+ flushPendingTweakSave,
197
+ id,
198
+ journalTweakOutboxEntry,
199
+ ],
200
+ );
201
+
202
+ useEffect(() => {
203
+ const handlePageHide = () => {
204
+ const pending = pendingTweakSaveRef.current;
205
+ if (!id || !pending || !canEditDesignRef.current) return;
206
+ if (tweakSaveInFlightRef.current) return;
207
+ // Keep the normal timer/pending entry intact for bfcache restores. The
208
+ // keepalive is the unload safety net; if the page survives, the regular
209
+ // mutation still settles state and confirms persistence.
210
+ const entry = createTweakSaveOutboxEntry(pending);
211
+ if (!entry) return;
212
+ void sendJournaledTweakSaveKeepalive({
213
+ journal: () => journalTweakOutboxEntry(entry),
214
+ send: () =>
215
+ tryCallActionKeepalive("apply-tweaks", entry.payload as any),
216
+ acknowledge: () => acknowledgeOutboxEntry(entry),
217
+ }).catch(() => {});
218
+ };
219
+ const handleOnline = () => {
220
+ if (pendingTweakSaveRef.current && !tweakSaveInFlightRef.current) {
221
+ flushPendingTweakSave();
222
+ }
223
+ };
224
+ window.addEventListener("pagehide", handlePageHide);
225
+ window.addEventListener("online", handleOnline);
226
+ return () => {
227
+ window.removeEventListener("pagehide", handlePageHide);
228
+ window.removeEventListener("online", handleOnline);
229
+ // Client-side navigation used to cancel the 600 ms debounce and drop
230
+ // the user's final knob position. Flush it synchronously into the
231
+ // mutation pipeline before this keyed editor instance unmounts.
232
+ flushPendingTweakSave();
233
+ };
234
+ }, [
235
+ acknowledgeOutboxEntry,
236
+ createTweakSaveOutboxEntry,
237
+ flushPendingTweakSave,
238
+ id,
239
+ journalTweakOutboxEntry,
240
+ ]);
241
+
242
+ // Parse design.data for agent-supplied tweaks. The agent writes a JSON blob
243
+ // to designs.data containing { tweaks: TweakDefinition[], ... }; we surface
244
+ // the tweaks as live controls bound to the design's CSS custom properties.
245
+ const tweaks: TweakDefinition[] = useMemo(() => {
246
+ if (!design?.data) return [];
247
+ try {
248
+ const parsed = JSON.parse(design.data);
249
+ if (Array.isArray(parsed?.tweaks)) return parsed.tweaks;
250
+ return [];
251
+ // coercion-ok: unreadable design data means "no tweaks configured", which the empty list already expresses.
252
+ } catch {
253
+ return [];
254
+ }
255
+ }, [design?.data]);
256
+
257
+ // Persisted user knob values live in designs.data.tweakSelections (written by
258
+ // the apply-tweaks action). Restoring them on load is what makes the
259
+ // visual-tune round-trip survive a refresh and feed the snapshot/handoff.
260
+ const persistedSelections: TweakSelections = useMemo(() => {
261
+ if (!design?.data) return {};
262
+ try {
263
+ const parsed = JSON.parse(design.data);
264
+ const sel = parsed?.tweakSelections;
265
+ return sel && typeof sel === "object" && !Array.isArray(sel) ? sel : {};
266
+ // coercion-ok: unreadable design data means "no tweak selections", which the empty object already expresses.
267
+ } catch {
268
+ return {};
269
+ }
270
+ }, [design?.data]);
271
+ useLayoutEffect(() => {
272
+ if (
273
+ !tweakSaveActive &&
274
+ !tweakSaveInFlightRef.current &&
275
+ pendingTweakSaveRef.current === null
276
+ ) {
277
+ confirmedTweakSelectionsHashRef.current =
278
+ tweakSelectionsHash(persistedSelections);
279
+ }
280
+ }, [persistedSelections, tweakSaveActive]);
281
+
282
+ // Tweak values are keyed by tweak id while in the panel, then mapped to
283
+ // CSS-var -> value for the iframe so the design's :root block picks them up.
284
+ // Persisted selections are authoritative for agent edits; a local queued
285
+ // save temporarily pauses adoption so stale refetches don't clobber a drag.
286
+ const authoritativeTweakSelections = useMemo(
287
+ () => buildAuthoritativeTweakSelections(tweaks, persistedSelections),
288
+ [tweaks, persistedSelections],
289
+ );
290
+ const [tweakSelections, setTweakSelections] = useReconciledState(
291
+ authoritativeTweakSelections,
292
+ {
293
+ active: tweakSaveActive,
294
+ equals: areTweakSelectionsEqual,
295
+ },
296
+ );
297
+
298
+ // Map tweak selections (id -> value) to CSS-var assignments (--var -> value)
299
+ // for the iframe bridge. Shared with the snapshot/handoff actions via
300
+ // `@shared/resolve-tweaks` so the UI and external agents resolve identically.
301
+ const cssVarValues = useMemo(
302
+ () => resolveTweaksToCssVars(tweaks, tweakSelections),
303
+ [tweaks, tweakSelections],
304
+ );
305
+
306
+ const handleTweakChange = useCallback(
307
+ (tweakId: string, value: string | number | boolean) => {
308
+ setTweakSelections((prev) => {
309
+ if (!canEditDesign) return prev;
310
+ const next = { ...prev, [tweakId]: value };
311
+ queueTweakSave(next);
312
+ return next;
313
+ });
314
+ },
315
+ [canEditDesign, queueTweakSave],
316
+ );
317
+
318
+ return {
319
+ cssVarValues,
320
+ flushPendingTweakSave,
321
+ handleTweakChange,
322
+ setTweakSelections,
323
+ tweakSelections,
324
+ tweaks,
325
+ };
326
+ }
@@ -0,0 +1,54 @@
1
+ export const MAX_GENERATION_ATTEMPTS = 3;
2
+ export const AUTO_RETRY_DELAY_MS = 1200;
3
+ export const HOST_CHAT_SLOT_MESSAGE = "agentNative.chatSlot";
4
+ export const HOST_TURN_START_TIMEOUT_MS = 15_000;
5
+ export const STORED_RUN_LIVENESS_GRACE_MS = 20_000;
6
+
7
+ export const OVERVIEW_ZOOM_THRESHOLD = 60;
8
+ export const MOTION_DOCK_TRANSITION_MS = 200;
9
+ export const MOTION_DOCK_EXIT_SETTLE_MS = 80;
10
+ export const MOTION_DOCK_EXIT_FALLBACK_MS = MOTION_DOCK_TRANSITION_MS * 2 + 600;
11
+ export const MOTION_AUTOSAVE_DELAY_MS = 500;
12
+ export const DESIGN_SELECTION_ZOOM_SAVE_DELAY_MS = 150;
13
+
14
+ /** Retry window for dropping an untouched text node whose screen content has
15
+ * not caught up with the insert yet — see removeEmptyTextNodeWithRetry. */
16
+ export const EMPTY_TEXT_CLEANUP_RETRY_MS = 400;
17
+
18
+ /** Floor for an inspector-typed frame size, matching the frame tool's own
19
+ * drawing minimum (see getDraftGeometryForTool). */
20
+ export const MIN_FRAME_SIZE_PX = 24;
21
+ export const BOARD_SURFACE_SIZE = 131_072;
22
+
23
+ /** Gates non-essential diagnostic console.warn calls (e.g. the cross-screen
24
+ * anchor-stamp fallback warning) so production consoles stay quiet while
25
+ * dev builds keep the signal. Real correctness-guard warnings (frame
26
+ * geometry rejection, poisoned-coord normalization) stay unconditional —
27
+ * this flag is only for lower-signal "known degraded path taken" notices. */
28
+ export const DESIGN_EDITOR_DEBUG_LOGS = import.meta.env.DEV;
29
+
30
+ /** Extensions that the localhost bridge allows to be written back to source. */
31
+ export const LOCALHOST_WRITE_EXTENSIONS = new Set([".html", ".htm", ".css"]);
32
+
33
+ /**
34
+ * Compiled framework route extensions we can *detect* as local source but
35
+ * cannot yet write back to (React/TS component files require build-time
36
+ * source mapping, not a raw HTML/CSS write). "Apply to source" shows as a
37
+ * disabled affordance with an explanatory tooltip for these instead of
38
+ * disappearing entirely.
39
+ */
40
+ export const LOCALHOST_COMPILED_SOURCE_EXTENSIONS = new Set([".jsx", ".tsx"]);
41
+
42
+ export const NO_LOCALHOST_WRITE_CONTENT_MESSAGE =
43
+ "No content to write. Open the screen first."; /* i18n-ignore */
44
+ export const NO_LOCALHOST_CONNECTION_MESSAGE =
45
+ "No localhost connection for this screen. Reconnect and try again."; /* i18n-ignore */
46
+ export const NO_LOCALHOST_WRITE_PATH_MESSAGE =
47
+ "Can't determine the source file for this screen."; /* i18n-ignore */
48
+ export const TWEAK_CONTROLS_EDIT_ACCESS_MESSAGE =
49
+ "You need edit access to add tweak controls."; /* i18n-ignore */
50
+
51
+ export const PENDING_STRUCTURE_VERIFICATION_TIMEOUT_MS = 60_000;
52
+ export const PENDING_STRUCTURE_RUNTIME_TIMEOUT_MS = 15_000;
53
+ export const PENDING_STRUCTURE_SOURCE_POLL_MS = 750;
54
+ export const PENDING_STRUCTURE_RUNTIME_POLL_MS = 150;