@odla-ai/brand 0.7.0 → 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 +1 -1
- package/dist/{index-r36KQK5n.d.cts → index-r6SGidYT.d.cts} +6 -1
- package/dist/{index-r36KQK5n.d.ts → index-r6SGidYT.d.ts} +6 -1
- package/dist/index.cjs +28 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +28 -4
- package/dist/index.js.map +1 -1
- package/dist/tokens/index.d.cts +1 -1
- package/dist/tokens/index.d.ts +1 -1
- package/package.json +1 -1
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
|
-
/**
|
|
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
|
-
/**
|
|
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) {
|
|
@@ -4043,7 +4059,8 @@ async function handleDesignPreview(ctx, req, bookId, assetId) {
|
|
|
4043
4059
|
const asset = await linkedAsset(ctx, book, assetId);
|
|
4044
4060
|
if (asset.kind !== DESIGN_ASSET_KIND) throw new BrandNotFoundError(`design ${assetId}`);
|
|
4045
4061
|
const signed = await ctx.db.storage.sign(asset.path, SIGNED_READ_TTL_SECONDS);
|
|
4046
|
-
const
|
|
4062
|
+
const fetchPrivateAsset = ctx.fetchPrivateAsset;
|
|
4063
|
+
const fetched = await fetchPrivateAsset(signed, {
|
|
4047
4064
|
headers: { accept: "text/html" },
|
|
4048
4065
|
redirect: "manual",
|
|
4049
4066
|
signal: req.signal
|
|
@@ -4142,7 +4159,10 @@ var proposalGuardEquals = (snapshot) => ({ ...snapshot });
|
|
|
4142
4159
|
|
|
4143
4160
|
// src/routes/proposal-effects.ts
|
|
4144
4161
|
async function tokenSnapshot(swatches, typography, input, paletteId, paletteReceiptId, paletteActionDigest, typographyReceiptId, typographyActionDigest) {
|
|
4145
|
-
const compiled = compileBrandTokens({
|
|
4162
|
+
const compiled = compileBrandTokens({
|
|
4163
|
+
swatches,
|
|
4164
|
+
...typography ? { typography, overrides: typography.tokenOverrides } : {}
|
|
4165
|
+
});
|
|
4146
4166
|
const sources = {
|
|
4147
4167
|
paletteId,
|
|
4148
4168
|
paletteReceiptId,
|
|
@@ -4928,7 +4948,8 @@ async function handleBrandDiscussionAsset(ctx, req, target) {
|
|
|
4928
4948
|
await ctx.db.storage.sign(before.path, SIGN_TTL_SECONDS)
|
|
4929
4949
|
);
|
|
4930
4950
|
if (!signed) throw new BrandInputError("private asset signing failed");
|
|
4931
|
-
const
|
|
4951
|
+
const fetchPrivateAsset = ctx.fetchPrivateAsset;
|
|
4952
|
+
const response = await fetchPrivateAsset(signed, {
|
|
4932
4953
|
headers: { accept: contentType },
|
|
4933
4954
|
redirect: "manual",
|
|
4934
4955
|
signal: req.signal
|
|
@@ -5165,7 +5186,10 @@ function createBrandRoutes(options) {
|
|
|
5165
5186
|
newId: options.newId ?? (() => crypto.randomUUID()),
|
|
5166
5187
|
maxUploadBytes: options.maxUploadBytes ?? 8 * 1024 * 1024,
|
|
5167
5188
|
discussionBasePath: options.discussionBasePath ?? "/",
|
|
5168
|
-
|
|
5189
|
+
// Bound: this is stored on the context and invoked from route handlers,
|
|
5190
|
+
// where a property call would set the receiver to that context and
|
|
5191
|
+
// Cloudflare's fetch refuses a foreign receiver ("Illegal invocation").
|
|
5192
|
+
fetchPrivateAsset: options.fetchPrivateAsset ?? globalThis.fetch.bind(globalThis),
|
|
5169
5193
|
previewFrameAncestors: options.previewFrameAncestors ?? ["'self'"]
|
|
5170
5194
|
};
|
|
5171
5195
|
return async (req) => {
|