@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.
@@ -224,6 +224,171 @@ interface ThemeBrandResult {
224
224
  warnings: string[];
225
225
  }
226
226
 
227
+ /**
228
+ * Brand-strategy types for the Visor theme engine (VI-505).
229
+ *
230
+ * The Brand Record as validated, serializable, theme-aware data — a top-level
231
+ * `brand-strategy` block in `.visor.yaml`, SIBLING to the asset-only `brand`
232
+ * block (`packages/theme-engine/src/brand/`). The two have different lifecycles
233
+ * and consumers: `brand` declares logo/wordmark/etc. assets; `brand-strategy`
234
+ * declares positioning, personality, pillars, voice, and tone.
235
+ *
236
+ * Shape follows F1 — Visor's authored Brand Record
237
+ * (`docs/brand/visor-brand-record.yaml`) — itself derived from the VI-498
238
+ * research sketch (§5a).
239
+ *
240
+ * This module is deliberately self-contained and engine-decoupled (D4): the
241
+ * types, the pure validators (coherence context is injected, never imported),
242
+ * and the serializer lift cleanly into a future `@loworbitstudio/visor-brand`
243
+ * package. Engine-specific wiring (the known-token set, the comprehensive
244
+ * `validate()` pass, the manifest serialization call-site) lives outside.
245
+ */
246
+ /** Positioning — the onliness, category, and differentiation wedge. */
247
+ interface BrandPositioning {
248
+ /** The single sentence that passes Neumeier's "only" test. */
249
+ onliness: string;
250
+ /** The category the brand competes in (e.g. "design system"). */
251
+ category: string;
252
+ /** What sets the brand apart within that category. */
253
+ differentiation: string;
254
+ }
255
+ /** A personality trait sharpened by its antonym (brand-as-person). */
256
+ interface BrandPersonalityTrait {
257
+ /** The trait (e.g. "precise"). */
258
+ trait: string;
259
+ /** What the trait is NOT — the antonym that earns it its keep (e.g. "fussy"). */
260
+ not: string;
261
+ }
262
+ /**
263
+ * Brand archetype assignment (Pearson & Mark — twelve archetypes). Primary is
264
+ * required; secondary and tertiary are optional refinements.
265
+ */
266
+ interface BrandArchetype {
267
+ primary: string;
268
+ secondary?: string;
269
+ tertiary?: string;
270
+ }
271
+ /**
272
+ * What a pillar governs — the link that turns a slogan into a checkable claim.
273
+ * Targets span three namespaces: design tokens, registry components, and
274
+ * meta-surfaces (the manifest, the CLI, component metadata). The `openness`
275
+ * pillar governs the last of these, which is why `governs` accepts more than
276
+ * tokens/components (F1 schema note). At least one target list is expected.
277
+ */
278
+ interface BrandGoverns {
279
+ /** Semantic token refs (with or without the leading `--`), or `"*"` for all. */
280
+ tokens?: string[];
281
+ /** Registry component names, or `"*"` for all. */
282
+ components?: string[];
283
+ /** Meta-surfaces (e.g. `manifest`, `cli`, `component-metadata`). */
284
+ surfaces?: string[];
285
+ }
286
+ /** A strategic pillar — an essence word made operational. */
287
+ interface BrandPillar {
288
+ /** Stable id (e.g. "coherence"). */
289
+ id: string;
290
+ /** The pillar's claim in one line. */
291
+ statement: string;
292
+ /** What the pillar governs (coherence-checked against the live system). */
293
+ governs: BrandGoverns;
294
+ }
295
+ /** A fixed voice trait with a worked example. */
296
+ interface BrandVoiceTrait {
297
+ /** Trait name (e.g. "plainspoken"). */
298
+ name: string;
299
+ /** What to do. */
300
+ do: string;
301
+ /** What not to do. */
302
+ dont: string;
303
+ /** A worked example sentence (F1 carries one on every trait). */
304
+ example?: string;
305
+ }
306
+ /** Voice — fixed across the brand; never flexes. */
307
+ interface BrandVoice {
308
+ traits: BrandVoiceTrait[];
309
+ }
310
+ /** A single tone entry, keyed (in `tone`) to a recognized UI state. */
311
+ interface BrandToneEntry {
312
+ /** The feeling the copy should evoke in this state. */
313
+ feeling: string;
314
+ /** A worked example message for this state. */
315
+ example: string;
316
+ }
317
+ /** A lexicon pairing — the word to use and the one to avoid. */
318
+ interface BrandLexiconEntry {
319
+ use: string;
320
+ avoid: string;
321
+ }
322
+ /** Visibility of a brand strategy. Client brands are `private`. */
323
+ type BrandVisibility = "public" | "private";
324
+ /**
325
+ * The `brand-strategy` block — strategy + verbal identity as data.
326
+ *
327
+ * `tone` is keyed by UI state (`error`, `success`, …); the keys are validated
328
+ * against the recognized UI states (coherence check D2). All ten fields are
329
+ * required in v1 — F1 authors the full record, and the downstream Workbench
330
+ * surfaces render each section.
331
+ */
332
+ interface BrandStrategy {
333
+ positioning: BrandPositioning;
334
+ /** 2–3 internal-facing core words (Aaker essence). */
335
+ essence: string[];
336
+ personality: BrandPersonalityTrait[];
337
+ archetype: BrandArchetype;
338
+ pillars: BrandPillar[];
339
+ voice: BrandVoice;
340
+ /** Voice flexed per UI state. Keys ∈ recognized UI states. */
341
+ tone: Record<string, BrandToneEntry>;
342
+ lexicon: BrandLexiconEntry[];
343
+ /** Aaker core/extended — the immutable subset, as section names. */
344
+ core: string[];
345
+ visibility: BrandVisibility;
346
+ }
347
+ /**
348
+ * The agent-facing projection of a brand strategy, embedded in
349
+ * `visor-manifest.json` under `brand_strategy` (D3). Structurally identical to
350
+ * the authored shape so an agent reads `voice.traits` / `tone.error` the way it
351
+ * reads a component's `when_to_use`. Only PUBLIC strategies are serialized.
352
+ */
353
+ type SerializedBrandStrategy = BrandStrategy;
354
+ /** Recognized UI states a `tone` key may target (coherence check D2). */
355
+ declare const DEFAULT_BRAND_STRATEGY_TONE_STATES: readonly string[];
356
+ /** Recognized meta-surfaces a pillar may govern (coherence check D2). */
357
+ declare const DEFAULT_BRAND_STRATEGY_SURFACES: readonly string[];
358
+ /** Valid `visibility` values. */
359
+ declare const BRAND_VISIBILITIES: readonly BrandVisibility[];
360
+ /** Wildcard accepted in any `governs` target list — matches all of that namespace. */
361
+ declare const GOVERNS_WILDCARD = "*";
362
+ type BrandStrategyIssueSeverity = "error" | "warning";
363
+ /** A single validation finding. Mirrors the engine's `ValidationIssue` shape. */
364
+ interface BrandStrategyIssue {
365
+ severity: BrandStrategyIssueSeverity;
366
+ code: string;
367
+ message: string;
368
+ path?: string;
369
+ }
370
+ /** Structured validation result for a brand-strategy block. */
371
+ interface BrandStrategyValidationResult {
372
+ valid: boolean;
373
+ errors: BrandStrategyIssue[];
374
+ warnings: BrandStrategyIssue[];
375
+ }
376
+ /**
377
+ * Coherence context — the real-world sets a strategy's links are checked
378
+ * against. Injected by the caller (the engine `validate()` pass, the manifest
379
+ * builder, or a test) so the validator stays pure and engine-decoupled (D4).
380
+ */
381
+ interface BrandStrategyContext {
382
+ /** Known semantic token names WITHOUT the leading `--` (e.g. "primary", "surface-card"). When omitted, token coherence is skipped. */
383
+ tokens?: ReadonlySet<string>;
384
+ /** Known registry component names. When omitted, only `"*"` is accepted for components. */
385
+ components?: ReadonlySet<string>;
386
+ /** Recognized meta-surfaces. Defaults to {@link DEFAULT_BRAND_STRATEGY_SURFACES}. */
387
+ surfaces?: ReadonlySet<string>;
388
+ /** Recognized UI states for `tone` keys. Defaults to {@link DEFAULT_BRAND_STRATEGY_TONE_STATES}. */
389
+ states?: ReadonlySet<string>;
390
+ }
391
+
227
392
  /**
228
393
  * Types for the Visor Theme Engine
229
394
  *
@@ -381,6 +546,15 @@ interface VisorThemeConfig {
381
546
  * Omitted → the Visor default brand (stock themes are not logo-less).
382
547
  */
383
548
  brand?: VisorBrand;
549
+ /**
550
+ * Brand-strategy block (VI-505) — positioning, personality, pillars, voice,
551
+ * and tone as validated, serializable data. SIBLING to `brand` (assets):
552
+ * different lifecycle and consumer. NOT expanded into derived CSS values;
553
+ * coherence-checked (pillars→tokens/components/surfaces; tone→UI states) and
554
+ * serialized to the agent manifest. Liftable into a future
555
+ * `@loworbitstudio/visor-brand` package (D4).
556
+ */
557
+ "brand-strategy"?: BrandStrategy;
384
558
  spacing?: {
385
559
  base?: number;
386
560
  };
@@ -605,4 +779,4 @@ interface ThemeData {
605
779
  output: ThemeOutput;
606
780
  }
607
781
 
608
- export { type BrandSlot as B, type ColorRole as C, type FontResolveOptions as F, type GoogleFontEntry as G, type OKLCH as O, type ParsedColor as P, type ResolvedThemeConfig as R, type SelectiveShadeScale as S, type ThemeFontResult as T, type VisorTypography as V, type FontResolution as a, type FontDisplayStrategy as b, type VisorBrand as c, type BrandSource as d, type BrandResolution as e, type ThemeBrandResult as f, type GeneratedPrimitives as g, type ThemeOutput as h, type ThemeData as i, type VisorThemeConfig as j, type FullShadeScale as k, type RGB as l, type SemanticTokens as m, type ShadeStep as n, BRAND_VARIANTS as o, type BrandVariant as p, type ColorFormat as q, type FontSource as r, type RGBA as s, type SemanticTokenValue as t };
782
+ export { type BrandPositioning as A, type BrandSlot as B, type ColorRole as C, type BrandStrategyIssueSeverity as D, type BrandToneEntry as E, type FontResolveOptions as F, type GoogleFontEntry as G, type BrandVariant as H, type BrandVisibility as I, type BrandVoice as J, type BrandVoiceTrait as K, type ColorFormat as L, DEFAULT_BRAND_STRATEGY_SURFACES as M, DEFAULT_BRAND_STRATEGY_TONE_STATES as N, type OKLCH as O, type ParsedColor as P, type FontSource as Q, type ResolvedThemeConfig as R, type SerializedBrandStrategy as S, type ThemeFontResult as T, GOVERNS_WILDCARD as U, type VisorTypography as V, type RGBA as W, type SemanticTokenValue as X, type FontResolution as a, type FontDisplayStrategy as b, type VisorBrand as c, type BrandSource as d, type BrandResolution as e, type ThemeBrandResult as f, type BrandStrategy as g, type BrandStrategyContext as h, type BrandStrategyIssue as i, type BrandStrategyValidationResult as j, type GeneratedPrimitives as k, type ThemeOutput as l, type ThemeData as m, type VisorThemeConfig as n, type FullShadeScale as o, type SelectiveShadeScale as p, type RGB as q, type SemanticTokens as r, type ShadeStep as s, BRAND_VARIANTS as t, BRAND_VISIBILITIES as u, type BrandArchetype as v, type BrandGoverns as w, type BrandLexiconEntry as x, type BrandPersonalityTrait as y, type BrandPillar as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loworbitstudio/visor-theme-engine",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "Theme engine for the Visor design system — shade generation, token mapping, font resolution, and import/export for .visor.yaml themes.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "label": {
24
24
  "type": "string",
25
- "description": "Human-readable display name for the theme (e.g. 'Blacklight Pro'). Overrides the name-derived label in the docs theme switcher. Optional."
25
+ "description": "Human-readable display name for the theme (e.g. 'My Brand Pro'). Overrides the name-derived label in the docs theme switcher. Optional."
26
26
  },
27
27
  "default-mode": {
28
28
  "type": "string",
@@ -293,6 +293,59 @@
293
293
  ]
294
294
  }
295
295
  },
296
+ "brand-strategy": {
297
+ "type": "object",
298
+ "description": "Brand-strategy block (VI-505) — 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`.",
299
+ "additionalProperties": false,
300
+ "required": ["positioning", "essence", "personality", "archetype", "pillars", "voice", "tone", "lexicon", "core", "visibility"],
301
+ "properties": {
302
+ "positioning": { "$ref": "#/$defs/brandPositioning" },
303
+ "essence": {
304
+ "type": "array",
305
+ "items": { "type": "string" },
306
+ "minItems": 1,
307
+ "description": "2–3 internal-facing core words (Aaker essence)."
308
+ },
309
+ "personality": {
310
+ "type": "array",
311
+ "items": { "$ref": "#/$defs/brandPersonalityTrait" },
312
+ "minItems": 1,
313
+ "description": "Brand-as-person traits, each sharpened by its antonym."
314
+ },
315
+ "archetype": { "$ref": "#/$defs/brandArchetype" },
316
+ "pillars": {
317
+ "type": "array",
318
+ "items": { "$ref": "#/$defs/brandPillar" },
319
+ "minItems": 1,
320
+ "description": "Strategic pillars — essence words made operational, each governing real tokens/components/surfaces."
321
+ },
322
+ "voice": { "$ref": "#/$defs/brandVoice" },
323
+ "tone": {
324
+ "type": "object",
325
+ "description": "Voice flexed per UI state. Keys must be recognized UI states.",
326
+ "minProperties": 1,
327
+ "propertyNames": { "enum": ["error", "success", "warning", "info", "empty", "loading", "validation-warning"] },
328
+ "additionalProperties": { "$ref": "#/$defs/brandToneEntry" }
329
+ },
330
+ "lexicon": {
331
+ "type": "array",
332
+ "items": { "$ref": "#/$defs/brandLexiconEntry" },
333
+ "minItems": 1,
334
+ "description": "Words to use and the ones to avoid."
335
+ },
336
+ "core": {
337
+ "type": "array",
338
+ "items": { "type": "string" },
339
+ "minItems": 1,
340
+ "description": "Aaker core/extended — the immutable subset, as section names."
341
+ },
342
+ "visibility": {
343
+ "type": "string",
344
+ "enum": ["public", "private"],
345
+ "description": "Client brands are private and are omitted from the public agent manifest."
346
+ }
347
+ }
348
+ },
296
349
  "spacing": {
297
350
  "type": "object",
298
351
  "description": "Spacing configuration.",
@@ -460,6 +513,101 @@
460
513
  "description": "Letter spacing in logical pixels (Flutter TextStyle.letterSpacing). Material defaults include negative values, e.g. -0.25 for displayLarge."
461
514
  }
462
515
  }
516
+ },
517
+ "brandPositioning": {
518
+ "type": "object",
519
+ "description": "Positioning — the onliness, category, and differentiation wedge.",
520
+ "additionalProperties": false,
521
+ "required": ["onliness", "category", "differentiation"],
522
+ "properties": {
523
+ "onliness": { "type": "string", "minLength": 1, "description": "The single sentence that passes Neumeier's \"only\" test." },
524
+ "category": { "type": "string", "minLength": 1, "description": "The category the brand competes in (e.g. \"design system\")." },
525
+ "differentiation": { "type": "string", "minLength": 1, "description": "What sets the brand apart within that category." }
526
+ }
527
+ },
528
+ "brandPersonalityTrait": {
529
+ "type": "object",
530
+ "description": "A personality trait sharpened by its antonym (brand-as-person).",
531
+ "additionalProperties": false,
532
+ "required": ["trait", "not"],
533
+ "properties": {
534
+ "trait": { "type": "string", "minLength": 1 },
535
+ "not": { "type": "string", "minLength": 1, "description": "The antonym — what the trait is not." }
536
+ }
537
+ },
538
+ "brandArchetype": {
539
+ "type": "object",
540
+ "description": "Brand archetype assignment (Pearson & Mark). Primary required; secondary/tertiary optional.",
541
+ "additionalProperties": false,
542
+ "required": ["primary"],
543
+ "properties": {
544
+ "primary": { "type": "string", "minLength": 1 },
545
+ "secondary": { "type": "string", "minLength": 1 },
546
+ "tertiary": { "type": "string", "minLength": 1 }
547
+ }
548
+ },
549
+ "brandGoverns": {
550
+ "type": "object",
551
+ "description": "What a pillar governs — design tokens, registry components, and/or meta-surfaces. At least one list is expected; use \"*\" for all of a namespace.",
552
+ "additionalProperties": false,
553
+ "minProperties": 1,
554
+ "properties": {
555
+ "tokens": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "Semantic token refs (with or without the leading --), or \"*\"." },
556
+ "components": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "Registry component names, or \"*\" for all." },
557
+ "surfaces": { "type": "array", "items": { "type": "string", "enum": ["manifest", "cli", "component-metadata"] }, "minItems": 1, "description": "Meta-surfaces a pillar may govern." }
558
+ }
559
+ },
560
+ "brandPillar": {
561
+ "type": "object",
562
+ "description": "A strategic pillar — an essence word made operational.",
563
+ "additionalProperties": false,
564
+ "required": ["id", "statement", "governs"],
565
+ "properties": {
566
+ "id": { "type": "string", "minLength": 1 },
567
+ "statement": { "type": "string", "minLength": 1, "description": "The pillar's claim in one line." },
568
+ "governs": { "$ref": "#/$defs/brandGoverns" }
569
+ }
570
+ },
571
+ "brandVoice": {
572
+ "type": "object",
573
+ "description": "Voice — fixed across the brand; never flexes.",
574
+ "additionalProperties": false,
575
+ "required": ["traits"],
576
+ "properties": {
577
+ "traits": { "type": "array", "items": { "$ref": "#/$defs/brandVoiceTrait" }, "minItems": 1 }
578
+ }
579
+ },
580
+ "brandVoiceTrait": {
581
+ "type": "object",
582
+ "description": "A fixed voice trait with a worked example.",
583
+ "additionalProperties": false,
584
+ "required": ["name", "do", "dont"],
585
+ "properties": {
586
+ "name": { "type": "string", "minLength": 1 },
587
+ "do": { "type": "string", "minLength": 1, "description": "What to do." },
588
+ "dont": { "type": "string", "minLength": 1, "description": "What not to do." },
589
+ "example": { "type": "string", "minLength": 1, "description": "A worked example sentence." }
590
+ }
591
+ },
592
+ "brandToneEntry": {
593
+ "type": "object",
594
+ "description": "A single tone entry, keyed (in `tone`) to a recognized UI state.",
595
+ "additionalProperties": false,
596
+ "required": ["feeling", "example"],
597
+ "properties": {
598
+ "feeling": { "type": "string", "minLength": 1, "description": "The feeling the copy should evoke in this state." },
599
+ "example": { "type": "string", "minLength": 1, "description": "A worked example message for this state." }
600
+ }
601
+ },
602
+ "brandLexiconEntry": {
603
+ "type": "object",
604
+ "description": "A lexicon pairing — the word to use and the one to avoid.",
605
+ "additionalProperties": false,
606
+ "required": ["use", "avoid"],
607
+ "properties": {
608
+ "use": { "type": "string", "minLength": 1 },
609
+ "avoid": { "type": "string", "minLength": 1 }
610
+ }
463
611
  }
464
612
  }
465
613
  }