@agent-native/core 0.153.9 → 0.154.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 (149) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/AGENTS.md +20 -30
  3. package/corpus/templates/analytics/actions/search-dashboard-references.ts +58 -0
  4. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
  5. package/corpus/templates/analytics/app/lib/utils.ts +1 -1
  6. package/corpus/templates/analytics/app/pages/DataSources.tsx +2 -1
  7. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/ViewSqlPopover.tsx +1 -1
  8. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueList.tsx +4 -2
  9. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -2
  10. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +2 -0
  11. package/corpus/templates/analytics/server/lib/dashboards-store.ts +366 -3
  12. package/corpus/templates/analytics/server/lib/real-data-actions.ts +3 -1
  13. package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -2
  14. package/corpus/templates/calendar/app/components/calendar/EventDialog.tsx +1 -1
  15. package/corpus/templates/calendar/app/lib/utils.ts +1 -1
  16. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +32 -5
  17. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +15 -15
  18. package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
  19. package/corpus/templates/clips/app/lib/utils.ts +5 -6
  20. package/corpus/templates/clips/app/root.tsx +4 -1
  21. package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -2
  22. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +43 -8
  23. package/corpus/templates/clips/desktop/src/app.tsx +27 -13
  24. package/corpus/templates/clips/desktop/src/lib/recorder.ts +23 -9
  25. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +261 -11
  26. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +21 -3
  27. package/corpus/templates/clips/shared/mic-audio-cleanup.ts +350 -0
  28. package/corpus/templates/content/app/i18n-data.ts +10 -10
  29. package/corpus/templates/content/scripts/check-native-deps.mjs +181 -15
  30. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +79 -7
  31. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +14 -2
  32. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +2 -2
  33. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +35 -11
  34. package/corpus/templates/design/actions/get-design-system.ts +103 -3
  35. package/corpus/templates/design/actions/import-design-tokens.ts +37 -8
  36. package/corpus/templates/design/actions/import-github.ts +32 -138
  37. package/corpus/templates/design/actions/index-design-system-with-builder.ts +49 -16
  38. package/corpus/templates/design/actions/present-design-variants.ts +93 -4
  39. package/corpus/templates/design/actions/run-design-audit.ts +229 -0
  40. package/corpus/templates/design/actions/sync-design-system-with-builder.ts +64 -0
  41. package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +2 -1
  42. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +2 -1
  43. package/corpus/templates/design/app/components/design/code-workbench/commands.ts +2 -2
  44. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +17 -4
  45. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +5 -1
  46. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +5 -1
  47. package/corpus/templates/design/app/hooks/use-question-flow.ts +81 -5
  48. package/corpus/templates/design/app/i18n/en-US.ts +1 -1
  49. package/corpus/templates/design/app/i18n-data.ts +32 -1
  50. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +10 -10
  51. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +83 -11
  52. package/corpus/templates/design/app/pages/DesignSystems.tsx +14 -0
  53. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +22 -0
  54. package/corpus/templates/design/changelog/2026-08-13-a-screen-that-would-render-behind-an-invisible-alpine-overla.md +6 -0
  55. package/corpus/templates/design/changelog/2026-08-13-attach-a-ui-screenshot-and-the-design-is-reproduced-not-rein.md +6 -0
  56. package/corpus/templates/design/changelog/2026-08-13-linking-a-design-system-means-you-never-get-generic-directio.md +6 -0
  57. package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-reports-when-a-screen-ignores-its-linke.md +6 -0
  58. package/corpus/templates/design/changelog/2026-08-13-the-design-system-preview-now-shows-how-many-named-tokens-we.md +6 -0
  59. package/corpus/templates/design/changelog/2026-08-13-your-design-system-prompt-and-reference-screenshots-now-reac.md +6 -0
  60. package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +18 -2
  61. package/corpus/templates/design/shared/design-review.ts +1 -0
  62. package/corpus/templates/design/shared/html-integrity.ts +171 -0
  63. package/corpus/templates/mail/app/lib/utils.ts +1 -1
  64. package/corpus/templates/plan/app/pages/PlansPage.tsx +5 -2
  65. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +10 -6
  66. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +41 -3
  67. package/corpus/templates/slides/actions/sync-design-system-with-builder.ts +64 -0
  68. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
  69. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +193 -20
  70. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +58 -33
  71. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +28 -41
  72. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +119 -91
  73. package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +16 -0
  74. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +158 -31
  75. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +30 -14
  76. package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +2 -2
  77. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +4 -1
  78. package/corpus/templates/slides/app/components/editor/rich-text-selection.ts +14 -0
  79. package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +24 -0
  80. package/corpus/templates/slides/app/global.css +24 -0
  81. package/corpus/templates/slides/app/hooks/use-navigation-state.ts +5 -1
  82. package/corpus/templates/slides/app/i18n/en-US.ts +19 -10
  83. package/corpus/templates/slides/app/lib/deck-filter.ts +47 -0
  84. package/corpus/templates/slides/app/lib/export-google-slides-client.ts +15 -4
  85. package/corpus/templates/slides/app/lib/export-pptx-client.ts +637 -1
  86. package/corpus/templates/slides/app/lib/image-download.ts +84 -0
  87. package/corpus/templates/slides/app/lib/utils.ts +1 -1
  88. package/corpus/templates/slides/app/pages/Index.tsx +25 -9
  89. package/corpus/templates/slides/netlify.toml +1 -0
  90. package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +14 -2
  91. package/dist/agent/production-agent.js +1 -1
  92. package/dist/brand-kit/types.d.ts +10 -2
  93. package/dist/cli/code.js +1 -1
  94. package/dist/cli/index.js +30 -1
  95. package/dist/cli/native-dependencies.d.ts +24 -0
  96. package/dist/cli/native-dependencies.js +264 -0
  97. package/dist/cli/plan-local.js +1 -1
  98. package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
  99. package/dist/cli/skills-content/design-visual-edit-skill.js +2 -2
  100. package/dist/cli/skills-content/help.d.ts +1 -1
  101. package/dist/cli/skills-content/help.js +2 -1
  102. package/dist/cli/skills.js +2 -1
  103. package/dist/cli/visualize-repo.js +1 -1
  104. package/dist/client/FeedbackButton.js +12 -12
  105. package/dist/client/NewWorkspaceAppFlow.js +4 -1
  106. package/dist/client/agent-chat-adapter.js +3 -4
  107. package/dist/client/agent-page/AgentTabsPage.d.ts +2 -2
  108. package/dist/client/agent-page/AgentTabsPage.js +12 -9
  109. package/dist/client/assistant-ui-recovery.d.ts +1 -1
  110. package/dist/client/assistant-ui-recovery.js +4 -0
  111. package/dist/client/chat/message-components.d.ts +11 -3
  112. package/dist/client/chat/message-components.js +31 -5
  113. package/dist/client/chat/tool-call-display.d.ts +2 -1
  114. package/dist/client/chat/tool-call-display.js +33 -15
  115. package/dist/client/db-admin/SqlEditor.js +1 -1
  116. package/dist/client/dev-overlay/DevOverlay.js +2 -2
  117. package/dist/client/extensions/ExtensionsSidebarSection.js +2 -1
  118. package/dist/client/extensions/portable-extension.js +4 -0
  119. package/dist/client/guided-questions.d.ts +8 -0
  120. package/dist/client/guided-questions.js +12 -3
  121. package/dist/client/onboarding/FirstRunOnboarding.js +2 -1
  122. package/dist/client/org/TeamPage.js +14 -3
  123. package/dist/client/settings/SettingsPanel.js +26 -1
  124. package/dist/client/sharing/ShareButton.js +16 -24
  125. package/dist/client/sse-event-processor.js +35 -15
  126. package/dist/client/tool-display.d.ts +9 -0
  127. package/dist/client/tool-display.js +39 -0
  128. package/dist/client/vite-dev-recovery-script.js +1 -1
  129. package/dist/extensions/html-shell.js +5 -0
  130. package/dist/integrations/catalog.js +9 -8
  131. package/dist/observability/routes.d.ts +2 -2
  132. package/dist/secrets/routes.d.ts +6 -6
  133. package/dist/server/agent-chat/prompt-resources.js +17 -16
  134. package/dist/server/agent-chat-plugin.d.ts +9 -0
  135. package/dist/server/agent-chat-plugin.js +31 -9
  136. package/dist/server/builder-design-systems.d.ts +33 -1
  137. package/dist/server/builder-design-systems.js +293 -11
  138. package/dist/server/design-token-utils.d.ts +11 -0
  139. package/dist/server/design-token-utils.js +68 -16
  140. package/dist/server/index.d.ts +1 -1
  141. package/dist/server/index.js +1 -1
  142. package/dist/server/onboarding-html.js +2 -1
  143. package/dist/shared/docs-url.d.ts +24 -0
  144. package/dist/shared/docs-url.js +39 -0
  145. package/dist/shared/index.d.ts +1 -0
  146. package/dist/shared/index.js +1 -0
  147. package/dist/templates/default/app/routes/_index.tsx +2 -1
  148. package/package.json +2 -2
  149. package/src/templates/default/app/routes/_index.tsx +2 -1
@@ -10,7 +10,6 @@ import {
10
10
  import { z } from "zod";
11
11
 
12
12
  import { upsertBuilderProxyDesignSystem } from "../server/lib/builder-design-system-proxy.js";
13
- import importGithub from "./import-github.js";
14
13
 
15
14
  const codeFileSchema = z.object({
16
15
  filename: z.string().trim().min(1).describe("File name or relative path"),
@@ -33,6 +32,30 @@ const codeFileSchema = z.object({
33
32
  ),
34
33
  });
35
34
 
35
+ const githubSourceSchema = z.object({
36
+ repoUrl: z.string().trim().min(1).describe("GitHub repository URL"),
37
+ ref: z
38
+ .string()
39
+ .trim()
40
+ .min(1)
41
+ .optional()
42
+ .describe("Optional branch, tag, or commit"),
43
+ include: z
44
+ .array(z.string().trim().min(1))
45
+ .optional()
46
+ .describe("Optional repository-relative files or folders to include"),
47
+ exclude: z
48
+ .array(z.string().trim().min(1))
49
+ .optional()
50
+ .describe("Optional repository-relative files or folders to exclude"),
51
+ instructions: z
52
+ .string()
53
+ .trim()
54
+ .min(1)
55
+ .optional()
56
+ .describe("Optional indexing guidance for this repository"),
57
+ });
58
+
36
59
  export default defineAction({
37
60
  description:
38
61
  "Start Builder DSI design-system indexing from connected code, a GitHub repository, code/design files, and optional design.md guidance. " +
@@ -51,7 +74,15 @@ export default defineAction({
51
74
  githubRepoUrl: z
52
75
  .string()
53
76
  .optional()
54
- .describe("GitHub repository URL to index with Builder"),
77
+ .describe("Legacy single GitHub repository URL to index with Builder"),
78
+ githubSources: z
79
+ .array(githubSourceSchema)
80
+ .min(1)
81
+ .max(20)
82
+ .optional()
83
+ .describe(
84
+ "GitHub repositories to index in one design system. Each source may specify a branch/tag/commit and repository-relative files or folders.",
85
+ ),
55
86
  connectedProjectId: z
56
87
  .string()
57
88
  .optional()
@@ -71,21 +102,13 @@ export default defineAction({
71
102
  projectName,
72
103
  description,
73
104
  githubRepoUrl,
105
+ githubSources,
74
106
  connectedProjectId,
75
107
  codeFiles,
76
108
  designMd,
77
109
  }) => {
78
- const githubImport = githubRepoUrl
79
- ? await importGithub.run({ repoUrl: githubRepoUrl })
80
- : null;
81
- const githubFiles = githubImport?.rawFiles
82
- ? Object.entries(githubImport.rawFiles).map(([filename, content]) => ({
83
- filename,
84
- content,
85
- }))
86
- : [];
87
110
  const files = buildBuilderDesignSystemIndexFiles({
88
- codeFiles: [...(codeFiles ?? []), ...githubFiles],
111
+ codeFiles,
89
112
  designMd,
90
113
  // Agent action payloads intentionally stay at the core 2 MB inline
91
114
  // budget. Fail loudly instead of silently dropping a larger `.fig`;
@@ -95,10 +118,8 @@ export default defineAction({
95
118
  const result = await startBuilderDesignSystemIndex({
96
119
  projectName,
97
120
  description,
98
- // Upload token-authenticated GitHub files as Builder file sources. If
99
- // there was nothing relevant to upload, retain the public-repo source
100
- // so public repositories still work without a saved token.
101
- githubRepoUrl: githubFiles.length > 0 ? undefined : githubRepoUrl,
121
+ githubRepoUrl,
122
+ githubRepos: githubSources,
102
123
  connectedProjectId,
103
124
  files,
104
125
  });
@@ -111,12 +132,24 @@ export default defineAction({
111
132
  orgId: getRequestOrgId(),
112
133
  projectName,
113
134
  description,
135
+ githubSources:
136
+ githubSources ?? (githubRepoUrl ? [{ repoUrl: githubRepoUrl }] : []),
137
+ sourceKind:
138
+ (githubSources?.length || githubRepoUrl) &&
139
+ (codeFiles?.length || designMd)
140
+ ? "mixed"
141
+ : githubSources?.length || githubRepoUrl
142
+ ? "github"
143
+ : codeFiles?.length || designMd
144
+ ? "code"
145
+ : undefined,
114
146
  });
115
147
 
116
148
  return {
117
149
  ...result,
118
150
  ...proxy,
119
151
  uploadedFileCount: files.length,
152
+ githubSourceCount: githubSources?.length ?? (githubRepoUrl ? 1 : 0),
120
153
  };
121
154
  },
122
155
  });
@@ -46,6 +46,49 @@ const DEFAULT_RESPONSIVE_BREAKPOINTS = [MOBILE_WIDTH].map((widthPx) => ({
46
46
  prefix: widthToPrefix(widthPx),
47
47
  }));
48
48
 
49
+ const SPECIFICATION_SIGNAL_PATTERNS = [
50
+ /\b(?:attached|uploaded|reference|mockup|screenshot|wireframe|source of truth|source-of-truth)\b/i,
51
+ /\b(?:\d+\s*[- ]\s*col(?:umn)?|grid spec|layout spec|section order|feature list)\b/i,
52
+ /\b(?:design system|brand system|brand kit|visual language|tokens?)\b/i,
53
+ ] as const;
54
+
55
+ /**
56
+ * Direction summaries are useful for genuinely open-ended exploration, but
57
+ * the fallback renderer cannot reproduce a supplied layout or system. Keep a
58
+ * complete-HTML requirement at the action boundary so a model cannot turn a
59
+ * detailed screen brief into generic cards just by omitting `content`.
60
+ *
61
+ * The weakest of the two signals, and never the only one: `prompt` is the chat
62
+ * caption this same model writes for the picker, so an agent that has just
63
+ * decided to explore writes an exploration-flavored caption and clears the
64
+ * check for free. `hasLinkedDesignSystem` below is the fact the server owns.
65
+ */
66
+ export function hasSpecifiedDesignPrompt(prompt?: string): boolean {
67
+ const value = prompt?.trim() ?? "";
68
+ if (!value) return false;
69
+ return SPECIFICATION_SIGNAL_PATTERNS.some((pattern) => pattern.test(value));
70
+ }
71
+
72
+ /**
73
+ * The one specification signal that survives an intake round-trip. Attachments
74
+ * are turn-scoped (see `ActionRunContext.attachments`), so by the time the
75
+ * agent asks its questions and comes back to present variants, the reference
76
+ * screenshot the user supplied is no longer visible to this action — but the
77
+ * design system they linked still is.
78
+ */
79
+ async function hasLinkedDesignSystem(designId: string): Promise<boolean> {
80
+ const [design] = await getDb()
81
+ .select({ designSystemId: schema.designs.designSystemId })
82
+ .from(schema.designs)
83
+ .where(
84
+ and(
85
+ eq(schema.designs.id, designId),
86
+ accessFilter(schema.designs, schema.designShares),
87
+ ),
88
+ );
89
+ return Boolean(design?.designSystemId?.trim());
90
+ }
91
+
49
92
  function hasBreakpointSet(value: unknown): boolean {
50
93
  if (!value || typeof value !== "object" || Array.isArray(value)) {
51
94
  return false;
@@ -114,7 +157,7 @@ const variantSchema = z.object({
114
157
  .string()
115
158
  .optional()
116
159
  .describe(
117
- "Optional complete self-contained HTML document for this variant. Keep it compact: one representative screen or directional snapshot, not a full multi-screen app. For faster exploration, omit this and provide label/description/features; Design will generate a compact representative screen.",
160
+ "Complete self-contained HTML document for this variant. Keep it compact: one representative screen or directional snapshot, not a full multi-screen app. Omit it ONLY for genuinely open-ended exploration, where Design renders a generic direction card from label/description/features — that card ignores any supplied reference, layout, or design system, so omitting content is rejected when the design has a linked design system or the prompt specifies one.",
118
161
  ),
119
162
  width: z
120
163
  .number()
@@ -825,9 +868,12 @@ export default defineAction({
825
868
  "complex apps, " +
826
869
  "make each variant a " +
827
870
  "compact representative screen; pass concise labels/descriptions/features " +
828
- "and omit content when full HTML would be too large. Design will render " +
829
- "compact screens from the direction data. Expand the chosen direction " +
830
- "after the user picks. Screens from an earlier variant set are never " +
871
+ "and omit content only for open-ended exploration. For a prompt with a " +
872
+ "specific product surface, reference, layout, or design system, provide " +
873
+ "complete self-contained HTML for every variant; the generic fallback is " +
874
+ "blocked there. Design will render compact screens from direction data only " +
875
+ "for open-ended exploration. Expand the chosen direction after the user " +
876
+ "picks. Screens from an earlier variant set are never " +
831
877
  "deleted automatically: if you are knowingly replacing your own earlier " +
832
878
  "set that the user never picked from or discussed, pass its set id in " +
833
879
  "deleteSupersededSetIds; otherwise leave old sets in place.",
@@ -872,6 +918,24 @@ export default defineAction({
872
918
  run: async ({ designId, prompt, variants, deleteSupersededSetIds }) => {
873
919
  await assertAccess("design", designId, "editor");
874
920
 
921
+ const omittedContent = variants.filter(
922
+ (variant) => !variant.content?.trim(),
923
+ );
924
+ if (
925
+ omittedContent.length > 0 &&
926
+ (hasSpecifiedDesignPrompt(prompt) ||
927
+ (await hasLinkedDesignSystem(designId)))
928
+ ) {
929
+ throw new Error(
930
+ "present-design-variants requires complete self-contained HTML for " +
931
+ "every variant when the design has a linked design system, or the " +
932
+ "prompt specifies a layout, reference, or product surface. The " +
933
+ "generic direction fallback ignores the brief and the system's " +
934
+ "tokens; use generate-design, or provide each variant's complete " +
935
+ "content.",
936
+ );
937
+ }
938
+
875
939
  // Before any mutation. These are model-authored screens created by raw
876
940
  // insert, so they need the same well-formedness gate as generate-design —
877
941
  // though not its document-shape rules, since a variant may be a sketch with
@@ -1089,7 +1153,32 @@ export default defineAction({
1089
1153
  editorView: "overview",
1090
1154
  path: `/design/${encodeURIComponent(designId)}?view=overview`,
1091
1155
  });
1156
+ // The pick opens a continuation turn that inherits nothing from this one,
1157
+ // and that turn is what expands the kept placeholder into the real screen.
1158
+ // Without this, the expansion is design-system-blind even though the user
1159
+ // linked one before generating.
1160
+ const [linkedDesign] = await db
1161
+ .select({ designSystemId: schema.designs.designSystemId })
1162
+ .from(schema.designs)
1163
+ .where(
1164
+ and(
1165
+ eq(schema.designs.id, designId),
1166
+ accessFilter(schema.designs, schema.designShares),
1167
+ ),
1168
+ );
1169
+ const variantPickContext = [
1170
+ prompt?.trim()
1171
+ ? `The user's original request for this design: "${prompt.trim()}". Expand the kept direction into that, not into a generic version of it.`
1172
+ : "",
1173
+ linkedDesign?.designSystemId
1174
+ ? `This design is linked to design system "${linkedDesign.designSystemId}". Call \`get-design-system\` for that id and apply its tokens, typography, and usage notes while expanding the kept screen — do not substitute a generic palette or font.`
1175
+ : "",
1176
+ ]
1177
+ .filter(Boolean)
1178
+ .join("\n");
1179
+
1092
1180
  await writeAppStateForCurrentTab("guided-questions", {
1181
+ ...(variantPickContext ? { submitContext: variantPickContext } : {}),
1093
1182
  title: prompt ?? "Pick a direction",
1094
1183
  description:
1095
1184
  "All options are on the board. Choose one to keep; I will delete the others, read only the kept screen, and turn that direction into the final requested screen.",
@@ -489,6 +489,203 @@ export function checkTokenDrift(
489
489
  return findings;
490
490
  }
491
491
 
492
+ // ---------------------------------------------------------------------------
493
+ // Design-system adherence check
494
+ // ---------------------------------------------------------------------------
495
+ //
496
+ // checkTokenDrift above only proves the screens agree with EACH OTHER. A design
497
+ // can be perfectly self-consistent and still ignore the brand it is linked to,
498
+ // which is the most-reported failure of generation: the user links their system,
499
+ // the agent writes its own palette, and nothing anywhere notices. This compares
500
+ // the saved HTML against the linked kit's own values so the drift is reportable
501
+ // instead of invisible.
502
+
503
+ /** The subset of a Brand Kit this check can verify against rendered HTML. */
504
+ export interface DesignSystemExpectation {
505
+ title: string;
506
+ fonts: string[];
507
+ /** Lowercase six-digit hex values, normalized from the kit's own notation. */
508
+ colors: string[];
509
+ /** Custom-property names the kit names, e.g. `--color-primary`. */
510
+ cssVars: string[];
511
+ /**
512
+ * The kit's stored `data` could not be parsed. Adherence is then UNKNOWN,
513
+ * which is not the same as satisfied — a silent empty expectation would
514
+ * report a corrupt design system as a clean audit.
515
+ */
516
+ unreadable?: boolean;
517
+ }
518
+
519
+ function normalizeHex(value: unknown): string | null {
520
+ if (typeof value !== "string") return null;
521
+ const match = value.trim().match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
522
+ if (!match) return null;
523
+ const hex = match[1].toLowerCase();
524
+ // Expand shorthand so 3- and 6-digit forms compare in one space.
525
+ return hex.length === 3
526
+ ? `#${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}`
527
+ : `#${hex}`;
528
+ }
529
+
530
+ function expandShortHexes(html: string): string {
531
+ return html.replace(
532
+ /#([0-9a-f])([0-9a-f])([0-9a-f])\b/gi,
533
+ (_m, r: string, g: string, b: string) =>
534
+ `#${r}${r}${g}${g}${b}${b}`.toLowerCase(),
535
+ );
536
+ }
537
+
538
+ /**
539
+ * Build the checkable expectation from a kit's stored `data` JSON. Returns null
540
+ * when the kit carries nothing verifiable — "we cannot check" must not render
541
+ * as "the design complies".
542
+ */
543
+ export function designSystemExpectation(
544
+ title: string,
545
+ data: string | null | undefined,
546
+ ): DesignSystemExpectation | null {
547
+ const unreadable: DesignSystemExpectation = {
548
+ title,
549
+ fonts: [],
550
+ colors: [],
551
+ cssVars: [],
552
+ unreadable: true,
553
+ };
554
+ let parsed: Record<string, unknown> | null = null;
555
+ try {
556
+ parsed = data ? (JSON.parse(data) as Record<string, unknown>) : null;
557
+ } catch {
558
+ return unreadable;
559
+ }
560
+ if (!parsed) return null;
561
+
562
+ const typography = (parsed.typography ?? {}) as Record<string, unknown>;
563
+ const fonts = [typography.headingFont, typography.bodyFont]
564
+ .filter((font): font is string => typeof font === "string" && !!font.trim())
565
+ .map((font) => font.trim());
566
+
567
+ const colorRoles = (parsed.colors ?? {}) as Record<string, unknown>;
568
+ const colors = Object.values(colorRoles)
569
+ .map(normalizeHex)
570
+ .filter((hex): hex is string => hex !== null);
571
+
572
+ const cssVars = Array.isArray(parsed.tokens)
573
+ ? parsed.tokens
574
+ .map((token) =>
575
+ token && typeof token === "object"
576
+ ? (token as { cssVar?: unknown }).cssVar
577
+ : null,
578
+ )
579
+ .filter(
580
+ (cssVar): cssVar is string =>
581
+ typeof cssVar === "string" && cssVar.startsWith("--"),
582
+ )
583
+ : [];
584
+
585
+ if (fonts.length === 0 && colors.length === 0 && cssVars.length === 0) {
586
+ return null;
587
+ }
588
+ return {
589
+ title,
590
+ fonts: Array.from(new Set(fonts)),
591
+ colors: Array.from(new Set(colors)),
592
+ cssVars: Array.from(new Set(cssVars)),
593
+ };
594
+ }
595
+
596
+ /**
597
+ * Flag a screen that uses none of its linked design system's fonts, or none of
598
+ * its colors. Deliberately a "none of them" test rather than a per-value one:
599
+ * a design legitimately uses a subset of a kit, but a design using zero of it
600
+ * is not following it at all. Pure so it can be unit tested without a DB.
601
+ */
602
+ export function checkDesignSystemAdherence(
603
+ html: string,
604
+ expectation: DesignSystemExpectation | null,
605
+ filename: string,
606
+ ): A11yFinding[] {
607
+ if (!expectation) return [];
608
+ if (expectation.unreadable) {
609
+ return [
610
+ {
611
+ id: `design-system-drift:${filename}:unreadable`,
612
+ severity: "warning",
613
+ category: "design-system-drift",
614
+ message: `"${expectation.title}" could not be read, so adherence was not checked.`,
615
+ detail:
616
+ "The linked design system's stored token data is not valid JSON. " +
617
+ "Adherence is unknown, not satisfied — re-import or re-index the " +
618
+ "system before treating this screen as on-brand.",
619
+ selector: ":root",
620
+ fixAvailable: false,
621
+ },
622
+ ];
623
+ }
624
+ const findings: A11yFinding[] = [];
625
+ const haystack = expandShortHexes(html.toLowerCase());
626
+
627
+ if (expectation.fonts.length > 0) {
628
+ const used = expectation.fonts.filter((font) =>
629
+ haystack.includes(font.toLowerCase()),
630
+ );
631
+ if (used.length === 0) {
632
+ findings.push({
633
+ id: `design-system-drift:${filename}:fonts`,
634
+ severity: "warning",
635
+ category: "design-system-drift",
636
+ message: `${filename} uses none of "${expectation.title}"'s fonts.`,
637
+ detail:
638
+ `The linked design system specifies ${expectation.fonts.join(", ")}, ` +
639
+ `but ${filename} references none of them. Load and apply the system's ` +
640
+ "typography, or tell the user you are deviating and why.",
641
+ selector: ":root",
642
+ fixAvailable: false,
643
+ });
644
+ }
645
+ }
646
+
647
+ if (expectation.colors.length > 0) {
648
+ const used = expectation.colors.filter((hex) => haystack.includes(hex));
649
+ if (used.length === 0) {
650
+ findings.push({
651
+ id: `design-system-drift:${filename}:colors`,
652
+ severity: "warning",
653
+ category: "design-system-drift",
654
+ message: `${filename} uses none of "${expectation.title}"'s colors.`,
655
+ detail:
656
+ `The linked design system defines ${expectation.colors.slice(0, 6).join(", ")}` +
657
+ `${expectation.colors.length > 6 ? ", …" : ""}, but ${filename} uses none of ` +
658
+ "them. Put the system's values in the `:root` token block instead of an " +
659
+ "invented palette.",
660
+ selector: ":root",
661
+ fixAvailable: false,
662
+ });
663
+ }
664
+ }
665
+
666
+ if (expectation.cssVars.length > 0) {
667
+ const used = expectation.cssVars.filter((cssVar) =>
668
+ haystack.includes(cssVar.toLowerCase()),
669
+ );
670
+ if (used.length === 0) {
671
+ findings.push({
672
+ id: `design-system-drift:${filename}:css-vars`,
673
+ severity: "info",
674
+ category: "design-system-drift",
675
+ message: `${filename} uses none of "${expectation.title}"'s token names.`,
676
+ detail:
677
+ `The system names ${expectation.cssVars.length} custom properties (e.g. ` +
678
+ `${expectation.cssVars.slice(0, 4).join(", ")}). Reusing those exact names ` +
679
+ "keeps the prototype swappable with the real codebase.",
680
+ selector: ":root",
681
+ fixAvailable: false,
682
+ });
683
+ }
684
+ }
685
+
686
+ return findings;
687
+ }
688
+
492
689
  // ---------------------------------------------------------------------------
493
690
  // Live-content helper (matches the pattern in other actions)
494
691
  // ---------------------------------------------------------------------------
@@ -519,6 +716,11 @@ export default defineAction({
519
716
  "form labels, focus-visibility gaps, reduced-motion coverage, and — for " +
520
717
  "multi-screen designs — token drift (the audited screen's :root custom " +
521
718
  "properties diverging from index.html's). " +
719
+ "When the design is linked to a design system, also reports " +
720
+ "design-system drift: the screen using none of the linked system's fonts, " +
721
+ "colors, or token names. Treat that finding as a generation bug to fix, " +
722
+ "not a style opinion — reload the system with `get-design-system` and " +
723
+ "apply its values rather than explaining the deviation. " +
522
724
  "Returns A11yFinding[] that can be shown in the Review panel or persisted " +
523
725
  "via create-design-review-snapshot. No writes are performed.",
524
726
  schema: z.object({
@@ -612,6 +814,32 @@ export default defineAction({
612
814
  finding.id.includes(`token-drift:${file.filename}:`),
613
815
  );
614
816
 
817
+ // Adherence to the LINKED system, which token drift cannot see.
818
+ const [linkedSystem] = await db
819
+ .select({
820
+ title: schema.designSystems.title,
821
+ data: schema.designSystems.data,
822
+ })
823
+ .from(schema.designs)
824
+ .innerJoin(
825
+ schema.designSystems,
826
+ eq(schema.designs.designSystemId, schema.designSystems.id),
827
+ )
828
+ .where(
829
+ and(
830
+ eq(schema.designs.id, designId),
831
+ accessFilter(schema.designSystems, schema.designSystemShares),
832
+ ),
833
+ )
834
+ .limit(1);
835
+ const designSystemFindings = linkedSystem
836
+ ? checkDesignSystemAdherence(
837
+ html,
838
+ designSystemExpectation(linkedSystem.title, linkedSystem.data),
839
+ file.filename,
840
+ )
841
+ : [];
842
+
615
843
  // Run all audit checks over the static HTML.
616
844
  const findings: A11yFinding[] = [
617
845
  ...checkMissingAlt(html),
@@ -621,6 +849,7 @@ export default defineAction({
621
849
  ...checkFocusVisibility(html),
622
850
  ...checkContrastHint(html),
623
851
  ...tokenDriftFindings,
852
+ ...designSystemFindings,
624
853
  ];
625
854
 
626
855
  // Summarise by severity for the agent context.
@@ -0,0 +1,64 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import {
3
+ parseBuilderDesignSystemProxyReference,
4
+ startBuilderDesignSystemIndex,
5
+ } from "@agent-native/core/server";
6
+ import { getRequestUserEmail } from "@agent-native/core/server/request-context";
7
+ import { assertAccess, resolveAccess } from "@agent-native/core/sharing";
8
+ import { z } from "zod";
9
+
10
+ import "../server/db/index.js";
11
+ import { upsertBuilderProxyDesignSystem } from "../server/lib/builder-design-system-proxy.js";
12
+
13
+ export default defineAction({
14
+ description:
15
+ "Re-index a Builder-backed GitHub design system using its persisted repository, ref, and file/folder scope. Requires editor access.",
16
+ schema: z.object({
17
+ id: z.string().min(1).describe("Local design system id"),
18
+ }),
19
+ run: async ({ id }) => {
20
+ await assertAccess("design-system", id, "editor");
21
+ const access = await resolveAccess("design-system", id);
22
+ if (!access) throw new Error("Design system not found");
23
+
24
+ const reference = parseBuilderDesignSystemProxyReference(
25
+ access.resource.data,
26
+ );
27
+ if (!reference?.githubSources?.length) {
28
+ throw new Error(
29
+ "This design system has no persisted GitHub source scope to sync.",
30
+ );
31
+ }
32
+ if (reference.sourceKind === "mixed") {
33
+ throw new Error(
34
+ "This design system combines GitHub with non-replayable sources. Re-import all sources before syncing it.",
35
+ );
36
+ }
37
+
38
+ const result = await startBuilderDesignSystemIndex({
39
+ projectName: access.resource.title,
40
+ description: access.resource.description ?? undefined,
41
+ githubRepos: reference.githubSources,
42
+ });
43
+ const ownerEmail = getRequestUserEmail();
44
+ if (!ownerEmail) throw new Error("no authenticated user");
45
+
46
+ const proxy = await upsertBuilderProxyDesignSystem({
47
+ result,
48
+ ownerEmail: access.resource.ownerEmail,
49
+ orgId: access.resource.orgId,
50
+ projectName: access.resource.title,
51
+ description: access.resource.description ?? undefined,
52
+ sourceKind: reference.sourceKind,
53
+ githubSources: reference.githubSources,
54
+ localDesignSystemId: id,
55
+ });
56
+
57
+ return {
58
+ ...result,
59
+ ...proxy,
60
+ synced: true,
61
+ githubSourceCount: reference.githubSources.length,
62
+ };
63
+ },
64
+ });
@@ -1,5 +1,6 @@
1
1
  import { useActionMutation } from "@agent-native/core/client/hooks";
2
2
  import { useFormatters, useT } from "@agent-native/core/client/i18n";
3
+ import { docsUrl } from "@agent-native/core/shared";
3
4
  import { parseFigmaFileKey } from "@shared/figma-url";
4
5
  import {
5
6
  IconBrandFigma,
@@ -696,7 +697,7 @@ export function DesignImportPanel(p: DesignImportPanelProps) {
696
697
  <p className="text-[11px] leading-snug text-muted-foreground">
697
698
  {t("designEditor.import.visualEditGuidance")}{" "}
698
699
  <a
699
- href="/docs/template-design"
700
+ href={docsUrl("template-design")}
700
701
  target="_blank"
701
702
  rel="noreferrer"
702
703
  className="font-medium text-foreground underline-offset-2 hover:underline"
@@ -1,4 +1,5 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
+ import { docsUrl } from "@agent-native/core/shared";
2
3
  import { VisualTweakControl } from "@agent-native/toolkit/design-tweaks";
3
4
  import type { TweakDefinition } from "@shared/api";
4
5
  import {
@@ -80,7 +81,7 @@ export function TweaksPanelContent({
80
81
  >
81
82
  {t("designEditor.tweaksHelp")}{" "}
82
83
  <a
83
- href="/docs/template-design#tweaks"
84
+ href={docsUrl("template-design")}
84
85
  target="_blank"
85
86
  rel="noreferrer"
86
87
  className="font-medium text-foreground/80 underline-offset-2 hover:text-foreground hover:underline"
@@ -169,7 +169,7 @@ const MAC_KEY_LABELS: Record<string, string> = {
169
169
  escape: "⎋",
170
170
  };
171
171
 
172
- /** Human-readable keybinding, e.g. "⇧⌘P" on macOS or "Ctrl+Shift+P". */
172
+ /** Human-readable keybinding, e.g. "⇧⌘P" on macOS or "Ctrl Shift P". */
173
173
  export function formatKeybinding(binding: string): string {
174
174
  const parsed = parseKeybinding(binding);
175
175
  const keyLabel =
@@ -191,7 +191,7 @@ export function formatKeybinding(binding: string): string {
191
191
  if (parsed.alt) parts.push("Alt");
192
192
  if (parsed.shift) parts.push("Shift");
193
193
  parts.push(keyLabel.length === 1 ? keyLabel.toUpperCase() : keyLabel);
194
- return parts.join("+");
194
+ return parts.join(" ");
195
195
  }
196
196
 
197
197
  /**