@agent-native/core 0.139.0 → 0.140.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/design/.agents/skills/design-systems/SKILL.md +44 -1
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +265 -1
- package/corpus/templates/design/actions/create-design-system.ts +9 -10
- package/corpus/templates/design/actions/get-design-system.ts +53 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +5 -71
- package/corpus/templates/design/actions/index-design-tokens.ts +61 -80
- package/corpus/templates/design/actions/update-design-system.ts +7 -10
- package/corpus/templates/design/app/components/design/EditPanel.tsx +45 -2
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +12 -0
- package/corpus/templates/design/app/components/design/edit-panel/design-system-swatches.ts +215 -0
- package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +10 -6
- package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +19 -3
- package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +36 -10
- package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +15 -0
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +19 -1
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +6 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +53 -10
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +142 -4
- package/corpus/templates/design/app/components/design/types.ts +6 -0
- package/corpus/templates/design/app/i18n-data.ts +20 -0
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +1 -1
- package/corpus/templates/design/app/pages/DesignSystems.tsx +78 -1
- package/corpus/templates/design/app/pages/design-editor/html-layer-positioning.ts +6 -1
- package/corpus/templates/design/changelog/2026-08-04-design-system-tokens-show-by-name-in-the-editor.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +17 -0
- package/corpus/templates/design/server/lib/design-system-tokens.ts +35 -0
- package/corpus/templates/design/shared/authored-color-styles.ts +401 -0
- package/corpus/templates/design/shared/resolve-tweaks.ts +10 -18
- package/dist/brand-kit/index.d.ts +1 -0
- package/dist/brand-kit/index.js +1 -0
- package/dist/brand-kit/tokens.d.ts +92 -0
- package/dist/brand-kit/tokens.js +280 -0
- package/dist/brand-kit/types.d.ts +25 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
- package/dist/provider-api/actions/provider-api.d.ts +14 -14
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read the colour each property was authored with, walking the cascade. A
|
|
3
|
+
* generated design styles through classes, so `el.style` is empty and computed
|
|
4
|
+
* has already flattened `var()`. Dependency-free: bundled into the bridge.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const AUTHORED_COLOR_PROPERTIES = [
|
|
8
|
+
"color",
|
|
9
|
+
"backgroundColor",
|
|
10
|
+
"borderColor",
|
|
11
|
+
"outlineColor",
|
|
12
|
+
// Not colours themselves, but the only place a shadow's colour keeps its name.
|
|
13
|
+
"boxShadow",
|
|
14
|
+
"textShadow",
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A shorthand holding `var()` cannot be expanded, so CSSOM serialises its
|
|
19
|
+
* longhands as "" — indistinguishable from unset.
|
|
20
|
+
*/
|
|
21
|
+
const SHORTHAND_FALLBACK: Record<string, string> = {
|
|
22
|
+
backgroundColor: "background",
|
|
23
|
+
borderColor: "border",
|
|
24
|
+
outlineColor: "outline",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Colours these inherit from an ancestor when no rule targets the element. */
|
|
28
|
+
const INHERITED_PROPERTIES = ["color"];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Expanding a shorthand emits these for the longhands it resets, so treating
|
|
32
|
+
* them as authored would print "initial" in the fill row.
|
|
33
|
+
*/
|
|
34
|
+
const RESET_KEYWORDS = new Set([
|
|
35
|
+
"initial",
|
|
36
|
+
"inherit",
|
|
37
|
+
"unset",
|
|
38
|
+
"revert",
|
|
39
|
+
"revert-layer",
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
function isAuthoredColor(value: unknown): value is string {
|
|
43
|
+
return (
|
|
44
|
+
typeof value === "string" &&
|
|
45
|
+
value !== "" &&
|
|
46
|
+
!RESET_KEYWORDS.has(value.trim().toLowerCase())
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const LINE_STYLE_KEYWORDS = new Set([
|
|
51
|
+
"none",
|
|
52
|
+
"hidden",
|
|
53
|
+
"solid",
|
|
54
|
+
"dashed",
|
|
55
|
+
"dotted",
|
|
56
|
+
"double",
|
|
57
|
+
"groove",
|
|
58
|
+
"ridge",
|
|
59
|
+
"inset",
|
|
60
|
+
"outset",
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
const LINE_WIDTH = /^(thin|medium|thick|-?\d*\.?\d+[a-z%]*)$/i;
|
|
64
|
+
|
|
65
|
+
/** Split on spaces outside parentheses, so a `var()` fallback stays intact. */
|
|
66
|
+
function splitTopLevel(value: string): string[] {
|
|
67
|
+
const parts: string[] = [];
|
|
68
|
+
let depth = 0;
|
|
69
|
+
let current = "";
|
|
70
|
+
for (const char of value) {
|
|
71
|
+
if (char === "(") depth += 1;
|
|
72
|
+
else if (char === ")") depth -= 1;
|
|
73
|
+
if (depth === 0 && /\s/.test(char)) {
|
|
74
|
+
if (current) parts.push(current);
|
|
75
|
+
current = "";
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
current += char;
|
|
79
|
+
}
|
|
80
|
+
if (current) parts.push(current);
|
|
81
|
+
return parts;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The colour component of a `border`/`outline` shorthand. Width and style are
|
|
86
|
+
* a closed keyword set, so whatever single value is left over is the colour —
|
|
87
|
+
* without this, `border: 1px solid var(--x)` loses its token name.
|
|
88
|
+
*/
|
|
89
|
+
function lineShorthandColor(value: string): string | null {
|
|
90
|
+
const rest = splitTopLevel(value).filter((part) => {
|
|
91
|
+
const lower = part.toLowerCase();
|
|
92
|
+
return !LINE_STYLE_KEYWORDS.has(lower) && !LINE_WIDTH.test(part);
|
|
93
|
+
});
|
|
94
|
+
return rest.length === 1 ? rest[0] : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A shorthand only stands in for the colour when it isolates one. Exported for
|
|
99
|
+
* tests: a real engine leaves the longhand empty here, which happy-dom does not
|
|
100
|
+
* reproduce, so the wiring alone cannot exercise this.
|
|
101
|
+
*/
|
|
102
|
+
export function shorthandColorValue(
|
|
103
|
+
shorthand: string,
|
|
104
|
+
value: string,
|
|
105
|
+
): string | null {
|
|
106
|
+
const trimmed = value.trim();
|
|
107
|
+
if (!trimmed) return null;
|
|
108
|
+
if (/url\(|gradient\(/i.test(trimmed)) return null;
|
|
109
|
+
// A single `var()` may contain spaces (its fallback); anything else with a
|
|
110
|
+
// space is a layered shorthand, not a colour.
|
|
111
|
+
if (/^var\(.*\)$/.test(trimmed)) return trimmed;
|
|
112
|
+
if (shorthand === "border" || shorthand === "outline") {
|
|
113
|
+
return lineShorthandColor(trimmed);
|
|
114
|
+
}
|
|
115
|
+
return /\s/.test(trimmed) ? null : trimmed;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type StyleRuleLike = {
|
|
119
|
+
selectorText?: string;
|
|
120
|
+
style?: Record<string, string> & {
|
|
121
|
+
getPropertyPriority?: (property: string) => string;
|
|
122
|
+
};
|
|
123
|
+
conditionText?: string;
|
|
124
|
+
cssRules?: ArrayLike<unknown>;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Approximate CSS specificity as one comparable number. Approximate is safe:
|
|
129
|
+
* it only orders selectors that already match, and `authoredMatchesPainted`
|
|
130
|
+
* still discards a winner that is not what paints. `:is()`/`:not()` inner
|
|
131
|
+
* specificity is not counted.
|
|
132
|
+
*/
|
|
133
|
+
function selectorSpecificity(selector: string): number {
|
|
134
|
+
const cleaned = selector
|
|
135
|
+
.replace(/\\./g, "")
|
|
136
|
+
.replace(/\[[^\]]*\]/g, " :attr ")
|
|
137
|
+
.replace(/\(([^()]*)\)/g, " ");
|
|
138
|
+
const count = (pattern: RegExp) => (cleaned.match(pattern) ?? []).length;
|
|
139
|
+
const ids = count(/#[\w-]+/g);
|
|
140
|
+
const classes = count(/\.[\w-]+/g) + count(/:(?!:)[\w-]+/g);
|
|
141
|
+
const types = count(/(?:^|[\s>+~])[a-zA-Z][\w-]*/g) + count(/::[\w-]+/g);
|
|
142
|
+
return ids * 10000 + classes * 100 + types;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Specificity of the selector in a comma list that actually matches. */
|
|
146
|
+
function matchedSpecificity(element: Element, selectorText: string): number {
|
|
147
|
+
let best = -1;
|
|
148
|
+
for (const selector of selectorText.split(",")) {
|
|
149
|
+
const trimmed = selector.trim();
|
|
150
|
+
if (!trimmed) continue;
|
|
151
|
+
try {
|
|
152
|
+
if (!element.matches(trimmed)) continue;
|
|
153
|
+
} catch {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
best = Math.max(best, selectorSpecificity(trimmed));
|
|
157
|
+
}
|
|
158
|
+
return best;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Above any selector: only importance can outrank an inline declaration. */
|
|
162
|
+
const INLINE_SPECIFICITY = Number.MAX_SAFE_INTEGER;
|
|
163
|
+
|
|
164
|
+
interface Candidate {
|
|
165
|
+
value: string;
|
|
166
|
+
important: boolean;
|
|
167
|
+
specificity: number;
|
|
168
|
+
order: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Cascade order for declarations that all match: important, then specificity. */
|
|
172
|
+
function beats(next: Candidate, current: Candidate | undefined): boolean {
|
|
173
|
+
if (!current) return true;
|
|
174
|
+
if (next.important !== current.important) return next.important;
|
|
175
|
+
if (next.specificity !== current.specificity) {
|
|
176
|
+
return next.specificity > current.specificity;
|
|
177
|
+
}
|
|
178
|
+
return next.order >= current.order;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function readRules(
|
|
182
|
+
element: Element,
|
|
183
|
+
rules: ArrayLike<unknown> | undefined,
|
|
184
|
+
out: Record<string, Candidate>,
|
|
185
|
+
counter: { order: number },
|
|
186
|
+
): void {
|
|
187
|
+
if (!rules) return;
|
|
188
|
+
for (let index = 0; index < rules.length; index += 1) {
|
|
189
|
+
const rule = rules[index] as StyleRuleLike | undefined;
|
|
190
|
+
if (!rule) continue;
|
|
191
|
+
|
|
192
|
+
// Tested before `cssRules`: a style rule carries one too (CSS nesting), so
|
|
193
|
+
// treating anything with `cssRules` as a group skips every declaration.
|
|
194
|
+
const declaresOwnStyles = Boolean(rule.selectorText && rule.style);
|
|
195
|
+
|
|
196
|
+
if (!declaresOwnStyles) {
|
|
197
|
+
if (!rule.cssRules) continue;
|
|
198
|
+
// Any grouping rule, not just conditional ones: an `@layer` block holds
|
|
199
|
+
// cssRules with no conditionText, and Tailwind puts its utilities there.
|
|
200
|
+
// Layer precedence is not modelled, only membership.
|
|
201
|
+
if (rule.conditionText === undefined) {
|
|
202
|
+
readRules(element, rule.cssRules, out, counter);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
// A conditional group counts only while it applies, so the walk matches
|
|
206
|
+
// what the canvas paints.
|
|
207
|
+
let applies = false;
|
|
208
|
+
try {
|
|
209
|
+
applies =
|
|
210
|
+
typeof window !== "undefined" &&
|
|
211
|
+
typeof window.matchMedia === "function"
|
|
212
|
+
? window.matchMedia(rule.conditionText).matches
|
|
213
|
+
: false;
|
|
214
|
+
} catch {
|
|
215
|
+
applies = false;
|
|
216
|
+
}
|
|
217
|
+
if (applies) readRules(element, rule.cssRules, out, counter);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (rule.cssRules?.length) {
|
|
222
|
+
readRules(element, rule.cssRules, out, counter);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!rule.selectorText || !rule.style) continue;
|
|
226
|
+
const specificity = matchedSpecificity(element, rule.selectorText);
|
|
227
|
+
if (specificity < 0) continue;
|
|
228
|
+
const order = counter.order++;
|
|
229
|
+
const style = rule.style;
|
|
230
|
+
const priority = (name: string) =>
|
|
231
|
+
style.getPropertyPriority?.(name) === "important";
|
|
232
|
+
|
|
233
|
+
const offer = (property: string, value: string, cssName: string) => {
|
|
234
|
+
const candidate: Candidate = {
|
|
235
|
+
value,
|
|
236
|
+
important: priority(cssName),
|
|
237
|
+
specificity,
|
|
238
|
+
order,
|
|
239
|
+
};
|
|
240
|
+
if (beats(candidate, out[property])) out[property] = candidate;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
for (const property of AUTHORED_COLOR_PROPERTIES) {
|
|
244
|
+
const declared = style[property];
|
|
245
|
+
if (isAuthoredColor(declared)) {
|
|
246
|
+
offer(property, declared, cssPropertyName(property));
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const shorthand = SHORTHAND_FALLBACK[property];
|
|
250
|
+
if (!shorthand) continue;
|
|
251
|
+
const shorthandValue = style[shorthand];
|
|
252
|
+
if (typeof shorthandValue !== "string") continue;
|
|
253
|
+
const colorOnly = shorthandColorValue(shorthand, shorthandValue);
|
|
254
|
+
if (colorOnly) offer(property, colorOnly, shorthand);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function cssPropertyName(property: string): string {
|
|
260
|
+
return property.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Whether `authored` actually resolves to the colour the element paints.
|
|
265
|
+
*
|
|
266
|
+
* Without this an ancestor's `color: var(--color-text)` gets reported for a
|
|
267
|
+
* child painted green by its own utility class — a confidently wrong token name,
|
|
268
|
+
* which is worse than showing the hex. Resolved via a probe child so var()
|
|
269
|
+
* chains and shorthands are evaluated in the element's own scope.
|
|
270
|
+
*/
|
|
271
|
+
export function authoredMatchesPainted(
|
|
272
|
+
element: Element,
|
|
273
|
+
property: string,
|
|
274
|
+
authored: string,
|
|
275
|
+
): boolean {
|
|
276
|
+
const view = element.ownerDocument?.defaultView;
|
|
277
|
+
if (!view || typeof view.getComputedStyle !== "function") return true;
|
|
278
|
+
|
|
279
|
+
let painted = "";
|
|
280
|
+
try {
|
|
281
|
+
painted =
|
|
282
|
+
(view.getComputedStyle(element) as unknown as Record<string, string>)[
|
|
283
|
+
property
|
|
284
|
+
] ?? "";
|
|
285
|
+
} catch {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
if (!painted) return true;
|
|
289
|
+
|
|
290
|
+
const probe = element.ownerDocument!.createElement("span");
|
|
291
|
+
const name = cssPropertyName(property);
|
|
292
|
+
probe.style.setProperty("display", "none");
|
|
293
|
+
probe.style.setProperty(name, authored);
|
|
294
|
+
// An unparseable value never lands, and the probe would then just inherit the
|
|
295
|
+
// element's own colour and compare equal.
|
|
296
|
+
if (!probe.style.getPropertyValue(name)) return false;
|
|
297
|
+
|
|
298
|
+
element.appendChild(probe);
|
|
299
|
+
let resolved = "";
|
|
300
|
+
try {
|
|
301
|
+
resolved =
|
|
302
|
+
(view.getComputedStyle(probe) as unknown as Record<string, string>)[
|
|
303
|
+
property
|
|
304
|
+
] ?? "";
|
|
305
|
+
} catch {
|
|
306
|
+
resolved = "";
|
|
307
|
+
}
|
|
308
|
+
probe.remove();
|
|
309
|
+
|
|
310
|
+
return resolved === "" || resolved === painted;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function collectForElement(element: Element): Record<string, string> {
|
|
314
|
+
const ranked: Record<string, Candidate> = {};
|
|
315
|
+
const counter = { order: 0 };
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
const doc = element.ownerDocument;
|
|
319
|
+
// Adopted sheets are not in `styleSheets`, and a runtime CSS injector (the
|
|
320
|
+
// Tailwind CDN) puts every utility rule there.
|
|
321
|
+
const sheets = [
|
|
322
|
+
...Array.from(doc?.styleSheets ?? []),
|
|
323
|
+
...Array.from(
|
|
324
|
+
(doc as unknown as { adoptedStyleSheets?: ArrayLike<CSSStyleSheet> })
|
|
325
|
+
?.adoptedStyleSheets ?? [],
|
|
326
|
+
),
|
|
327
|
+
];
|
|
328
|
+
for (const sheet of sheets) {
|
|
329
|
+
let rules: ArrayLike<unknown> | undefined;
|
|
330
|
+
try {
|
|
331
|
+
rules = sheet.cssRules as unknown as ArrayLike<unknown>;
|
|
332
|
+
} catch {
|
|
333
|
+
// A cross-origin sheet cannot be read; skip it.
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
readRules(element, rules, ranked, counter);
|
|
337
|
+
}
|
|
338
|
+
} catch (error) {
|
|
339
|
+
// Silently losing the walk downgrades every token colour to a hex.
|
|
340
|
+
console.warn(
|
|
341
|
+
"[design] could not read stylesheets for authored colours; token names will fall back to resolved values",
|
|
342
|
+
error,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Inline outranks any selector, but a normal inline declaration still loses
|
|
347
|
+
// to an `!important` stylesheet one — so it competes rather than overwrites.
|
|
348
|
+
const inline = (element as HTMLElement).style;
|
|
349
|
+
if (inline) {
|
|
350
|
+
const order = counter.order++;
|
|
351
|
+
for (const property of AUTHORED_COLOR_PROPERTIES) {
|
|
352
|
+
const value = inline[property as never] as unknown as string;
|
|
353
|
+
if (!isAuthoredColor(value)) continue;
|
|
354
|
+
const candidate: Candidate = {
|
|
355
|
+
value,
|
|
356
|
+
important:
|
|
357
|
+
inline.getPropertyPriority?.(cssPropertyName(property)) ===
|
|
358
|
+
"important",
|
|
359
|
+
specificity: INLINE_SPECIFICITY,
|
|
360
|
+
order,
|
|
361
|
+
};
|
|
362
|
+
if (beats(candidate, ranked[property])) ranked[property] = candidate;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const out: Record<string, string> = {};
|
|
367
|
+
for (const [property, candidate] of Object.entries(ranked)) {
|
|
368
|
+
out[property] = candidate.value;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return out;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function collectAuthoredColorStyles(
|
|
375
|
+
element: Element,
|
|
376
|
+
): Record<string, string> {
|
|
377
|
+
const out = collectForElement(element);
|
|
378
|
+
for (const property of Object.keys(out)) {
|
|
379
|
+
if (!authoredMatchesPainted(element, property, out[property])) {
|
|
380
|
+
delete out[property];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// `color` is inherited: a heading with no rule of its own still has a token.
|
|
385
|
+
for (const property of INHERITED_PROPERTIES) {
|
|
386
|
+
if (out[property]) continue;
|
|
387
|
+
let ancestor = element.parentElement;
|
|
388
|
+
while (ancestor) {
|
|
389
|
+
const inherited = collectForElement(ancestor)[property];
|
|
390
|
+
if (inherited) {
|
|
391
|
+
if (authoredMatchesPainted(element, property, inherited)) {
|
|
392
|
+
out[property] = inherited;
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
ancestor = ancestor.parentElement;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return out;
|
|
401
|
+
}
|
|
@@ -11,13 +11,21 @@
|
|
|
11
11
|
* - the snapshot / coding-handoff actions inject so an external agent
|
|
12
12
|
* continues from the *tuned* design, not the original generated tokens.
|
|
13
13
|
*
|
|
14
|
-
* Keep it pure
|
|
15
|
-
*
|
|
14
|
+
* Keep it pure so the UI and the server actions produce byte-identical
|
|
15
|
+
* output. The CSS-safety predicates are re-exported from core so a Brand Kit
|
|
16
|
+
* token and a tweak value can never be validated by two drifting copies.
|
|
16
17
|
*/
|
|
17
18
|
|
|
19
|
+
import {
|
|
20
|
+
isSafeCssTokenValue,
|
|
21
|
+
isSafeCssVarName,
|
|
22
|
+
} from "@agent-native/core/brand-kit/tokens";
|
|
23
|
+
|
|
18
24
|
import type { TweakDefinition } from "./api.js";
|
|
19
25
|
import { sourceContentHash } from "./source-workspace.js";
|
|
20
26
|
|
|
27
|
+
export { isSafeCssTokenValue, isSafeCssVarName };
|
|
28
|
+
|
|
21
29
|
export type TweakSelections = Record<string, string | number | boolean>;
|
|
22
30
|
|
|
23
31
|
/**
|
|
@@ -49,22 +57,6 @@ export function tweakSelectionsHash(
|
|
|
49
57
|
* emitted directly, which lets token edits persist vars that were not part of
|
|
50
58
|
* the generated tweak definition list.
|
|
51
59
|
*/
|
|
52
|
-
const CSS_CUSTOM_PROPERTY_NAME = /^--[-_a-zA-Z0-9]+$/;
|
|
53
|
-
|
|
54
|
-
export function isSafeCssVarName(value: string): boolean {
|
|
55
|
-
return CSS_CUSTOM_PROPERTY_NAME.test(value);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function isSafeCssTokenValue(value: string): boolean {
|
|
59
|
-
return (
|
|
60
|
-
value.length > 0 &&
|
|
61
|
-
value.length <= 300 &&
|
|
62
|
-
!/[;{}<>]/.test(value) &&
|
|
63
|
-
!/\/\*/.test(value) &&
|
|
64
|
-
!/[\u0000-\u001f\u007f]/.test(value)
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
60
|
export function resolveTweaksToCssVars(
|
|
69
61
|
tweaks: TweakDefinition[],
|
|
70
62
|
selections: TweakSelections,
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export * from "./types.js";
|
|
18
18
|
export * from "./brand-signals.js";
|
|
19
|
+
export * from "./tokens.js";
|
|
19
20
|
export { extractDesignTokensFromUrl, validateUrl, parseOwnerRepo, fetchGitHubJson, fetchGitHubJsonResult, fetchGitHubRaw, parseTailwindConfig, parseCss, detectStylingFramework, createCodeAnalysisState, analyzeCodeFile, analyzeCssFile, analyzeTailwindConfig, analyzeJsonTheme, analyzePackageJson, analyzeThemeSourceFile, addFont, extractCssVars, extractCodeColors, extractCodeFonts, extractDocumentColors, extractDocumentFonts, classifyFile, suggestionsForType, unique, MAX_FILES, MAX_FILE_SIZE, FETCH_TIMEOUT, ROOT_PATTERNS, SECONDARY_PATHS, CODE_MAX_FILES, CODE_MAX_TOTAL_BYTES, } from "../server/design-token-utils.js";
|
|
20
21
|
export type { ContentType, ParsedCss, ParsedTailwindConfig, CodeAnalysisState, UrlExtractionResult, GitHubFetchOptions, GitHubJsonResult, } from "../server/design-token-utils.js";
|
|
21
22
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/brand-kit/index.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export * from "./types.js";
|
|
18
18
|
export * from "./brand-signals.js";
|
|
19
|
+
export * from "./tokens.js";
|
|
19
20
|
// Import-source parsing + token extraction (Tailwind/CSS/GitHub/URL/document/
|
|
20
21
|
// code analysis). These already power the import-* actions across templates.
|
|
21
22
|
export {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@agent-native/core/brand-kit/tokens`
|
|
3
|
+
*
|
|
4
|
+
* Pure helpers for the named-token vocabulary a Brand Kit stores alongside its
|
|
5
|
+
* seven color roles. Kept on its own subpath (no `design-token-utils` re-export)
|
|
6
|
+
* so browser code can import the CSS-safety predicates without pulling the
|
|
7
|
+
* server-side extraction stack into the bundle.
|
|
8
|
+
*/
|
|
9
|
+
import type { BrandKitToken, BrandKitTokenType } from "./types.js";
|
|
10
|
+
/**
|
|
11
|
+
* Upper bound on a single kit's stored vocabulary. Large systems legitimately
|
|
12
|
+
* define hundreds of tokens; this only stops an unbounded scrape landing in SQL.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MAX_BRAND_KIT_TOKENS = 500;
|
|
15
|
+
export declare function isSafeCssVarName(value: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Token values are spliced raw into `:root { … }` declarations, so anything that
|
|
18
|
+
* could terminate a declaration or break out of a `<style>` element is unsafe.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isSafeCssTokenValue(value: string): boolean;
|
|
21
|
+
export declare function isColorTokenValue(value: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Bucket a token, trusting the value's shape over the name's keywords.
|
|
24
|
+
*
|
|
25
|
+
* Name-first classification called Primer's `--text-body-size-medium: 1rem` a
|
|
26
|
+
* color because the name contains "text", which then rendered a swatch with
|
|
27
|
+
* `background: 1rem`. A dimension is never a color however the token is named.
|
|
28
|
+
*/
|
|
29
|
+
export declare function classifyBrandKitToken(name: string, value: string): BrandKitTokenType;
|
|
30
|
+
/** Title-case display name derived from a CSS var, e.g. `--primary-color`. */
|
|
31
|
+
export declare function friendlyTokenName(cssVar: string): string;
|
|
32
|
+
export type BrandKitTokenRejectionReason = "malformed" | "unsafe-css-var" | "unsafe-value" | "over-limit";
|
|
33
|
+
export interface BrandKitTokenRejection {
|
|
34
|
+
reason: BrandKitTokenRejectionReason;
|
|
35
|
+
/** Best-effort identifier for the offending entry, for error messages. */
|
|
36
|
+
label: string;
|
|
37
|
+
}
|
|
38
|
+
export interface NormalizedBrandKitTokens {
|
|
39
|
+
tokens: BrandKitToken[];
|
|
40
|
+
/**
|
|
41
|
+
* Entries that could not be stored. Callers must surface these — a kit that
|
|
42
|
+
* silently kept 4 of 40 tokens reads downstream as a 4-token design system.
|
|
43
|
+
*/
|
|
44
|
+
rejected: BrandKitTokenRejection[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Coerce untrusted token JSON into storable tokens, reporting every entry it
|
|
48
|
+
* could not accept. Later duplicates of a `cssVar` win, matching the overlay
|
|
49
|
+
* order importers use.
|
|
50
|
+
*/
|
|
51
|
+
export declare function normalizeBrandKitTokens(input: unknown): NormalizedBrandKitTokens;
|
|
52
|
+
/** Human-readable summary of why tokens were refused, for thrown errors. */
|
|
53
|
+
export declare function describeBrandKitTokenRejections(rejected: readonly BrandKitTokenRejection[]): string;
|
|
54
|
+
/**
|
|
55
|
+
* The seven colour roles plus radius/spacing, as tokens. Derived from a kit's
|
|
56
|
+
* summary fields rather than named by the source system, so they carry no
|
|
57
|
+
* `group`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function brandKitRoleTokens(data: {
|
|
60
|
+
colors?: object | null;
|
|
61
|
+
borders?: {
|
|
62
|
+
radius?: string;
|
|
63
|
+
} | null;
|
|
64
|
+
spacing?: object | null;
|
|
65
|
+
} | null | undefined, source?: string): BrandKitToken[];
|
|
66
|
+
/**
|
|
67
|
+
* Read a kit's named tokens out of a raw CSS block. A kit's `customCSS` already
|
|
68
|
+
* spells out the source system's real token names. Declaration order wins on
|
|
69
|
+
* duplicates, matching the cascade.
|
|
70
|
+
*/
|
|
71
|
+
export declare function parseBrandKitTokensFromCss(css: string, source?: string): BrandKitToken[];
|
|
72
|
+
/**
|
|
73
|
+
* The kit's named vocabulary: its stored `tokens` when it has them, else the
|
|
74
|
+
* names its `customCSS` declares. Kits predating `tokens` still carry real names
|
|
75
|
+
* in CSS, so reading only `tokens` would report them as having none.
|
|
76
|
+
*/
|
|
77
|
+
export declare function resolveBrandKitTokens(data: {
|
|
78
|
+
tokens?: unknown;
|
|
79
|
+
customCSS?: unknown;
|
|
80
|
+
} | null | undefined, cssSource?: string): BrandKitToken[];
|
|
81
|
+
export interface BrandKitTokenGroup {
|
|
82
|
+
/** The source's own group path when it has one, else the token type. */
|
|
83
|
+
label: string;
|
|
84
|
+
type: BrandKitTokenType;
|
|
85
|
+
tokens: BrandKitToken[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Group tokens for display, preserving the source's collection paths. Groups sort
|
|
89
|
+
* by token type so colors lead, then alphabetically within a type.
|
|
90
|
+
*/
|
|
91
|
+
export declare function groupBrandKitTokens(tokens: readonly BrandKitToken[]): BrandKitTokenGroup[];
|
|
92
|
+
//# sourceMappingURL=tokens.d.ts.map
|