@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
|
@@ -35,6 +35,53 @@ them:
|
|
|
35
35
|
|
|
36
36
|
Jumping straight to code is how you get slop. Let the phases (below) do the work.
|
|
37
37
|
|
|
38
|
+
## First decide the mode: reproduce or explore
|
|
39
|
+
|
|
40
|
+
The workflow above assumes an under-specified prompt. When the user has already
|
|
41
|
+
specified the design, that same workflow is the failure — exploration reads to
|
|
42
|
+
them as "the agent ignored everything I gave it".
|
|
43
|
+
|
|
44
|
+
Before Phase 1, check for **specification signals**:
|
|
45
|
+
|
|
46
|
+
- an attached screenshot, mockup, or photo of a UI,
|
|
47
|
+
- a linked design system / Brand Kit,
|
|
48
|
+
- a written outline naming concrete screens, sections, components, or copy,
|
|
49
|
+
- a named existing product/page to match, or pasted markup or tokens.
|
|
50
|
+
|
|
51
|
+
**Any one of these puts you in reproduce mode.** In reproduce mode:
|
|
52
|
+
|
|
53
|
+
- Do **not** call `present-design-variants`. Three divergent directions when the
|
|
54
|
+
user handed you one is not exploration, it is discarding their work. Produce
|
|
55
|
+
the one thing they specified.
|
|
56
|
+
- Do **not** call `show-design-questions` about anything the supplied material
|
|
57
|
+
already answers. Ask only about a genuine gap, and prefer stating an
|
|
58
|
+
assumption over asking.
|
|
59
|
+
- Every "Do" in the aesthetic quality bar below is **off** wherever it would
|
|
60
|
+
contradict the supplied material. Those rules break ties in the absence of a
|
|
61
|
+
spec; they never outvote one.
|
|
62
|
+
- Reproduce first, improve second. Match the structure, then raise quality
|
|
63
|
+
*within* it, and say what you changed and why.
|
|
64
|
+
|
|
65
|
+
Mixed input is normal: reproduce what was given, use the quality bar only for
|
|
66
|
+
the parts genuinely left open. When a supplied reference and the linked design
|
|
67
|
+
system disagree, follow the design system for tokens (color, type, spacing,
|
|
68
|
+
radius) and the reference for layout and composition, then say you did.
|
|
69
|
+
|
|
70
|
+
### A reference screenshot is a specification, not a mood board
|
|
71
|
+
|
|
72
|
+
When the user attaches an image of a UI, read it as a layout spec and
|
|
73
|
+
reproduce it: the same regions in the same positions, the same navigation
|
|
74
|
+
pattern, the same information hierarchy and density, the same component
|
|
75
|
+
grammar (tabs vs. pills, cards vs. rows, sidebar vs. topbar), the same
|
|
76
|
+
approximate proportions. Name what you see before you build, so the user can
|
|
77
|
+
correct a misread early. Deviate only where the image is genuinely unreadable
|
|
78
|
+
or an explicit instruction overrides it — and say which, rather than
|
|
79
|
+
silently substituting your own composition.
|
|
80
|
+
|
|
81
|
+
An attached image is only guaranteed to be visible on the turn it arrived.
|
|
82
|
+
If a screenshot was described earlier in the thread but you cannot see one now,
|
|
83
|
+
say so and ask for a re-attach — never quietly design from the text alone.
|
|
84
|
+
|
|
38
85
|
## Authority and direction record
|
|
39
86
|
|
|
40
87
|
Before generating, make a compact direction record: the job this surface must
|
|
@@ -69,13 +116,21 @@ change the design. Never smooth an evidence gap into confident copy.
|
|
|
69
116
|
|
|
70
117
|
## Aesthetic quality bar — beat distributional convergence
|
|
71
118
|
|
|
72
|
-
The rules in this section are fallback quality heuristics
|
|
73
|
-
override an active Agent-Native design system
|
|
119
|
+
The rules in this section are fallback quality heuristics for prompts that
|
|
120
|
+
leave the look open. They do not override an active Agent-Native design system,
|
|
121
|
+
a supplied reference, or an explicit brand constraint. **A banned item stops
|
|
122
|
+
being banned the moment the user's own material specifies it** — if their Brand
|
|
123
|
+
Kit's body font is Inter, the design ships Inter; if their screenshot is a
|
|
124
|
+
centered hero with three icon cards, the design ships that. Substituting a
|
|
125
|
+
"better" choice for a specified one is the single most-reported failure of this
|
|
126
|
+
app. When you follow a specified value that appears on a "don't" list below,
|
|
127
|
+
just follow it — no apology, no note, no compensating flourish elsewhere.
|
|
74
128
|
|
|
75
129
|
You sample toward the "on-distribution" center by default; refuse it. **Every
|
|
76
130
|
"don't" here carries a "do"** — a banned default plus where to go instead —
|
|
77
131
|
because banning Inter alone just makes you reach for Roboto next. Use a banned
|
|
78
|
-
item only if the user explicitly asks
|
|
132
|
+
item only if the user explicitly asks, or their design system or reference
|
|
133
|
+
already uses it.
|
|
79
134
|
|
|
80
135
|
- **Fonts.** Don't: Inter, Roboto, Arial, Open Sans, system-ui. Do: a distinctive
|
|
81
136
|
Google Font pairing matched to the chosen aesthetic (see the table) — editorial
|
|
@@ -97,7 +152,10 @@ item only if the user explicitly asks.
|
|
|
97
152
|
|
|
98
153
|
**Second-order convergence is real.** Even your "creative" picks converge (Space
|
|
99
154
|
Grotesk everywhere; teal accent + blinking dot + left accent bars). Vary
|
|
100
|
-
deliberately across generations so two designs never share a fingerprint
|
|
155
|
+
deliberately across generations so two designs never share a fingerprint —
|
|
156
|
+
but only across designs *you* had to invent. A token the user's own system
|
|
157
|
+
specifies is theirs, not your fingerprint: consistency with their brand is the
|
|
158
|
+
goal, and varying away from it to avoid repeating yourself is a bug.
|
|
101
159
|
|
|
102
160
|
**Principles to quote back while building:** color creates hierarchy, not
|
|
103
161
|
decoration · density over decoration · earn every animation · commit to one point
|
|
@@ -293,9 +351,15 @@ The WIDEST requested device is the base/primary frame; narrower devices become b
|
|
|
293
351
|
|
|
294
352
|
### Phase 2 — Generate side-by-side variations (2-5, three by default)
|
|
295
353
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
354
|
+
Skip this phase entirely in reproduce mode (see "First decide the mode" above) —
|
|
355
|
+
a screenshot, a written outline, or a linked design system means the direction
|
|
356
|
+
is already chosen, and offering three alternatives to it is the behavior users
|
|
357
|
+
report as "the agent ignored my design".
|
|
358
|
+
|
|
359
|
+
For new designs whose direction is genuinely open, default to **three**
|
|
360
|
+
variations (`present-design-variants` accepts 2-5; three is the sweet spot).
|
|
361
|
+
Call `present-design-variants` for both first-party and external MCP-host
|
|
362
|
+
flows. It saves each candidate as a normal
|
|
299
363
|
overview-board screen, then renders an inline chat choice with one button per
|
|
300
364
|
screen name.
|
|
301
365
|
|
|
@@ -389,6 +453,14 @@ structural issues, token drift), fix them directly with `edit-design`. **A
|
|
|
389
453
|
design with audit errors is not ready** — don't report a design as done while
|
|
390
454
|
`run-design-audit` still returns unresolved errors.
|
|
391
455
|
|
|
456
|
+
A `design-system-drift` finding means the screen uses none of the linked
|
|
457
|
+
system's fonts, colors, or token names. That is a generation bug, not a style
|
|
458
|
+
disagreement: the user linked that system precisely so the output would use it.
|
|
459
|
+
Call `get-design-system` for the linked id, apply the real values through
|
|
460
|
+
`edit-design`, and re-audit — never resolve it by explaining the deviation or
|
|
461
|
+
by unlinking the system. It is the one check that can catch a design that
|
|
462
|
+
looks fine and is still not the user's.
|
|
463
|
+
|
|
392
464
|
After the audit is clean, call `take-design-screenshot` on each changed screen
|
|
393
465
|
(default: 1280px desktop + 375px mobile). Fix everything its `diagnostics`
|
|
394
466
|
report flags — real computed contrast ratios, horizontal/container overflow,
|
|
@@ -201,10 +201,22 @@ it reports an explicit static SSRF-safe fallback when no browser is available.
|
|
|
201
201
|
### Source: GitHub Repository
|
|
202
202
|
|
|
203
203
|
```bash
|
|
204
|
-
pnpm action index-design-system-with-builder --
|
|
204
|
+
pnpm action index-design-system-with-builder --githubSources '[{"repoUrl":"https://github.com/acme/ui","ref":"main","include":["src/styles","design.md"]}]'
|
|
205
205
|
```
|
|
206
206
|
|
|
207
|
-
Starts Builder design-system
|
|
207
|
+
Starts one Builder design-system job with one or more GitHub sources. Each
|
|
208
|
+
source can pin a branch, tag, or commit and include repository-relative files or
|
|
209
|
+
folders. Unscoped public repositories stay native Builder sources so large
|
|
210
|
+
codebases are not truncated; private repos and scoped refs stay server-side and
|
|
211
|
+
use the saved `GITHUB_TOKEN` without exposing it to the browser or Builder.
|
|
212
|
+
Builder is the source of truth for the indexed brand kit, generated docs, and
|
|
213
|
+
usage guidance. If Builder is not connected, stop and ask the user to connect
|
|
214
|
+
Builder.
|
|
215
|
+
|
|
216
|
+
The legacy `githubRepoUrl` argument remains compatible for one unscoped repo.
|
|
217
|
+
For a saved GitHub-backed system, use `sync-design-system-with-builder --id
|
|
218
|
+
<localDesignSystemId>` to replay the stored repository/ref/scope after upstream
|
|
219
|
+
changes. Do not create a second local copy.
|
|
208
220
|
|
|
209
221
|
### Source: Local Code Files
|
|
210
222
|
|
|
@@ -51,8 +51,8 @@ Prefer the MCP App surface for a connected Design plugin, then the host's
|
|
|
51
51
|
browser/preview tool as the universal fallback. Keep the canvas beside chat
|
|
52
52
|
when the host supports rearrangeable panes.
|
|
53
53
|
|
|
54
|
-
Inside Design, use **Show/Hide UI** from the `Cmd
|
|
55
|
-
`Shift
|
|
54
|
+
Inside Design, use **Show/Hide UI** from the `Cmd K` menu or press Figma's
|
|
55
|
+
`Shift \` shortcut to toggle all editing chrome so only the canvas remains.
|
|
56
56
|
The same action is available from Design's empty-canvas context menu.
|
|
57
57
|
|
|
58
58
|
## Core Model
|
|
@@ -134,23 +134,46 @@ export const editorChromeBridgeScript: string = `"use strict";
|
|
|
134
134
|
const fromSouth = input.handle === "sw" || input.handle === "s" || input.handle === "se";
|
|
135
135
|
const resizesHorizontally = fromWest || fromEast;
|
|
136
136
|
const resizesVertically = fromNorth || fromSouth;
|
|
137
|
-
|
|
138
|
-
let
|
|
137
|
+
const resizeFromCenter = Boolean(input.altKey);
|
|
138
|
+
let width = start.width + (fromWest ? -input.delta.x : fromEast ? input.delta.x : 0) * (resizeFromCenter ? 2 : 1);
|
|
139
|
+
let height = start.height + (fromNorth ? -input.delta.y : fromSouth ? input.delta.y : 0) * (resizeFromCenter ? 2 : 1);
|
|
139
140
|
const minWidth = input.minWidth ?? DEFAULT_CANVAS_MIN_SIZE;
|
|
140
141
|
const minHeight = input.minHeight ?? DEFAULT_CANVAS_MIN_SIZE;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
height = start.
|
|
142
|
+
const preserveAspectRatio = Boolean(input.preserveAspectRatio) && start.width > 0 && start.height > 0;
|
|
143
|
+
const widthChange = Math.abs(width - start.width);
|
|
144
|
+
const heightChange = Math.abs(height - start.height);
|
|
145
|
+
const derivesHeight = preserveAspectRatio && resizesHorizontally && !resizesVertically;
|
|
146
|
+
const derivesWidth = preserveAspectRatio && resizesVertically && !resizesHorizontally;
|
|
147
|
+
if (derivesHeight) {
|
|
148
|
+
height = width / (start.width / start.height);
|
|
149
|
+
} else if (derivesWidth) {
|
|
150
|
+
width = height * (start.width / start.height);
|
|
151
|
+
} else if (preserveAspectRatio && resizesHorizontally && resizesVertically) {
|
|
152
|
+
if (widthChange >= heightChange) {
|
|
153
|
+
height = width / (start.width / start.height);
|
|
154
|
+
} else {
|
|
155
|
+
width = height * (start.width / start.height);
|
|
156
|
+
}
|
|
148
157
|
}
|
|
158
|
+
const widthBelowMinimum = width < minWidth;
|
|
159
|
+
const heightBelowMinimum = height < minHeight;
|
|
149
160
|
width = Math.max(minWidth, width);
|
|
150
161
|
height = Math.max(minHeight, height);
|
|
162
|
+
if (preserveAspectRatio) {
|
|
163
|
+
if (widthBelowMinimum && !heightBelowMinimum) {
|
|
164
|
+
height = Math.max(minHeight, width / (start.width / start.height));
|
|
165
|
+
} else if (heightBelowMinimum && !widthBelowMinimum) {
|
|
166
|
+
width = Math.max(minWidth, height * (start.width / start.height));
|
|
167
|
+
} else if (widthBelowMinimum && heightBelowMinimum) {
|
|
168
|
+
width = Math.max(minWidth, minHeight * (start.width / start.height));
|
|
169
|
+
height = width / (start.width / start.height);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const centerDerivedHeight = derivesHeight && !resizeFromCenter;
|
|
173
|
+
const centerDerivedWidth = derivesWidth && !resizeFromCenter;
|
|
151
174
|
return {
|
|
152
|
-
x: fromWest ? start.x + start.width - width : start.x,
|
|
153
|
-
y: fromNorth ? start.y + start.height - height : start.y,
|
|
175
|
+
x: resizeFromCenter || centerDerivedWidth ? start.x + (start.width - width) / 2 : fromWest ? start.x + start.width - width : start.x,
|
|
176
|
+
y: resizeFromCenter || centerDerivedHeight ? start.y + (start.height - height) / 2 : fromNorth ? start.y + start.height - height : start.y,
|
|
154
177
|
width,
|
|
155
178
|
height
|
|
156
179
|
};
|
|
@@ -311,6 +334,7 @@ export const editorChromeBridgeScript: string = `"use strict";
|
|
|
311
334
|
rect: core.resize(gestureStart.rect, {
|
|
312
335
|
handle: gestureStart.handle,
|
|
313
336
|
delta: convertedDelta,
|
|
337
|
+
altKey: pointer.altKey,
|
|
314
338
|
preserveAspectRatio: Boolean(pointer.shiftKey)
|
|
315
339
|
})
|
|
316
340
|
};
|
|
@@ -13,6 +13,15 @@ const MAX_JSON_CONTEXT_CHARS = 2_500;
|
|
|
13
13
|
const MAX_BUILDER_DOCS = 8;
|
|
14
14
|
const MAX_BUILDER_DOC_CHARS = 1_200;
|
|
15
15
|
const MAX_TOKEN_VALUES = 48;
|
|
16
|
+
// Per-section budgets for a locally-stored kit. These exist because one shared
|
|
17
|
+
// JSON dump starves whatever `JSON.stringify` happens to order last — which was
|
|
18
|
+
// `notes` and `customCSS`, the only carriers of component, shadow, and motion
|
|
19
|
+
// detail a rich import produces. Sectioning them means a 500-token kit loses
|
|
20
|
+
// tail tokens instead of losing its entire component vocabulary.
|
|
21
|
+
const MAX_NAMED_TOKENS = 220;
|
|
22
|
+
const MAX_CUSTOM_CSS_CHARS = 3_000;
|
|
23
|
+
const MAX_NOTES_CHARS = 3_000;
|
|
24
|
+
const MAX_CORE_TOKEN_JSON_CHARS = 2_000;
|
|
16
25
|
|
|
17
26
|
interface BuilderGenerationContext {
|
|
18
27
|
builderDesignSystemId: string;
|
|
@@ -50,6 +59,42 @@ function formatJson(value: unknown, maxChars = MAX_JSON_CONTEXT_CHARS): string {
|
|
|
50
59
|
return truncate(JSON.stringify(value, null, 2), maxChars);
|
|
51
60
|
}
|
|
52
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The source system's own token names. A kit that renders as seven color roles
|
|
64
|
+
* reads as "a few colors" no matter how much was imported — these names are the
|
|
65
|
+
* difference between the user's design system and a palette that resembles it.
|
|
66
|
+
*/
|
|
67
|
+
function formatNamedTokens(tokens: unknown): string[] {
|
|
68
|
+
if (!Array.isArray(tokens)) return [];
|
|
69
|
+
const usable = tokens.filter(
|
|
70
|
+
(token): token is Record<string, string> =>
|
|
71
|
+
Boolean(token) &&
|
|
72
|
+
typeof token === "object" &&
|
|
73
|
+
typeof (token as { name?: unknown }).name === "string" &&
|
|
74
|
+
typeof (token as { value?: unknown }).value === "string",
|
|
75
|
+
);
|
|
76
|
+
if (usable.length === 0) return [];
|
|
77
|
+
const shown = usable.slice(0, MAX_NAMED_TOKENS);
|
|
78
|
+
const lines = [
|
|
79
|
+
`Named tokens from the source system (${usable.length} total).`,
|
|
80
|
+
"These are the design team's own names. Use them verbatim as CSS custom " +
|
|
81
|
+
"properties instead of inventing generic ones:",
|
|
82
|
+
...shown.map((token) => {
|
|
83
|
+
const cssVar = token.cssVar ? ` (${token.cssVar})` : "";
|
|
84
|
+
const group = token.group ? ` [${token.group}]` : "";
|
|
85
|
+
const type = token.type ? ` {${token.type}}` : "";
|
|
86
|
+
return `- ${token.name}${cssVar}: ${token.value}${type}${group}`;
|
|
87
|
+
}),
|
|
88
|
+
];
|
|
89
|
+
if (shown.length < usable.length) {
|
|
90
|
+
lines.push(
|
|
91
|
+
`- [${usable.length - shown.length} further tokens stored but not listed here; ` +
|
|
92
|
+
"they exist in the kit — do not treat this list as the complete system]",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return lines;
|
|
96
|
+
}
|
|
97
|
+
|
|
53
98
|
function formatTokenValues(tokenValues: Record<string, string>): string[] {
|
|
54
99
|
const entries = Object.entries(tokenValues)
|
|
55
100
|
.filter(([, value]) => typeof value === "string" && value.trim())
|
|
@@ -135,10 +180,65 @@ function buildDesignSystemAgentContext({
|
|
|
135
180
|
);
|
|
136
181
|
}
|
|
137
182
|
}
|
|
138
|
-
}
|
|
139
|
-
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Builder hydration can succeed as a request and still carry nothing usable
|
|
186
|
+
// (a failed/incomplete index returns zero docs and zero token values). The
|
|
187
|
+
// stored local kit is then the only real content there is, so emit it rather
|
|
188
|
+
// than presenting placeholder proxy values as if they were the user's brand.
|
|
189
|
+
const builderUsable = Boolean(
|
|
190
|
+
builder &&
|
|
191
|
+
(builder.docCount > 0 || Object.keys(builder.tokenValues).length > 0),
|
|
192
|
+
);
|
|
193
|
+
if (builder && !builderUsable) {
|
|
194
|
+
lines.push(
|
|
195
|
+
"",
|
|
196
|
+
"- Builder returned no usable docs or token values for this system. " +
|
|
197
|
+
"Anything below comes from the locally stored kit; if that is also " +
|
|
198
|
+
"thin, say so and ask the user to finish indexing rather than " +
|
|
199
|
+
"filling the gap with a generic style.",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!builder || !builderUsable) {
|
|
204
|
+
const parsedData = parseJson(data) as Record<string, unknown> | null;
|
|
140
205
|
if (parsedData) {
|
|
141
|
-
|
|
206
|
+
const { tokens, customCSS, notes, ...coreTokens } = parsedData as {
|
|
207
|
+
tokens?: unknown;
|
|
208
|
+
customCSS?: unknown;
|
|
209
|
+
notes?: unknown;
|
|
210
|
+
} & Record<string, unknown>;
|
|
211
|
+
|
|
212
|
+
if (Object.keys(coreTokens).length > 0) {
|
|
213
|
+
lines.push(
|
|
214
|
+
"",
|
|
215
|
+
"Core design-system tokens:",
|
|
216
|
+
formatJson(coreTokens, MAX_CORE_TOKEN_JSON_CHARS),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const namedTokens = formatNamedTokens(tokens);
|
|
221
|
+
if (namedTokens.length > 0) {
|
|
222
|
+
lines.push("", ...namedTokens);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (typeof customCSS === "string" && customCSS.trim()) {
|
|
226
|
+
lines.push(
|
|
227
|
+
"",
|
|
228
|
+
"Design-system CSS to place in the generated document's `:root` " +
|
|
229
|
+
"(use these declarations directly; do not re-derive them):",
|
|
230
|
+
truncate(customCSS.trim(), MAX_CUSTOM_CSS_CHARS),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (typeof notes === "string" && notes.trim()) {
|
|
235
|
+
lines.push(
|
|
236
|
+
"",
|
|
237
|
+
"Design-system notes — component, elevation, motion, and usage " +
|
|
238
|
+
"detail captured at import. Follow these as rules, not trivia:",
|
|
239
|
+
truncate(notes.trim(), MAX_NOTES_CHARS),
|
|
240
|
+
);
|
|
241
|
+
}
|
|
142
242
|
}
|
|
143
243
|
}
|
|
144
244
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import type { BrandKitTokenType } from "@agent-native/core/brand-kit";
|
|
2
3
|
import { buildDeepLink } from "@agent-native/core/server";
|
|
3
4
|
import {
|
|
4
5
|
CODE_MAX_FILES,
|
|
@@ -28,13 +29,10 @@ import {
|
|
|
28
29
|
resolveTweaksToCssVars,
|
|
29
30
|
} from "../shared/resolve-tweaks.js";
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
| "radius"
|
|
36
|
-
| "shadow"
|
|
37
|
-
| "other";
|
|
32
|
+
// Aliased, not re-declared: this was a hand-copy of BrandKitTokenType that
|
|
33
|
+
// drifted, so tokens classified here could name a category the stored kit had
|
|
34
|
+
// no word for.
|
|
35
|
+
type ImportedTokenType = BrandKitTokenType;
|
|
38
36
|
|
|
39
37
|
interface ImportedDesignToken {
|
|
40
38
|
name: string;
|
|
@@ -116,6 +114,23 @@ function designDeepLink(designId: string): string {
|
|
|
116
114
|
});
|
|
117
115
|
}
|
|
118
116
|
|
|
117
|
+
/** `120ms`, `0.2s`, `cubic-bezier(...)`, `ease-in-out`, `steps(4, end)`. */
|
|
118
|
+
function isMotionValue(value: string): boolean {
|
|
119
|
+
const v = value.trim().toLowerCase();
|
|
120
|
+
return (
|
|
121
|
+
/^-?\d*\.?\d+m?s$/.test(v) ||
|
|
122
|
+
/^(cubic-bezier|steps|linear)\s*\(/.test(v) ||
|
|
123
|
+
/^(linear|ease|ease-in|ease-out|ease-in-out|step-start|step-end)$/.test(v)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** An offset/blur triple in px/rem/em with any color notation, or `none`. */
|
|
128
|
+
function isShadowValue(value: string): boolean {
|
|
129
|
+
const v = value.trim().toLowerCase();
|
|
130
|
+
if (v === "none") return true;
|
|
131
|
+
return /\d/.test(v) && /(px|rem|em)\b/.test(v);
|
|
132
|
+
}
|
|
133
|
+
|
|
119
134
|
function classifyVar(name: string, value: string): ImportedTokenType {
|
|
120
135
|
const n = name.toLowerCase();
|
|
121
136
|
if (isColorValue(value)) return "color";
|
|
@@ -124,7 +139,10 @@ function classifyVar(name: string, value: string): ImportedTokenType {
|
|
|
124
139
|
return "typography";
|
|
125
140
|
}
|
|
126
141
|
if (/spacing|gap|padding|margin|space/i.test(n)) return "spacing";
|
|
127
|
-
if (/shadow|blur|drop/i.test(n)) return "shadow";
|
|
142
|
+
if (/shadow|blur|drop|elevation/i.test(n)) return "shadow";
|
|
143
|
+
if (/duration|easing|ease|transition|animation|motion|delay/i.test(n)) {
|
|
144
|
+
return "motion";
|
|
145
|
+
}
|
|
128
146
|
if (
|
|
129
147
|
/color|bg|background|text|border|accent|primary|secondary|surface|muted|foreground|fill|stroke/i.test(
|
|
130
148
|
n,
|
|
@@ -272,6 +290,17 @@ function parseNamedLines(
|
|
|
272
290
|
cssVar = tokenVar("spacing", label);
|
|
273
291
|
} else if (/font|typeface|typography/.test(lower)) {
|
|
274
292
|
cssVar = tokenVar("font", label);
|
|
293
|
+
} else if (/shadow|elevation/.test(lower) && isShadowValue(value)) {
|
|
294
|
+
cssVar = tokenVar("shadow", label);
|
|
295
|
+
} else if (
|
|
296
|
+
/duration|easing|ease|transition|animation|motion|delay/.test(lower) &&
|
|
297
|
+
isMotionValue(value)
|
|
298
|
+
) {
|
|
299
|
+
// Motion is part of the brand. Dropping these here is why an imported
|
|
300
|
+
// system arrives with colors and type but nothing that moves. The
|
|
301
|
+
// value guard keeps prose out: this runs over free-text design.md
|
|
302
|
+
// lines, where "Release notes: v2" matches the label pattern.
|
|
303
|
+
cssVar = tokenVar("motion", label);
|
|
275
304
|
}
|
|
276
305
|
|
|
277
306
|
if (!cssVar) continue;
|
|
@@ -1,60 +1,18 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
-
import {
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
//
|
|
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:
|
|
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,
|