@odla-ai/brand 0.7.1 → 0.7.2

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/README.md CHANGED
@@ -51,7 +51,7 @@ The package is a **schema + rules + an agent skill + a color engine + a token co
51
51
  - **The upload surface is intentionally narrow.** Raster PNG/JPEG/GIF/WebP and
52
52
  PDF inputs are supported. SVG active content and binary font files are not;
53
53
  typography remains an exact, reviewable family/style proposal.
54
- - **Tokens compile to the @odla-ai/ui contract.** `compileBrandTokens` maps swatch roles onto `--ui-*` names — always emitting every required token plus the accent-composing derived set, so a scoped preview island never keeps stale root composites — and `renderTokensCss` emits theme-structured CSS (light, dark, invert). Dark is derived algorithmically and contrast re-tuned per token.
54
+ - **Tokens compile to the @odla-ai/ui contract.** `compileBrandTokens` maps swatch roles onto `--ui-*` names — always emitting every required token plus the accent-composing derived set, so a scoped preview island never keeps stale root composites — and `renderTokensCss` emits theme-structured CSS (light, dark, invert). Dark is derived algorithmically and contrast re-tuned per token. A reviewed typography proposal may also carry bounded `tokenOverrides`; those `--ui-*` component and layout values are validated, stored with the approved section, and compiled into both published token maps.
55
55
  - **Zero runtime dependencies.** The db client is injected structurally (a real `@odla-ai/db` `AdminDb` satisfies `BrandDb`); `@odla-ai/ai` is a types-only optional peer.
56
56
 
57
57
  - **Claude Designs are a first-class input.** A design exported from
@@ -66,13 +66,18 @@ interface PaletteSection {
66
66
  name: string;
67
67
  swatches: Swatch[];
68
68
  }
69
- /** `typography`-kind section content. `scale` is a modular type-scale ratio. */
69
+ /**
70
+ * `typography`-kind section content. `scale` is a modular type-scale ratio.
71
+ * `tokenOverrides` carries bounded `--ui-*` component/layout values through
72
+ * the same reviewed activation that publishes the font choices.
73
+ */
70
74
  interface TypographySection {
71
75
  fontDisplay?: string;
72
76
  fontBody?: string;
73
77
  fontMono?: string;
74
78
  scale?: number;
75
79
  notes?: string;
80
+ tokenOverrides?: Record<string, string>;
76
81
  }
77
82
  /** `voice`-kind section content: tone plus writing principles/examples. */
78
83
  interface VoiceSection {
@@ -66,13 +66,18 @@ interface PaletteSection {
66
66
  name: string;
67
67
  swatches: Swatch[];
68
68
  }
69
- /** `typography`-kind section content. `scale` is a modular type-scale ratio. */
69
+ /**
70
+ * `typography`-kind section content. `scale` is a modular type-scale ratio.
71
+ * `tokenOverrides` carries bounded `--ui-*` component/layout values through
72
+ * the same reviewed activation that publishes the font choices.
73
+ */
70
74
  interface TypographySection {
71
75
  fontDisplay?: string;
72
76
  fontBody?: string;
73
77
  fontMono?: string;
74
78
  scale?: number;
75
79
  notes?: string;
80
+ tokenOverrides?: Record<string, string>;
76
81
  }
77
82
  /** `voice`-kind section content: tone plus writing principles/examples. */
78
83
  interface VoiceSection {
package/dist/index.cjs CHANGED
@@ -598,6 +598,7 @@ var DESIGN_CONTENT_TYPES = /* @__PURE__ */ new Set(["text/html"]);
598
598
  var HEX_RGB = /^#[0-9a-f]{3}$/;
599
599
  var HEX_RRGGBB = /^#[0-9a-f]{6}$/;
600
600
  var HEX_ALPHA = /^#[0-9a-f]{4}$|^#[0-9a-f]{8}$/;
601
+ var UI_TOKEN_NAME = /^--ui-[a-z0-9-]+$/;
601
602
  var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
602
603
  function assertHex(value, label = "color") {
603
604
  if (typeof value !== "string")
@@ -664,6 +665,21 @@ function assertTypographySection(c) {
664
665
  out.scale = c.scale;
665
666
  }
666
667
  if (c.notes !== void 0) out.notes = capString(c.notes, "content.notes", 2e3);
668
+ if (c.tokenOverrides !== void 0) {
669
+ if (!isRecord(c.tokenOverrides))
670
+ throw new BrandInputError("content.tokenOverrides must be an object");
671
+ const entries = Object.entries(c.tokenOverrides);
672
+ if (entries.length > 64)
673
+ throw new BrandInputError("content.tokenOverrides must have at most 64 entries");
674
+ out.tokenOverrides = Object.fromEntries(entries.map(([name, raw]) => {
675
+ if (!UI_TOKEN_NAME.test(name))
676
+ throw new BrandInputError(`content.tokenOverrides.${name} must be a --ui-* token name`);
677
+ const value = capString(raw, `content.tokenOverrides.${name}`, 500);
678
+ if (/[;{}]/.test(value) || /url\s*\(/i.test(value))
679
+ throw new BrandInputError(`content.tokenOverrides.${name} contains an unsafe CSS value`);
680
+ return [name, value];
681
+ }));
682
+ }
667
683
  return out;
668
684
  }
669
685
  function assertVoiceSection(c) {
@@ -4143,7 +4159,10 @@ var proposalGuardEquals = (snapshot) => ({ ...snapshot });
4143
4159
 
4144
4160
  // src/routes/proposal-effects.ts
4145
4161
  async function tokenSnapshot(swatches, typography, input, paletteId, paletteReceiptId, paletteActionDigest, typographyReceiptId, typographyActionDigest) {
4146
- const compiled = compileBrandTokens({ swatches, ...typography ? { typography } : {} });
4162
+ const compiled = compileBrandTokens({
4163
+ swatches,
4164
+ ...typography ? { typography, overrides: typography.tokenOverrides } : {}
4165
+ });
4147
4166
  const sources = {
4148
4167
  paletteId,
4149
4168
  paletteReceiptId,