@agent-native/core 0.153.10 → 0.154.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 (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/adhoc/sql-dashboard/ViewSqlPopover.tsx +1 -1
  7. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +2 -0
  8. package/corpus/templates/analytics/server/lib/dashboards-store.ts +366 -3
  9. package/corpus/templates/analytics/server/lib/real-data-actions.ts +3 -1
  10. package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -2
  11. package/corpus/templates/calendar/app/components/calendar/EventDialog.tsx +1 -1
  12. package/corpus/templates/calendar/app/lib/utils.ts +1 -1
  13. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +32 -5
  14. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +15 -15
  15. package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
  16. package/corpus/templates/clips/app/lib/utils.ts +5 -6
  17. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +43 -8
  18. package/corpus/templates/clips/desktop/src/app.tsx +26 -12
  19. package/corpus/templates/clips/desktop/src/lib/recorder.ts +23 -9
  20. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +261 -11
  21. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +21 -3
  22. package/corpus/templates/clips/shared/mic-audio-cleanup.ts +350 -0
  23. package/corpus/templates/content/actions/resolve-content-landing.ts +162 -32
  24. package/corpus/templates/content/app/i18n-data.ts +10 -10
  25. package/corpus/templates/content/scripts/check-native-deps.mjs +181 -15
  26. package/corpus/templates/content/shared/content-landing.ts +5 -0
  27. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +79 -7
  28. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +14 -2
  29. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +2 -2
  30. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +35 -11
  31. package/corpus/templates/design/actions/get-design-system.ts +103 -3
  32. package/corpus/templates/design/actions/import-design-tokens.ts +37 -8
  33. package/corpus/templates/design/actions/import-github.ts +32 -138
  34. package/corpus/templates/design/actions/index-design-system-with-builder.ts +49 -16
  35. package/corpus/templates/design/actions/present-design-variants.ts +93 -4
  36. package/corpus/templates/design/actions/run-design-audit.ts +229 -0
  37. package/corpus/templates/design/actions/sync-design-system-with-builder.ts +64 -0
  38. package/corpus/templates/design/app/components/design/code-workbench/commands.ts +2 -2
  39. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +17 -4
  40. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +5 -1
  41. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +5 -1
  42. package/corpus/templates/design/app/hooks/use-question-flow.ts +81 -5
  43. package/corpus/templates/design/app/i18n/en-US.ts +1 -1
  44. package/corpus/templates/design/app/i18n-data.ts +32 -1
  45. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +10 -10
  46. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +83 -11
  47. package/corpus/templates/design/app/pages/DesignSystems.tsx +14 -0
  48. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +22 -0
  49. package/corpus/templates/design/changelog/2026-08-13-a-screen-that-would-render-behind-an-invisible-alpine-overla.md +6 -0
  50. package/corpus/templates/design/changelog/2026-08-13-attach-a-ui-screenshot-and-the-design-is-reproduced-not-rein.md +6 -0
  51. package/corpus/templates/design/changelog/2026-08-13-linking-a-design-system-means-you-never-get-generic-directio.md +6 -0
  52. package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-reports-when-a-screen-ignores-its-linke.md +6 -0
  53. package/corpus/templates/design/changelog/2026-08-13-the-design-system-preview-now-shows-how-many-named-tokens-we.md +6 -0
  54. package/corpus/templates/design/changelog/2026-08-13-your-design-system-prompt-and-reference-screenshots-now-reac.md +6 -0
  55. package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +18 -2
  56. package/corpus/templates/design/shared/design-review.ts +1 -0
  57. package/corpus/templates/design/shared/html-integrity.ts +171 -0
  58. package/corpus/templates/mail/app/lib/utils.ts +1 -1
  59. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +10 -6
  60. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +41 -3
  61. package/corpus/templates/slides/actions/patch-deck.ts +12 -3
  62. package/corpus/templates/slides/actions/sync-design-system-with-builder.ts +64 -0
  63. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
  64. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +193 -20
  65. package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +73 -1
  66. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +64 -34
  67. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +34 -41
  68. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +119 -91
  69. package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +16 -0
  70. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +15 -0
  71. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +158 -31
  72. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +30 -14
  73. package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +2 -2
  74. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +4 -1
  75. package/corpus/templates/slides/app/components/editor/rich-text-selection.ts +14 -0
  76. package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +24 -0
  77. package/corpus/templates/slides/app/context/DeckContext.tsx +19 -31
  78. package/corpus/templates/slides/app/global.css +24 -0
  79. package/corpus/templates/slides/app/hooks/use-navigation-state.ts +20 -17
  80. package/corpus/templates/slides/app/i18n/en-US.ts +19 -10
  81. package/corpus/templates/slides/app/lib/deck-filter.ts +47 -0
  82. package/corpus/templates/slides/app/lib/export-google-slides-client.ts +15 -4
  83. package/corpus/templates/slides/app/lib/export-pptx-client.ts +637 -1
  84. package/corpus/templates/slides/app/lib/image-download.ts +84 -0
  85. package/corpus/templates/slides/app/lib/utils.ts +1 -1
  86. package/corpus/templates/slides/app/pages/DeckEditor.tsx +9 -0
  87. package/corpus/templates/slides/app/pages/Index.tsx +25 -9
  88. package/corpus/templates/slides/changelog/2026-08-13-clicking-a-slide-thumbnail-now-focuses-it-so-the-slide-shortc.md +6 -0
  89. package/corpus/templates/slides/changelog/2026-08-13-duplicating-or-restoring-a-slide-now-keeps-its-transition-ani.md +6 -0
  90. package/corpus/templates/slides/changelog/2026-08-13-opening-presentation-mode-from-the-agent-now-starts-on-the-re.md +6 -0
  91. package/corpus/templates/slides/changelog/2026-08-13-slide-transitions-can-be-set-from-a-visible-control-in-the-sl.md +6 -0
  92. package/corpus/templates/slides/netlify.toml +1 -0
  93. package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +14 -2
  94. package/dist/a2a/audience.d.ts +2 -0
  95. package/dist/a2a/audience.js +23 -0
  96. package/dist/a2a/client.d.ts +4 -0
  97. package/dist/a2a/client.js +32 -14
  98. package/dist/a2a/server.d.ts +9 -4
  99. package/dist/a2a/server.js +52 -25
  100. package/dist/a2a/types.d.ts +4 -4
  101. package/dist/agent/production-agent.js +1 -1
  102. package/dist/brand-kit/types.d.ts +10 -2
  103. package/dist/cli/code.js +1 -1
  104. package/dist/cli/index.js +30 -1
  105. package/dist/cli/native-dependencies.d.ts +24 -0
  106. package/dist/cli/native-dependencies.js +264 -0
  107. package/dist/cli/plan-local.js +1 -1
  108. package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
  109. package/dist/cli/skills-content/design-visual-edit-skill.js +2 -2
  110. package/dist/cli/visualize-repo.js +1 -1
  111. package/dist/client/FeedbackButton.js +12 -12
  112. package/dist/client/agent-chat-adapter.js +3 -4
  113. package/dist/client/assistant-ui-recovery.d.ts +1 -1
  114. package/dist/client/assistant-ui-recovery.js +4 -0
  115. package/dist/client/chat/message-components.d.ts +11 -3
  116. package/dist/client/chat/message-components.js +31 -5
  117. package/dist/client/chat/tool-call-display.d.ts +2 -1
  118. package/dist/client/chat/tool-call-display.js +33 -15
  119. package/dist/client/db-admin/SqlEditor.js +1 -1
  120. package/dist/client/dev-overlay/DevOverlay.js +2 -2
  121. package/dist/client/extensions/portable-extension.js +4 -0
  122. package/dist/client/guided-questions.d.ts +8 -0
  123. package/dist/client/guided-questions.js +12 -3
  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/collab/awareness.d.ts +2 -2
  130. package/dist/collab/routes.d.ts +1 -1
  131. package/dist/collab/struct-routes.d.ts +1 -1
  132. package/dist/extensions/html-shell.js +5 -0
  133. package/dist/notifications/routes.d.ts +1 -1
  134. package/dist/observability/routes.d.ts +3 -3
  135. package/dist/provider-api/actions/custom-provider-registration.d.ts +2 -2
  136. package/dist/server/agent-capabilities.js +0 -0
  137. package/dist/server/agent-chat/action-filters-a2a.d.ts +14 -13
  138. package/dist/server/agent-chat/action-filters-a2a.js +59 -23
  139. package/dist/server/agent-chat/prompt-resources.js +17 -16
  140. package/dist/server/agent-chat-plugin.d.ts +9 -0
  141. package/dist/server/agent-chat-plugin.js +29 -7
  142. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  143. package/dist/server/builder-design-systems.d.ts +33 -1
  144. package/dist/server/builder-design-systems.js +293 -11
  145. package/dist/server/design-token-utils.d.ts +11 -0
  146. package/dist/server/design-token-utils.js +68 -16
  147. package/dist/server/index.d.ts +1 -1
  148. package/dist/server/index.js +1 -1
  149. package/package.json +2 -2
@@ -1,60 +1,18 @@
1
1
  import { defineAction } from "@agent-native/core";
2
- import { resolveSecret } from "@agent-native/core/server";
2
+ import { collectBuilderDesignSystemGitHubFiles } from "@agent-native/core/server";
3
3
  import {
4
- validateUrl,
5
- parseOwnerRepo,
6
- fetchGitHubJson,
7
- fetchGitHubJsonResult,
8
- fetchGitHubRaw,
9
4
  parseTailwindConfig,
10
5
  parseCss,
11
6
  detectStylingFramework,
12
- MAX_FILES,
13
- MAX_FILE_SIZE,
14
- ROOT_PATTERNS,
15
- SECONDARY_PATHS,
16
7
  COLOR_VAR_PATTERN,
17
8
  } from "@agent-native/core/server/design-token-utils";
18
9
  import { z } from "zod";
19
10
 
20
- function githubAccessError(
21
- owner: string,
22
- repo: string,
23
- status: number,
24
- hasToken: boolean,
25
- message?: string,
26
- ): Error {
27
- const repoLabel = `${owner}/${repo}`;
28
- const suffix = message ? ` GitHub said: ${message}` : "";
29
-
30
- if (!hasToken && (status === 401 || status === 403 || status === 404)) {
31
- return new Error(
32
- `Could not access ${repoLabel}. Public repositories work without setup; private repositories require a fine-grained GitHub personal access token saved as GITHUB_TOKEN in Settings > Secrets. Limit the token to this repository and grant Repository permissions > Contents: Read-only.${suffix}`,
33
- );
34
- }
35
-
36
- if (hasToken && (status === 401 || status === 403 || status === 404)) {
37
- return new Error(
38
- `Could not access ${repoLabel} with the saved GITHUB_TOKEN. Check that the token is not expired, the repository is selected for the token, organization SSO/approval is complete, and Repository permissions > Contents is set to Read-only.${suffix}`,
39
- );
40
- }
41
-
42
- if (status === 429) {
43
- return new Error(
44
- `GitHub rate-limited requests for ${repoLabel}. Save a GITHUB_TOKEN in Settings > Secrets or try again after the rate limit resets.${suffix}`,
45
- );
46
- }
47
-
48
- return new Error(
49
- `Could not list repository contents for ${repoLabel} (GitHub returned ${status || "an error"}).${suffix}`,
50
- );
51
- }
52
-
53
11
  export default defineAction({
54
12
  description:
55
13
  "Import design tokens from a GitHub repository. " +
56
14
  "Reads Tailwind configs, CSS files, theme/token files, and package.json " +
57
- "to extract colors, fonts, spacing, and CSS custom properties. " +
15
+ "to extract colors, fonts, spacing, and CSS custom properties. Supports a pinned ref and file/folder scope. " +
58
16
  "Private repositories require a saved GITHUB_TOKEN secret; never ask users to paste a token into chat.",
59
17
  schema: z.object({
60
18
  repoUrl: z
@@ -62,103 +20,36 @@ export default defineAction({
62
20
  .describe(
63
21
  'GitHub repository URL (e.g. "https://github.com/org/repo" or "org/repo")',
64
22
  ),
23
+ ref: z
24
+ .string()
25
+ .trim()
26
+ .min(1)
27
+ .optional()
28
+ .describe("Optional branch, tag, or commit"),
29
+ include: z
30
+ .array(z.string().trim().min(1))
31
+ .optional()
32
+ .describe("Optional repository-relative files or folders to include"),
33
+ exclude: z
34
+ .array(z.string().trim().min(1))
35
+ .optional()
36
+ .describe("Optional repository-relative files or folders to exclude"),
65
37
  }),
66
38
  readOnly: true,
67
39
  http: { method: "GET" },
68
- run: async ({ repoUrl }) => {
69
- const { owner, repo } = parseOwnerRepo(repoUrl.trim());
70
- const githubToken = await resolveSecret("GITHUB_TOKEN");
71
- const githubOptions = { token: githubToken };
72
-
73
- validateUrl(`https://api.github.com/repos/${owner}/${repo}`);
74
-
75
- const rawFiles: Record<string, string> = {};
76
- let fetchedCount = 0;
77
-
78
- async function collectFile(path: string): Promise<void> {
79
- if (fetchedCount >= MAX_FILES) return;
80
- const content = await fetchGitHubRaw(owner, repo, path, githubOptions);
81
- if (content) {
82
- rawFiles[path] = content;
83
- fetchedCount++;
84
- }
85
- }
86
-
87
- // 1. List repository root
88
- const rootResult = await fetchGitHubJsonResult<
89
- Array<{
90
- name: string;
91
- type: string;
92
- size?: number;
93
- }>
94
- >(owner, repo, "", githubOptions);
95
- const rootListing = rootResult.data;
96
-
97
- if (!rootResult.ok || !Array.isArray(rootListing)) {
98
- throw githubAccessError(
99
- owner,
100
- repo,
101
- rootResult.status,
102
- !!githubToken,
103
- rootResult.message,
104
- );
105
- }
106
-
107
- // 2. Fetch root-level files matching patterns
108
- const rootFilePromises: Promise<void>[] = [];
109
- for (const entry of rootListing) {
110
- if (entry.type !== "file") continue;
111
- if (entry.size && entry.size > MAX_FILE_SIZE) continue;
112
- const matches = ROOT_PATTERNS.some((p) => p.test(entry.name));
113
- if (matches && fetchedCount < MAX_FILES) {
114
- rootFilePromises.push(collectFile(entry.name));
115
- }
116
- }
117
- await Promise.all(rootFilePromises);
118
-
119
- // 3. Fetch secondary paths (files and directories)
120
- const secondaryPromises: Promise<void>[] = [];
121
- for (const path of SECONDARY_PATHS) {
122
- if (fetchedCount >= MAX_FILES) break;
123
-
124
- if (/\.\w+$/.test(path)) {
125
- secondaryPromises.push(collectFile(path));
126
- } else {
127
- secondaryPromises.push(
128
- (async () => {
129
- const listing = (await fetchGitHubJson(
130
- owner,
131
- repo,
132
- path,
133
- githubOptions,
134
- )) as Array<{
135
- name: string;
136
- type: string;
137
- path: string;
138
- size?: number;
139
- }> | null;
140
- if (!listing || !Array.isArray(listing)) return;
141
- const innerPromises: Promise<void>[] = [];
142
- for (const entry of listing) {
143
- if (fetchedCount >= MAX_FILES) break;
144
- if (entry.type !== "file") continue;
145
- if (entry.size && entry.size > MAX_FILE_SIZE) continue;
146
- if (
147
- /\.(css|scss|less)$/.test(entry.name) ||
148
- /theme/i.test(entry.name) ||
149
- /tokens?/i.test(entry.name)
150
- ) {
151
- innerPromises.push(collectFile(entry.path));
152
- }
153
- }
154
- await Promise.all(innerPromises);
155
- })(),
156
- );
157
- }
158
- }
159
- await Promise.all(secondaryPromises);
40
+ run: async ({ repoUrl, ref, include, exclude }) => {
41
+ const collection = await collectBuilderDesignSystemGitHubFiles({
42
+ repoUrl: repoUrl.trim(),
43
+ ...(ref ? { ref } : {}),
44
+ ...(include ? { include } : {}),
45
+ ...(exclude ? { exclude } : {}),
46
+ });
47
+ const { source } = collection;
48
+ const rawFiles = Object.fromEntries(
49
+ collection.files.map(({ path, content }) => [path, content]),
50
+ );
160
51
 
161
- // 4. Parse collected files
52
+ // Parse collected files
162
53
  let colors: Record<string, unknown> = {};
163
54
  let fonts: string[] = [];
164
55
  let spacing: Record<string, string> = {};
@@ -210,7 +101,10 @@ export default defineAction({
210
101
 
211
102
  return {
212
103
  source: "github" as const,
213
- repoUrl: `https://github.com/${owner}/${repo}`,
104
+ repoUrl: source.repoUrl,
105
+ ...(source.ref ? { ref: source.ref } : {}),
106
+ ...(source.include?.length ? { include: source.include } : {}),
107
+ ...(source.exclude?.length ? { exclude: source.exclude } : {}),
214
108
  colors: Object.keys(colors).length > 0 ? colors : undefined,
215
109
  fonts: fonts.length > 0 ? fonts : undefined,
216
110
  spacing: Object.keys(spacing).length > 0 ? spacing : undefined,
@@ -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.",