@loworbitstudio/visor-theme-engine 0.15.1 → 0.16.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/dist/adapters/index.d.ts +1 -1
- package/dist/fowt.d.ts +46 -1
- package/dist/fowt.js +22 -1
- package/dist/index.d.ts +390 -4
- package/dist/index.js +465 -1
- package/dist/{types-zug1_eLX.d.ts → types-BstIS9rL.d.ts} +175 -1
- package/package.json +1 -1
- package/src/visor-theme.schema.json +149 -1
package/dist/index.js
CHANGED
|
@@ -202,6 +202,289 @@ function validateFontCoverage(css) {
|
|
|
202
202
|
return { errors };
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
// src/brand-strategy/types.ts
|
|
206
|
+
var DEFAULT_BRAND_STRATEGY_TONE_STATES = [
|
|
207
|
+
"error",
|
|
208
|
+
"success",
|
|
209
|
+
"warning",
|
|
210
|
+
"info",
|
|
211
|
+
"empty",
|
|
212
|
+
"loading",
|
|
213
|
+
"validation-warning"
|
|
214
|
+
];
|
|
215
|
+
var DEFAULT_BRAND_STRATEGY_SURFACES = [
|
|
216
|
+
"manifest",
|
|
217
|
+
"cli",
|
|
218
|
+
"component-metadata"
|
|
219
|
+
];
|
|
220
|
+
var BRAND_VISIBILITIES = ["public", "private"];
|
|
221
|
+
var GOVERNS_WILDCARD = "*";
|
|
222
|
+
|
|
223
|
+
// src/brand-strategy/validate.ts
|
|
224
|
+
function error(code, message, path) {
|
|
225
|
+
const issue2 = { severity: "error", code, message };
|
|
226
|
+
if (path !== void 0) issue2.path = path;
|
|
227
|
+
return issue2;
|
|
228
|
+
}
|
|
229
|
+
function isObject(value) {
|
|
230
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
231
|
+
}
|
|
232
|
+
function isNonEmptyString(value) {
|
|
233
|
+
return typeof value === "string" && value.length > 0;
|
|
234
|
+
}
|
|
235
|
+
function isStringArray(value) {
|
|
236
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
237
|
+
}
|
|
238
|
+
function normalizeTokenRef(ref) {
|
|
239
|
+
return ref.startsWith("--") ? ref.slice(2) : ref;
|
|
240
|
+
}
|
|
241
|
+
var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
242
|
+
"positioning",
|
|
243
|
+
"essence",
|
|
244
|
+
"personality",
|
|
245
|
+
"archetype",
|
|
246
|
+
"pillars",
|
|
247
|
+
"voice",
|
|
248
|
+
"tone",
|
|
249
|
+
"lexicon",
|
|
250
|
+
"core",
|
|
251
|
+
"visibility"
|
|
252
|
+
]);
|
|
253
|
+
var GOVERNS_KEYS = /* @__PURE__ */ new Set(["tokens", "components", "surfaces"]);
|
|
254
|
+
function checkBrandStrategyStructure(block) {
|
|
255
|
+
const errors = [];
|
|
256
|
+
if (!isObject(block)) {
|
|
257
|
+
errors.push(error("BRAND_STRATEGY_STRUCTURAL", "'brand-strategy' must be an object", "brand-strategy"));
|
|
258
|
+
return errors;
|
|
259
|
+
}
|
|
260
|
+
for (const key of Object.keys(block)) {
|
|
261
|
+
if (!TOP_LEVEL_KEYS.has(key)) {
|
|
262
|
+
errors.push(
|
|
263
|
+
error(
|
|
264
|
+
"BRAND_STRATEGY_UNKNOWN_KEY",
|
|
265
|
+
`Unknown key 'brand-strategy.${key}'. Valid keys: ${[...TOP_LEVEL_KEYS].join(", ")}`,
|
|
266
|
+
`brand-strategy.${key}`
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const positioning = block.positioning;
|
|
272
|
+
if (!isObject(positioning)) {
|
|
273
|
+
errors.push(error("BRAND_STRATEGY_POSITIONING", "'brand-strategy.positioning' is required and must be an object", "brand-strategy.positioning"));
|
|
274
|
+
} else {
|
|
275
|
+
for (const field of ["onliness", "category", "differentiation"]) {
|
|
276
|
+
if (!isNonEmptyString(positioning[field])) {
|
|
277
|
+
errors.push(error("BRAND_STRATEGY_POSITIONING", `'brand-strategy.positioning.${field}' is required and must be a non-empty string`, `brand-strategy.positioning.${field}`));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (!isStringArray(block.essence) || block.essence.length === 0) {
|
|
282
|
+
errors.push(error("BRAND_STRATEGY_ESSENCE", "'brand-strategy.essence' is required and must be a non-empty array of strings", "brand-strategy.essence"));
|
|
283
|
+
}
|
|
284
|
+
if (!Array.isArray(block.personality) || block.personality.length === 0) {
|
|
285
|
+
errors.push(error("BRAND_STRATEGY_PERSONALITY", "'brand-strategy.personality' is required and must be a non-empty array", "brand-strategy.personality"));
|
|
286
|
+
} else {
|
|
287
|
+
block.personality.forEach((trait, i) => {
|
|
288
|
+
if (!isObject(trait) || !isNonEmptyString(trait.trait) || !isNonEmptyString(trait.not)) {
|
|
289
|
+
errors.push(error("BRAND_STRATEGY_PERSONALITY", `'brand-strategy.personality[${i}]' must have non-empty 'trait' and 'not' strings`, `brand-strategy.personality[${i}]`));
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
const archetype = block.archetype;
|
|
294
|
+
if (!isObject(archetype)) {
|
|
295
|
+
errors.push(error("BRAND_STRATEGY_ARCHETYPE", "'brand-strategy.archetype' is required and must be an object", "brand-strategy.archetype"));
|
|
296
|
+
} else {
|
|
297
|
+
if (!isNonEmptyString(archetype.primary)) {
|
|
298
|
+
errors.push(error("BRAND_STRATEGY_ARCHETYPE", "'brand-strategy.archetype.primary' is required and must be a non-empty string", "brand-strategy.archetype.primary"));
|
|
299
|
+
}
|
|
300
|
+
for (const field of ["secondary", "tertiary"]) {
|
|
301
|
+
if (archetype[field] !== void 0 && !isNonEmptyString(archetype[field])) {
|
|
302
|
+
errors.push(error("BRAND_STRATEGY_ARCHETYPE", `'brand-strategy.archetype.${field}' must be a non-empty string when present`, `brand-strategy.archetype.${field}`));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (!Array.isArray(block.pillars) || block.pillars.length === 0) {
|
|
307
|
+
errors.push(error("BRAND_STRATEGY_PILLARS", "'brand-strategy.pillars' is required and must be a non-empty array", "brand-strategy.pillars"));
|
|
308
|
+
} else {
|
|
309
|
+
block.pillars.forEach((pillar, i) => {
|
|
310
|
+
errors.push(...checkPillarStructure(pillar, i));
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
const voice = block.voice;
|
|
314
|
+
if (!isObject(voice) || !Array.isArray(voice.traits) || voice.traits.length === 0) {
|
|
315
|
+
errors.push(error("BRAND_STRATEGY_VOICE", "'brand-strategy.voice' is required and must have a non-empty 'traits' array", "brand-strategy.voice"));
|
|
316
|
+
} else {
|
|
317
|
+
voice.traits.forEach((trait, i) => {
|
|
318
|
+
if (!isObject(trait) || !isNonEmptyString(trait.name) || !isNonEmptyString(trait.do) || !isNonEmptyString(trait.dont)) {
|
|
319
|
+
errors.push(error("BRAND_STRATEGY_VOICE", `'brand-strategy.voice.traits[${i}]' must have non-empty 'name', 'do', and 'dont' strings`, `brand-strategy.voice.traits[${i}]`));
|
|
320
|
+
} else if (trait.example !== void 0 && !isNonEmptyString(trait.example)) {
|
|
321
|
+
errors.push(error("BRAND_STRATEGY_VOICE", `'brand-strategy.voice.traits[${i}].example' must be a non-empty string when present`, `brand-strategy.voice.traits[${i}].example`));
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
const tone = block.tone;
|
|
326
|
+
if (!isObject(tone) || Object.keys(tone).length === 0) {
|
|
327
|
+
errors.push(error("BRAND_STRATEGY_TONE", "'brand-strategy.tone' is required and must be a non-empty object keyed by UI state", "brand-strategy.tone"));
|
|
328
|
+
} else {
|
|
329
|
+
for (const [state, entry] of Object.entries(tone)) {
|
|
330
|
+
if (!isObject(entry) || !isNonEmptyString(entry.feeling) || !isNonEmptyString(entry.example)) {
|
|
331
|
+
errors.push(error("BRAND_STRATEGY_TONE", `'brand-strategy.tone.${state}' must have non-empty 'feeling' and 'example' strings`, `brand-strategy.tone.${state}`));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (!Array.isArray(block.lexicon) || block.lexicon.length === 0) {
|
|
336
|
+
errors.push(error("BRAND_STRATEGY_LEXICON", "'brand-strategy.lexicon' is required and must be a non-empty array", "brand-strategy.lexicon"));
|
|
337
|
+
} else {
|
|
338
|
+
block.lexicon.forEach((entry, i) => {
|
|
339
|
+
if (!isObject(entry) || !isNonEmptyString(entry.use) || !isNonEmptyString(entry.avoid)) {
|
|
340
|
+
errors.push(error("BRAND_STRATEGY_LEXICON", `'brand-strategy.lexicon[${i}]' must have non-empty 'use' and 'avoid' strings`, `brand-strategy.lexicon[${i}]`));
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
if (!isStringArray(block.core) || block.core.length === 0) {
|
|
345
|
+
errors.push(error("BRAND_STRATEGY_CORE", "'brand-strategy.core' is required and must be a non-empty array of strings", "brand-strategy.core"));
|
|
346
|
+
}
|
|
347
|
+
if (!isNonEmptyString(block.visibility) || !BRAND_VISIBILITIES.includes(block.visibility)) {
|
|
348
|
+
errors.push(error("BRAND_STRATEGY_VISIBILITY", `'brand-strategy.visibility' is required and must be one of: ${BRAND_VISIBILITIES.join(", ")}`, "brand-strategy.visibility"));
|
|
349
|
+
}
|
|
350
|
+
return errors;
|
|
351
|
+
}
|
|
352
|
+
function checkPillarStructure(pillar, index) {
|
|
353
|
+
const path = `brand-strategy.pillars[${index}]`;
|
|
354
|
+
if (!isObject(pillar)) {
|
|
355
|
+
return [error("BRAND_STRATEGY_PILLARS", `'${path}' must be an object`, path)];
|
|
356
|
+
}
|
|
357
|
+
const errors = [];
|
|
358
|
+
if (!isNonEmptyString(pillar.id)) {
|
|
359
|
+
errors.push(error("BRAND_STRATEGY_PILLARS", `'${path}.id' is required and must be a non-empty string`, `${path}.id`));
|
|
360
|
+
}
|
|
361
|
+
if (!isNonEmptyString(pillar.statement)) {
|
|
362
|
+
errors.push(error("BRAND_STRATEGY_PILLARS", `'${path}.statement' is required and must be a non-empty string`, `${path}.statement`));
|
|
363
|
+
}
|
|
364
|
+
const governs = pillar.governs;
|
|
365
|
+
if (!isObject(governs)) {
|
|
366
|
+
errors.push(error("BRAND_STRATEGY_PILLARS", `'${path}.governs' is required and must be an object`, `${path}.governs`));
|
|
367
|
+
} else {
|
|
368
|
+
for (const key of Object.keys(governs)) {
|
|
369
|
+
if (!GOVERNS_KEYS.has(key)) {
|
|
370
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS", `Unknown key '${path}.governs.${key}'. Valid keys: ${[...GOVERNS_KEYS].join(", ")}`, `${path}.governs.${key}`));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
for (const key of GOVERNS_KEYS) {
|
|
374
|
+
const list = governs[key];
|
|
375
|
+
if (list === void 0) continue;
|
|
376
|
+
if (!isStringArray(list)) {
|
|
377
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS", `'${path}.governs.${key}' must be an array of strings`, `${path}.governs.${key}`));
|
|
378
|
+
} else if (list.length === 0) {
|
|
379
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS", `'${path}.governs.${key}' must not be empty when present`, `${path}.governs.${key}`));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (governs.tokens === void 0 && governs.components === void 0 && governs.surfaces === void 0) {
|
|
383
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS", `'${path}.governs' must declare at least one of: ${[...GOVERNS_KEYS].join(", ")}`, `${path}.governs`));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return errors;
|
|
387
|
+
}
|
|
388
|
+
function checkBrandStrategyCoherence(strategy, ctx = {}) {
|
|
389
|
+
const errors = [];
|
|
390
|
+
const surfaces = ctx.surfaces ?? new Set(DEFAULT_BRAND_STRATEGY_SURFACES);
|
|
391
|
+
const states = ctx.states ?? new Set(DEFAULT_BRAND_STRATEGY_TONE_STATES);
|
|
392
|
+
strategy.pillars.forEach((pillar, i) => {
|
|
393
|
+
const path = `brand-strategy.pillars[${i}].governs`;
|
|
394
|
+
if (ctx.tokens && pillar.governs.tokens) {
|
|
395
|
+
for (const ref of pillar.governs.tokens) {
|
|
396
|
+
if (ref === GOVERNS_WILDCARD) continue;
|
|
397
|
+
if (!ctx.tokens.has(normalizeTokenRef(ref))) {
|
|
398
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS_TOKEN", `Pillar '${pillar.id}' governs token '${ref}', which is not a known Visor token`, `${path}.tokens`));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (pillar.governs.components) {
|
|
403
|
+
for (const ref of pillar.governs.components) {
|
|
404
|
+
if (ref === GOVERNS_WILDCARD) continue;
|
|
405
|
+
if (ctx.components && !ctx.components.has(ref)) {
|
|
406
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS_COMPONENT", `Pillar '${pillar.id}' governs component '${ref}', which is not a known Visor component`, `${path}.components`));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (pillar.governs.surfaces) {
|
|
411
|
+
for (const ref of pillar.governs.surfaces) {
|
|
412
|
+
if (!surfaces.has(ref)) {
|
|
413
|
+
errors.push(error("BRAND_STRATEGY_GOVERNS_SURFACE", `Pillar '${pillar.id}' governs surface '${ref}', which is not a recognized meta-surface (${[...surfaces].join(", ")})`, `${path}.surfaces`));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
for (const state of Object.keys(strategy.tone)) {
|
|
419
|
+
if (!states.has(state)) {
|
|
420
|
+
errors.push(error("BRAND_STRATEGY_TONE_STATE", `tone key '${state}' does not map to a recognized UI state (${[...states].join(", ")})`, `brand-strategy.tone.${state}`));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return errors;
|
|
424
|
+
}
|
|
425
|
+
function validateBrandStrategy(block, ctx = {}) {
|
|
426
|
+
const errors = checkBrandStrategyStructure(block);
|
|
427
|
+
const warnings = [];
|
|
428
|
+
if (errors.length > 0) {
|
|
429
|
+
return { valid: false, errors, warnings };
|
|
430
|
+
}
|
|
431
|
+
for (const issue2 of checkBrandStrategyCoherence(block, ctx)) {
|
|
432
|
+
(issue2.severity === "error" ? errors : warnings).push(issue2);
|
|
433
|
+
}
|
|
434
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/brand-strategy/serialize.ts
|
|
438
|
+
function compactGoverns(governs) {
|
|
439
|
+
const out = {};
|
|
440
|
+
if (governs.tokens) out.tokens = [...governs.tokens];
|
|
441
|
+
if (governs.components) out.components = [...governs.components];
|
|
442
|
+
if (governs.surfaces) out.surfaces = [...governs.surfaces];
|
|
443
|
+
return out;
|
|
444
|
+
}
|
|
445
|
+
function serializeBrandStrategy(strategy) {
|
|
446
|
+
if (strategy.visibility === "private") {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
const archetype = { primary: strategy.archetype.primary };
|
|
450
|
+
if (strategy.archetype.secondary !== void 0) archetype.secondary = strategy.archetype.secondary;
|
|
451
|
+
if (strategy.archetype.tertiary !== void 0) archetype.tertiary = strategy.archetype.tertiary;
|
|
452
|
+
const tone = {};
|
|
453
|
+
for (const [state, entry] of Object.entries(strategy.tone)) {
|
|
454
|
+
tone[state] = { feeling: entry.feeling, example: entry.example };
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
positioning: {
|
|
458
|
+
onliness: strategy.positioning.onliness,
|
|
459
|
+
category: strategy.positioning.category,
|
|
460
|
+
differentiation: strategy.positioning.differentiation
|
|
461
|
+
},
|
|
462
|
+
essence: [...strategy.essence],
|
|
463
|
+
personality: strategy.personality.map((p) => ({ trait: p.trait, not: p.not })),
|
|
464
|
+
archetype,
|
|
465
|
+
pillars: strategy.pillars.map((p) => ({
|
|
466
|
+
id: p.id,
|
|
467
|
+
statement: p.statement,
|
|
468
|
+
governs: compactGoverns(p.governs)
|
|
469
|
+
})),
|
|
470
|
+
voice: {
|
|
471
|
+
traits: strategy.voice.traits.map((t) => {
|
|
472
|
+
const trait = {
|
|
473
|
+
name: t.name,
|
|
474
|
+
do: t.do,
|
|
475
|
+
dont: t.dont
|
|
476
|
+
};
|
|
477
|
+
if (t.example !== void 0) trait.example = t.example;
|
|
478
|
+
return trait;
|
|
479
|
+
})
|
|
480
|
+
},
|
|
481
|
+
tone,
|
|
482
|
+
lexicon: strategy.lexicon.map((l) => ({ use: l.use, avoid: l.avoid })),
|
|
483
|
+
core: [...strategy.core],
|
|
484
|
+
visibility: strategy.visibility
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
205
488
|
// src/pipeline.ts
|
|
206
489
|
import { parse as parseYaml } from "yaml";
|
|
207
490
|
|
|
@@ -230,7 +513,7 @@ var visor_theme_schema_default = {
|
|
|
230
513
|
},
|
|
231
514
|
label: {
|
|
232
515
|
type: "string",
|
|
233
|
-
description: "Human-readable display name for the theme (e.g. '
|
|
516
|
+
description: "Human-readable display name for the theme (e.g. 'My Brand Pro'). Overrides the name-derived label in the docs theme switcher. Optional."
|
|
234
517
|
},
|
|
235
518
|
"default-mode": {
|
|
236
519
|
type: "string",
|
|
@@ -501,6 +784,59 @@ var visor_theme_schema_default = {
|
|
|
501
784
|
]
|
|
502
785
|
}
|
|
503
786
|
},
|
|
787
|
+
"brand-strategy": {
|
|
788
|
+
type: "object",
|
|
789
|
+
description: "Brand-strategy block (VI-505) \u2014 positioning, personality, pillars, voice, and tone as validated, serializable data. Sibling to `brand` (assets): different lifecycle and consumer. NOT expanded into derived CSS values; coherence-checked (pillars govern real tokens/components/surfaces; tone keys map to real UI states) and serialized to the agent manifest under `brand_strategy`.",
|
|
790
|
+
additionalProperties: false,
|
|
791
|
+
required: ["positioning", "essence", "personality", "archetype", "pillars", "voice", "tone", "lexicon", "core", "visibility"],
|
|
792
|
+
properties: {
|
|
793
|
+
positioning: { $ref: "#/$defs/brandPositioning" },
|
|
794
|
+
essence: {
|
|
795
|
+
type: "array",
|
|
796
|
+
items: { type: "string" },
|
|
797
|
+
minItems: 1,
|
|
798
|
+
description: "2\u20133 internal-facing core words (Aaker essence)."
|
|
799
|
+
},
|
|
800
|
+
personality: {
|
|
801
|
+
type: "array",
|
|
802
|
+
items: { $ref: "#/$defs/brandPersonalityTrait" },
|
|
803
|
+
minItems: 1,
|
|
804
|
+
description: "Brand-as-person traits, each sharpened by its antonym."
|
|
805
|
+
},
|
|
806
|
+
archetype: { $ref: "#/$defs/brandArchetype" },
|
|
807
|
+
pillars: {
|
|
808
|
+
type: "array",
|
|
809
|
+
items: { $ref: "#/$defs/brandPillar" },
|
|
810
|
+
minItems: 1,
|
|
811
|
+
description: "Strategic pillars \u2014 essence words made operational, each governing real tokens/components/surfaces."
|
|
812
|
+
},
|
|
813
|
+
voice: { $ref: "#/$defs/brandVoice" },
|
|
814
|
+
tone: {
|
|
815
|
+
type: "object",
|
|
816
|
+
description: "Voice flexed per UI state. Keys must be recognized UI states.",
|
|
817
|
+
minProperties: 1,
|
|
818
|
+
propertyNames: { enum: ["error", "success", "warning", "info", "empty", "loading", "validation-warning"] },
|
|
819
|
+
additionalProperties: { $ref: "#/$defs/brandToneEntry" }
|
|
820
|
+
},
|
|
821
|
+
lexicon: {
|
|
822
|
+
type: "array",
|
|
823
|
+
items: { $ref: "#/$defs/brandLexiconEntry" },
|
|
824
|
+
minItems: 1,
|
|
825
|
+
description: "Words to use and the ones to avoid."
|
|
826
|
+
},
|
|
827
|
+
core: {
|
|
828
|
+
type: "array",
|
|
829
|
+
items: { type: "string" },
|
|
830
|
+
minItems: 1,
|
|
831
|
+
description: "Aaker core/extended \u2014 the immutable subset, as section names."
|
|
832
|
+
},
|
|
833
|
+
visibility: {
|
|
834
|
+
type: "string",
|
|
835
|
+
enum: ["public", "private"],
|
|
836
|
+
description: "Client brands are private and are omitted from the public agent manifest."
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
},
|
|
504
840
|
spacing: {
|
|
505
841
|
type: "object",
|
|
506
842
|
description: "Spacing configuration.",
|
|
@@ -668,6 +1004,101 @@ var visor_theme_schema_default = {
|
|
|
668
1004
|
description: "Letter spacing in logical pixels (Flutter TextStyle.letterSpacing). Material defaults include negative values, e.g. -0.25 for displayLarge."
|
|
669
1005
|
}
|
|
670
1006
|
}
|
|
1007
|
+
},
|
|
1008
|
+
brandPositioning: {
|
|
1009
|
+
type: "object",
|
|
1010
|
+
description: "Positioning \u2014 the onliness, category, and differentiation wedge.",
|
|
1011
|
+
additionalProperties: false,
|
|
1012
|
+
required: ["onliness", "category", "differentiation"],
|
|
1013
|
+
properties: {
|
|
1014
|
+
onliness: { type: "string", minLength: 1, description: `The single sentence that passes Neumeier's "only" test.` },
|
|
1015
|
+
category: { type: "string", minLength: 1, description: 'The category the brand competes in (e.g. "design system").' },
|
|
1016
|
+
differentiation: { type: "string", minLength: 1, description: "What sets the brand apart within that category." }
|
|
1017
|
+
}
|
|
1018
|
+
},
|
|
1019
|
+
brandPersonalityTrait: {
|
|
1020
|
+
type: "object",
|
|
1021
|
+
description: "A personality trait sharpened by its antonym (brand-as-person).",
|
|
1022
|
+
additionalProperties: false,
|
|
1023
|
+
required: ["trait", "not"],
|
|
1024
|
+
properties: {
|
|
1025
|
+
trait: { type: "string", minLength: 1 },
|
|
1026
|
+
not: { type: "string", minLength: 1, description: "The antonym \u2014 what the trait is not." }
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
brandArchetype: {
|
|
1030
|
+
type: "object",
|
|
1031
|
+
description: "Brand archetype assignment (Pearson & Mark). Primary required; secondary/tertiary optional.",
|
|
1032
|
+
additionalProperties: false,
|
|
1033
|
+
required: ["primary"],
|
|
1034
|
+
properties: {
|
|
1035
|
+
primary: { type: "string", minLength: 1 },
|
|
1036
|
+
secondary: { type: "string", minLength: 1 },
|
|
1037
|
+
tertiary: { type: "string", minLength: 1 }
|
|
1038
|
+
}
|
|
1039
|
+
},
|
|
1040
|
+
brandGoverns: {
|
|
1041
|
+
type: "object",
|
|
1042
|
+
description: 'What a pillar governs \u2014 design tokens, registry components, and/or meta-surfaces. At least one list is expected; use "*" for all of a namespace.',
|
|
1043
|
+
additionalProperties: false,
|
|
1044
|
+
minProperties: 1,
|
|
1045
|
+
properties: {
|
|
1046
|
+
tokens: { type: "array", items: { type: "string" }, minItems: 1, description: 'Semantic token refs (with or without the leading --), or "*".' },
|
|
1047
|
+
components: { type: "array", items: { type: "string" }, minItems: 1, description: 'Registry component names, or "*" for all.' },
|
|
1048
|
+
surfaces: { type: "array", items: { type: "string", enum: ["manifest", "cli", "component-metadata"] }, minItems: 1, description: "Meta-surfaces a pillar may govern." }
|
|
1049
|
+
}
|
|
1050
|
+
},
|
|
1051
|
+
brandPillar: {
|
|
1052
|
+
type: "object",
|
|
1053
|
+
description: "A strategic pillar \u2014 an essence word made operational.",
|
|
1054
|
+
additionalProperties: false,
|
|
1055
|
+
required: ["id", "statement", "governs"],
|
|
1056
|
+
properties: {
|
|
1057
|
+
id: { type: "string", minLength: 1 },
|
|
1058
|
+
statement: { type: "string", minLength: 1, description: "The pillar's claim in one line." },
|
|
1059
|
+
governs: { $ref: "#/$defs/brandGoverns" }
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
1062
|
+
brandVoice: {
|
|
1063
|
+
type: "object",
|
|
1064
|
+
description: "Voice \u2014 fixed across the brand; never flexes.",
|
|
1065
|
+
additionalProperties: false,
|
|
1066
|
+
required: ["traits"],
|
|
1067
|
+
properties: {
|
|
1068
|
+
traits: { type: "array", items: { $ref: "#/$defs/brandVoiceTrait" }, minItems: 1 }
|
|
1069
|
+
}
|
|
1070
|
+
},
|
|
1071
|
+
brandVoiceTrait: {
|
|
1072
|
+
type: "object",
|
|
1073
|
+
description: "A fixed voice trait with a worked example.",
|
|
1074
|
+
additionalProperties: false,
|
|
1075
|
+
required: ["name", "do", "dont"],
|
|
1076
|
+
properties: {
|
|
1077
|
+
name: { type: "string", minLength: 1 },
|
|
1078
|
+
do: { type: "string", minLength: 1, description: "What to do." },
|
|
1079
|
+
dont: { type: "string", minLength: 1, description: "What not to do." },
|
|
1080
|
+
example: { type: "string", minLength: 1, description: "A worked example sentence." }
|
|
1081
|
+
}
|
|
1082
|
+
},
|
|
1083
|
+
brandToneEntry: {
|
|
1084
|
+
type: "object",
|
|
1085
|
+
description: "A single tone entry, keyed (in `tone`) to a recognized UI state.",
|
|
1086
|
+
additionalProperties: false,
|
|
1087
|
+
required: ["feeling", "example"],
|
|
1088
|
+
properties: {
|
|
1089
|
+
feeling: { type: "string", minLength: 1, description: "The feeling the copy should evoke in this state." },
|
|
1090
|
+
example: { type: "string", minLength: 1, description: "A worked example message for this state." }
|
|
1091
|
+
}
|
|
1092
|
+
},
|
|
1093
|
+
brandLexiconEntry: {
|
|
1094
|
+
type: "object",
|
|
1095
|
+
description: "A lexicon pairing \u2014 the word to use and the one to avoid.",
|
|
1096
|
+
additionalProperties: false,
|
|
1097
|
+
required: ["use", "avoid"],
|
|
1098
|
+
properties: {
|
|
1099
|
+
use: { type: "string", minLength: 1 },
|
|
1100
|
+
avoid: { type: "string", minLength: 1 }
|
|
1101
|
+
}
|
|
671
1102
|
}
|
|
672
1103
|
}
|
|
673
1104
|
};
|
|
@@ -683,6 +1114,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
683
1114
|
"colors-dark",
|
|
684
1115
|
"typography",
|
|
685
1116
|
"brand",
|
|
1117
|
+
"brand-strategy",
|
|
686
1118
|
"spacing",
|
|
687
1119
|
"radius",
|
|
688
1120
|
"shadows",
|
|
@@ -1102,6 +1534,11 @@ function validateConfig(config) {
|
|
|
1102
1534
|
}
|
|
1103
1535
|
}
|
|
1104
1536
|
}
|
|
1537
|
+
if (obj["brand-strategy"] !== void 0) {
|
|
1538
|
+
for (const iss of checkBrandStrategyStructure(obj["brand-strategy"])) {
|
|
1539
|
+
errors.push(iss.path ? `${iss.message} (at ${iss.path})` : iss.message);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1105
1542
|
if (obj.overrides !== void 0) {
|
|
1106
1543
|
if (typeof obj.overrides !== "object" || obj.overrides === null) {
|
|
1107
1544
|
errors.push("'overrides' must be an object");
|
|
@@ -1735,6 +2172,18 @@ var SEMANTIC_MAP = {
|
|
|
1735
2172
|
intent: SEMANTIC_INTENT_MAP,
|
|
1736
2173
|
hairline: SEMANTIC_HAIRLINE_MAP
|
|
1737
2174
|
};
|
|
2175
|
+
function getKnownTokenRefs() {
|
|
2176
|
+
const refs = /* @__PURE__ */ new Set();
|
|
2177
|
+
for (const key of Object.keys(SEMANTIC_TEXT_MAP)) refs.add(`text-${key}`);
|
|
2178
|
+
for (const key of Object.keys(SEMANTIC_SURFACE_MAP)) refs.add(`surface-${key}`);
|
|
2179
|
+
for (const key of Object.keys(SEMANTIC_BORDER_MAP)) refs.add(`border-${key}`);
|
|
2180
|
+
for (const key of Object.keys(SEMANTIC_INTERACTIVE_MAP)) refs.add(`interactive-${key}`);
|
|
2181
|
+
for (const key of Object.keys(SEMANTIC_INTENT_MAP)) refs.add(key);
|
|
2182
|
+
for (const key of Object.keys(SEMANTIC_HAIRLINE_MAP)) {
|
|
2183
|
+
refs.add(key === "default" ? "hairline" : `hairline-${key}`);
|
|
2184
|
+
}
|
|
2185
|
+
return refs;
|
|
2186
|
+
}
|
|
1738
2187
|
|
|
1739
2188
|
// src/assign.ts
|
|
1740
2189
|
function lookupShade(primitives, role, shade) {
|
|
@@ -2862,6 +3311,12 @@ function validate(config, options) {
|
|
|
2862
3311
|
const resolved = resolveConfig(typedConfig);
|
|
2863
3312
|
checkResolvedCompleteness(resolved, errors);
|
|
2864
3313
|
}
|
|
3314
|
+
const brandStrategy = typedConfig["brand-strategy"];
|
|
3315
|
+
if (brandStrategy) {
|
|
3316
|
+
for (const iss of checkBrandStrategyCoherence(brandStrategy, { tokens: getKnownTokenRefs() })) {
|
|
3317
|
+
(iss.severity === "error" ? errors : warnings).push(iss);
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
2865
3320
|
if (errors.length === 0) {
|
|
2866
3321
|
checkContrastWarnings(typedConfig, warnings);
|
|
2867
3322
|
checkColorSimilarity(typedConfig, warnings);
|
|
@@ -3368,8 +3823,12 @@ function extractFromCSS(files, name = "extracted-theme") {
|
|
|
3368
3823
|
}
|
|
3369
3824
|
export {
|
|
3370
3825
|
BRAND_VARIANTS,
|
|
3826
|
+
BRAND_VISIBILITIES,
|
|
3827
|
+
DEFAULT_BRAND_STRATEGY_SURFACES,
|
|
3828
|
+
DEFAULT_BRAND_STRATEGY_TONE_STATES,
|
|
3371
3829
|
DEFAULT_VISOR_BRAND,
|
|
3372
3830
|
FONT_WEIGHT_ALIASES,
|
|
3831
|
+
GOVERNS_WILDCARD,
|
|
3373
3832
|
SEMANTIC_MAP,
|
|
3374
3833
|
TAILWIND_GRAY,
|
|
3375
3834
|
VISOR_BRANDS_CDN,
|
|
@@ -3379,6 +3838,8 @@ export {
|
|
|
3379
3838
|
assignSemanticTokens,
|
|
3380
3839
|
buildVisorBrandUrl,
|
|
3381
3840
|
buildVisorFontUrl,
|
|
3841
|
+
checkBrandStrategyCoherence,
|
|
3842
|
+
checkBrandStrategyStructure,
|
|
3382
3843
|
clampToSrgb,
|
|
3383
3844
|
cleanFontValue,
|
|
3384
3845
|
collectBrandPassthrough,
|
|
@@ -3400,6 +3861,7 @@ export {
|
|
|
3400
3861
|
generateThemeDataFromConfig,
|
|
3401
3862
|
generateThemeFromConfig,
|
|
3402
3863
|
getContrastRatio,
|
|
3864
|
+
getKnownTokenRefs,
|
|
3403
3865
|
googleFontsCatalog,
|
|
3404
3866
|
hasBrandPassthrough,
|
|
3405
3867
|
hexToOklch,
|
|
@@ -3426,8 +3888,10 @@ export {
|
|
|
3426
3888
|
resolveThemeBrand,
|
|
3427
3889
|
resolveThemeFonts,
|
|
3428
3890
|
rgbToHex,
|
|
3891
|
+
serializeBrandStrategy,
|
|
3429
3892
|
serializeColor,
|
|
3430
3893
|
validate,
|
|
3894
|
+
validateBrandStrategy,
|
|
3431
3895
|
validateConfig,
|
|
3432
3896
|
validateFontCoverage,
|
|
3433
3897
|
visor_theme_schema_default as visorThemeSchema
|