@apollion-dsi/tokens 5.2.0 → 5.4.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/README.md +5 -1
- package/lib/build.cjs +11 -6
- package/lib/build.esm.js +11 -6
- package/lib/builders/colors.d.ts +3 -3
- package/lib/cli.mjs +13 -8
- package/lib/config-loader.cjs +2 -2
- package/lib/config-loader.esm.js +1 -1
- package/lib/config-schema.cjs +1 -1
- package/lib/config-schema.esm.js +1 -1
- package/lib/runtime/v1.cjs +1 -1
- package/lib/runtime/v1.esm.js +1 -1
- package/package.json +3 -2
- package/src/build.ts +50 -4
- package/src/builders/colors.ts +45 -9
- package/src/cli.ts +1 -1
- package/src/config-schema-runtime.ts +2 -0
- package/src/config-schema.ts +10 -1
- package/src/ir.ts +3 -0
- package/src/manifest.ts +1 -1
- package/src/renderers/css.ts +36 -1
- package/src/renderers/design-md.ts +107 -0
- package/src/renderers/resolver.ts +4 -3
- package/src/renderers/tailwind.ts +195 -0
- package/src/renderers/ts.ts +16 -2
- package/src/theme-factory.ts +32 -8
package/lib/config-loader.esm.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var h=Object.defineProperty;var r=(n,i)=>h(n,"name",{value:i,configurable:!0});import{readFile as w}from"node:fs/promises";import{resolve as x}from"node:path";import{createContext as y,Script as C}from"node:vm";import{z as o}from"zod";var e=o.string().regex(/^#([0-9a-fA-F]{3}){1,2}$|^transparent$|^rgb\(.*\)$|^oklch\(.*\)$/,{message:'Must be hex, rgb(), oklch(), or "transparent"'}),b=o.object({baseDark:o.string().optional(),baseLight:o.string().optional(),deepDark:e,deepLight:e,transparent:o.string().optional(),main:e.optional(),opposite:e.optional(),complementary:e.optional(),information:e.optional(),success:e.optional(),danger:e.optional(),warning:e.optional(),primary:e.optional(),secondary:e.optional(),tertiary:e.optional()}).passthrough(),u=o.object({brands:o.record(o.string(),b).refine(n=>Object.keys(n).length>0,{message:"apollion.config.mjs: brands must contain at least 1 entry"}),modes:o.array(o.enum(["light","dark"])).optional(),surfaces:o.array(o.enum(["positive","negative"])).optional(),dimensions:o.array(o.enum(["compact","normal","spacious"])).optional(),output:o.object({runtime:o.boolean().optional(),css:o.boolean().optional(),json:o.boolean().optional(),ts:o.boolean().optional()}).passthrough().optional()}).passthrough();var _=[/\brequire\s*\(/,/\bimport\s*\(/,/^\s*import\s+/m],k=1e3;function v(n){return n}r(v,"defineConfig");var f=class f extends Error{constructor(s,p){super(s);this.configPath=p;this.name="ConfigLoadError"}};r(f,"ConfigLoadError");var a=f;async function I(n){let i=x(n.configPath),s=await w(i,"utf8");for(let t of _)if(t.test(s))throw new a(`apollion.config: dynamic require/import not allowed (matched ${t}). Declarative config only \u2014 see ADR-006 \xA73.8 B3.`,i);let p=s.replace(/(^|\n)\s*export\s+default\s+/,(t,l)=>`${l}__apollion_config_export = `);if(p===s)throw new a("apollion.config: missing `export default <object>;` \u2014 config must export a default object literal.",i);let g={__apollion_config_export:void 0,console:j(),Symbol,Number,String,Boolean,Array,Object,JSON,Math,defineConfig:r(t=>t,"defineConfig")},d=y(g);try{new C(p,{filename:i}).runInContext(d,{timeout:k})}catch(t){throw new a(`apollion.config: evaluation failed \u2014 ${t instanceof Error?t.message:String(t)}`,i)}let m=g.__apollion_config_export;if(m===void 0)throw new a("apollion.config: `export default` produced undefined.",i);let c=u.safeParse(m);if(!c.success){let t=c.error.issues.map(l=>` ${l.path.join(".")||"(root)"}: ${l.message}`).join(`
|
|
1
|
+
var h=Object.defineProperty;var r=(n,i)=>h(n,"name",{value:i,configurable:!0});import{readFile as w}from"node:fs/promises";import{resolve as x}from"node:path";import{createContext as y,Script as C}from"node:vm";import{z as o}from"zod";var e=o.string().regex(/^#([0-9a-fA-F]{3}){1,2}$|^transparent$|^rgb\(.*\)$|^oklch\(.*\)$/,{message:'Must be hex, rgb(), oklch(), or "transparent"'}),b=o.object({baseDark:o.string().optional(),baseLight:o.string().optional(),deepDark:e,deepLight:e,transparent:o.string().optional(),main:e.optional(),opposite:e.optional(),complementary:e.optional(),information:e.optional(),success:e.optional(),danger:e.optional(),warning:e.optional(),primary:e.optional(),secondary:e.optional(),tertiary:e.optional()}).passthrough(),u=o.object({brands:o.record(o.string(),b).refine(n=>Object.keys(n).length>0,{message:"apollion.config.mjs: brands must contain at least 1 entry"}),modes:o.array(o.enum(["light","dark"])).optional(),surfaces:o.array(o.enum(["positive","negative"])).optional(),dimensions:o.array(o.enum(["compact","normal","spacious"])).optional(),output:o.object({runtime:o.boolean().optional(),css:o.boolean().optional(),json:o.boolean().optional(),ts:o.boolean().optional(),designMd:o.boolean().optional(),tailwind:o.boolean().optional()}).passthrough().optional()}).passthrough();var _=[/\brequire\s*\(/,/\bimport\s*\(/,/^\s*import\s+/m],k=1e3;function v(n){return n}r(v,"defineConfig");var f=class f extends Error{constructor(s,p){super(s);this.configPath=p;this.name="ConfigLoadError"}};r(f,"ConfigLoadError");var a=f;async function I(n){let i=x(n.configPath),s=await w(i,"utf8");for(let t of _)if(t.test(s))throw new a(`apollion.config: dynamic require/import not allowed (matched ${t}). Declarative config only \u2014 see ADR-006 \xA73.8 B3.`,i);let p=s.replace(/(^|\n)\s*export\s+default\s+/,(t,l)=>`${l}__apollion_config_export = `);if(p===s)throw new a("apollion.config: missing `export default <object>;` \u2014 config must export a default object literal.",i);let g={__apollion_config_export:void 0,console:j(),Symbol,Number,String,Boolean,Array,Object,JSON,Math,defineConfig:r(t=>t,"defineConfig")},d=y(g);try{new C(p,{filename:i}).runInContext(d,{timeout:k})}catch(t){throw new a(`apollion.config: evaluation failed \u2014 ${t instanceof Error?t.message:String(t)}`,i)}let m=g.__apollion_config_export;if(m===void 0)throw new a("apollion.config: `export default` produced undefined.",i);let c=u.safeParse(m);if(!c.success){let t=c.error.issues.map(l=>` ${l.path.join(".")||"(root)"}: ${l.message}`).join(`
|
|
2
2
|
`);throw new a(`apollion.config: schema validation failed \u2014
|
|
3
3
|
${t}`,i)}return c.data}r(I,"loadConfig");function j(){return{log:r((...n)=>console.log("[apollion.config]",...n),"log"),warn:r((...n)=>console.warn("[apollion.config]",...n),"warn"),error:r((...n)=>console.error("[apollion.config]",...n),"error")}}r(j,"makeReadOnlyConsole");export{a as ConfigLoadError,v as defineConfig,I as loadConfig};
|
package/lib/config-schema.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var c=(o,n)=>t(o,"name",{value:n,configurable:!0});var
|
|
1
|
+
"use strict";var t=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var c=(o,n)=>t(o,"name",{value:n,configurable:!0});var g=(o,n)=>{for(var s in n)t(o,s,{get:n[s],enumerable:!0})},D=(o,n,s,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let e of f(n))!b.call(o,e)&&e!==s&&t(o,e,{get:()=>n[e],enumerable:!(r=u(n,e))||r.enumerable});return o};var S=o=>D(t({},"__esModule",{value:!0}),o);var M={};g(M,{expandVariants:()=>C,variantName:()=>E});module.exports=S(M);var x=["light","dark"],A=["positive","negative"],y=["compact","normal","spacious"];function C(o){let n=o.modes??x,s=o.surfaces??A,r=o.dimensions??y,e=Object.entries(o.brands).sort(([i],[a])=>i.localeCompare(a)),d=[];for(let[i,a]of e)for(let l of n)for(let p of s)for(let m of r)d.push({brand:i,colors:a,mode:l,surface:p,dimension:m});return d}c(C,"expandVariants");function E(o){return`${o.brand}.${o.mode}.${o.surface}.${o.dimension}`}c(E,"variantName");0&&(module.exports={expandVariants,variantName});
|
package/lib/config-schema.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var m=Object.defineProperty;var r=(o,n)=>m(o,"name",{value:n,configurable:!0});var u=["light","dark"],f=["positive","negative"],b=["compact","normal","spacious"];function
|
|
1
|
+
var m=Object.defineProperty;var r=(o,n)=>m(o,"name",{value:n,configurable:!0});var u=["light","dark"],f=["positive","negative"],b=["compact","normal","spacious"];function D(o){let n=o.modes??u,i=o.surfaces??f,a=o.dimensions??b,c=Object.entries(o.brands).sort(([e],[s])=>e.localeCompare(s)),t=[];for(let[e,s]of c)for(let d of n)for(let l of i)for(let p of a)t.push({brand:e,colors:s,mode:d,surface:l,dimension:p});return t}r(D,"expandVariants");function S(o){return`${o.brand}.${o.mode}.${o.surface}.${o.dimension}`}r(S,"variantName");export{D as expandVariants,S as variantName};
|
package/lib/runtime/v1.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var g=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var l=(e,t)=>g(e,"name",{value:t,configurable:!0});var R=(e,t)=>{for(var r in t)g(e,r,{get:t[r],enumerable:!0})},F=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of E(t))!D.call(e,n)&&n!==r&&g(e,n,{get:()=>t[n],enumerable:!(o=P(t,n))||o.enumerable});return e};var G=e=>F(g({},"__esModule",{value:!0}),e);var V={};R(V,{TOKENS_RUNTIME_API_VERSION:()=>U,createFoundation:()=>C.createFoundation,createSpacing:()=>O,getOppositeColor:()=>A,invertForSurface:()=>S.invertForSurface,mountGreyscaleColors:()=>N,mountNeutralColors:()=>M,mountSingleColor:()=>v});module.exports=G(V);var a=require("culori");var h=(0,a.converter)("oklch");function i(e,t,r){let o=(0,a.parse)(e),n=(0,a.parse)(t);if(!o||!n)return e;let c=(0,a.interpolate)([o,n],"oklch");return(0,a.formatHex)(c(r))??e}l(i,"mixOklch");function L(e){return e?typeof e=="string"?e:e.base:"#000"}l(L,"coerceToHex");function A(e){let t=L(e),r=h((0,a.parse)(t));if(!r)return t;let o={...r,h:((r.h??0)+180)%360};return(0,a.formatHex)(o)??t}l(A,"getOppositeColor");var d=1.3,j=.05,T=.04,f=l((e,t)=>{let r=(0,a.parse)(e),o=(0,a.parse)(t);return!r||!o?1:(0,a.wcagContrast)(r,o)},"contrastRatio"),y=l(e=>{let t=h((0,a.parse)(e));return t?t.l??0:null},"oklchLightness"),B=4.5;function x(e,t,r=B){let o=(0,a.parse)(t),n=h((0,a.parse)(e));if(!o||!n)return e;let c=f("#fff",t)>=f("#000",t)?.04:-.04;for(let s=0;s<25;s+=1){let u=(0,a.formatHex)(n)??e,p=(0,a.parse)(u);if(p&&(0,a.wcagContrast)(p,o)>=r)return u;let m=Math.min(1,Math.max(0,(n.l??0)+c));if(m===(n.l??0))break;n={...n,l:m}}return(0,a.formatHex)(n)??e}l(x,"readableColor");var I=l((e,t)=>{if(f(e,t)>=d)return!0;let r=y(e),o=y(t);return r!==null&&o!==null&&Math.abs(r-o)>=j},"isPerceptibleState"),H=l(e=>e?f("#fff",e)>f("#000",e):!1,"isDarkSurface");function b(e,t,r){let o=h((0,a.parse)(e));if(!o)return null;let n={...o,l:r??o.l??0},c=t?T:-T;for(let s=0;s<25;s+=1){let u=Math.min(1,Math.max(0,(n.l??0)+c));if(u===(n.l??0))break;n={...n,l:u};let p=(0,a.formatHex)(n);if(p&&f(e,p)>=d)return p}return(0,a.formatHex)(n)??null}l(b,"derivePerceptibleState");function k(e,t,r){let o=b(e,t,r);if(o&&f(e,o)>=d)return o;let n=b(e,!t);return n&&f(e,n)>=d?n:o??n}l(k,"perceptibleStateFor");function v(e,t){let r=typeof e=="object"&&e!==null?e:void 0,o=L(e),n=t.deepDark,c=t.deepLight,s={base:o,dark:i(o,n,.6),action:i(o,n,.2),active:i(o,c,.6),light:i(o,c,.9),onLight:o};if((0,a.parse)(o)){let u=H(t.baseLight),p=I(o,s.action);if(p||(s.action=k(o,u)??s.action),!I(o,s.active)){let m=p?void 0:y(s.action)??void 0;s.active=k(o,u,m)??s.active}}if(s.onLight=x(s.action,s.light),r){let u=r.action??s.action,p=r.light??s.light;return{base:o,dark:r.dark??s.dark,action:u,active:r.active??s.active,light:p,onLight:r.onLight??x(u,p)}}return s}l(v,"mountSingleColor");function N(e){let t=e.deepLight,r=e.deepDark;return{0:t,5:i(t,r,.05),10:i(t,r,.1),20:i(t,r,.2),30:i(t,r,.3),40:i(t,r,.4),50:i(t,r,.5),60:i(t,r,.6),70:i(t,r,.7),80:i(t,r,.8),90:i(t,r,.9),100:i(t,r,1)}}l(N,"mountGreyscaleColors");function M(e){let t=e.deepLight,r=e.deepDark,o=typeof e.primary=="object"&&e.primary?e.primary.base:e.primary,n=l(s=>1/17*(s-1),"interpolation"),c=i(o,r,.5);return{0:i(c,t,n(18)),5:i(c,t,n(17.5)),10:i(c,t,n(17)),20:i(c,t,n(16)),30:i(c,t,n(15)),40:i(c,t,n(14)),50:i(c,t,n(13)),60:i(c,t,n(12)),70:i(c,t,n(11)),80:i(c,t,n(10)),90:i(c,t,n(9)),100:i(c,t,n(8)),110:i(c,t,n(7)),120:i(c,t,n(6)),130:i(c,t,n(5)),140:i(c,t,n(4)),150:i(c,t,n(3)),160:i(c,t,n(2)),170:c,180:i(c,r,.25)}}l(M,"mountNeutralColors");var _=require("@apollion-dsi/core/themes/dimension");function O({multiplier:e=.25,alias:t},r){let o=r!==void 0?_.DIMENSION_MULTIPLIERS[r]:e;return(...n)=>n.map(c=>{let s=typeof c=="string"?t[c]:c;return`${o*s}rem`}).join(" ")}l(O,"createSpacing");var C=require("@apollion-dsi/core/themes/foundation");var S=require("@apollion-dsi/core/themes/surface");var U="v1";0&&(module.exports={TOKENS_RUNTIME_API_VERSION,createFoundation,createSpacing,getOppositeColor,invertForSurface,mountGreyscaleColors,mountNeutralColors,mountSingleColor});
|
package/lib/runtime/v1.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var L=Object.defineProperty;var s=(e,t)=>L(e,"name",{value:t,configurable:!0});import{converter as A,formatHex as m,interpolate as v,parse as u,wcagContrast as b}from"culori";var h=A("oklch");function i(e,t,r){let o=u(e),n=u(t);if(!o||!n)return e;let c=v([o,n],"oklch");return m(c(r))??e}s(i,"mixOklch");function k(e){return e?typeof e=="string"?e:e.base:"#000"}s(k,"coerceToHex");function N(e){let t=k(e),r=h(u(t));if(!r)return t;let o={...r,h:((r.h??0)+180)%360};return m(o)??t}s(N,"getOppositeColor");var d=1.3,M=.05,C=.04,f=s((e,t)=>{let r=u(e),o=u(t);return!r||!o?1:b(r,o)},"contrastRatio"),y=s(e=>{let t=h(u(e));return t?t.l??0:null},"oklchLightness"),_=4.5;function S(e,t,r=_){let o=u(t),n=h(u(e));if(!o||!n)return e;let c=f("#fff",t)>=f("#000",t)?.04:-.04;for(let a=0;a<25;a+=1){let p=m(n)??e,l=u(p);if(l&&b(l,o)>=r)return p;let g=Math.min(1,Math.max(0,(n.l??0)+c));if(g===(n.l??0))break;n={...n,l:g}}return m(n)??e}s(S,"readableColor");var T=s((e,t)=>{if(f(e,t)>=d)return!0;let r=y(e),o=y(t);return r!==null&&o!==null&&Math.abs(r-o)>=M},"isPerceptibleState"),O=s(e=>e?f("#fff",e)>f("#000",e):!1,"isDarkSurface");function x(e,t,r){let o=h(u(e));if(!o)return null;let n={...o,l:r??o.l??0},c=t?C:-C;for(let a=0;a<25;a+=1){let p=Math.min(1,Math.max(0,(n.l??0)+c));if(p===(n.l??0))break;n={...n,l:p};let l=m(n);if(l&&f(e,l)>=d)return l}return m(n)??null}s(x,"derivePerceptibleState");function I(e,t,r){let o=x(e,t,r);if(o&&f(e,o)>=d)return o;let n=x(e,!t);return n&&f(e,n)>=d?n:o??n}s(I,"perceptibleStateFor");function P(e,t){let r=typeof e=="object"&&e!==null?e:void 0,o=k(e),n=t.deepDark,c=t.deepLight,a={base:o,dark:i(o,n,.6),action:i(o,n,.2),active:i(o,c,.6),light:i(o,c,.9),onLight:o};if(u(o)){let p=O(t.baseLight),l=T(o,a.action);if(l||(a.action=I(o,p)??a.action),!T(o,a.active)){let g=l?void 0:y(a.action)??void 0;a.active=I(o,p,g)??a.active}}if(a.onLight=S(a.action,a.light),r){let p=r.action??a.action,l=r.light??a.light;return{base:o,dark:r.dark??a.dark,action:p,active:r.active??a.active,light:l,onLight:r.onLight??S(p,l)}}return a}s(P,"mountSingleColor");function E(e){let t=e.deepLight,r=e.deepDark;return{0:t,5:i(t,r,.05),10:i(t,r,.1),20:i(t,r,.2),30:i(t,r,.3),40:i(t,r,.4),50:i(t,r,.5),60:i(t,r,.6),70:i(t,r,.7),80:i(t,r,.8),90:i(t,r,.9),100:i(t,r,1)}}s(E,"mountGreyscaleColors");function D(e){let t=e.deepLight,r=e.deepDark,o=typeof e.primary=="object"&&e.primary?e.primary.base:e.primary,n=s(a=>1/17*(a-1),"interpolation"),c=i(o,r,.5);return{0:i(c,t,n(18)),5:i(c,t,n(17.5)),10:i(c,t,n(17)),20:i(c,t,n(16)),30:i(c,t,n(15)),40:i(c,t,n(14)),50:i(c,t,n(13)),60:i(c,t,n(12)),70:i(c,t,n(11)),80:i(c,t,n(10)),90:i(c,t,n(9)),100:i(c,t,n(8)),110:i(c,t,n(7)),120:i(c,t,n(6)),130:i(c,t,n(5)),140:i(c,t,n(4)),150:i(c,t,n(3)),160:i(c,t,n(2)),170:c,180:i(c,r,.25)}}s(D,"mountNeutralColors");import{DIMENSION_MULTIPLIERS as R}from"@apollion-dsi/core/themes/dimension";function F({multiplier:e=.25,alias:t},r){let o=r!==void 0?R[r]:e;return(...n)=>n.map(c=>{let a=typeof c=="string"?t[c]:c;return`${o*a}rem`}).join(" ")}s(F,"createSpacing");import{createFoundation as G}from"@apollion-dsi/core/themes/foundation";import{invertForSurface as j}from"@apollion-dsi/core/themes/surface";var W="v1";export{W as TOKENS_RUNTIME_API_VERSION,G as createFoundation,F as createSpacing,N as getOppositeColor,j as invertForSurface,E as mountGreyscaleColors,D as mountNeutralColors,P as mountSingleColor};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apollion-dsi/tokens",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"description": "Apollion Design System — Framework-agnostic design tokens",
|
|
5
5
|
"homepage": "https://www.apollion.com.br",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"./dist/json/sets/*": "./dist/json/sets/*",
|
|
38
38
|
"./dist/json/*": "./dist/json/*",
|
|
39
39
|
"./dist/ts/*": "./dist/ts/*",
|
|
40
|
+
"./dist/tailwind/*": "./dist/tailwind/*",
|
|
40
41
|
"./dist/manifest.json": "./dist/manifest.json"
|
|
41
42
|
},
|
|
42
43
|
"sideEffects": false,
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
},
|
|
67
68
|
"devDependencies": {
|
|
68
69
|
"@apollion-dsi/core": "workspace:*",
|
|
69
|
-
"@apollion-dsi/eslint-config": "0.9.
|
|
70
|
+
"@apollion-dsi/eslint-config": "0.9.1",
|
|
70
71
|
"@types/culori": "4.0.1",
|
|
71
72
|
"@types/jest": "30.0.0",
|
|
72
73
|
"@types/node": "25.7.0",
|
package/src/build.ts
CHANGED
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
* Verified by `__tests__/build.test.ts`.
|
|
20
20
|
*
|
|
21
21
|
* **`--check` mode (CI gate):** read existing `manifest.json`, recompute
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* `configHash` from current config, then re-hash every file the manifest
|
|
23
|
+
* lists and compare against its recorded sha256 — `configHash` alone only
|
|
24
|
+
* proves "the input didn't change", not "dist/ still reflects it" (a file
|
|
25
|
+
* can be hand-edited or corrupted without touching config). Exit 1 on
|
|
26
|
+
* drift. Never writes anything. Suitable for pre-push hook or local lint.
|
|
24
27
|
*
|
|
25
28
|
* @see ADR-006 §3.5 (atomic+idempotent build)
|
|
26
29
|
* @see PRD-002 §S5 acceptance (rerun byte-identical, --check blocks drift)
|
|
@@ -31,7 +34,9 @@ import { tmpdir } from 'node:os';
|
|
|
31
34
|
import { dirname, join } from 'node:path';
|
|
32
35
|
|
|
33
36
|
import { renderCss } from './renderers/css';
|
|
37
|
+
import { renderDesignMd } from './renderers/design-md';
|
|
34
38
|
import { renderBaseSet, renderColorSet, renderFontSet, renderResolver, renderSpacingSet } from './renderers/resolver';
|
|
39
|
+
import { renderTailwindPreset, renderTwMergeExtension } from './renderers/tailwind';
|
|
35
40
|
import { renderTs } from './renderers/ts';
|
|
36
41
|
import type { ApollionConfig, Variant } from './config-schema';
|
|
37
42
|
import { expandVariants, variantName } from './config-schema';
|
|
@@ -127,11 +132,37 @@ export async function build(opts: BuildOptions): Promise<BuildResult> {
|
|
|
127
132
|
const ir = buildFullIR(foundation, colors, variant);
|
|
128
133
|
const baseName = variantName(variant);
|
|
129
134
|
|
|
130
|
-
if (output.css) await emit(`css/${baseName}.css`, renderCss(ir));
|
|
135
|
+
if (output.css) await emit(`css/${baseName}.css`, renderCss(ir, { rgbCompanions: output.tailwind }));
|
|
131
136
|
if (output.ts) await emit(`ts/${baseName}.d.ts`, renderTs(ir));
|
|
132
137
|
}
|
|
133
138
|
}
|
|
134
139
|
|
|
140
|
+
// ── Tailwind surface: preset + tw-merge extension (naming layer) ─────────
|
|
141
|
+
// Token *topology* is variant-invariant (same groups/keys across the axes),
|
|
142
|
+
// and every entry maps through `var(--apollion-*)` indirection — so one
|
|
143
|
+
// preset serves all variants. The first expanded variant is the
|
|
144
|
+
// representative (deterministic: `expandVariants` ordering is stable).
|
|
145
|
+
if (output.tailwind) {
|
|
146
|
+
const variant = variants[0];
|
|
147
|
+
const { foundation, colors } = buildVariantThemes(variant);
|
|
148
|
+
const ir = buildFullIR(foundation, colors, variant);
|
|
149
|
+
await emit('tailwind/preset.mjs', renderTailwindPreset(ir));
|
|
150
|
+
await emit('tailwind/tw-merge.mjs', renderTwMergeExtension(ir));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Design.md surface: resolved-values AI context, one doc per brand ─────
|
|
154
|
+
if (output.designMd) {
|
|
155
|
+
const brands = [...new Set(variants.map((v) => v.brand))];
|
|
156
|
+
for (const brand of brands) {
|
|
157
|
+
const brandVariants = variants.filter((v) => v.brand === brand);
|
|
158
|
+
const irs = brandVariants.map((variant) => {
|
|
159
|
+
const { foundation, colors } = buildVariantThemes(variant);
|
|
160
|
+
return buildFullIR(foundation, colors, variant);
|
|
161
|
+
});
|
|
162
|
+
await emit(`design/${brand}.design.md`, renderDesignMd(brand, irs));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
135
166
|
const manifest: BuildManifest = {
|
|
136
167
|
configHash: hashConfig(config),
|
|
137
168
|
files,
|
|
@@ -170,7 +201,22 @@ export async function check(opts: { config: ApollionConfig; outDir: string }): P
|
|
|
170
201
|
} catch {
|
|
171
202
|
return false; // no manifest = needs build
|
|
172
203
|
}
|
|
173
|
-
|
|
204
|
+
if (current.configHash !== hashConfig(config)) return false;
|
|
205
|
+
|
|
206
|
+
// configHash only proves the input didn't change — re-verify each emitted
|
|
207
|
+
// file's bytes still match what the manifest recorded, so a hand-edited or
|
|
208
|
+
// corrupted dist/ file (config untouched) is still caught as drift.
|
|
209
|
+
for (const entry of current.files) {
|
|
210
|
+
let content: Buffer;
|
|
211
|
+
try {
|
|
212
|
+
content = await readFile(join(outDir, entry.path));
|
|
213
|
+
} catch {
|
|
214
|
+
return false; // file missing
|
|
215
|
+
}
|
|
216
|
+
if (sha256(content) !== entry.sha256) return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return true;
|
|
174
220
|
}
|
|
175
221
|
|
|
176
222
|
async function writeAt(root: string, relativePath: string, content: string): Promise<void> {
|
package/src/builders/colors.ts
CHANGED
|
@@ -67,13 +67,13 @@ export function getOppositeColor(color: AcceptColorType): string {
|
|
|
67
67
|
return formatHex(rotated) ?? hex;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
/**
|
|
70
|
+
/** Perceptual state floor between base and interaction state (WCAG ratio) — mirror of the core (ADR-012). */
|
|
71
71
|
const MIN_STATE_CONTRAST = 1.3;
|
|
72
72
|
|
|
73
|
-
/** ΔL OKLch JND —
|
|
73
|
+
/** ΔL OKLch JND — alternative criterion where the WCAG ratio saturates (near-black). */
|
|
74
74
|
const STATE_DELTA_L_JND = 0.05;
|
|
75
75
|
|
|
76
|
-
/**
|
|
76
|
+
/** L step per iteration during state correction. */
|
|
77
77
|
const STATE_L_STEP = 0.04;
|
|
78
78
|
|
|
79
79
|
const contrastRatio = (a: string, b: string): number => {
|
|
@@ -88,6 +88,34 @@ const oklchLightness = (color: string): number | null => {
|
|
|
88
88
|
return parsed ? (parsed.l ?? 0) : null;
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
+
/** WCAG AA contrast floor (normal text) — mirror of the core. */
|
|
92
|
+
const MIN_CONTRAST_AA = 4.5;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Adjusts the OKLch L of `color` until it passes `minRatio` against `background`,
|
|
96
|
+
* preserving hue/chroma — **byte-for-byte mirror** of the core's `readableColor`
|
|
97
|
+
* (`Colors.helpers.ts`; parity.test.ts). Used to derive the tonal ink
|
|
98
|
+
* `onLight` (APOLLION-CONTRAST-GUARANTEE).
|
|
99
|
+
*/
|
|
100
|
+
function readableColor(color: string, background: string, minRatio = MIN_CONTRAST_AA): string {
|
|
101
|
+
const bgParsed = parse(background);
|
|
102
|
+
let oklch = toOklch(parse(color));
|
|
103
|
+
if (!bgParsed || !oklch) return color;
|
|
104
|
+
|
|
105
|
+
const step = contrastRatio('#fff', background) >= contrastRatio('#000', background) ? 0.04 : -0.04;
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < 25; i += 1) {
|
|
108
|
+
const hex = formatHex(oklch) ?? color;
|
|
109
|
+
const parsed = parse(hex);
|
|
110
|
+
if (parsed && wcagContrast(parsed, bgParsed) >= minRatio) return hex;
|
|
111
|
+
const nextL = Math.min(1, Math.max(0, (oklch.l ?? 0) + step));
|
|
112
|
+
if (nextL === (oklch.l ?? 0)) break;
|
|
113
|
+
oklch = { ...oklch, l: nextL };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return formatHex(oklch) ?? color;
|
|
117
|
+
}
|
|
118
|
+
|
|
91
119
|
const isPerceptibleState = (base: string, candidate: string): boolean => {
|
|
92
120
|
if (contrastRatio(base, candidate) >= MIN_STATE_CONTRAST) return true;
|
|
93
121
|
const baseL = oklchLightness(base);
|
|
@@ -129,9 +157,9 @@ function perceptibleStateFor(base: string, surfaceIsDark: boolean, startL?: numb
|
|
|
129
157
|
}
|
|
130
158
|
|
|
131
159
|
/**
|
|
132
|
-
* Derive `{ base, dark, action, active, light }` via OKLch lerp,
|
|
133
|
-
*
|
|
134
|
-
* core (ADR-012; parity.test.ts).
|
|
160
|
+
* Derive `{ base, dark, action, active, light }` via OKLch lerp, with a
|
|
161
|
+
* mode-aware perceptual state floor on the interaction states — byte-for-byte
|
|
162
|
+
* mirror of the core (ADR-012; parity.test.ts). Per-slot overrides pass through verbatim.
|
|
135
163
|
*/
|
|
136
164
|
export function mountSingleColor(
|
|
137
165
|
color: AcceptColorType | ColorPalleteOverrideInterface,
|
|
@@ -148,6 +176,8 @@ export function mountSingleColor(
|
|
|
148
176
|
action: mixOklch(base, darken!, 0.2),
|
|
149
177
|
active: mixOklch(base, lighten!, 0.6),
|
|
150
178
|
light: mixOklch(base, lighten!, 0.9),
|
|
179
|
+
// Placeholder — recomputed from the FINAL `action` below (mirror of the core).
|
|
180
|
+
onLight: base,
|
|
151
181
|
};
|
|
152
182
|
|
|
153
183
|
if (parse(base)) {
|
|
@@ -164,13 +194,19 @@ export function mountSingleColor(
|
|
|
164
194
|
}
|
|
165
195
|
}
|
|
166
196
|
|
|
197
|
+
// Tonal ink AA by construction (APOLLION-CONTRAST-GUARANTEE) — mirror of the core.
|
|
198
|
+
derived.onLight = readableColor(derived.action, derived.light);
|
|
199
|
+
|
|
167
200
|
if (override) {
|
|
201
|
+
const finalAction = override.action ?? derived.action;
|
|
202
|
+
const finalLight = override.light ?? derived.light;
|
|
168
203
|
return {
|
|
169
204
|
base,
|
|
170
205
|
dark: override.dark ?? derived.dark,
|
|
171
|
-
action:
|
|
206
|
+
action: finalAction,
|
|
172
207
|
active: override.active ?? derived.active,
|
|
173
|
-
light:
|
|
208
|
+
light: finalLight,
|
|
209
|
+
onLight: override.onLight ?? readableColor(finalAction, finalLight),
|
|
174
210
|
};
|
|
175
211
|
}
|
|
176
212
|
|
|
@@ -202,7 +238,7 @@ export function mountGreyscaleColors(colors: ColorsInput): Record<GrayscaleColor
|
|
|
202
238
|
export function mountNeutralColors(colors: ColorsInput): Record<NeutralColorsType, string> {
|
|
203
239
|
const lighter = colors.deepLight!;
|
|
204
240
|
const darker = colors.deepDark!;
|
|
205
|
-
//
|
|
241
|
+
// The slot may be overridden per slot (ADR-012) — the ramp anchors on the seed only.
|
|
206
242
|
const primary =
|
|
207
243
|
typeof colors.primary === 'object' && colors.primary ? colors.primary.base : (colors.primary as string);
|
|
208
244
|
|
package/src/cli.ts
CHANGED
|
@@ -103,7 +103,7 @@ async function main(): Promise<void> {
|
|
|
103
103
|
const ok = await check({ config, outDir: resolve(args.outDir) });
|
|
104
104
|
if (!ok) {
|
|
105
105
|
console.error(
|
|
106
|
-
`apollion-tokens: dist/ is stale or
|
|
106
|
+
`apollion-tokens: dist/ is stale, missing, or has drifted from its manifest. Re-run \`apollion-tokens build\` without --check.`,
|
|
107
107
|
);
|
|
108
108
|
process.exit(1);
|
|
109
109
|
}
|
package/src/config-schema.ts
CHANGED
|
@@ -36,6 +36,15 @@ export interface ApollionOutputFlags {
|
|
|
36
36
|
css?: boolean;
|
|
37
37
|
json?: boolean;
|
|
38
38
|
ts?: boolean;
|
|
39
|
+
/** `dist/design/<brand>.design.md` — resolved-values AI context per brand
|
|
40
|
+
* (YAML frontmatter + markdown body). See `renderers/design-md.ts`. */
|
|
41
|
+
designMd?: boolean;
|
|
42
|
+
/** `dist/tailwind/{preset,tw-merge}.mjs` — generated Tailwind preset mapping
|
|
43
|
+
* foundation tokens to theme keys (`var(--apollion-*)` indirection) plus a
|
|
44
|
+
* tailwind-merge extension derived from the same IR. Also adds `-rgb`
|
|
45
|
+
* channel-triplet companion vars to `dist/css/` so opaque colour tokens
|
|
46
|
+
* support Tailwind's alpha modifier. See `renderers/tailwind.ts`. */
|
|
47
|
+
tailwind?: boolean;
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
export interface ApollionConfig {
|
|
@@ -102,7 +111,7 @@ export function expandVariants(config: ApollionConfig): Variant[] {
|
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
/**
|
|
105
|
-
* Stable variant filename (
|
|
114
|
+
* Stable variant filename (without extension).
|
|
106
115
|
* Format: `<brand>.<mode>.<surface>.<dimension>` — kebab where needed.
|
|
107
116
|
*/
|
|
108
117
|
export function variantName(v: Variant): string {
|
package/src/ir.ts
CHANGED
|
@@ -258,6 +258,9 @@ function buildColorPrimitives(colors: ColorsThemeInterface): IRGroup {
|
|
|
258
258
|
action: colorToken(palette.action),
|
|
259
259
|
active: colorToken(palette.active),
|
|
260
260
|
light: colorToken(palette.light),
|
|
261
|
+
// AA tonal pair (APOLLION-CONTRAST-GUARANTEE): ink legible over `light`,
|
|
262
|
+
// emitted as `--apollion-color-<palette>-on-light`.
|
|
263
|
+
onLight: colorToken(palette.onLight),
|
|
261
264
|
};
|
|
262
265
|
}
|
|
263
266
|
out.grayscale = recordColorGroup(colors.grayscale);
|
package/src/manifest.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* **Threat-model fields (ADR-006 §3.9):** `gitCommit` and `buildEnv` give
|
|
10
10
|
* provenance without requiring CI/SLSA-3 (decision Q-G9 — single-user org,
|
|
11
|
-
*
|
|
11
|
+
* no CI). `signedBy` slot reserved for sigstore in future PRD-003.
|
|
12
12
|
*
|
|
13
13
|
* @see ADR-006 §3.9 + §8.5 risk T1/R2
|
|
14
14
|
* @see PRD-002 §S5
|
package/src/renderers/css.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* @see ../ir.ts (source of truth) · ADR-006 §3.3 + §3.10 · tech-radar R3
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { colorPayload, isOpaqueColor, rgbTriplet } from './tailwind';
|
|
17
|
+
|
|
16
18
|
import type { IRDocument, IRGroup } from '../ir';
|
|
17
19
|
import { isToken, kebab, rawValue } from '../ir';
|
|
18
20
|
|
|
@@ -26,6 +28,28 @@ function emitVars(group: IRGroup, prefix: string[], lines: string[]): void {
|
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
/**
|
|
32
|
+
* `-rgb` channel-triplet companions for opaque colour tokens (Tailwind
|
|
33
|
+
* `<alpha-value>` support — `output.tailwind` only). Projection rule shared
|
|
34
|
+
* with the preset renderer ({@link colorPayload}/{@link isOpaqueColor}/
|
|
35
|
+
* {@link rgbTriplet}) so preset entries and companions cannot drift.
|
|
36
|
+
*
|
|
37
|
+
* NEVER mirrored into the `@property` blocks — `syntax: '<color>'` is invalid
|
|
38
|
+
* for a channel triplet (`emitProperties` walks tokens, companions are not
|
|
39
|
+
* tokens, so the exclusion is structural).
|
|
40
|
+
*/
|
|
41
|
+
function emitRgbVars(group: IRGroup, prefix: string[], lines: string[]): void {
|
|
42
|
+
for (const [key, node] of Object.entries(group)) {
|
|
43
|
+
const path = [...prefix, kebab(key)];
|
|
44
|
+
if (!isToken(node)) {
|
|
45
|
+
emitRgbVars(node, path, lines);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const color = colorPayload(node);
|
|
49
|
+
if (color && isOpaqueColor(color)) lines.push(` --apollion-${path.join('-')}-rgb: ${rgbTriplet(color)};`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
29
53
|
function emitProperties(group: IRGroup, prefix: string[], lines: string[]): void {
|
|
30
54
|
for (const [key, node] of Object.entries(group)) {
|
|
31
55
|
const path = [...prefix, kebab(key)];
|
|
@@ -39,13 +63,24 @@ function emitProperties(group: IRGroup, prefix: string[], lines: string[]): void
|
|
|
39
63
|
}
|
|
40
64
|
}
|
|
41
65
|
|
|
42
|
-
export
|
|
66
|
+
export interface RenderCssOptions {
|
|
67
|
+
/** Emit `-rgb` channel-triplet companions for opaque colour tokens
|
|
68
|
+
* (`output.tailwind` — see {@link emitRgbVars}). Default false: the byte
|
|
69
|
+
* output without the flag is unchanged. */
|
|
70
|
+
rgbCompanions?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function renderCss(ir: IRDocument, options: RenderCssOptions = {}): string {
|
|
43
74
|
const { brand, mode, surface, dimension } = ir.meta;
|
|
44
75
|
const lines: string[] = [HEADER_LINE, ''];
|
|
45
76
|
|
|
46
77
|
// Variant marker as a comment (NOT a var — keep cascade clean).
|
|
47
78
|
lines.push(`/* Variant: brand=${brand} mode=${mode} surface=${surface} dimension=${dimension} */`, '', ':root {');
|
|
48
79
|
emitVars(ir.groups, [], lines);
|
|
80
|
+
if (options.rgbCompanions) {
|
|
81
|
+
lines.push('', ' /* -rgb channel triplets — Tailwind <alpha-value> companions (opaque colour tokens only). */');
|
|
82
|
+
emitRgbVars(ir.groups, [], lines);
|
|
83
|
+
}
|
|
49
84
|
lines.push('}', '');
|
|
50
85
|
|
|
51
86
|
// @property declarations for color tokens — opt-in via @supports.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design.md renderer — resolved-values AI context per brand.
|
|
3
|
+
*
|
|
4
|
+
* The rules layer (`llms.txt`) teaches *which* token to reach
|
|
5
|
+
* for; this emission supplies the *resolved values* those rules reference,
|
|
6
|
+
* generated from the consumer's own build so it can never drift from the
|
|
7
|
+
* shipped theme. YAML frontmatter for machines + a markdown body for humans.
|
|
8
|
+
*
|
|
9
|
+
* One document per brand, covering every (mode × surface × dimension) variant
|
|
10
|
+
* of that brand. Pure projection over per-variant `IRDocument`s — no value is
|
|
11
|
+
* computed here; a missing value is an IR gap by contract.
|
|
12
|
+
*
|
|
13
|
+
* @see ../../../../docs/references/design-md-decision.md (adoption decision)
|
|
14
|
+
* @see ../ir.ts (source of truth) · ./ts.ts (sibling resolved-surface renderer)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { IRDocument, IRGroup } from '../ir';
|
|
18
|
+
import { isToken, rawValue } from '../ir';
|
|
19
|
+
|
|
20
|
+
const HEADER_LINE = '<!-- Generated by apollion-tokens build — DO NOT EDIT. See apollion.config.mjs. -->';
|
|
21
|
+
|
|
22
|
+
/** Flatten a resolved IR group into dot-path → raw value pairs. */
|
|
23
|
+
function flatten(group: IRGroup, prefix: string, out: Array<[string, string | number]>): void {
|
|
24
|
+
for (const [key, node] of Object.entries(group)) {
|
|
25
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
26
|
+
if (isToken(node)) {
|
|
27
|
+
out.push([path, rawValue(node.value) as string | number]);
|
|
28
|
+
} else {
|
|
29
|
+
flatten(node, path, out);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function yamlEscape(value: string | number): string {
|
|
35
|
+
if (typeof value === 'number') return String(value);
|
|
36
|
+
// hex colors and px/rem strings are safe unquoted except when starting with
|
|
37
|
+
// YAML-significant characters — quote everything for uniformity/safety.
|
|
38
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function variantKey(ir: IRDocument): string {
|
|
42
|
+
const { mode, surface, dimension } = ir.meta;
|
|
43
|
+
return `${mode}.${surface}.${dimension}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Render one Design.md for a brand from its variant IRDocuments.
|
|
48
|
+
* Deterministic: variant order is the caller's (build order), path order is
|
|
49
|
+
* IR insertion order — both stable for a given config.
|
|
50
|
+
*/
|
|
51
|
+
export function renderDesignMd(brand: string, variants: readonly IRDocument[]): string {
|
|
52
|
+
if (variants.length === 0) {
|
|
53
|
+
throw new Error(`renderDesignMd: no variants for brand "${brand}"`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── YAML frontmatter (machine surface) ────────────────────────────────────
|
|
57
|
+
const fm: string[] = ['---', `brand: '${brand}'`, 'generator: apollion-tokens', 'variants:'];
|
|
58
|
+
for (const ir of variants) {
|
|
59
|
+
const flat: Array<[string, string | number]> = [];
|
|
60
|
+
flatten(ir.groups, '', flat);
|
|
61
|
+
fm.push(` ${variantKey(ir)}:`);
|
|
62
|
+
for (const [path, value] of flat) {
|
|
63
|
+
fm.push(` ${path}: ${yamlEscape(value)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
fm.push('---');
|
|
67
|
+
|
|
68
|
+
// ── Markdown body (human surface) ─────────────────────────────────────────
|
|
69
|
+
const body: string[] = [
|
|
70
|
+
'',
|
|
71
|
+
HEADER_LINE,
|
|
72
|
+
'',
|
|
73
|
+
`# Design.md — ${brand}`,
|
|
74
|
+
'',
|
|
75
|
+
'Resolved design-token values for this brand, one section per variant.',
|
|
76
|
+
'Consumption rules (which token to reach for, layer boundaries) live in the',
|
|
77
|
+
'package `llms.txt`; this file carries the values those rules reference.',
|
|
78
|
+
'Regenerate with `apollion-tokens build` — never edit.',
|
|
79
|
+
'',
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
for (const ir of variants) {
|
|
83
|
+
const { mode, surface, dimension } = ir.meta;
|
|
84
|
+
body.push(`## Variant \`${mode} · ${surface} · ${dimension}\``, '');
|
|
85
|
+
const flat: Array<[string, string | number]> = [];
|
|
86
|
+
flatten(ir.groups, '', flat);
|
|
87
|
+
|
|
88
|
+
// group rows by top-level namespace for readable tables
|
|
89
|
+
const byNs = new Map<string, Array<[string, string | number]>>();
|
|
90
|
+
for (const [path, value] of flat) {
|
|
91
|
+
const ns = path.split('.')[0] ?? path;
|
|
92
|
+
const rows = byNs.get(ns) ?? [];
|
|
93
|
+
rows.push([path, value]);
|
|
94
|
+
byNs.set(ns, rows);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const [ns, rows] of byNs) {
|
|
98
|
+
body.push(`### ${ns}`, '', '| Token | Value |', '| --- | --- |');
|
|
99
|
+
for (const [path, value] of rows) {
|
|
100
|
+
body.push(`| \`${path}\` | \`${value}\` |`);
|
|
101
|
+
}
|
|
102
|
+
body.push('');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return `${fm.join('\n')}\n${body.join('\n')}`;
|
|
107
|
+
}
|
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
* manifest + O(brand·mode·surface + 2·dimension + 1) sets (spacing + font per
|
|
11
11
|
* dimension).
|
|
12
12
|
*
|
|
13
|
-
* **
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* **Mode modifier:** `mode` is emitted as a real modifier (values + default),
|
|
14
|
+
* and since ADR-019 the dark contexts carry real values — palette generation
|
|
15
|
+
* derives dark through the canonical seed swap in `theme-factory.ts` (the S5
|
|
16
|
+
* dark == light placeholder is retired).
|
|
16
17
|
*
|
|
17
18
|
* Set documents are projected from `IRSlice`s via the shared `dtcg-project`
|
|
18
19
|
* mapping, so a composed (base + color + spacing + font) tree is byte-equivalent
|