@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/AGENTS.md +20 -30
- package/corpus/templates/analytics/actions/search-dashboard-references.ts +58 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/analytics/app/lib/utils.ts +1 -1
- package/corpus/templates/analytics/app/pages/DataSources.tsx +2 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/ViewSqlPopover.tsx +1 -1
- package/corpus/templates/analytics/app/pages/monitoring/errors/IssueList.tsx +4 -2
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -2
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +2 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +366 -3
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +3 -1
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -2
- package/corpus/templates/calendar/app/components/calendar/EventDialog.tsx +1 -1
- package/corpus/templates/calendar/app/lib/utils.ts +1 -1
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +32 -5
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +15 -15
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
- package/corpus/templates/clips/app/lib/utils.ts +5 -6
- package/corpus/templates/clips/app/root.tsx +4 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -2
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +43 -8
- package/corpus/templates/clips/desktop/src/app.tsx +27 -13
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +23 -9
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +261 -11
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +21 -3
- package/corpus/templates/clips/shared/mic-audio-cleanup.ts +350 -0
- package/corpus/templates/content/app/i18n-data.ts +10 -10
- package/corpus/templates/content/scripts/check-native-deps.mjs +181 -15
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +79 -7
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +14 -2
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +2 -2
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +35 -11
- package/corpus/templates/design/actions/get-design-system.ts +103 -3
- package/corpus/templates/design/actions/import-design-tokens.ts +37 -8
- package/corpus/templates/design/actions/import-github.ts +32 -138
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +49 -16
- package/corpus/templates/design/actions/present-design-variants.ts +93 -4
- package/corpus/templates/design/actions/run-design-audit.ts +229 -0
- package/corpus/templates/design/actions/sync-design-system-with-builder.ts +64 -0
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +2 -1
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +2 -1
- package/corpus/templates/design/app/components/design/code-workbench/commands.ts +2 -2
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +17 -4
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +5 -1
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +5 -1
- package/corpus/templates/design/app/hooks/use-question-flow.ts +81 -5
- package/corpus/templates/design/app/i18n/en-US.ts +1 -1
- package/corpus/templates/design/app/i18n-data.ts +32 -1
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +10 -10
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +83 -11
- package/corpus/templates/design/app/pages/DesignSystems.tsx +14 -0
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +22 -0
- package/corpus/templates/design/changelog/2026-08-13-a-screen-that-would-render-behind-an-invisible-alpine-overla.md +6 -0
- package/corpus/templates/design/changelog/2026-08-13-attach-a-ui-screenshot-and-the-design-is-reproduced-not-rein.md +6 -0
- package/corpus/templates/design/changelog/2026-08-13-linking-a-design-system-means-you-never-get-generic-directio.md +6 -0
- package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-reports-when-a-screen-ignores-its-linke.md +6 -0
- package/corpus/templates/design/changelog/2026-08-13-the-design-system-preview-now-shows-how-many-named-tokens-we.md +6 -0
- package/corpus/templates/design/changelog/2026-08-13-your-design-system-prompt-and-reference-screenshots-now-reac.md +6 -0
- package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +18 -2
- package/corpus/templates/design/shared/design-review.ts +1 -0
- package/corpus/templates/design/shared/html-integrity.ts +171 -0
- package/corpus/templates/mail/app/lib/utils.ts +1 -1
- package/corpus/templates/plan/app/pages/PlansPage.tsx +5 -2
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +10 -6
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +41 -3
- package/corpus/templates/slides/actions/sync-design-system-with-builder.ts +64 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +193 -20
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +58 -33
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +28 -41
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +119 -91
- package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +16 -0
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +158 -31
- package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +30 -14
- package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +2 -2
- package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +4 -1
- package/corpus/templates/slides/app/components/editor/rich-text-selection.ts +14 -0
- package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +24 -0
- package/corpus/templates/slides/app/global.css +24 -0
- package/corpus/templates/slides/app/hooks/use-navigation-state.ts +5 -1
- package/corpus/templates/slides/app/i18n/en-US.ts +19 -10
- package/corpus/templates/slides/app/lib/deck-filter.ts +47 -0
- package/corpus/templates/slides/app/lib/export-google-slides-client.ts +15 -4
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +637 -1
- package/corpus/templates/slides/app/lib/image-download.ts +84 -0
- package/corpus/templates/slides/app/lib/utils.ts +1 -1
- package/corpus/templates/slides/app/pages/Index.tsx +25 -9
- package/corpus/templates/slides/netlify.toml +1 -0
- package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +14 -2
- package/dist/agent/production-agent.js +1 -1
- package/dist/brand-kit/types.d.ts +10 -2
- package/dist/cli/code.js +1 -1
- package/dist/cli/index.js +30 -1
- package/dist/cli/native-dependencies.d.ts +24 -0
- package/dist/cli/native-dependencies.js +264 -0
- package/dist/cli/plan-local.js +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
- package/dist/cli/skills-content/design-visual-edit-skill.js +2 -2
- package/dist/cli/skills-content/help.d.ts +1 -1
- package/dist/cli/skills-content/help.js +2 -1
- package/dist/cli/skills.js +2 -1
- package/dist/cli/visualize-repo.js +1 -1
- package/dist/client/FeedbackButton.js +12 -12
- package/dist/client/NewWorkspaceAppFlow.js +4 -1
- package/dist/client/agent-chat-adapter.js +3 -4
- package/dist/client/agent-page/AgentTabsPage.d.ts +2 -2
- package/dist/client/agent-page/AgentTabsPage.js +12 -9
- package/dist/client/assistant-ui-recovery.d.ts +1 -1
- package/dist/client/assistant-ui-recovery.js +4 -0
- package/dist/client/chat/message-components.d.ts +11 -3
- package/dist/client/chat/message-components.js +31 -5
- package/dist/client/chat/tool-call-display.d.ts +2 -1
- package/dist/client/chat/tool-call-display.js +33 -15
- package/dist/client/db-admin/SqlEditor.js +1 -1
- package/dist/client/dev-overlay/DevOverlay.js +2 -2
- package/dist/client/extensions/ExtensionsSidebarSection.js +2 -1
- package/dist/client/extensions/portable-extension.js +4 -0
- package/dist/client/guided-questions.d.ts +8 -0
- package/dist/client/guided-questions.js +12 -3
- package/dist/client/onboarding/FirstRunOnboarding.js +2 -1
- package/dist/client/org/TeamPage.js +14 -3
- package/dist/client/settings/SettingsPanel.js +26 -1
- package/dist/client/sharing/ShareButton.js +16 -24
- package/dist/client/sse-event-processor.js +35 -15
- package/dist/client/tool-display.d.ts +9 -0
- package/dist/client/tool-display.js +39 -0
- package/dist/client/vite-dev-recovery-script.js +1 -1
- package/dist/extensions/html-shell.js +5 -0
- package/dist/integrations/catalog.js +9 -8
- package/dist/observability/routes.d.ts +2 -2
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat/prompt-resources.js +17 -16
- package/dist/server/agent-chat-plugin.d.ts +9 -0
- package/dist/server/agent-chat-plugin.js +31 -9
- package/dist/server/builder-design-systems.d.ts +33 -1
- package/dist/server/builder-design-systems.js +293 -11
- package/dist/server/design-token-utils.d.ts +11 -0
- package/dist/server/design-token-utils.js +68 -16
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/onboarding-html.js +2 -1
- package/dist/shared/docs-url.d.ts +24 -0
- package/dist/shared/docs-url.js +39 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/templates/default/app/routes/_index.tsx +2 -1
- package/package.json +2 -2
- package/src/templates/default/app/routes/_index.tsx +2 -1
|
@@ -43,6 +43,8 @@ import { cn } from "@/lib/utils";
|
|
|
43
43
|
interface GitHubLink {
|
|
44
44
|
id: string;
|
|
45
45
|
url: string;
|
|
46
|
+
ref?: string;
|
|
47
|
+
include?: string[];
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
interface UploadedFile {
|
|
@@ -73,6 +75,12 @@ interface BuilderIndexInput {
|
|
|
73
75
|
projectName?: string;
|
|
74
76
|
description?: string;
|
|
75
77
|
githubRepoUrl?: string;
|
|
78
|
+
githubSources?: Array<{
|
|
79
|
+
repoUrl: string;
|
|
80
|
+
ref?: string;
|
|
81
|
+
include?: string[];
|
|
82
|
+
exclude?: string[];
|
|
83
|
+
}>;
|
|
76
84
|
connectedProjectId?: string;
|
|
77
85
|
codeFiles?: Array<{
|
|
78
86
|
filename: string;
|
|
@@ -93,6 +101,8 @@ export default function DesignSystemSetup() {
|
|
|
93
101
|
const [websiteUrl, setWebsiteUrl] = useState("");
|
|
94
102
|
const [websiteUrls, setWebsiteUrls] = useState<string[]>([]);
|
|
95
103
|
const [githubUrl, setGithubUrl] = useState("");
|
|
104
|
+
const [githubRef, setGithubRef] = useState("");
|
|
105
|
+
const [githubPaths, setGithubPaths] = useState("");
|
|
96
106
|
const [githubLinks, setGithubLinks] = useState<GitHubLink[]>([]);
|
|
97
107
|
const [codeFiles, setCodeFiles] = useState<UploadedFile[]>([]);
|
|
98
108
|
const [docFiles, setDocFiles] = useState<UploadedFile[]>([]);
|
|
@@ -308,10 +318,24 @@ export default function DesignSystemSetup() {
|
|
|
308
318
|
setValidationError(t("designSystemSetup.errors.githubUrl"));
|
|
309
319
|
return;
|
|
310
320
|
}
|
|
311
|
-
|
|
321
|
+
const include = githubPaths
|
|
322
|
+
.split(/[\n,]/)
|
|
323
|
+
.map((path) => path.trim().replace(/^\/+|\/+$/g, ""))
|
|
324
|
+
.filter(Boolean);
|
|
325
|
+
setGithubLinks((prev) => [
|
|
326
|
+
...prev,
|
|
327
|
+
{
|
|
328
|
+
id: crypto.randomUUID(),
|
|
329
|
+
url,
|
|
330
|
+
...(githubRef.trim() ? { ref: githubRef.trim() } : {}),
|
|
331
|
+
...(include.length > 0 ? { include: [...new Set(include)] } : {}),
|
|
332
|
+
},
|
|
333
|
+
]);
|
|
312
334
|
setGithubUrl("");
|
|
335
|
+
setGithubRef("");
|
|
336
|
+
setGithubPaths("");
|
|
313
337
|
setValidationError(null);
|
|
314
|
-
}, [githubUrl, t]);
|
|
338
|
+
}, [githubPaths, githubRef, githubUrl, t]);
|
|
315
339
|
|
|
316
340
|
const removeGithubLink = useCallback((id: string) => {
|
|
317
341
|
setGithubLinks((prev) => prev.filter((l) => l.id !== id));
|
|
@@ -441,12 +465,26 @@ export default function DesignSystemSetup() {
|
|
|
441
465
|
const normalizedWebsiteUrls = pendingWebsiteUrl
|
|
442
466
|
? [...websiteUrls, pendingWebsiteUrl]
|
|
443
467
|
: websiteUrls;
|
|
468
|
+
const pendingGithubInclude = githubPaths
|
|
469
|
+
.split(/[\n,]/)
|
|
470
|
+
.map((path) => path.trim().replace(/^\/+|\/+$/g, ""))
|
|
471
|
+
.filter(Boolean);
|
|
444
472
|
const normalizedGithubLinks = pendingGithubUrl
|
|
445
|
-
? [
|
|
473
|
+
? [
|
|
474
|
+
...githubLinks,
|
|
475
|
+
{
|
|
476
|
+
id: "pending",
|
|
477
|
+
url: pendingGithubUrl,
|
|
478
|
+
...(githubRef.trim() ? { ref: githubRef.trim() } : {}),
|
|
479
|
+
...(pendingGithubInclude.length > 0
|
|
480
|
+
? { include: [...new Set(pendingGithubInclude)] }
|
|
481
|
+
: {}),
|
|
482
|
+
},
|
|
483
|
+
]
|
|
446
484
|
: githubLinks;
|
|
447
485
|
|
|
448
|
-
const
|
|
449
|
-
normalizedGithubLinks.length
|
|
486
|
+
const isGithubOnlySource =
|
|
487
|
+
normalizedGithubLinks.length > 0 &&
|
|
450
488
|
normalizedWebsiteUrls.length === 0 &&
|
|
451
489
|
codeFiles.length === 0 &&
|
|
452
490
|
!builderIndexResult &&
|
|
@@ -455,10 +493,7 @@ export default function DesignSystemSetup() {
|
|
|
455
493
|
assets.length === 0 &&
|
|
456
494
|
!selectedProjectId;
|
|
457
495
|
|
|
458
|
-
if (
|
|
459
|
-
const githubRepoUrl = normalizedGithubLinks[0]?.url;
|
|
460
|
-
if (!githubRepoUrl) return;
|
|
461
|
-
|
|
496
|
+
if (isGithubOnlySource) {
|
|
462
497
|
setValidationError(null);
|
|
463
498
|
try {
|
|
464
499
|
await indexSystemMutation.mutateAsync({
|
|
@@ -467,7 +502,11 @@ export default function DesignSystemSetup() {
|
|
|
467
502
|
[notes.trim(), customInstructions.trim()]
|
|
468
503
|
.filter(Boolean)
|
|
469
504
|
.join("\n\n") || undefined,
|
|
470
|
-
|
|
505
|
+
githubSources: normalizedGithubLinks.map((link) => ({
|
|
506
|
+
repoUrl: link.url,
|
|
507
|
+
...(link.ref ? { ref: link.ref } : {}),
|
|
508
|
+
...(link.include?.length ? { include: link.include } : {}),
|
|
509
|
+
})),
|
|
471
510
|
});
|
|
472
511
|
toast.success(t("designSystemSetup.githubIndexStarted"));
|
|
473
512
|
navigate("/design-systems");
|
|
@@ -507,7 +546,15 @@ export default function DesignSystemSetup() {
|
|
|
507
546
|
|
|
508
547
|
if (normalizedGithubLinks.length > 0) {
|
|
509
548
|
parts.push(
|
|
510
|
-
`\n## Connect Code: GitHub Repositories\
|
|
549
|
+
`\n## Connect Code: GitHub Repositories\nMake one call to \`index-design-system-with-builder\` with \`githubSources\` set to this JSON array:\n\n\`\`\`json\n${JSON.stringify(
|
|
550
|
+
normalizedGithubLinks.map((link) => ({
|
|
551
|
+
repoUrl: link.url,
|
|
552
|
+
...(link.ref ? { ref: link.ref } : {}),
|
|
553
|
+
...(link.include?.length ? { include: link.include } : {}),
|
|
554
|
+
})),
|
|
555
|
+
null,
|
|
556
|
+
2,
|
|
557
|
+
)}\n\`\`\`\n\nBuilder is the source of truth for repo/code design-system indexing. The action creates one local selectable proxy design system for Design flows. If Builder is not connected, stop and tell me to connect Builder (free tier available) from Settings instead of asking me to paste repository credentials into chat.`,
|
|
511
558
|
);
|
|
512
559
|
}
|
|
513
560
|
|
|
@@ -629,6 +676,8 @@ export default function DesignSystemSetup() {
|
|
|
629
676
|
websiteUrl,
|
|
630
677
|
websiteUrls,
|
|
631
678
|
githubUrl,
|
|
679
|
+
githubRef,
|
|
680
|
+
githubPaths,
|
|
632
681
|
githubLinks,
|
|
633
682
|
codeFiles,
|
|
634
683
|
builderIndexResult,
|
|
@@ -935,6 +984,22 @@ export default function DesignSystemSetup() {
|
|
|
935
984
|
{t("designSystemSetup.add")}
|
|
936
985
|
</Button>
|
|
937
986
|
</div>
|
|
987
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
988
|
+
<Input
|
|
989
|
+
value={githubRef}
|
|
990
|
+
onChange={(e) => setGithubRef(e.target.value)}
|
|
991
|
+
placeholder={t("designSystemSetup.githubRef")}
|
|
992
|
+
aria-label={t("designSystemSetup.githubRef")}
|
|
993
|
+
className="bg-accent/50 border-border"
|
|
994
|
+
/>
|
|
995
|
+
<Input
|
|
996
|
+
value={githubPaths}
|
|
997
|
+
onChange={(e) => setGithubPaths(e.target.value)}
|
|
998
|
+
placeholder={t("designSystemSetup.githubPaths")}
|
|
999
|
+
aria-label={t("designSystemSetup.githubPaths")}
|
|
1000
|
+
className="bg-accent/50 border-border"
|
|
1001
|
+
/>
|
|
1002
|
+
</div>
|
|
938
1003
|
<p className="mt-2 text-xs text-muted-foreground/80">
|
|
939
1004
|
{t("designSystemSetup.privateRepoPrefix")}{" "}
|
|
940
1005
|
<a
|
|
@@ -954,6 +1019,13 @@ export default function DesignSystemSetup() {
|
|
|
954
1019
|
>
|
|
955
1020
|
<IconCheck className="w-3.5 h-3.5 text-green-400/60 shrink-0" />
|
|
956
1021
|
<span className="truncate flex-1">{link.url}</span>
|
|
1022
|
+
{link.ref || link.include?.length ? (
|
|
1023
|
+
<span className="shrink-0 text-[10px] text-muted-foreground/70">
|
|
1024
|
+
{[link.ref, link.include?.join(", ")]
|
|
1025
|
+
.filter(Boolean)
|
|
1026
|
+
.join(" · ")}
|
|
1027
|
+
</span>
|
|
1028
|
+
) : null}
|
|
957
1029
|
<button
|
|
958
1030
|
onClick={() => removeGithubLink(link.id)}
|
|
959
1031
|
className="text-muted-foreground/70 hover:text-muted-foreground shrink-0 cursor-pointer"
|
|
@@ -109,6 +109,8 @@ interface DesignSystemData {
|
|
|
109
109
|
logos?: Array<{ url?: string; name?: string; variant?: string }>;
|
|
110
110
|
defaults?: Record<string, unknown>;
|
|
111
111
|
notes?: unknown;
|
|
112
|
+
/** The source system's own named vocabulary; absent on kits predating it. */
|
|
113
|
+
tokens?: unknown;
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
export default function DesignSystems() {
|
|
@@ -1105,10 +1107,22 @@ function getDetailTokens(
|
|
|
1105
1107
|
const borders = data?.borders ?? {};
|
|
1106
1108
|
const defaults = data?.defaults ?? {};
|
|
1107
1109
|
const logos = data?.logos ?? [];
|
|
1110
|
+
const namedTokenCount = Array.isArray(data?.tokens) ? data.tokens.length : 0;
|
|
1108
1111
|
return [
|
|
1109
1112
|
...objectPreviewItems(t("designSystems.tokenPreview.spacing"), spacing),
|
|
1110
1113
|
...objectPreviewItems(t("designSystems.tokenPreview.borders"), borders),
|
|
1111
1114
|
...objectPreviewItems(t("designSystems.tokenPreview.defaults"), defaults),
|
|
1115
|
+
// The seven color roles are a summary of an import, not its extent. Without
|
|
1116
|
+
// this the panel renders a 200-token system identically to a 7-token one,
|
|
1117
|
+
// which reads as "it only captured a few colors".
|
|
1118
|
+
namedTokenCount > 0
|
|
1119
|
+
? {
|
|
1120
|
+
label: t("designSystems.tokenPreview.namedTokens"),
|
|
1121
|
+
value: t("designSystems.tokenPreview.savedCount", {
|
|
1122
|
+
count: namedTokenCount,
|
|
1123
|
+
}),
|
|
1124
|
+
}
|
|
1125
|
+
: null,
|
|
1112
1126
|
logos.length > 0
|
|
1113
1127
|
? {
|
|
1114
1128
|
label: t("designSystems.tokenPreview.logos"),
|
|
@@ -121,10 +121,12 @@ export async function loadDesignSystemGenerationContext(
|
|
|
121
121
|
export function designIntakeQuestionDirectives(
|
|
122
122
|
designId: string,
|
|
123
123
|
designSystemId?: string | null,
|
|
124
|
+
referenceImageCount = 0,
|
|
124
125
|
): string[] {
|
|
125
126
|
return [
|
|
126
127
|
`This is a new UI-started design for design id "${designId}". The design shell already exists - DO NOT call create-design.`,
|
|
127
128
|
...designSystemGenerationDirectives(designSystemId),
|
|
129
|
+
...referenceImageDirectives(referenceImageCount),
|
|
128
130
|
"First, call `show-design-questions` with 4-6 tailored questions and then stop. Do NOT call generate-design or present-design-variants until the user submits or skips the questions.",
|
|
129
131
|
"Make the questions feel like Claude Design intake: form factor, aesthetic direction, important features/content, special interactions/polish, and whether to explore variations. Omit or rephrase anything the user's prompt already answered.",
|
|
130
132
|
"Use concise option chips with `allowOther: true`; include a practical `Decide for me` option where useful. Use `multiSelect: true` for feature/interactions questions.",
|
|
@@ -159,13 +161,33 @@ export function designVariantGenerationDirectives(
|
|
|
159
161
|
];
|
|
160
162
|
}
|
|
161
163
|
|
|
164
|
+
/**
|
|
165
|
+
* An attached UI screenshot is the specification. Reproducing it is the whole
|
|
166
|
+
* job, so these directives ship in the same prompt as the image — the skill
|
|
167
|
+
* body is not in the system prompt and loses every conflict against directives
|
|
168
|
+
* that are.
|
|
169
|
+
*/
|
|
170
|
+
export function referenceImageDirectives(
|
|
171
|
+
referenceImageCount: number,
|
|
172
|
+
): string[] {
|
|
173
|
+
if (referenceImageCount < 1) return [];
|
|
174
|
+
return [
|
|
175
|
+
`The user attached ${referenceImageCount} reference image(s) to this message. Treat any image showing a UI as a layout specification to reproduce, not as loose inspiration.`,
|
|
176
|
+
"Match its regions and their positions, navigation pattern, information hierarchy, density, component grammar (tabs vs pills, cards vs rows, sidebar vs topbar), and approximate proportions. Briefly name what you see before building so a misread can be corrected early.",
|
|
177
|
+
"Do NOT substitute your own composition, palette, or font for something the image or the linked design system already specifies — including choices a generic quality heuristic would discourage. Deviate only where the image is genuinely unreadable, and say so when you do.",
|
|
178
|
+
"Do NOT call `show-design-questions` or `present-design-variants` about anything the image already answers; the direction is chosen. Generate the one design it specifies.",
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
|
|
162
182
|
export function designGenerationDirectives(
|
|
163
183
|
designId: string,
|
|
164
184
|
designSystemId?: string | null,
|
|
185
|
+
referenceImageCount = 0,
|
|
165
186
|
): string[] {
|
|
166
187
|
return [
|
|
167
188
|
`Use the \`generate-design --designId="${designId}"\` action with exactly one complete, renderable \`index.html\` file first. The design already exists - DO NOT call create-design.`,
|
|
168
189
|
...designSystemGenerationDirectives(designSystemId),
|
|
190
|
+
...referenceImageDirectives(referenceImageCount),
|
|
169
191
|
'If the user asked to explore variations, call `present-design-variants` with 2-5 concise directions. Prefer label, description, accentColor, and feature bullets; omit large content HTML when needed because the action can render compact representative screens. Wait for their chat pick, delete each unchosen variant screen at most once, call `get-design-snapshot` exactly once with `fileId` for the kept screen, then call `edit-design` exactly once on that same `fileId` in a bounded pass. Use `mode: "replace-file"` when expanding the representative placeholder into a complete but compact product UI in the chosen direction. Prioritize the primary workflow and render secondary details as visible controls, states, or affordances if the feature list is too large for one reliable edit. Do not repeat delete/snapshot cycles. Do not call `generate-design` after a variant pick. Stop after the first successful `edit-design` save. Otherwise generate one polished first direction.',
|
|
170
192
|
'Responsive behavior is mandatory for every web design: use a mobile-first layout, include a viewport meta tag, stack or collapse desktop columns at narrow widths, and never rely on a fixed-width desktop shell. Default to a desktop primary artboard. For a Desktop or Both/responsive intake answer, pass `primaryViewport: "desktop"` and `canvasFrames` with width 1440 and height 1024; pass `primaryViewport: "mobile"` only when the user explicitly chooses a mobile-primary artboard.',
|
|
171
193
|
"Keep the first pass bounded enough to finish quickly: one self-contained Alpine.js + Tailwind CDN HTML document, polished but concise. Add 3-6 tweaks only when they naturally fit the design.",
|
|
@@ -2,6 +2,8 @@ import {
|
|
|
2
2
|
createBuilderDesignSystemProxyFields,
|
|
3
3
|
localBuilderDesignSystemId,
|
|
4
4
|
type BuilderDesignSystemIndexResult,
|
|
5
|
+
type BuilderDesignSystemGitHubSource,
|
|
6
|
+
type BuilderDesignSystemSourceKind,
|
|
5
7
|
} from "@agent-native/core/server";
|
|
6
8
|
import { and, eq, isNull } from "drizzle-orm";
|
|
7
9
|
import { nanoid } from "nanoid";
|
|
@@ -14,12 +16,18 @@ export async function upsertBuilderProxyDesignSystem({
|
|
|
14
16
|
orgId,
|
|
15
17
|
projectName,
|
|
16
18
|
description,
|
|
19
|
+
sourceKind,
|
|
20
|
+
githubSources,
|
|
21
|
+
localDesignSystemId: requestedLocalDesignSystemId,
|
|
17
22
|
}: {
|
|
18
23
|
result: BuilderDesignSystemIndexResult;
|
|
19
24
|
ownerEmail: string;
|
|
20
25
|
orgId?: string | null;
|
|
21
26
|
projectName?: string;
|
|
22
27
|
description?: string;
|
|
28
|
+
sourceKind?: BuilderDesignSystemSourceKind;
|
|
29
|
+
githubSources?: BuilderDesignSystemGitHubSource[];
|
|
30
|
+
localDesignSystemId?: string;
|
|
23
31
|
}) {
|
|
24
32
|
const db = getDb();
|
|
25
33
|
const now = new Date().toISOString();
|
|
@@ -31,6 +39,9 @@ export async function upsertBuilderProxyDesignSystem({
|
|
|
31
39
|
projectName,
|
|
32
40
|
description,
|
|
33
41
|
surface: "design",
|
|
42
|
+
sourceKind,
|
|
43
|
+
githubSources,
|
|
44
|
+
syncedAt: githubSources?.length ? now : undefined,
|
|
34
45
|
});
|
|
35
46
|
const [existing] = await db
|
|
36
47
|
.select({
|
|
@@ -39,7 +50,12 @@ export async function upsertBuilderProxyDesignSystem({
|
|
|
39
50
|
orgId: schema.designSystems.orgId,
|
|
40
51
|
})
|
|
41
52
|
.from(schema.designSystems)
|
|
42
|
-
.where(
|
|
53
|
+
.where(
|
|
54
|
+
eq(
|
|
55
|
+
schema.designSystems.id,
|
|
56
|
+
requestedLocalDesignSystemId ?? baseLocalDesignSystemId,
|
|
57
|
+
),
|
|
58
|
+
)
|
|
43
59
|
.limit(1);
|
|
44
60
|
const existingBelongsToScope =
|
|
45
61
|
existing?.ownerEmail === ownerEmail &&
|
|
@@ -47,7 +63,7 @@ export async function upsertBuilderProxyDesignSystem({
|
|
|
47
63
|
const localDesignSystemId =
|
|
48
64
|
existing && !existingBelongsToScope
|
|
49
65
|
? `${baseLocalDesignSystemId}-${nanoid(8)}`
|
|
50
|
-
: baseLocalDesignSystemId;
|
|
66
|
+
: (requestedLocalDesignSystemId ?? baseLocalDesignSystemId);
|
|
51
67
|
if (existingBelongsToScope) {
|
|
52
68
|
await db
|
|
53
69
|
.update(schema.designSystems)
|
|
@@ -32,6 +32,8 @@ export type DesignHtmlIntegrityIssue =
|
|
|
32
32
|
| "close-tag-orphaned"
|
|
33
33
|
| "content-truncated"
|
|
34
34
|
| "runtime-missing"
|
|
35
|
+
| "runtime-alpine-missing"
|
|
36
|
+
| "runtime-cloak-missing"
|
|
35
37
|
| "url-backed-screen-replaced";
|
|
36
38
|
|
|
37
39
|
/**
|
|
@@ -86,6 +88,10 @@ const DOCUMENT_SHAPE_MESSAGES: Partial<
|
|
|
86
88
|
"an editor-managed <style>/<script> block appears more than once; there must be exactly one of each",
|
|
87
89
|
"url-backed-screen-replaced":
|
|
88
90
|
"this screen's content is its live route URL, and the write would replace it with document markup — that permanently unbinds the screen from the running app. Edit the app's own source instead",
|
|
91
|
+
"runtime-cloak-missing":
|
|
92
|
+
"this document uses x-cloak without the CSS rule that keeps Alpine-controlled content hidden before Alpine starts",
|
|
93
|
+
"runtime-alpine-missing":
|
|
94
|
+
"this document uses x-cloak but does not load Alpine.js, so the hidden state can never be removed",
|
|
89
95
|
};
|
|
90
96
|
|
|
91
97
|
export function describeDesignHtmlIntegrityIssue(
|
|
@@ -141,6 +147,19 @@ export function describeDesignHtmlIntegrityIssue(
|
|
|
141
147
|
`<script src="…@tailwindcss/browser@4"> or a <style type="text/tailwindcss">). ` +
|
|
142
148
|
`Utility classes will not apply and the design renders unstyled.`
|
|
143
149
|
);
|
|
150
|
+
case "runtime-alpine-missing":
|
|
151
|
+
return (
|
|
152
|
+
`the document uses x-cloak on <${detail.tag ?? "element"}> at ${at}, ` +
|
|
153
|
+
`but no script source contains Alpine.js. The element will remain ` +
|
|
154
|
+
`hidden forever; load Alpine.js before relying on x-cloak.`
|
|
155
|
+
);
|
|
156
|
+
case "runtime-cloak-missing":
|
|
157
|
+
return (
|
|
158
|
+
`the document uses x-cloak on <${detail.tag ?? "element"}> at ${at}, ` +
|
|
159
|
+
`but no style block defines [x-cloak] { display: none !important; }. ` +
|
|
160
|
+
`Alpine-controlled overlays can flash or cover the entire screen before ` +
|
|
161
|
+
`Alpine starts. Add that exact hiding rule to the document.`
|
|
162
|
+
);
|
|
144
163
|
default:
|
|
145
164
|
return `the document structure is invalid (${detail.issue}) at ${at}.`;
|
|
146
165
|
}
|
|
@@ -1077,6 +1096,85 @@ function collectManagedMarkerIssue(
|
|
|
1077
1096
|
return null;
|
|
1078
1097
|
}
|
|
1079
1098
|
|
|
1099
|
+
/**
|
|
1100
|
+
* The selector may sit anywhere in a comma-separated list, and any of the three
|
|
1101
|
+
* hidings below is a correct pre-Alpine hide. Matching only
|
|
1102
|
+
* `[x-cloak] { display: none }` rejects documents that render perfectly.
|
|
1103
|
+
*/
|
|
1104
|
+
const X_CLOAK_RULE =
|
|
1105
|
+
/\[x-cloak\][^{}]*\{[^}]*(?:display\s*:\s*none|visibility\s*:\s*hidden|@apply[^;}]*\bhidden\b)/i;
|
|
1106
|
+
const ALPINE_RUNTIME = /\balpinejs\b/i;
|
|
1107
|
+
const INLINE_PRE_HIDE = /(?:display\s*:\s*none|visibility\s*:\s*hidden)/i;
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* `x-cloak` is an Alpine convention, not a runtime feature: it only works
|
|
1111
|
+
* when the authored document also supplies the CSS rule that hides the node
|
|
1112
|
+
* before Alpine initializes. A missing rule is especially dangerous for
|
|
1113
|
+
* fixed overlays, which can make a correct screen look completely replaced.
|
|
1114
|
+
*/
|
|
1115
|
+
function collectInteractiveRuntimeIssues(
|
|
1116
|
+
parsed: ParsedDocument,
|
|
1117
|
+
locate: Locator,
|
|
1118
|
+
): DesignHtmlIntegrityIssueDetail[] {
|
|
1119
|
+
const cloaked = parsed.elements.find((element) =>
|
|
1120
|
+
element.attrs.some(
|
|
1121
|
+
(attribute) => attribute.name.toLowerCase() === "x-cloak",
|
|
1122
|
+
),
|
|
1123
|
+
);
|
|
1124
|
+
if (!cloaked) return [];
|
|
1125
|
+
|
|
1126
|
+
const hasAlpineRuntime = parsed.elements.some(
|
|
1127
|
+
(element) =>
|
|
1128
|
+
element.tagName === "script" &&
|
|
1129
|
+
ALPINE_RUNTIME.test(attributeOf(element, "src") ?? ""),
|
|
1130
|
+
);
|
|
1131
|
+
|
|
1132
|
+
// A linked stylesheet is content this parser cannot read, so its rules are
|
|
1133
|
+
// unknown — not missing. Reporting "no rule defined" here would be the
|
|
1134
|
+
// coercion this whole module exists to prevent.
|
|
1135
|
+
const hasUnreadableStylesheet = parsed.elements.some(
|
|
1136
|
+
(element) =>
|
|
1137
|
+
element.tagName === "link" &&
|
|
1138
|
+
/\bstylesheet\b/i.test(attributeOf(element, "rel") ?? "") &&
|
|
1139
|
+
(attributeOf(element, "href") ?? "").trim().length > 0,
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
const hasCloakRule =
|
|
1143
|
+
hasUnreadableStylesheet ||
|
|
1144
|
+
INLINE_PRE_HIDE.test(attributeOf(cloaked, "style") ?? "") ||
|
|
1145
|
+
parsed.elements.some((element) => {
|
|
1146
|
+
if (element.tagName !== "style") return false;
|
|
1147
|
+
const css = childrenOf(element)
|
|
1148
|
+
.filter((node) => node.nodeName === "#text")
|
|
1149
|
+
.map((node) => (node as DefaultTreeAdapterTypes.TextNode).value)
|
|
1150
|
+
.join("\n");
|
|
1151
|
+
return X_CLOAK_RULE.test(css);
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
const attribute = cloaked.attrs.find(
|
|
1155
|
+
(entry) => entry.name.toLowerCase() === "x-cloak",
|
|
1156
|
+
);
|
|
1157
|
+
const offset =
|
|
1158
|
+
locationOf(cloaked)?.attrs?.[attribute?.name ?? ""]?.startOffset ??
|
|
1159
|
+
locationOf(cloaked)?.startOffset ??
|
|
1160
|
+
0;
|
|
1161
|
+
const detail = {
|
|
1162
|
+
line: locate(offset).line,
|
|
1163
|
+
column: locate(offset).column,
|
|
1164
|
+
excerpt: locate(offset).excerpt,
|
|
1165
|
+
tag: cloaked.tagName,
|
|
1166
|
+
attribute: "x-cloak",
|
|
1167
|
+
};
|
|
1168
|
+
return [
|
|
1169
|
+
...(hasAlpineRuntime
|
|
1170
|
+
? []
|
|
1171
|
+
: [{ issue: "runtime-alpine-missing" as const, ...detail }]),
|
|
1172
|
+
...(hasCloakRule
|
|
1173
|
+
? []
|
|
1174
|
+
: [{ issue: "runtime-cloak-missing" as const, ...detail }]),
|
|
1175
|
+
];
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1080
1178
|
/**
|
|
1081
1179
|
* Reported, never enforced: legitimate fragments and token-only screens carry no
|
|
1082
1180
|
* runtime of their own, so blocking here would reject valid work.
|
|
@@ -1142,10 +1240,45 @@ export function inspectDesignHtmlDocumentIntegrity(
|
|
|
1142
1240
|
const marker = collectManagedMarkerIssue(parsed);
|
|
1143
1241
|
if (marker) return { valid: false, issue: marker };
|
|
1144
1242
|
|
|
1243
|
+
const interactiveRuntime = collectInteractiveRuntimeIssues(parsed, locate);
|
|
1244
|
+
if (interactiveRuntime.length > 0) {
|
|
1245
|
+
return {
|
|
1246
|
+
valid: false,
|
|
1247
|
+
issue: interactiveRuntime[0]!.issue,
|
|
1248
|
+
detail: interactiveRuntime,
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1145
1252
|
const advisory = collectAdvisoryIssues(parsed, locate);
|
|
1146
1253
|
return advisory.length > 0 ? { valid: true, advisory } : { valid: true };
|
|
1147
1254
|
}
|
|
1148
1255
|
|
|
1256
|
+
const RUNTIME_ISSUES: ReadonlySet<DesignHtmlIntegrityIssue> = new Set([
|
|
1257
|
+
"runtime-alpine-missing",
|
|
1258
|
+
"runtime-cloak-missing",
|
|
1259
|
+
]);
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* The runtime issues are the one class an edit may carry forward. A screen
|
|
1263
|
+
* saved before these checks existed still renders, and the person dragging on
|
|
1264
|
+
* the canvas did not author its markup and cannot add a CSS rule — refusing
|
|
1265
|
+
* their unrelated edit strands the screen. Block what the edit introduces;
|
|
1266
|
+
* the create path still refuses to author the defect in the first place.
|
|
1267
|
+
*/
|
|
1268
|
+
function introducedRuntimeIssues(
|
|
1269
|
+
next: DesignHtmlIntegrityIssueDetail[],
|
|
1270
|
+
previousContent: string,
|
|
1271
|
+
): DesignHtmlIntegrityIssueDetail[] {
|
|
1272
|
+
if (next.length === 0 || !previousContent.trim()) return next;
|
|
1273
|
+
const inherited = new Set(
|
|
1274
|
+
collectInteractiveRuntimeIssues(
|
|
1275
|
+
parseDocument(previousContent),
|
|
1276
|
+
createLocator(previousContent),
|
|
1277
|
+
).map((entry) => entry.issue),
|
|
1278
|
+
);
|
|
1279
|
+
return next.filter((entry) => !inherited.has(entry.issue));
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1149
1282
|
/**
|
|
1150
1283
|
* Fail closed only for document edits. Standalone Alpine fragments remain
|
|
1151
1284
|
* supported. Existing malformed documents can still be repaired: a candidate
|
|
@@ -1186,6 +1319,19 @@ export function assertDesignHtmlEditIntegrity(args: {
|
|
|
1186
1319
|
detail: structural,
|
|
1187
1320
|
});
|
|
1188
1321
|
}
|
|
1322
|
+
const interactiveRuntime = introducedRuntimeIssues(
|
|
1323
|
+
collectInteractiveRuntimeIssues(
|
|
1324
|
+
parseDocument(args.nextContent),
|
|
1325
|
+
createLocator(args.nextContent),
|
|
1326
|
+
),
|
|
1327
|
+
args.previousContent,
|
|
1328
|
+
);
|
|
1329
|
+
if (interactiveRuntime.length > 0) {
|
|
1330
|
+
throw new DesignHtmlIntegrityError(interactiveRuntime[0]!.issue, {
|
|
1331
|
+
filename: args.filename,
|
|
1332
|
+
detail: interactiveRuntime,
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1189
1335
|
return;
|
|
1190
1336
|
}
|
|
1191
1337
|
if (previousIsDocument && !nextIsDocument) {
|
|
@@ -1195,6 +1341,17 @@ export function assertDesignHtmlEditIntegrity(args: {
|
|
|
1195
1341
|
}
|
|
1196
1342
|
const result = inspectDesignHtmlDocumentIntegrity(args.nextContent);
|
|
1197
1343
|
if (!result.valid) {
|
|
1344
|
+
if (result.issue && RUNTIME_ISSUES.has(result.issue)) {
|
|
1345
|
+
const introduced = introducedRuntimeIssues(
|
|
1346
|
+
result.detail ?? [],
|
|
1347
|
+
args.previousContent,
|
|
1348
|
+
);
|
|
1349
|
+
if (introduced.length === 0) return;
|
|
1350
|
+
throw new DesignHtmlIntegrityError(introduced[0]!.issue, {
|
|
1351
|
+
filename: args.filename,
|
|
1352
|
+
detail: introduced,
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1198
1355
|
throw new DesignHtmlIntegrityError(result.issue ?? "document-root", {
|
|
1199
1356
|
filename: args.filename,
|
|
1200
1357
|
detail: result.detail,
|
|
@@ -1227,6 +1384,20 @@ export function assertDesignHtmlWellFormed(args: {
|
|
|
1227
1384
|
detail: structural,
|
|
1228
1385
|
});
|
|
1229
1386
|
}
|
|
1387
|
+
// Fragments are checked too, unlike the document-shape rules above: a screen
|
|
1388
|
+
// is rendered as its own `srcdoc`, so nothing injects Alpine or a `[x-cloak]`
|
|
1389
|
+
// rule around it. A sketch that omits them is broken exactly as a full
|
|
1390
|
+
// document would be.
|
|
1391
|
+
const interactiveRuntime = collectInteractiveRuntimeIssues(
|
|
1392
|
+
parseDocument(args.content),
|
|
1393
|
+
createLocator(args.content),
|
|
1394
|
+
);
|
|
1395
|
+
if (interactiveRuntime.length > 0) {
|
|
1396
|
+
throw new DesignHtmlIntegrityError(interactiveRuntime[0]!.issue, {
|
|
1397
|
+
filename: args.filename,
|
|
1398
|
+
detail: interactiveRuntime,
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1230
1401
|
}
|
|
1231
1402
|
|
|
1232
1403
|
export function assertDesignHtmlCreateIntegrity(args: {
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
ErrorReportActions,
|
|
24
24
|
type ErrorReportDebugItem,
|
|
25
25
|
} from "@agent-native/core/client/ui";
|
|
26
|
+
import { docsUrl } from "@agent-native/core/shared";
|
|
26
27
|
import {
|
|
27
28
|
useSetHeaderActions,
|
|
28
29
|
useSetPageTitle,
|
|
@@ -388,8 +389,10 @@ const ENABLE_PLAN_STATUS_FEATURE = false;
|
|
|
388
389
|
const GITHUB_LIGHT_CANVAS_BACKGROUND = "#ffffff";
|
|
389
390
|
const GITHUB_DARK_CANVAS_BACKGROUND = "#0d1117";
|
|
390
391
|
const LOCAL_PLAN_OWNER_EMAIL = "local@agent-native.local";
|
|
391
|
-
const PLAN_DOCS_URL = "
|
|
392
|
-
const LOCAL_FILES_DOCS_URL =
|
|
392
|
+
const PLAN_DOCS_URL = docsUrl("template-plan");
|
|
393
|
+
const LOCAL_FILES_DOCS_URL = docsUrl("template-plan-local-and-desktop", {
|
|
394
|
+
hash: "local-files",
|
|
395
|
+
});
|
|
393
396
|
const AUTO_DEV_COMMENT_EMAILS = new Set(["dev@local.test", "dev@local"]);
|
|
394
397
|
const CURRENT_USER_FALLBACK_NAME = "You";
|
|
395
398
|
const CURRENT_USER_FALLBACK_INITIALS = "You";
|
|
@@ -72,12 +72,16 @@ Builder indexing flow.
|
|
|
72
72
|
|
|
73
73
|
### Source: connected code, GitHub, or `design.md`
|
|
74
74
|
|
|
75
|
-
For any other reusable source
|
|
76
|
-
code/design files, or an optional `design.md`
|
|
77
|
-
indexing through `index-design-system-with-builder`. Pass
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
and
|
|
75
|
+
For any other reusable source - connected code, a GitHub repo, local
|
|
76
|
+
code/design files, or an optional `design.md` - use Builder-backed DSI
|
|
77
|
+
indexing through `index-design-system-with-builder`. Pass GitHub sources as one
|
|
78
|
+
`githubSources` array; each source may pin a branch/tag/commit and include
|
|
79
|
+
repository-relative files or folders. Pass readable `design.md` content as
|
|
80
|
+
`designMd`, and use the returned local design system id in the rest of the
|
|
81
|
+
Slides flow. Call `get-design-system` before generation so Builder docs and
|
|
82
|
+
tokens are hydrated when available. For a saved GitHub-backed system, call
|
|
83
|
+
`sync-design-system-with-builder --id <localDesignSystemId>` to replay its
|
|
84
|
+
persisted source scope after upstream changes.
|
|
81
85
|
|
|
82
86
|
Never create a duplicate local design system from raw Figma or code sources.
|
|
83
87
|
Builder owns the indexed brand kit; a second local copy drifts from it and
|