@agent-native/core 0.80.11 → 0.81.0

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 (225) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +47 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/design-connect.ts +419 -19
  5. package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
  6. package/corpus/core/src/client/chat/index.ts +1 -0
  7. package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
  8. package/corpus/core/src/client/chat-view-transition.ts +33 -2
  9. package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
  10. package/corpus/core/src/client/index.ts +1 -0
  11. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
  12. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
  13. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
  14. package/corpus/core/src/db/client.ts +29 -2
  15. package/corpus/core/src/provider-api/index.ts +29 -0
  16. package/corpus/core/src/server/core-routes-plugin.ts +58 -20
  17. package/corpus/core/src/styles/agent-native.css +54 -0
  18. package/corpus/core/src/usage/store.ts +2 -2
  19. package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
  20. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
  21. package/corpus/templates/analytics/app/global.css +8 -0
  22. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
  23. package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
  24. package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
  25. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
  26. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
  27. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  28. package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
  29. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
  33. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
  34. package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
  35. package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
  36. package/corpus/templates/assets/app/global.css +15 -0
  37. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
  38. package/corpus/templates/assets/app/routes/library.tsx +90 -16
  39. package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
  40. package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
  41. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
  42. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
  43. package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
  44. package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
  45. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  46. package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
  47. package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
  48. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
  49. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
  50. package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
  51. package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
  52. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
  53. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
  54. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
  55. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
  56. package/corpus/templates/design/AGENTS.md +27 -0
  57. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
  58. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
  59. package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
  60. package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
  61. package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
  62. package/corpus/templates/design/actions/capture-design-state.ts +27 -4
  63. package/corpus/templates/design/actions/connect-localhost.ts +59 -22
  64. package/corpus/templates/design/actions/export-zip.ts +23 -8
  65. package/corpus/templates/design/actions/generate-design.ts +53 -1
  66. package/corpus/templates/design/actions/get-component-details.ts +5 -19
  67. package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
  68. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
  69. package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
  70. package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
  71. package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
  72. package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
  73. package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
  74. package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
  75. package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
  76. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
  77. package/corpus/templates/design/actions/navigate.ts +18 -3
  78. package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
  79. package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
  80. package/corpus/templates/design/actions/provider-api-request.ts +3 -3
  81. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
  82. package/corpus/templates/design/actions/run-design-audit.ts +53 -4
  83. package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
  84. package/corpus/templates/design/actions/view-screen.ts +1 -1
  85. package/corpus/templates/design/actions/write-local-file.ts +213 -0
  86. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
  87. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
  88. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
  89. package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
  90. package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
  91. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
  92. package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
  93. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
  94. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
  95. package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
  96. package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
  97. package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
  98. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
  99. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
  100. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
  101. package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
  102. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
  103. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
  104. package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
  105. package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
  106. package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
  107. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
  108. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
  109. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
  110. package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
  111. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  112. package/corpus/templates/design/app/global.css +7 -0
  113. package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
  114. package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
  115. package/corpus/templates/design/app/i18n-data.ts +358 -0
  116. package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
  117. package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
  118. package/corpus/templates/design/app/pages/Index.tsx +5 -1
  119. package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
  120. package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
  121. package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
  122. package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
  123. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
  124. package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
  125. package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
  126. package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
  127. package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
  128. package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
  129. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
  130. package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
  131. package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
  132. package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
  133. package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
  134. package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
  135. package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
  136. package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
  137. package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
  138. package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
  139. package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
  140. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
  141. package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
  142. package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
  143. package/corpus/templates/design/e2e/helpers.ts +168 -65
  144. package/corpus/templates/design/package.json +3 -1
  145. package/corpus/templates/design/server/db/schema.ts +23 -0
  146. package/corpus/templates/design/server/handlers/uploads.ts +22 -1
  147. package/corpus/templates/design/server/lib/provider-api.ts +7 -4
  148. package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
  149. package/corpus/templates/design/server/plugins/db.ts +39 -0
  150. package/corpus/templates/design/server/register-secrets.ts +38 -0
  151. package/corpus/templates/design/shared/board-file.ts +228 -0
  152. package/corpus/templates/design/shared/board-objects.ts +288 -0
  153. package/corpus/templates/design/shared/code-layer.ts +52 -0
  154. package/corpus/templates/design/shared/motion-compiler.ts +58 -7
  155. package/corpus/templates/design/shared/motion-timeline.ts +2 -2
  156. package/corpus/templates/design/tsconfig.json +2 -1
  157. package/corpus/templates/slides/AGENTS.md +9 -0
  158. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
  159. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
  160. package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
  161. package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
  162. package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
  163. package/corpus/templates/slides/app/vite-env.d.ts +18 -0
  164. package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
  165. package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
  166. package/corpus/templates/slides/package.json +1 -0
  167. package/corpus/templates/slides/vite.config.ts +1 -0
  168. package/dist/cli/design-connect.d.ts +25 -0
  169. package/dist/cli/design-connect.d.ts.map +1 -1
  170. package/dist/cli/design-connect.js +357 -18
  171. package/dist/cli/design-connect.js.map +1 -1
  172. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  173. package/dist/client/ConnectBuilderCard.js +1 -0
  174. package/dist/client/ConnectBuilderCard.js.map +1 -1
  175. package/dist/client/chat/index.d.ts +1 -1
  176. package/dist/client/chat/index.d.ts.map +1 -1
  177. package/dist/client/chat/index.js +1 -1
  178. package/dist/client/chat/index.js.map +1 -1
  179. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  180. package/dist/client/chat/run-recovery.js +5 -5
  181. package/dist/client/chat/run-recovery.js.map +1 -1
  182. package/dist/client/chat-view-transition.d.ts +6 -0
  183. package/dist/client/chat-view-transition.d.ts.map +1 -1
  184. package/dist/client/chat-view-transition.js +23 -2
  185. package/dist/client/chat-view-transition.js.map +1 -1
  186. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  187. package/dist/client/composer/PromptComposer.js +3 -2
  188. package/dist/client/composer/PromptComposer.js.map +1 -1
  189. package/dist/client/index.d.ts +1 -1
  190. package/dist/client/index.d.ts.map +1 -1
  191. package/dist/client/index.js +1 -1
  192. package/dist/client/index.js.map +1 -1
  193. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  194. package/dist/client/org/OrgSwitcher.js +11 -4
  195. package/dist/client/org/OrgSwitcher.js.map +1 -1
  196. package/dist/client/sharing/ShareButton.js +3 -3
  197. package/dist/client/sharing/ShareButton.js.map +1 -1
  198. package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
  199. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  200. package/dist/client/use-agent-chat-home-handoff.js +20 -6
  201. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  202. package/dist/collab/awareness.d.ts +2 -2
  203. package/dist/collab/awareness.d.ts.map +1 -1
  204. package/dist/collab/routes.d.ts +2 -2
  205. package/dist/db/client.d.ts +1 -0
  206. package/dist/db/client.d.ts.map +1 -1
  207. package/dist/db/client.js +28 -2
  208. package/dist/db/client.js.map +1 -1
  209. package/dist/notifications/routes.d.ts +3 -3
  210. package/dist/observability/routes.d.ts +2 -2
  211. package/dist/progress/routes.d.ts +1 -1
  212. package/dist/provider-api/index.d.ts +5 -4
  213. package/dist/provider-api/index.d.ts.map +1 -1
  214. package/dist/provider-api/index.js +28 -0
  215. package/dist/provider-api/index.js.map +1 -1
  216. package/dist/resources/handlers.d.ts +3 -3
  217. package/dist/server/core-routes-plugin.d.ts +25 -0
  218. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  219. package/dist/server/core-routes-plugin.js +43 -18
  220. package/dist/server/core-routes-plugin.js.map +1 -1
  221. package/dist/server/transcribe-voice.d.ts +1 -1
  222. package/dist/styles/agent-native.css +54 -0
  223. package/dist/usage/store.js +2 -2
  224. package/dist/usage/store.js.map +1 -1
  225. package/package.json +1 -1
@@ -108,6 +108,8 @@ function checkMissingLabels(html: string): A11yFinding[] {
108
108
  let idx = 0;
109
109
  for (const m of html.matchAll(inputPattern)) {
110
110
  const tag = m[0];
111
+ const inputStart = m.index ?? 0;
112
+ const inputEnd = inputStart + tag.length;
111
113
  const typeMatch = tag.match(/\btype\s*=\s*(?:"([^"]*?)"|'([^']*?)')/i);
112
114
  const type = (typeMatch?.[1] ?? typeMatch?.[2] ?? "text").toLowerCase();
113
115
  // Hidden and submit/button/image inputs don't need visible labels
@@ -123,8 +125,9 @@ function checkMissingLabels(html: string): A11yFinding[] {
123
125
  const hasExplicitLabel = inputId
124
126
  ? new RegExp(`for\\s*=\\s*(?:"${inputId}"|'${inputId}')`, "i").test(html)
125
127
  : false;
128
+ const hasImplicitLabel = isWrappedByLabel(html, inputStart, inputEnd);
126
129
 
127
- if (!hasAriaLabel && !hasExplicitLabel) {
130
+ if (!hasAriaLabel && !hasExplicitLabel && !hasImplicitLabel) {
128
131
  findings.push({
129
132
  id: `missing-label:input-${idx}`,
130
133
  severity: "error" as A11ySeverity,
@@ -143,8 +146,53 @@ function checkMissingLabels(html: string): A11yFinding[] {
143
146
  return findings;
144
147
  }
145
148
 
146
- /** Check interactive elements that are likely too small for touch targets (< ~44px heuristic via Tailwind class). */
147
- function checkTapTargets(html: string): A11yFinding[] {
149
+ function isWrappedByLabel(
150
+ html: string,
151
+ inputStart: number,
152
+ inputEnd: number,
153
+ ): boolean {
154
+ const labelOpen = html.lastIndexOf("<label", inputStart);
155
+ if (labelOpen === -1) return false;
156
+ const labelCloseBeforeInput = html.lastIndexOf("</label", inputStart);
157
+ if (labelCloseBeforeInput > labelOpen) return false;
158
+ const labelCloseAfterInput = html.indexOf("</label", inputEnd);
159
+ return labelCloseAfterInput !== -1;
160
+ }
161
+
162
+ /**
163
+ * Whether an interactive element already declares a minimum size that meets the
164
+ * 44px tap-target floor. This recognises exactly what the inline auto-fix adds
165
+ * (`min-h-[44px] min-w-[44px]`) plus equivalents — arbitrary `min-h`/`min-w`
166
+ * values in px/rem/em ≥ 44px, the Tailwind spacing scale (`min-h-11` = 44px on a
167
+ * 4px step), and full-bleed minimums (`min-h-full` / `min-h-screen`). Without
168
+ * this, a fixed element keeps its original tiny `h-4` class and the audit would
169
+ * re-flag it forever, so the audit↔fix loop would never converge.
170
+ */
171
+ function hasAdequateMinTapSize(tag: string): boolean {
172
+ // Arbitrary values: min-h-[44px], min-w-[2.75rem], etc.
173
+ const arbitraryPattern = /\bmin-(?:h|w)-\[([\d.]+)(px|rem|em)\]/gi;
174
+ let m: RegExpExecArray | null;
175
+ while ((m = arbitraryPattern.exec(tag)) !== null) {
176
+ const value = Number.parseFloat(m[1] ?? "");
177
+ if (!Number.isFinite(value)) continue;
178
+ const px = m[2]?.toLowerCase() === "px" ? value : value * 16;
179
+ if (px >= 44) return true;
180
+ }
181
+ // Tailwind spacing scale: min-h-11 / min-w-11 = 2.75rem = 44px (4px per step).
182
+ const scalePattern = /\bmin-(?:h|w)-(\d+)\b/gi;
183
+ while ((m = scalePattern.exec(tag)) !== null) {
184
+ if (Number.parseInt(m[1] ?? "", 10) * 4 >= 44) return true;
185
+ }
186
+ // Full-bleed minimums always clear the tap floor.
187
+ return /\bmin-(?:h|w)-(?:full|screen)\b/.test(tag);
188
+ }
189
+
190
+ /**
191
+ * Check interactive elements that are likely too small for touch targets
192
+ * (< ~44px heuristic via Tailwind class). Exported for unit tests that assert
193
+ * the audit↔fix loop converges (a fixed element must stop being flagged).
194
+ */
195
+ export function checkTapTargets(html: string): A11yFinding[] {
148
196
  const findings: A11yFinding[] = [];
149
197
  // Heuristic: buttons/links with explicit tiny size classes (h-4, h-5, w-4, w-5, size-4, size-5)
150
198
  // and no explicit larger override or sr-only are flagged.
@@ -158,7 +206,8 @@ function checkTapTargets(html: string): A11yFinding[] {
158
206
  if (
159
207
  tinyPattern.test(tag) &&
160
208
  !largePattern.test(tag) &&
161
- !srOnlyPattern.test(tag)
209
+ !srOnlyPattern.test(tag) &&
210
+ !hasAdequateMinTapSize(tag)
162
211
  ) {
163
212
  findings.push({
164
213
  id: `tap-target:interactive-${idx}`,
@@ -146,7 +146,7 @@ const CAPABILITY_ROUTES: Record<string, CapabilityRoute> = {
146
146
  "design.motion-presets:apply": {
147
147
  action: "apply-motion-edit",
148
148
  paramHint:
149
- "Call apply-motion-edit with { designId, fileId?, timeline: { tracks, durationMs } } for an atomic Write to CSS commit.",
149
+ "Call apply-motion-edit with { designId, fileId?, timeline: { tracks, durationMs } } for an atomic managed CSS timeline save.",
150
150
  readOnly: false,
151
151
  },
152
152
  "design.motion-presets:write-source": {
@@ -95,7 +95,7 @@ function resolveActiveScreen(
95
95
 
96
96
  export default defineAction({
97
97
  description:
98
- "See what the user is currently looking at on screen. Returns the current navigation state including which design is open, which view they are on (list, editor, design-systems, present, templates, settings), active/focused design screen, selected element, active inspector tab (design, tweaks, or extensions), overview canvas state, plus any pending question overlay. Always call this first before taking any action.",
98
+ "See what the user is currently looking at on screen. Returns the current navigation state including which design is open, which view they are on (list, editor, design-systems, present, templates, settings), active/focused design screen, selected element, active inspector tab (design or tweaks), active left rail panel (file, agent, assets, tools, or tokens), overview canvas state, plus any pending question overlay. Always call this first before taking any action.",
99
99
  schema: z.object({}),
100
100
  http: false,
101
101
  run: async () => {
@@ -0,0 +1,213 @@
1
+ /**
2
+ * write-local-file — write or patch a local file through the design bridge.
3
+ *
4
+ * Security gates (in order):
5
+ * 1. assertAccess: the caller must have editor access to the design.
6
+ * 2. File extension: only .html, .htm, and .css files are allowed.
7
+ * 3. verifyWriteGrant: a valid (non-expired) user-approved write-consent grant
8
+ * must exist. The agent CANNOT bypass this check.
9
+ * 4. Path confinement: assertPathInside ensures the target stays inside
10
+ * rootPath (pre-bridge check; bridge also validates with realpath).
11
+ * 5. Bridge token: the X-Bridge-Token header is set to the per-grant token
12
+ * minted at consent time so the bridge can reject unauthorized calls.
13
+ */
14
+
15
+ import path from "node:path";
16
+
17
+ import { defineAction } from "@agent-native/core";
18
+ import { getRequestUserEmail } from "@agent-native/core/server/request-context";
19
+ import { assertAccess } from "@agent-native/core/sharing";
20
+ import { and, eq } from "drizzle-orm";
21
+ import { z } from "zod";
22
+
23
+ import { getDb, schema } from "../server/db/index.js";
24
+ import { verifyWriteGrant } from "../server/lib/verify-write-grant.js";
25
+
26
+ /** File extensions the agent is permitted to write via the bridge. */
27
+ const ALLOWED_EXTENSIONS = new Set([".html", ".htm", ".css"]);
28
+
29
+ function assertAllowedExtension(relPath: string): void {
30
+ const ext = path.extname(relPath).toLowerCase();
31
+ if (!ALLOWED_EXTENSIONS.has(ext)) {
32
+ throw new Error(
33
+ `File "${relPath}" has extension "${ext}" which is not allowed. ` +
34
+ "Only .html, .htm, and .css files may be written through the bridge.",
35
+ );
36
+ }
37
+ }
38
+
39
+ function isLoopbackHostname(hostname: string): boolean {
40
+ const normalized = hostname.toLowerCase();
41
+ if (
42
+ normalized === "localhost" ||
43
+ normalized === "::1" ||
44
+ normalized === "[::1]"
45
+ ) {
46
+ return true;
47
+ }
48
+ const parts = normalized.split(".");
49
+ return (
50
+ parts.length === 4 &&
51
+ parts[0] === "127" &&
52
+ parts.every((part) => /^\d+$/.test(part) && Number(part) <= 255)
53
+ );
54
+ }
55
+
56
+ function normalizeBridgeUrl(value: string): string {
57
+ const parsed = new URL(value.trim());
58
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
59
+ throw new Error("bridgeUrl must be an http(s) URL");
60
+ }
61
+ if (parsed.username || parsed.password) {
62
+ throw new Error("bridgeUrl must not include credentials");
63
+ }
64
+ if (parsed.pathname !== "/" && parsed.pathname !== "") {
65
+ throw new Error("bridgeUrl must not include a path");
66
+ }
67
+ if (!isLoopbackHostname(parsed.hostname)) {
68
+ throw new Error("bridgeUrl must use localhost or a loopback IP address");
69
+ }
70
+ parsed.search = "";
71
+ parsed.hash = "";
72
+ parsed.pathname = "";
73
+ return parsed.toString().replace(/\/$/, "");
74
+ }
75
+
76
+ export default defineAction({
77
+ description:
78
+ "Write or patch a local file (HTML or CSS only) via the localhost design bridge. " +
79
+ "The user MUST have already granted write consent via grant-localhost-write-consent; " +
80
+ "this action will reject the request if no valid grant exists. " +
81
+ "Pass content for a full file write, or {search, replace} for a targeted patch. " +
82
+ "Requires editor access on the design.",
83
+ schema: z.object({
84
+ designId: z.string().describe("Design ID."),
85
+ connectionId: z
86
+ .string()
87
+ .describe("Localhost connection ID (must have an active write grant)."),
88
+ relPath: z
89
+ .string()
90
+ .describe(
91
+ "Path to the file relative to the connection rootPath. " +
92
+ "Only .html, .htm, and .css files are accepted.",
93
+ ),
94
+ content: z
95
+ .string()
96
+ .optional()
97
+ .describe(
98
+ "Full replacement file content. Use for new files or complete rewrites.",
99
+ ),
100
+ patch: z
101
+ .object({
102
+ search: z
103
+ .string()
104
+ .describe("Exact text to search for (must appear exactly once)."),
105
+ replace: z.string().describe("Replacement text."),
106
+ })
107
+ .optional()
108
+ .describe(
109
+ "Search-and-replace patch. Use for targeted edits. " +
110
+ "Mutually exclusive with content.",
111
+ ),
112
+ }),
113
+ run: async ({ designId, connectionId, relPath, content, patch }) => {
114
+ // --- Gate 1: access ---
115
+ await assertAccess("design", designId, "editor");
116
+
117
+ const ownerEmail = getRequestUserEmail();
118
+ if (!ownerEmail) throw new Error("no authenticated user");
119
+
120
+ // --- Gate 2: extension whitelist ---
121
+ assertAllowedExtension(relPath);
122
+
123
+ // --- Gate 3: valid write-consent grant ---
124
+ const grant = await verifyWriteGrant({
125
+ designId,
126
+ connectionId,
127
+ ownerEmail,
128
+ targetPath: relPath,
129
+ });
130
+
131
+ // --- Gate 4: exactly one of content/patch must be provided ---
132
+ if (content === undefined && patch === undefined) {
133
+ throw new Error(
134
+ "Either content (full file write) or patch (search/replace) must be provided.",
135
+ );
136
+ }
137
+ if (content !== undefined && patch !== undefined) {
138
+ throw new Error(
139
+ "content and patch are mutually exclusive. Provide one or the other.",
140
+ );
141
+ }
142
+
143
+ // --- Resolve bridge URL ---
144
+ const db = getDb();
145
+ const [connection] = await db
146
+ .select({ bridgeUrl: schema.designLocalhostConnections.bridgeUrl })
147
+ .from(schema.designLocalhostConnections)
148
+ .where(
149
+ and(
150
+ eq(schema.designLocalhostConnections.id, connectionId),
151
+ eq(schema.designLocalhostConnections.ownerEmail, ownerEmail),
152
+ ),
153
+ )
154
+ .limit(1);
155
+
156
+ if (!connection?.bridgeUrl) {
157
+ throw new Error(
158
+ `No bridge URL found for connection "${connectionId}". ` +
159
+ "Ensure the design bridge is running (npx @agent-native/core@latest design connect).",
160
+ );
161
+ }
162
+
163
+ const bridgeUrl = normalizeBridgeUrl(connection.bridgeUrl);
164
+ const headers: Record<string, string> = {
165
+ "Content-Type": "application/json",
166
+ "X-Bridge-Token": grant.bridgeToken,
167
+ };
168
+
169
+ if (content !== undefined) {
170
+ // Full file write
171
+ const res = await fetch(`${bridgeUrl}/write-file`, {
172
+ method: "POST",
173
+ headers,
174
+ body: JSON.stringify({ relPath, content }),
175
+ });
176
+ if (!res.ok) {
177
+ const errText = await res.text().catch(() => res.statusText);
178
+ throw new Error(`Bridge write-file failed (${res.status}): ${errText}`);
179
+ }
180
+ return { designId, relPath, operation: "write" as const, written: true };
181
+ } else {
182
+ // Search-and-replace patch — first read, then apply-edit
183
+ const readRes = await fetch(`${bridgeUrl}/read-file`, {
184
+ method: "POST",
185
+ headers,
186
+ body: JSON.stringify({ relPath }),
187
+ });
188
+ if (!readRes.ok) {
189
+ const errText = await readRes.text().catch(() => readRes.statusText);
190
+ throw new Error(
191
+ `Bridge read-file failed (${readRes.status}): ${errText}`,
192
+ );
193
+ }
194
+
195
+ const applyRes = await fetch(`${bridgeUrl}/apply-edit`, {
196
+ method: "POST",
197
+ headers,
198
+ body: JSON.stringify({
199
+ relPath,
200
+ search: patch!.search,
201
+ replace: patch!.replace,
202
+ }),
203
+ });
204
+ if (!applyRes.ok) {
205
+ const errText = await applyRes.text().catch(() => applyRes.statusText);
206
+ throw new Error(
207
+ `Bridge apply-edit failed (${applyRes.status}): ${errText}`,
208
+ );
209
+ }
210
+ return { designId, relPath, operation: "patch" as const, written: true };
211
+ }
212
+ },
213
+ });