@santi020k/lumen 0.1.0 → 0.3.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 CHANGED
@@ -26,8 +26,37 @@ lumen add Button --target elements
26
26
  lumen add scheduler
27
27
  lumen add scheduler --target react
28
28
  lumen add scheduler --target elements
29
+ lumen audit-tokens ./src
29
30
  ```
30
31
 
31
32
  Use `lumen add <component>` for a local Astro wrapper, `--target react` for a React wrapper, or
32
33
  `--target elements` for a custom-elements starter. Bundled recipes support the same Astro, React,
33
34
  and Elements targets.
35
+
36
+ Run `lumen audit-tokens [path]` before incremental adoption when an existing stylesheet may already
37
+ declare names such as `--surface`, `--ink`, or `--line`. The audit reports complete CSS colors that
38
+ are incompatible with Lumen's HSL-channel token format and exits non-zero when it finds conflicts.
39
+
40
+ ## Coordinated consumer rollout
41
+
42
+ Inventory one or more pnpm consumers before a release:
43
+
44
+ ```bash
45
+ lumen rollout 0.2.0 ../site ../dashboard --exclude ../legacy
46
+ ```
47
+
48
+ The report separates manifest, workspace-catalog, and lockfile references; identifies each
49
+ framework; checks the declared pnpm and Node contracts; and reports the complete resolved Lumen
50
+ package graph. Add `--report ./lumen-rollout.json` for a durable JSON record.
51
+
52
+ After committing an intentional baseline in each consumer, apply the upgrade serially:
53
+
54
+ ```bash
55
+ lumen rollout 0.2.0 ../site ../dashboard --apply --report ./lumen-rollout.json
56
+ ```
57
+
58
+ The command preserves `catalog:` indirection and pinned/caret range style, temporarily admits the
59
+ new release through `minimumReleaseAgeExclude`, runs installs through Corepack from each consumer
60
+ root, removes obsolete Lumen release-age exceptions, verifies the unified resolved version, and
61
+ runs available framework checks, builds, and browser scripts. It refuses dirty repositories,
62
+ missing exact pnpm declarations, and unsupported Node runtimes unless `--allow-dirty` is explicit.
package/dist/cli.js CHANGED
@@ -1,4 +1,8 @@
1
+ import { readdir, readFile, stat } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { consumerRolloutSucceeded, formatConsumerRollout, runConsumerRollout } from './consumer-rollout.js';
1
4
  import { addLumenRegistryItem, getLumenRegistryEntries, getLumenRegistryItem, loadLumenRegistry, lumenRegistry } from './registry.js';
5
+ import { auditLumenTokenCss } from './token-audit.js';
2
6
  const args = process.argv.slice(2);
3
7
  const [command = 'help', name] = args;
4
8
  const dryRun = args.includes('--dry-run');
@@ -14,7 +18,36 @@ const registryToken = registryTokenIndex >= 0 ? args[registryTokenIndex + 1] : p
14
18
  const targetIndex = args.indexOf('--target');
15
19
  const targetProvided = targetIndex >= 0;
16
20
  const targetValue = targetIndex >= 0 ? args[targetIndex + 1] : undefined;
21
+ const applyRollout = args.includes('--apply');
22
+ const allowDirty = args.includes('--allow-dirty');
23
+ const noVerify = args.includes('--no-verify');
24
+ const reportIndex = args.indexOf('--report');
25
+ const reportPath = reportIndex >= 0 ? args[reportIndex + 1] : undefined;
26
+ const excludeValues = args.flatMap((value, index) => {
27
+ const nextValue = args[index + 1];
28
+ return value === '--exclude' && nextValue ? [nextValue] : [];
29
+ });
17
30
  let conflict = 'skip';
31
+ const getRolloutArguments = () => {
32
+ const values = [];
33
+ const optionsWithValues = new Set(['--exclude', '--report']);
34
+ for (let index = 1; index < args.length; index += 1) {
35
+ const value = args[index];
36
+ if (!value)
37
+ continue;
38
+ if (optionsWithValues.has(value)) {
39
+ index += 1;
40
+ continue;
41
+ }
42
+ if (!value.startsWith('--'))
43
+ values.push(value);
44
+ }
45
+ const [targetVersion, ...repositories] = values;
46
+ return {
47
+ repositories: repositories.length ? repositories : [process.cwd()],
48
+ ...(targetVersion ? { targetVersion } : {})
49
+ };
50
+ };
18
51
  const getAddTarget = (value, provided = false) => {
19
52
  if (provided && !value) {
20
53
  throw new Error('Missing Lumen add target. Use astro, react, or elements.');
@@ -83,6 +116,34 @@ const formatAddResult = async (itemName, registry) => {
83
116
  ...result.skipped.map(file => `- skipped existing ${file}`)
84
117
  ].join('\n');
85
118
  };
119
+ const getCssFiles = async (path) => {
120
+ const details = await stat(path);
121
+ if (details.isFile())
122
+ return path.endsWith('.css') ? [path] : [];
123
+ const entries = await readdir(path, { withFileTypes: true });
124
+ const nested = await Promise.all(entries
125
+ .filter(entry => entry.name !== 'node_modules' && !entry.name.startsWith('.'))
126
+ .map(entry => getCssFiles(resolve(path, entry.name))));
127
+ return nested.flat();
128
+ };
129
+ const formatTokenAudit = async (inputPath) => {
130
+ const target = resolve(inputPath);
131
+ const files = await getCssFiles(target);
132
+ const findings = [];
133
+ for (const file of files) {
134
+ findings.push(...auditLumenTokenCss(await readFile(file, 'utf8'), file));
135
+ }
136
+ if (!findings.length)
137
+ return `No incompatible Lumen semantic token declarations found in ${target}.`;
138
+ process.exitCode = 1;
139
+ return [
140
+ `Found ${findings.length} incompatible Lumen semantic token declaration${findings.length === 1 ? '' : 's'}:`,
141
+ ...findings.map(finding => (`${finding.file}:${finding.line}:${finding.column} --${finding.token}: ${finding.value}`)),
142
+ '',
143
+ 'Lumen semantic colors must be HSL channels such as "220 13% 96%", not complete colors such as "#fff" or "hsl(...)".',
144
+ 'Rename legacy variables during incremental migration or convert their values to channels before importing Lumen.'
145
+ ].join('\n');
146
+ };
86
147
  const help = [
87
148
  'Lumen registry helper',
88
149
  '',
@@ -91,6 +152,8 @@ const help = [
91
152
  ' lumen show <name> Show components and files for an item',
92
153
  ' lumen add <name> Install a recipe or component into the current project',
93
154
  ' lumen install Print install commands',
155
+ ' lumen audit-tokens Report incompatible semantic color custom properties',
156
+ ' lumen rollout [version] [repositories...] Inventory or upgrade pnpm consumers',
94
157
  '',
95
158
  'Options:',
96
159
  ' --cwd <path> Target directory for lumen add',
@@ -101,14 +164,24 @@ const help = [
101
164
  ' --registry <source> Load list/show data from a local or remote registry JSON file',
102
165
  ' --registry-token <key> Use a bearer token for private remote registries',
103
166
  ' --target <framework> Generate wrappers or recipe starters for astro, react, or elements',
167
+ ' --apply Apply a rollout (requires a target version)',
168
+ ' --allow-dirty Allow rollout after recording an uncommitted baseline',
169
+ ' --exclude <path> Exclude a repository (repeatable)',
170
+ ' --no-verify Skip downstream check, typecheck, build, and browser scripts',
171
+ ' --report <path> Write the machine-readable rollout report as JSON',
104
172
  ''
105
173
  ].join('\n');
174
+ // eslint-disable-next-line complexity -- Command dispatch keeps CLI exit behavior and output in one place.
106
175
  const run = async () => {
107
176
  let output = help;
108
177
  const registry = registrySource
109
178
  ? await loadLumenRegistry(registrySource, registryToken ? { token: registryToken } : {})
110
179
  : lumenRegistry;
111
180
  switch (command) {
181
+ case 'audit-tokens': {
182
+ output = await formatTokenAudit(name ?? cwd ?? process.cwd());
183
+ break;
184
+ }
112
185
  case 'add': {
113
186
  if (name)
114
187
  output = await formatAddResult(name, registry);
@@ -122,6 +195,22 @@ const run = async () => {
122
195
  output = formatList(registry);
123
196
  break;
124
197
  }
198
+ case 'rollout': {
199
+ const rollout = getRolloutArguments();
200
+ const report = await runConsumerRollout({
201
+ allowDirty,
202
+ apply: applyRollout,
203
+ exclude: excludeValues,
204
+ ...(reportPath ? { report: reportPath } : {}),
205
+ repositories: rollout.repositories,
206
+ ...(rollout.targetVersion ? { targetVersion: rollout.targetVersion } : {}),
207
+ verify: !noVerify
208
+ });
209
+ output = formatConsumerRollout(report);
210
+ if (!consumerRolloutSucceeded(report))
211
+ process.exitCode = 1;
212
+ break;
213
+ }
125
214
  case 'show': {
126
215
  if (name)
127
216
  output = formatItem(name, registry);
@@ -0,0 +1,52 @@
1
+ export type LumenFramework = 'astro' | 'elements' | 'react';
2
+ export interface ConsumerReference {
3
+ file: string;
4
+ packageName: string;
5
+ source: 'catalog' | 'manifest' | 'lockfile';
6
+ version: string;
7
+ }
8
+ export interface ConsumerInventory {
9
+ branch: string | undefined;
10
+ currentCommit: string | undefined;
11
+ dirty: boolean;
12
+ frameworks: LumenFramework[];
13
+ nodeEngine: string | undefined;
14
+ nodeEngineSatisfied: boolean | undefined;
15
+ packageManager: string | undefined;
16
+ references: ConsumerReference[];
17
+ repository: string;
18
+ resolvedVersions: Record<string, string[]>;
19
+ targetVersion?: string;
20
+ valid: boolean;
21
+ warnings: string[];
22
+ }
23
+ export interface ConsumerRolloutOptions {
24
+ allowDirty?: boolean;
25
+ apply?: boolean;
26
+ exclude?: string[];
27
+ report?: string;
28
+ repositories: string[];
29
+ targetVersion?: string;
30
+ verify?: boolean;
31
+ }
32
+ export interface ConsumerRolloutReport {
33
+ generatedAt: string;
34
+ mode: 'apply' | 'inventory';
35
+ repositories: (ConsumerInventory & {
36
+ commands: {
37
+ command: string;
38
+ ok: boolean;
39
+ output: string;
40
+ }[];
41
+ })[];
42
+ targetVersion?: string;
43
+ }
44
+ export declare const consumerRolloutSucceeded: (report: ConsumerRolloutReport) => boolean;
45
+ export declare const satisfiesNodeEngine: (engine: string | undefined, currentVersion?: string) => boolean | undefined;
46
+ export declare const inspectLumenConsumer: (repository: string, targetVersion?: string) => Promise<ConsumerInventory>;
47
+ export declare const updateLumenManifestSource: (source: string, targetVersion: string) => string;
48
+ export declare const updateLumenWorkspaceSource: (source: string, targetVersion: string) => string;
49
+ export declare const addReleaseAgeTarget: (source: string, packages: string[], version: string) => string;
50
+ export declare const runConsumerRollout: (options: ConsumerRolloutOptions) => Promise<ConsumerRolloutReport>;
51
+ export declare const formatConsumerRollout: (report: ConsumerRolloutReport) => string;
52
+ //# sourceMappingURL=consumer-rollout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consumer-rollout.d.ts","sourceRoot":"","sources":["../src/consumer-rollout.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,CAAA;AAE3D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,CAAA;IAC3C,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,KAAK,EAAE,OAAO,CAAA;IACd,UAAU,EAAE,cAAc,EAAE,CAAA;IAC5B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,mBAAmB,EAAE,OAAO,GAAG,SAAS,CAAA;IACxC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,UAAU,EAAE,iBAAiB,EAAE,CAAA;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,OAAO,GAAG,WAAW,CAAA;IAC3B,YAAY,EAAE,CAAC,iBAAiB,GAAG;QACjC,QAAQ,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,OAAO,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAC7D,CAAC,EAAE,CAAA;IACJ,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,eAAO,MAAM,wBAAwB,GAAI,QAAQ,qBAAqB,KAAG,OAGtE,CAAA;AA8DH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,GAAG,SAAS,EAC1B,uBAAsC,KACrC,OAAO,GAAG,SAIZ,CAAA;AA+DD,eAAO,MAAM,oBAAoB,GAC/B,YAAY,MAAM,EAClB,gBAAgB,MAAM,KACrB,OAAO,CAAC,iBAAiB,CAiG3B,CAAA;AAUD,eAAO,MAAM,yBAAyB,GACpC,QAAQ,MAAM,EACd,eAAe,MAAM,KACpB,MAYF,CAAA;AAED,eAAO,MAAM,0BAA0B,GACrC,QAAQ,MAAM,EACd,eAAe,MAAM,KACpB,MAIF,CAAA;AAuFD,eAAO,MAAM,mBAAmB,GAAI,QAAQ,MAAM,EAAE,UAAU,MAAM,EAAE,EAAE,SAAS,MAAM,KAAG,MAoCzF,CAAA;AAqGD,eAAO,MAAM,kBAAkB,GAC7B,SAAS,sBAAsB,KAC9B,OAAO,CAAC,qBAAqB,CAmD/B,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,QAAQ,qBAAqB,KAAG,MAyBrE,CAAA"}
@@ -0,0 +1,397 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { readdir, readFile, stat, writeFile } from 'node:fs/promises';
3
+ import { join, relative, resolve } from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import { satisfies } from 'semver';
6
+ /* eslint-disable complexity -- Consumer inventory, semver checks, mutation, and verification intentionally evaluate full repository contracts. */
7
+ const execFileAsync = promisify(execFile);
8
+ const lumenPackagePattern = /^@santi020k\/lumen(?:-(?:astro|core|elements|mcp|react))?$/;
9
+ const dependencyFields = [
10
+ 'dependencies',
11
+ 'devDependencies',
12
+ 'optionalDependencies',
13
+ 'peerDependencies'
14
+ ];
15
+ export const consumerRolloutSucceeded = (report) => report.repositories.every(repository => repository.valid && repository.commands.every(command => command.ok));
16
+ const isLumenPackage = (name) => lumenPackagePattern.test(name);
17
+ const pathExists = async (path) => {
18
+ try {
19
+ await stat(path);
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ };
26
+ const findPackageManifests = async (root) => {
27
+ const results = [];
28
+ const visit = async (directory) => {
29
+ const entries = await readdir(directory, { withFileTypes: true });
30
+ for (const entry of entries) {
31
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.turbo'))
32
+ continue;
33
+ const path = join(directory, entry.name);
34
+ if (entry.isDirectory()) {
35
+ await visit(path);
36
+ }
37
+ else if (entry.name === 'package.json') {
38
+ results.push(path);
39
+ }
40
+ }
41
+ };
42
+ await visit(root);
43
+ return results;
44
+ };
45
+ const readManifest = async (path) => JSON.parse(await readFile(path, 'utf8'));
46
+ const readGitValue = async (root, args) => {
47
+ try {
48
+ const { stdout } = await execFileAsync('git', ['-C', root, ...args]);
49
+ return stdout.trim() || undefined;
50
+ }
51
+ catch {
52
+ return undefined;
53
+ }
54
+ };
55
+ export const satisfiesNodeEngine = (engine, currentVersion = process.versions.node) => {
56
+ if (!engine)
57
+ return undefined;
58
+ return satisfies(currentVersion, engine);
59
+ };
60
+ const collectManifestReferences = (manifest, file, root) => dependencyFields.flatMap(field => Object.entries(manifest[field] ?? {})
61
+ .filter(([packageName]) => isLumenPackage(packageName))
62
+ .map(([packageName, version]) => ({
63
+ file: relative(root, file),
64
+ packageName,
65
+ source: 'manifest',
66
+ version
67
+ })));
68
+ const collectYamlReferences = (source, file) => {
69
+ const references = [];
70
+ for (const line of source.split(/\r?\n/)) {
71
+ const match = /^\s*['"]?(@santi020k\/lumen(?:-(?:astro|core|elements|mcp|react))?)['"]?\s*:\s*['"]?([^'"\s#]+)['"]?/.exec(line);
72
+ if (match?.[1] && match[2]) {
73
+ references.push({ file, packageName: match[1], source: 'catalog', version: match[2] });
74
+ }
75
+ }
76
+ return references;
77
+ };
78
+ const collectLockReferences = (source) => {
79
+ const references = [];
80
+ const pattern = /@santi020k\/(lumen(?:-(?:astro|core|elements|mcp|react))?)@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/g;
81
+ for (const match of source.matchAll(pattern)) {
82
+ if (match[1] && match[2]) {
83
+ references.push({
84
+ file: 'pnpm-lock.yaml',
85
+ packageName: `@santi020k/${match[1]}`,
86
+ source: 'lockfile',
87
+ version: match[2]
88
+ });
89
+ }
90
+ }
91
+ return references;
92
+ };
93
+ const getResolvedVersions = (references) => {
94
+ const graph = {};
95
+ for (const reference of references.filter(item => item.source === 'lockfile')) {
96
+ graph[reference.packageName] ??= new Set();
97
+ graph[reference.packageName]?.add(reference.version);
98
+ }
99
+ return Object.fromEntries(Object.entries(graph).map(([name, versions]) => [name, [...versions].sort()]));
100
+ };
101
+ export const inspectLumenConsumer = async (repository, targetVersion) => {
102
+ const root = resolve(repository);
103
+ const rootManifestPath = join(root, 'package.json');
104
+ if (!await pathExists(rootManifestPath)) {
105
+ throw new Error(`No package.json found at consumer root: ${root}`);
106
+ }
107
+ const manifestPaths = await findPackageManifests(root);
108
+ const manifests = await Promise.all(manifestPaths.map(async (file) => ({
109
+ file,
110
+ manifest: await readManifest(file)
111
+ })));
112
+ const rootManifest = await readManifest(rootManifestPath);
113
+ const references = manifests.flatMap(({ file, manifest }) => collectManifestReferences(manifest, file, root));
114
+ const workspacePath = join(root, 'pnpm-workspace.yaml');
115
+ const lockPath = join(root, 'pnpm-lock.yaml');
116
+ if (await pathExists(workspacePath)) {
117
+ references.push(...collectYamlReferences(await readFile(workspacePath, 'utf8'), 'pnpm-workspace.yaml'));
118
+ }
119
+ if (await pathExists(lockPath)) {
120
+ references.push(...collectLockReferences(await readFile(lockPath, 'utf8')));
121
+ }
122
+ const resolvedVersions = getResolvedVersions(references);
123
+ const frameworks = ([
124
+ references.some(item => item.packageName === '@santi020k/lumen-astro') && 'astro',
125
+ references.some(item => item.packageName === '@santi020k/lumen-elements') && 'elements',
126
+ references.some(item => item.packageName === '@santi020k/lumen-react') && 'react'
127
+ ].filter(Boolean));
128
+ const warnings = [];
129
+ const nodeEngineSatisfied = satisfiesNodeEngine(rootManifest.engines?.node);
130
+ const status = await readGitValue(root, ['status', '--porcelain']);
131
+ const dirty = Boolean(status);
132
+ if (!/^pnpm@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(rootManifest.packageManager ?? '')) {
133
+ warnings.push('Missing an exact pnpm packageManager declaration.');
134
+ }
135
+ if (nodeEngineSatisfied === false) {
136
+ warnings.push(`Current Node ${process.versions.node} does not satisfy ${rootManifest.engines?.node ?? 'the declared engine'}.`);
137
+ }
138
+ if (dirty) {
139
+ warnings.push('Repository has uncommitted changes; create an intentional baseline or use --allow-dirty.');
140
+ }
141
+ if (targetVersion) {
142
+ for (const reference of references.filter(item => item.source !== 'lockfile')) {
143
+ const declaredVersion = reference.version.replace(/^[~^]/, '');
144
+ if (reference.version !== 'catalog:' &&
145
+ !reference.version.startsWith('workspace:') &&
146
+ declaredVersion !== targetVersion) {
147
+ warnings.push(`${reference.packageName} declares ${reference.version} in ${reference.file} instead of ${targetVersion}.`);
148
+ }
149
+ }
150
+ for (const [packageName, versions] of Object.entries(resolvedVersions)) {
151
+ if (versions.some(version => version !== targetVersion)) {
152
+ warnings.push(`${packageName} resolves ${versions.join(', ')} instead of only ${targetVersion}.`);
153
+ }
154
+ }
155
+ }
156
+ return {
157
+ branch: await readGitValue(root, ['branch', '--show-current']),
158
+ currentCommit: await readGitValue(root, ['rev-parse', 'HEAD']),
159
+ dirty,
160
+ frameworks,
161
+ nodeEngine: rootManifest.engines?.node,
162
+ nodeEngineSatisfied,
163
+ packageManager: rootManifest.packageManager,
164
+ references,
165
+ repository: root,
166
+ resolvedVersions,
167
+ ...(targetVersion ? { targetVersion } : {}),
168
+ valid: warnings.length === 0,
169
+ warnings
170
+ };
171
+ };
172
+ const replaceRange = (range, targetVersion) => {
173
+ if (range === 'catalog:' || range.startsWith('workspace:') || range.startsWith('file:'))
174
+ return range;
175
+ const prefix = range.startsWith('^') || range.startsWith('~') ? (range.at(0) ?? '') : '';
176
+ return `${prefix}${targetVersion}`;
177
+ };
178
+ export const updateLumenManifestSource = (source, targetVersion) => {
179
+ const manifest = JSON.parse(source);
180
+ for (const field of dependencyFields) {
181
+ const dependencies = manifest[field];
182
+ for (const [name, range] of Object.entries(dependencies ?? {})) {
183
+ if (isLumenPackage(name) && dependencies)
184
+ dependencies[name] = replaceRange(range, targetVersion);
185
+ }
186
+ }
187
+ return `${JSON.stringify(manifest, undefined, 2)}\n`;
188
+ };
189
+ export const updateLumenWorkspaceSource = (source, targetVersion) => source.replaceAll(/^(\s*['"]?@santi020k\/lumen(?:-(?:astro|core|elements|mcp|react))?['"]?\s*:\s*['"]?)([~^]?)(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(['"]?\s*(?:#.*)?)$/gm, (_match, before, prefix, _version, after) => `${before}${prefix}${targetVersion}${after}`);
190
+ const getVerificationScripts = (manifest, frameworks) => {
191
+ const scripts = manifest.scripts ?? {};
192
+ const preferred = [
193
+ frameworks.includes('astro') && 'check',
194
+ (frameworks.includes('react') || frameworks.includes('elements')) && 'typecheck',
195
+ 'build',
196
+ scripts['test:browser'] && 'test:browser',
197
+ scripts['test:e2e'] && 'test:e2e'
198
+ ].filter((script) => Boolean(script && scripts[script]));
199
+ return [...new Set(preferred)];
200
+ };
201
+ const runCommand = async (root, executable, args) => {
202
+ const command = [executable, ...args].join(' ');
203
+ try {
204
+ const { stderr, stdout } = await execFileAsync(executable, args, {
205
+ cwd: root,
206
+ maxBuffer: 10 * 1024 * 1024
207
+ });
208
+ return { command, ok: true, output: `${stdout}${stderr}`.trim() };
209
+ }
210
+ catch (error) {
211
+ const detail = error;
212
+ return {
213
+ command,
214
+ ok: false,
215
+ output: `${detail.stdout ?? ''}${detail.stderr ?? ''}`.trim() || detail.message || 'Command failed.'
216
+ };
217
+ }
218
+ };
219
+ const parseFlowSequence = (source) => {
220
+ const match = /^\[([\s\S]*)\]\s*(?:#.*)?$/.exec(source.trim());
221
+ if (!match)
222
+ return undefined;
223
+ const values = [];
224
+ let current = '';
225
+ let quote;
226
+ for (const character of match[1] ?? '') {
227
+ if (quote) {
228
+ current += character;
229
+ if (character === quote)
230
+ quote = undefined;
231
+ }
232
+ else if (character === '"' || character === "'") {
233
+ current += character;
234
+ quote = character;
235
+ }
236
+ else if (character === ',') {
237
+ values.push(current);
238
+ current = '';
239
+ }
240
+ else {
241
+ current += character;
242
+ }
243
+ }
244
+ if (current.trim())
245
+ values.push(current);
246
+ return values.map(value => {
247
+ const trimmed = value.trim();
248
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
249
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
250
+ return trimmed.slice(1, -1);
251
+ }
252
+ return trimmed;
253
+ }).filter(Boolean);
254
+ };
255
+ export const addReleaseAgeTarget = (source, packages, version) => {
256
+ if (!/^\s*minimumReleaseAgeExclude\s*:/m.test(source))
257
+ return source;
258
+ const lines = source.split(/\r?\n/);
259
+ const index = lines.findIndex(line => /^\s*minimumReleaseAgeExclude\s*:/.test(line));
260
+ const declaration = lines[index] ?? '';
261
+ const rootIndent = /^\s*/.exec(declaration)?.[0] ?? '';
262
+ const indent = `${rootIndent} `;
263
+ const declarationValue = declaration.slice(declaration.indexOf(':') + 1);
264
+ const inlineValues = parseFlowSequence(declarationValue);
265
+ if (declarationValue.trim() && !inlineValues)
266
+ return source;
267
+ const existing = new Set([
268
+ ...(inlineValues ?? []),
269
+ ...lines.flatMap(line => {
270
+ const value = /^\s*-\s*['"]?([^'"]+)['"]?\s*$/.exec(line)?.[1];
271
+ return value ? [value] : [];
272
+ })
273
+ ]);
274
+ const additions = packages
275
+ .map(packageName => `${packageName}@${version}`)
276
+ .filter(value => !existing.has(value))
277
+ .map(value => `${indent}- "${value}"`);
278
+ if (declaration.slice(declaration.indexOf(':') + 1).trim()) {
279
+ lines[index] = `${rootIndent}minimumReleaseAgeExclude:`;
280
+ additions.unshift(...(inlineValues ?? []).map(value => `${indent}- "${value}"`));
281
+ }
282
+ lines.splice(index + 1, 0, ...additions);
283
+ return lines.join('\n');
284
+ };
285
+ const removeObsoleteReleaseAgeEntries = (source, targetVersion) => source.split(/\r?\n/)
286
+ .filter(line => {
287
+ const match = /^\s*-\s*['"]?(@santi020k\/lumen(?:-(?:astro|core|elements|mcp|react))?)@([^'"]+)['"]?\s*$/.exec(line);
288
+ return !match || match[2] === targetVersion;
289
+ })
290
+ .join('\n');
291
+ const upgradeConsumer = async (inventory, targetVersion, allowDirty, verify) => {
292
+ if (inventory.dirty && !allowDirty) {
293
+ throw new Error(`${inventory.repository} is dirty. Commit a baseline or pass --allow-dirty.`);
294
+ }
295
+ if (!/^pnpm@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(inventory.packageManager ?? '')) {
296
+ throw new Error(`${inventory.repository} must declare an exact pnpm packageManager version.`);
297
+ }
298
+ if (inventory.nodeEngineSatisfied === false) {
299
+ throw new Error(`${inventory.repository} must be upgraded under Node ${inventory.nodeEngine ?? 'matching its engine declaration'}.`);
300
+ }
301
+ const manifestPaths = await findPackageManifests(inventory.repository);
302
+ const desiredManifests = new Map();
303
+ for (const path of manifestPaths) {
304
+ const source = await readFile(path, 'utf8');
305
+ const updated = updateLumenManifestSource(source, targetVersion);
306
+ desiredManifests.set(path, updated);
307
+ if (updated !== source)
308
+ await writeFile(path, updated);
309
+ }
310
+ const workspacePath = join(inventory.repository, 'pnpm-workspace.yaml');
311
+ if (await pathExists(workspacePath)) {
312
+ const source = await readFile(workspacePath, 'utf8');
313
+ const packages = [...new Set(inventory.references.map(item => item.packageName))];
314
+ const updated = addReleaseAgeTarget(updateLumenWorkspaceSource(source, targetVersion), packages, targetVersion);
315
+ if (updated !== source)
316
+ await writeFile(workspacePath, updated);
317
+ }
318
+ const packageNames = [...new Set(inventory.references.map(item => item.packageName))];
319
+ const commands = [await runCommand(inventory.repository, 'corepack', [
320
+ 'pnpm',
321
+ 'update',
322
+ '--recursive',
323
+ ...packageNames.map(packageName => `${packageName}@${targetVersion}`)
324
+ ])];
325
+ if (!commands[0]?.ok)
326
+ return commands;
327
+ for (const [path, source] of desiredManifests) {
328
+ await writeFile(path, source);
329
+ }
330
+ commands.push(await runCommand(inventory.repository, 'corepack', ['pnpm', 'install']));
331
+ if (!commands.at(-1)?.ok)
332
+ return commands;
333
+ if (await pathExists(workspacePath)) {
334
+ const source = await readFile(workspacePath, 'utf8');
335
+ const updated = removeObsoleteReleaseAgeEntries(source, targetVersion);
336
+ if (updated !== source) {
337
+ await writeFile(workspacePath, updated);
338
+ commands.push(await runCommand(inventory.repository, 'corepack', ['pnpm', 'install']));
339
+ }
340
+ }
341
+ if (verify && commands.every(command => command.ok)) {
342
+ const manifest = await readManifest(join(inventory.repository, 'package.json'));
343
+ for (const script of getVerificationScripts(manifest, inventory.frameworks)) {
344
+ const result = await runCommand(inventory.repository, 'corepack', ['pnpm', 'run', script]);
345
+ commands.push(result);
346
+ if (!result.ok)
347
+ break;
348
+ }
349
+ }
350
+ return commands;
351
+ };
352
+ export const runConsumerRollout = async (options) => {
353
+ if (options.apply && !options.targetVersion) {
354
+ throw new Error('A target version is required when applying a consumer rollout.');
355
+ }
356
+ const excluded = new Set((options.exclude ?? []).map(path => resolve(path)));
357
+ const repositories = options.repositories.map(path => resolve(path))
358
+ .filter(path => !excluded.has(path));
359
+ const report = {
360
+ generatedAt: new Date().toISOString(),
361
+ mode: options.apply ? 'apply' : 'inventory',
362
+ repositories: [],
363
+ ...(options.targetVersion ? { targetVersion: options.targetVersion } : {})
364
+ };
365
+ for (const repository of repositories) {
366
+ const before = await inspectLumenConsumer(repository, options.targetVersion);
367
+ const commands = options.apply
368
+ ? await upgradeConsumer(before, options.targetVersion ?? '', options.allowDirty ?? false, options.verify ?? true)
369
+ : [];
370
+ const after = options.apply
371
+ ? await inspectLumenConsumer(repository, options.targetVersion)
372
+ : before;
373
+ if (options.apply) {
374
+ after.warnings = after.warnings.filter(warning => !warning.startsWith('Repository has uncommitted changes'));
375
+ after.valid = after.warnings.length === 0;
376
+ }
377
+ report.repositories.push({ ...after, commands });
378
+ if (commands.some(command => !command.ok))
379
+ break;
380
+ }
381
+ if (options.report) {
382
+ await writeFile(resolve(options.report), `${JSON.stringify(report, undefined, 2)}\n`);
383
+ }
384
+ return report;
385
+ };
386
+ export const formatConsumerRollout = (report) => {
387
+ const lines = [
388
+ `Lumen consumer ${report.mode} (${report.repositories.length} repositories)`,
389
+ ...(report.targetVersion ? [`Target: ${report.targetVersion}`] : [])
390
+ ];
391
+ for (const repository of report.repositories) {
392
+ lines.push('', repository.repository, ` package manager: ${repository.packageManager ?? 'missing'}`, ` node engine: ${repository.nodeEngine ?? 'unspecified'}`, ` frameworks: ${repository.frameworks.join(', ') || 'none'}`, ` resolved graph: ${JSON.stringify(repository.resolvedVersions)}`, ` status: ${repository.valid && repository.commands.every(command => command.ok) ? 'ok' : 'attention required'}`);
393
+ lines.push(...repository.warnings.map(warning => ` warning: ${warning}`));
394
+ lines.push(...repository.commands.map(command => ` ${command.ok ? 'pass' : 'fail'}: ${command.command}`));
395
+ }
396
+ return lines.join('\n');
397
+ };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { type ConsumerInventory, type ConsumerReference, type ConsumerRolloutOptions, type ConsumerRolloutReport, formatConsumerRollout, inspectLumenConsumer, type LumenFramework, runConsumerRollout, updateLumenManifestSource, updateLumenWorkspaceSource } from './consumer-rollout.js';
1
2
  export { addLumenRegistryItem, getLumenRegistryEntries, getLumenRegistryItem, loadLumenRegistry, type LumenAddOptions, type LumenAddResult, type LumenAddTarget, type LumenMergeConflict, type LumenRegistry, lumenRegistry, type LumenRegistryComponent, type LumenRegistryEntry, type LumenRegistryFile, type LumenRegistryItem, type LumenRegistryLoadOptions } from './registry.js';
2
3
  export { applyDataViewState, canPlaceScheduleEvent, coerceThemeBuilderExportFormat, coerceThemeBuilderMode, coerceThemeBuilderScheme, composeClassName, createDataViewRequestUrl, createDataViewSearchParams, createDataViewServerRequest, createDataViewState, createDataViewStorageKey, createScheduleSlots, createScheduleStorageKey, createThemeBuilderTokens, createThemePalette, expandRecurringScheduleEvent, exportThemeBuilderCss, exportThemeBuilderValue, exportThemeCss, filterDataRecords, getContrastRatio, getScheduleConflicts, getVirtualRange, loadDataViewState, loadScheduleEvents, type LumenCodeToken, lumenCodeTokenClassNames, type LumenCodeTokenKind, lumenColors, type LumenColorTokenName, lumenColorTokenNames, type LumenComponentName, lumenComponentNames, type LumenContrastScore, lumenDarkTheme, type LumenDataRecord, type LumenDataViewServerRequest, type LumenDataViewState, lumenGlass, type LumenGlassColorTokenName, lumenGlassColorTokenNames, type LumenGlassEffectTokenName, lumenGlassEffectTokenNames, type LumenGlassTokenName, lumenGlassTokenNames, type LumenKeyValueReader, type LumenKeyValueStorage, type LumenKeyValueWriter, lumenLightTheme, lumenPackages, type LumenPackageTarget, type LumenScheduleConflict, type LumenScheduleEvent, type LumenScheduleKeyValueReader, type LumenScheduleKeyValueStorage, type LumenScheduleKeyValueWriter, type LumenScheduleResizeEdge, type LumenScheduleResizeOptions, type LumenScheduleResource, type LumenScheduleSlot, type LumenSemanticColorTokenName, lumenSemanticColorTokenNames, type LumenSortDirection, lumenThemeAttribute, type LumenThemeBuilderExportFormat, type LumenThemeBuilderMode, type LumenThemeBuilderOptions, type LumenThemeBuilderResult, type LumenThemeBuilderScheme, type LumenThemeContrastSuggestion, type LumenThemeTokens, lumenTokenNames, mergeThemeTokens, moveScheduleEvent, normalizeLumenCode, normalizeThemeBuilderHex, normalizeThemeBuilderHue, paginateDataRecords, parseDataViewState, parseScheduleEvents, parseThemeCss, pinDataViewColumn, renderLumenCodeHtml, resizeScheduleEvent, resizeScheduleEvents, saveDataViewState, saveScheduleEvents, scheduleEventsOverlap, scoreThemeContrast, serializeDataViewState, serializeScheduleEvents, sortDataRecords, suggestReadableInk, themeBuilderHexToHsl, toggleDataViewSelection, tokenizeLumenCode, tuneThemeContrast, unpinDataViewColumn } from '@santi020k/lumen-core';
3
4
  export declare const lumen: {
@@ -5,4 +6,5 @@ export declare const lumen: {
5
6
  readonly packageName: "@santi020k/lumen";
6
7
  readonly scope: "@santi020k";
7
8
  };
9
+ export { auditLumenTokenCss, type LumenTokenAuditFinding } from './token-audit.js';
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,aAAa,EACb,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC9B,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,8BAA8B,EAC9B,sBAAsB,EACtB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,EACxB,0BAA0B,EAC1B,2BAA2B,EAC3B,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,kBAAkB,EAClB,4BAA4B,EAC5B,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,cAAc,EACnB,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,WAAW,EACX,KAAK,mBAAmB,EACxB,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,mBAAmB,EACnB,KAAK,kBAAkB,EACvB,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACvB,UAAU,EACV,KAAK,wBAAwB,EAC7B,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,0BAA0B,EAC1B,KAAK,mBAAmB,EACxB,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,eAAe,EACf,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,2BAA2B,EAChC,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,2BAA2B,EAChC,4BAA4B,EAC5B,KAAK,kBAAkB,EACvB,mBAAmB,EACnB,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,4BAA4B,EACjC,KAAK,gBAAgB,EACrB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,EACvB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,uBAAuB,CAAA;AAE9B,eAAO,MAAM,KAAK;;;;CAIR,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,qBAAqB,EACrB,oBAAoB,EACpB,KAAK,cAAc,EACnB,kBAAkB,EAClB,yBAAyB,EACzB,0BAA0B,EAC3B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,aAAa,EACb,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC9B,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,8BAA8B,EAC9B,sBAAsB,EACtB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,EACxB,0BAA0B,EAC1B,2BAA2B,EAC3B,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,kBAAkB,EAClB,4BAA4B,EAC5B,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,cAAc,EACnB,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,WAAW,EACX,KAAK,mBAAmB,EACxB,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,mBAAmB,EACnB,KAAK,kBAAkB,EACvB,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACvB,UAAU,EACV,KAAK,wBAAwB,EAC7B,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,0BAA0B,EAC1B,KAAK,mBAAmB,EACxB,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,eAAe,EACf,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,2BAA2B,EAChC,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAC/B,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,2BAA2B,EAChC,4BAA4B,EAC5B,KAAK,kBAAkB,EACvB,mBAAmB,EACnB,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,4BAA4B,EACjC,KAAK,gBAAgB,EACrB,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,EACvB,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,uBAAuB,CAAA;AAE9B,eAAO,MAAM,KAAK;;;;CAIR,CAAA;AAEV,OAAO,EACL,kBAAkB,EAClB,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAA"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { formatConsumerRollout, inspectLumenConsumer, runConsumerRollout, updateLumenManifestSource, updateLumenWorkspaceSource } from './consumer-rollout.js';
1
2
  export { addLumenRegistryItem, getLumenRegistryEntries, getLumenRegistryItem, loadLumenRegistry, lumenRegistry } from './registry.js';
2
3
  export { applyDataViewState, canPlaceScheduleEvent, coerceThemeBuilderExportFormat, coerceThemeBuilderMode, coerceThemeBuilderScheme, composeClassName, createDataViewRequestUrl, createDataViewSearchParams, createDataViewServerRequest, createDataViewState, createDataViewStorageKey, createScheduleSlots, createScheduleStorageKey, createThemeBuilderTokens, createThemePalette, expandRecurringScheduleEvent, exportThemeBuilderCss, exportThemeBuilderValue, exportThemeCss, filterDataRecords, getContrastRatio, getScheduleConflicts, getVirtualRange, loadDataViewState, loadScheduleEvents, lumenCodeTokenClassNames, lumenColors, lumenColorTokenNames, lumenComponentNames, lumenDarkTheme, lumenGlass, lumenGlassColorTokenNames, lumenGlassEffectTokenNames, lumenGlassTokenNames, lumenLightTheme, lumenPackages, lumenSemanticColorTokenNames, lumenThemeAttribute, lumenTokenNames, mergeThemeTokens, moveScheduleEvent, normalizeLumenCode, normalizeThemeBuilderHex, normalizeThemeBuilderHue, paginateDataRecords, parseDataViewState, parseScheduleEvents, parseThemeCss, pinDataViewColumn, renderLumenCodeHtml, resizeScheduleEvent, resizeScheduleEvents, saveDataViewState, saveScheduleEvents, scheduleEventsOverlap, scoreThemeContrast, serializeDataViewState, serializeScheduleEvents, sortDataRecords, suggestReadableInk, themeBuilderHexToHsl, toggleDataViewSelection, tokenizeLumenCode, tuneThemeContrast, unpinDataViewColumn } from '@santi020k/lumen-core';
3
4
  export const lumen = {
@@ -5,3 +6,4 @@ export const lumen = {
5
6
  packageName: '@santi020k/lumen',
6
7
  scope: '@santi020k'
7
8
  };
9
+ export { auditLumenTokenCss } from './token-audit.js';
@@ -39,7 +39,7 @@ export declare const lumenRegistry: {
39
39
  readonly components: readonly [{
40
40
  readonly name: "Accordion";
41
41
  readonly type: "component";
42
- readonly description: "Stacks collapsible content sections.";
42
+ readonly description: "Stacks collapsible content sections as cards or a compact flush list.";
43
43
  readonly category: "Layout";
44
44
  readonly files: readonly ["packages/astro/components/Accordion.astro", "packages/astro/styles/lumen.css"];
45
45
  readonly dependencies: readonly ["styles"];
@@ -190,6 +190,13 @@ export declare const lumenRegistry: {
190
190
  readonly category: "Data display";
191
191
  readonly files: readonly ["packages/astro/components/Code.astro", "packages/astro/styles/lumen.css", "packages/astro/runtime/UIPrimitives.astro"];
192
192
  readonly dependencies: readonly ["styles", "runtime"];
193
+ }, {
194
+ readonly name: "CodeTabs";
195
+ readonly type: "component";
196
+ readonly description: "Groups related code examples into accessible, persistent tabs.";
197
+ readonly category: "Data display";
198
+ readonly files: readonly ["packages/astro/components/CodeTabs.astro", "packages/astro/components/Code.astro", "packages/astro/components/Tabs.astro", "packages/astro/styles/lumen.css", "packages/astro/runtime/UIPrimitives.astro"];
199
+ readonly dependencies: readonly ["Code", "Tabs", "styles", "runtime"];
193
200
  }, {
194
201
  readonly name: "Combobox";
195
202
  readonly type: "component";
@@ -697,19 +704,22 @@ export declare const lumenRegistry: {
697
704
  readonly type: "component";
698
705
  readonly category: "Data display";
699
706
  readonly description: "Animates a formatted metric while keeping one stable accessible value.";
700
- readonly files: readonly ["packages/astro/components/AnimatedNumber.astro", "packages/react/src/components.tsx"];
707
+ readonly files: readonly ["packages/astro/components/AnimatedNumber.astro", "packages/astro/runtime/UIPrimitives.astro", "packages/astro/styles/lumen.css", "packages/react/src/components.tsx"];
708
+ readonly dependencies: readonly ["styles", "runtime"];
701
709
  }, {
702
710
  readonly name: "RevealGroup";
703
711
  readonly type: "component";
704
712
  readonly category: "Layout";
705
713
  readonly description: "Reveals a group of direct children with tokenized, reduced-motion-aware staggering.";
706
- readonly files: readonly ["packages/astro/components/RevealGroup.astro", "packages/react/src/components.tsx"];
714
+ readonly files: readonly ["packages/astro/components/RevealGroup.astro", "packages/astro/runtime/UIPrimitives.astro", "packages/astro/styles/lumen.css", "packages/react/src/components.tsx"];
715
+ readonly dependencies: readonly ["styles", "runtime"];
707
716
  }, {
708
717
  readonly name: "ScrollReveal";
709
718
  readonly type: "component";
710
719
  readonly category: "Layout";
711
- readonly description: "A Scroll Reveal component.";
712
- readonly files: readonly ["packages/astro/components/ScrollReveal.astro", "packages/react/src/components.tsx"];
720
+ readonly description: "Animates wrapped content as it first enters the viewport.";
721
+ readonly files: readonly ["packages/astro/components/ScrollReveal.astro", "packages/astro/runtime/UIPrimitives.astro", "packages/astro/styles/lumen.css", "packages/react/src/components.tsx"];
722
+ readonly dependencies: readonly ["styles", "runtime"];
713
723
  }, {
714
724
  readonly name: "Stat";
715
725
  readonly type: "component";
@@ -1 +1 @@
1
- {"version":3,"file":"registry-data.d.ts","sourceRoot":"","sources":["../src/registry-data.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuqDQ,CAAA"}
1
+ {"version":3,"file":"registry-data.d.ts","sourceRoot":"","sources":["../src/registry-data.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4sDQ,CAAA"}
@@ -98,7 +98,7 @@ export const lumenRegistry = {
98
98
  {
99
99
  "name": "Accordion",
100
100
  "type": "component",
101
- "description": "Stacks collapsible content sections.",
101
+ "description": "Stacks collapsible content sections as cards or a compact flush list.",
102
102
  "category": "Layout",
103
103
  "files": [
104
104
  "packages/astro/components/Accordion.astro",
@@ -393,6 +393,25 @@ export const lumenRegistry = {
393
393
  "runtime"
394
394
  ]
395
395
  },
396
+ {
397
+ "name": "CodeTabs",
398
+ "type": "component",
399
+ "description": "Groups related code examples into accessible, persistent tabs.",
400
+ "category": "Data display",
401
+ "files": [
402
+ "packages/astro/components/CodeTabs.astro",
403
+ "packages/astro/components/Code.astro",
404
+ "packages/astro/components/Tabs.astro",
405
+ "packages/astro/styles/lumen.css",
406
+ "packages/astro/runtime/UIPrimitives.astro"
407
+ ],
408
+ "dependencies": [
409
+ "Code",
410
+ "Tabs",
411
+ "styles",
412
+ "runtime"
413
+ ]
414
+ },
396
415
  {
397
416
  "name": "Combobox",
398
417
  "type": "component",
@@ -1417,7 +1436,13 @@ export const lumenRegistry = {
1417
1436
  "description": "Animates a formatted metric while keeping one stable accessible value.",
1418
1437
  "files": [
1419
1438
  "packages/astro/components/AnimatedNumber.astro",
1439
+ "packages/astro/runtime/UIPrimitives.astro",
1440
+ "packages/astro/styles/lumen.css",
1420
1441
  "packages/react/src/components.tsx"
1442
+ ],
1443
+ "dependencies": [
1444
+ "styles",
1445
+ "runtime"
1421
1446
  ]
1422
1447
  },
1423
1448
  {
@@ -1427,17 +1452,29 @@ export const lumenRegistry = {
1427
1452
  "description": "Reveals a group of direct children with tokenized, reduced-motion-aware staggering.",
1428
1453
  "files": [
1429
1454
  "packages/astro/components/RevealGroup.astro",
1455
+ "packages/astro/runtime/UIPrimitives.astro",
1456
+ "packages/astro/styles/lumen.css",
1430
1457
  "packages/react/src/components.tsx"
1458
+ ],
1459
+ "dependencies": [
1460
+ "styles",
1461
+ "runtime"
1431
1462
  ]
1432
1463
  },
1433
1464
  {
1434
1465
  "name": "ScrollReveal",
1435
1466
  "type": "component",
1436
1467
  "category": "Layout",
1437
- "description": "A Scroll Reveal component.",
1468
+ "description": "Animates wrapped content as it first enters the viewport.",
1438
1469
  "files": [
1439
1470
  "packages/astro/components/ScrollReveal.astro",
1471
+ "packages/astro/runtime/UIPrimitives.astro",
1472
+ "packages/astro/styles/lumen.css",
1440
1473
  "packages/react/src/components.tsx"
1474
+ ],
1475
+ "dependencies": [
1476
+ "styles",
1477
+ "runtime"
1441
1478
  ]
1442
1479
  },
1443
1480
  {
@@ -0,0 +1,9 @@
1
+ export interface LumenTokenAuditFinding {
2
+ column: number;
3
+ file: string;
4
+ line: number;
5
+ token: string;
6
+ value: string;
7
+ }
8
+ export declare const auditLumenTokenCss: (source: string, file?: string) => LumenTokenAuditFinding[];
9
+ //# sourceMappingURL=token-audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-audit.d.ts","sourceRoot":"","sources":["../src/token-audit.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAMD,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,EAAE,aAAgB,KAAG,sBAAsB,EAwB3F,CAAA"}
@@ -0,0 +1,25 @@
1
+ import { lumenSemanticColorTokenNames } from '@santi020k/lumen-core';
2
+ const declarationPattern = /--([a-z][\w-]*)\s*:\s*([^;}\n]+)/giu;
3
+ const channelPattern = /^(?:var\(.+\)|-?(?:\d+(?:\.\d+)?|\.\d+)(?:deg|grad|rad|turn)?\s+-?(?:\d+(?:\.\d+)?|\.\d+)%\s+-?(?:\d+(?:\.\d+)?|\.\d+)%(?:\s*\/\s*(?:\d+(?:\.\d+)?|\.\d+)%?)?)$/iu;
4
+ const semanticTokens = new Set(lumenSemanticColorTokenNames);
5
+ export const auditLumenTokenCss = (source, file = '<input>') => {
6
+ const findings = [];
7
+ for (const match of source.matchAll(declarationPattern)) {
8
+ const token = match[1];
9
+ const value = match[2]?.trim();
10
+ if (!token || !value || !semanticTokens.has(token) || channelPattern.test(value))
11
+ continue;
12
+ const index = match.index;
13
+ const before = source.slice(0, index);
14
+ const line = before.split('\n').length;
15
+ const lineStart = before.lastIndexOf('\n') + 1;
16
+ findings.push({
17
+ column: index - lineStart + 1,
18
+ file,
19
+ line,
20
+ token,
21
+ value
22
+ });
23
+ }
24
+ return findings;
25
+ };
package/layers.css ADDED
@@ -0,0 +1,5 @@
1
+ /*
2
+ * Import before Tailwind and Lumen so Tailwind base styles run before Lumen components while
3
+ * Tailwind utilities remain able to override component presentation.
4
+ */
5
+ @layer theme, base, lumen.tokens, lumen.base, components, lumen.components, lumen.glass, utilities;
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@santi020k/lumen",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
7
  "description": "Umbrella package for the Lumen multi-framework primitive UI system.",
8
8
  "type": "module",
9
9
  "sideEffects": [
10
+ "./layers.css",
10
11
  "./styles.css"
11
12
  ],
12
13
  "main": "./dist/index.js",
@@ -22,11 +23,13 @@
22
23
  "import": "./dist/registry.js"
23
24
  },
24
25
  "./styles.css": "./styles.css",
26
+ "./layers.css": "./layers.css",
25
27
  "./package.json": "./package.json"
26
28
  },
27
29
  "files": [
28
30
  "bin",
29
31
  "dist",
32
+ "layers.css",
30
33
  "styles.css",
31
34
  "README.md",
32
35
  "LICENSE"
@@ -35,16 +38,19 @@
35
38
  "lumen": "./bin/lumen.mjs"
36
39
  },
37
40
  "dependencies": {
38
- "@santi020k/lumen-core": "^0.1.0"
41
+ "semver": "^7.8.0",
42
+ "@santi020k/lumen-core": "^0.3.0"
39
43
  },
40
44
  "devDependencies": {
41
45
  "@types/node": "^26.1.1",
46
+ "@types/semver": "^7.7.1",
42
47
  "@vitest/coverage-v8": "^4.1.10",
43
48
  "typescript": "^6.0.3",
49
+ "tailwindcss": "^4.3.3",
44
50
  "vitest": "^4.1.10"
45
51
  },
46
52
  "engines": {
47
- "node": ">=22.18.0"
53
+ "node": ">=22.12.0"
48
54
  },
49
55
  "repository": {
50
56
  "type": "git",
package/styles.css CHANGED
@@ -264,7 +264,7 @@
264
264
  font-family: var(--ui-font);
265
265
  }
266
266
 
267
- :where(.ui-button, .ui-back-to-top, .ui-code__copy, .ui-input, .ui-language-toggle, .ui-link, .ui-select, .ui-skip-link, .ui-textarea, .ui-checkbox, .ui-switch, .ui-toggle, .ui-slider, .ui-color-picker, .ui-resizable__handle):focus-visible {
267
+ :where(.ui-button, .ui-back-to-top, .ui-code__copy, .ui-input, .ui-language-toggle, .ui-link, .ui-select, .ui-skip-link, .ui-textarea, .ui-checkbox, .ui-switch, .ui-toggle, .ui-theme-toggle, .ui-slider, .ui-color-picker, .ui-resizable__handle):focus-visible {
268
268
  outline: 2px solid hsl(var(--brand));
269
269
  outline-offset: 2px;
270
270
  }
@@ -623,6 +623,11 @@
623
623
  background: hsl(var(--surface-muted) / 0.72);
624
624
  }
625
625
 
626
+ .ui-card--unstyled,
627
+ .ui-button-link--unstyled {
628
+ all: revert-layer;
629
+ }
630
+
626
631
  .ui-card--glass.ui-card--interactive:hover,
627
632
  .ui-card--glass.card-interactive:hover {
628
633
  border-color: hsl(var(--glass-edge));
@@ -881,6 +886,15 @@
881
886
  color: hsl(var(--brand-solid));
882
887
  }
883
888
 
889
+ .ui-link--inherit {
890
+ color: inherit;
891
+ font-weight: inherit;
892
+ }
893
+
894
+ .ui-link--inherit:hover {
895
+ color: inherit;
896
+ }
897
+
884
898
  .ui-prose {
885
899
  max-width: 65ch;
886
900
  color: hsl(var(--ink-soft));
@@ -1702,31 +1716,184 @@
1702
1716
 
1703
1717
  .ui-accordion {
1704
1718
  display: grid;
1705
- gap: 0.5rem;
1719
+ gap: 0.75rem;
1706
1720
  }
1707
1721
 
1708
1722
  .ui-accordion details,
1709
1723
  .ui-collapsible {
1724
+ overflow: clip;
1710
1725
  border: 1px solid hsl(var(--line));
1711
- border-radius: var(--ui-radius);
1726
+ border-radius: var(--ui-radius-lg);
1712
1727
  background: hsl(var(--surface));
1713
1728
  color: hsl(var(--ink-soft));
1714
- padding: 0.85rem 1rem;
1729
+ box-shadow: var(--ui-shadow-sm);
1730
+ transition:
1731
+ border-color var(--ui-duration-fast) var(--ui-ease),
1732
+ box-shadow var(--ui-duration-fast) var(--ui-ease);
1733
+ }
1734
+
1735
+ .ui-accordion details[open],
1736
+ .ui-collapsible[open] {
1737
+ border-color: hsl(var(--brand) / 0.38);
1738
+ box-shadow:
1739
+ 0 0 0 1px hsl(var(--brand) / 0.08),
1740
+ var(--ui-shadow-sm);
1741
+ }
1742
+
1743
+ .ui-accordion--flush {
1744
+ gap: 0;
1745
+ border-top: 1px solid hsl(var(--line));
1746
+ }
1747
+
1748
+ .ui-accordion--flush details {
1749
+ overflow: visible;
1750
+ border: 0;
1751
+ border-bottom: 1px solid hsl(var(--line));
1752
+ border-radius: 0;
1753
+ background: transparent;
1754
+ box-shadow: none;
1755
+ }
1756
+
1757
+ .ui-accordion--flush details[open] {
1758
+ border-color: hsl(var(--line));
1759
+ background: transparent;
1760
+ box-shadow: none;
1715
1761
  }
1716
1762
 
1717
1763
  .ui-accordion summary,
1718
1764
  .ui-collapsible summary {
1765
+ display: grid;
1766
+ grid-template-columns: minmax(0, 1fr) auto;
1767
+ align-items: center;
1768
+ gap: 1rem;
1719
1769
  cursor: pointer;
1770
+ list-style: none;
1720
1771
  color: hsl(var(--ink));
1721
1772
  font-weight: 650;
1773
+ line-height: 1.35;
1774
+ min-height: 3.5rem;
1775
+ padding: 1rem 1.125rem;
1776
+ transition:
1777
+ background-color var(--ui-duration-fast) var(--ui-ease),
1778
+ color var(--ui-duration-fast) var(--ui-ease);
1779
+ }
1780
+
1781
+ .ui-accordion summary:hover,
1782
+ .ui-collapsible summary:hover {
1783
+ background: hsl(var(--surface-muted));
1784
+ }
1785
+
1786
+ .ui-accordion details[open] > summary,
1787
+ .ui-collapsible[open] > summary {
1788
+ color: hsl(var(--brand));
1789
+ }
1790
+
1791
+ .ui-accordion details[open],
1792
+ .ui-collapsible[open] {
1793
+ padding-block-end: 1rem;
1794
+ }
1795
+
1796
+ .ui-accordion details > :not(summary),
1797
+ .ui-collapsible > :not(summary) {
1798
+ margin-inline: 1.125rem;
1799
+ }
1800
+
1801
+ .ui-accordion summary::-webkit-details-marker,
1802
+ .ui-collapsible summary::-webkit-details-marker {
1803
+ display: none;
1804
+ }
1805
+
1806
+ .ui-accordion summary::after,
1807
+ .ui-collapsible summary::after {
1808
+ width: 0.5rem;
1809
+ height: 0.5rem;
1810
+ border-right: 1.5px solid hsl(var(--ink-muted));
1811
+ border-bottom: 1.5px solid hsl(var(--ink-muted));
1812
+ content: "";
1813
+ border-radius: 1px;
1814
+ box-shadow: 0 0 0 0.45rem hsl(var(--surface-muted));
1815
+ transform: rotate(45deg) translate(-0.1rem, -0.1rem);
1816
+ transition:
1817
+ border-color var(--ui-duration-fast) var(--ui-ease),
1818
+ box-shadow var(--ui-duration-fast) var(--ui-ease),
1819
+ transform var(--ui-duration-fast) var(--ui-ease);
1820
+ }
1821
+
1822
+ .ui-accordion details[open] > summary::after,
1823
+ .ui-collapsible[open] > summary::after {
1824
+ border-color: hsl(var(--brand));
1825
+ box-shadow: 0 0 0 0.45rem hsl(var(--brand-soft));
1826
+ transform: rotate(225deg) translate(-0.1rem, -0.1rem);
1827
+ }
1828
+
1829
+ .ui-accordion summary:hover::after,
1830
+ .ui-collapsible summary:hover::after {
1831
+ border-color: hsl(var(--brand));
1832
+ box-shadow: 0 0 0 0.45rem hsl(var(--brand-soft));
1833
+ }
1834
+
1835
+ .ui-accordion summary:focus-visible,
1836
+ .ui-collapsible summary:focus-visible {
1837
+ border-radius: calc(var(--ui-radius-lg) - 1px);
1838
+ outline: 2px solid hsl(var(--brand));
1839
+ outline-offset: -3px;
1840
+ }
1841
+
1842
+ .ui-accordion--flush details[open] {
1843
+ padding-block-end: 1.25rem;
1844
+ }
1845
+
1846
+ .ui-accordion--flush summary {
1847
+ min-height: 4rem;
1848
+ padding: 1.125rem 0.25rem;
1849
+ }
1850
+
1851
+ .ui-accordion--flush summary:hover {
1852
+ border-radius: var(--ui-radius);
1853
+ background: hsl(var(--surface-muted) / 0.72);
1854
+ box-shadow: 0 0 0 0.625rem hsl(var(--surface-muted) / 0.72);
1855
+ }
1856
+
1857
+ .ui-accordion--flush details[open] > summary {
1858
+ color: hsl(var(--ink));
1859
+ }
1860
+
1861
+ .ui-accordion--flush details > :not(summary) {
1862
+ margin-inline: 0.25rem;
1863
+ }
1864
+
1865
+ .ui-accordion--flush details[open] > summary::before {
1866
+ width: 0.2rem;
1867
+ height: 1.25rem;
1868
+ border-radius: 999px;
1869
+ background: hsl(var(--brand));
1870
+ content: "";
1871
+ position: absolute;
1872
+ inset-inline-start: -0.875rem;
1873
+ }
1874
+
1875
+ .ui-accordion--flush summary {
1876
+ position: relative;
1722
1877
  }
1723
1878
 
1724
1879
  .ui-accordion p,
1725
1880
  .ui-collapsible p {
1881
+ color: hsl(var(--ink-soft));
1726
1882
  margin-block-end: 0;
1727
1883
  line-height: 1.6;
1728
1884
  }
1729
1885
 
1886
+ @media (prefers-reduced-motion: reduce) {
1887
+ .ui-accordion details,
1888
+ .ui-collapsible,
1889
+ .ui-accordion summary,
1890
+ .ui-collapsible summary,
1891
+ .ui-accordion summary::after,
1892
+ .ui-collapsible summary::after {
1893
+ transition: none;
1894
+ }
1895
+ }
1896
+
1730
1897
  .ui-dialog,
1731
1898
  .ui-alert-dialog,
1732
1899
  .ui-drawer,
@@ -1752,6 +1919,19 @@
1752
1919
  transform: translate(-50%, -50%);
1753
1920
  }
1754
1921
 
1922
+ .ui-dialog--fullscreen {
1923
+ inset: 0;
1924
+ width: 100%;
1925
+ max-width: none;
1926
+ height: 100dvh;
1927
+ max-height: none;
1928
+ margin: 0;
1929
+ border: 0;
1930
+ border-radius: 0;
1931
+ padding: 0;
1932
+ transform: none;
1933
+ }
1934
+
1755
1935
  .ui-dialog::backdrop,
1756
1936
  .ui-alert-dialog::backdrop,
1757
1937
  .ui-drawer::backdrop,
@@ -1944,6 +2124,19 @@
1944
2124
  color: hsl(var(--ink));
1945
2125
  }
1946
2126
 
2127
+ .ui-navigation-menu--unstyled {
2128
+ all: revert-layer;
2129
+ }
2130
+
2131
+ .ui-navigation-menu--unstyled :where(a, button) {
2132
+ all: revert-layer;
2133
+ }
2134
+
2135
+ .ui-navigation-menu--unstyled :where(a, button):hover,
2136
+ .ui-navigation-menu--unstyled :where(a, button)[aria-current="page"] {
2137
+ all: revert-layer;
2138
+ }
2139
+
1947
2140
  .ui-breadcrumb {
1948
2141
  color: hsl(var(--ink-muted));
1949
2142
  font-size: 0.875rem;
@@ -2100,6 +2293,96 @@
2100
2293
  color: hsl(var(--ink-soft));
2101
2294
  }
2102
2295
 
2296
+ .ui-code-tabs {
2297
+ display: block;
2298
+ overflow: hidden;
2299
+ border: 1px solid hsl(var(--line));
2300
+ border-radius: var(--ui-radius-lg);
2301
+ background: hsl(var(--surface-muted));
2302
+ }
2303
+
2304
+ .ui-code-tabs .ui-code-tabs__list {
2305
+ display: flex;
2306
+ width: auto;
2307
+ gap: 0.25rem;
2308
+ border: 0;
2309
+ border-bottom: 1px solid hsl(var(--line));
2310
+ border-radius: 0;
2311
+ background: hsl(var(--surface));
2312
+ padding: 0.375rem;
2313
+ }
2314
+
2315
+ .ui-code-tabs .ui-code-tabs__tab {
2316
+ min-width: 4.25rem;
2317
+ border: 0;
2318
+ border-radius: var(--ui-radius-sm);
2319
+ background: transparent;
2320
+ color: hsl(var(--ink-muted));
2321
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
2322
+ font-size: 0.8125rem;
2323
+ font-weight: 500;
2324
+ padding: 0.5rem 0.875rem;
2325
+ transition:
2326
+ background-color 150ms ease,
2327
+ color 150ms ease;
2328
+ }
2329
+
2330
+ .ui-code-tabs .ui-code-tabs__tab:hover {
2331
+ background: hsl(var(--surface-muted));
2332
+ color: hsl(var(--ink));
2333
+ }
2334
+
2335
+ .ui-code-tabs .ui-code-tabs__tab[aria-selected="true"] {
2336
+ background: hsl(var(--brand-soft));
2337
+ box-shadow: none;
2338
+ color: hsl(var(--brand));
2339
+ }
2340
+
2341
+ .ui-code-tabs .ui-code-tabs__tab:focus-visible {
2342
+ outline: 2px solid hsl(var(--brand));
2343
+ outline-offset: 1px;
2344
+ }
2345
+
2346
+ .ui-code-tabs .ui-code-tabs__panel {
2347
+ color: hsl(var(--ink));
2348
+ }
2349
+
2350
+ .ui-code-tabs .ui-code--block {
2351
+ margin: 0;
2352
+ border: 0;
2353
+ border-radius: 0;
2354
+ background: transparent;
2355
+ box-shadow: none;
2356
+ }
2357
+
2358
+ .ui-code-tabs .ui-code__header {
2359
+ position: absolute;
2360
+ z-index: 1;
2361
+ inset-block-start: 0.75rem;
2362
+ inset-inline-end: 0.75rem;
2363
+ display: block;
2364
+ min-height: 0;
2365
+ border: 0;
2366
+ background: transparent;
2367
+ padding: 0;
2368
+ }
2369
+
2370
+ .ui-code-tabs .ui-code__dots,
2371
+ .ui-code-tabs .ui-code__meta {
2372
+ display: none;
2373
+ }
2374
+
2375
+ .ui-code-tabs .ui-code--block :where(pre) {
2376
+ min-height: 3.5rem;
2377
+ padding: 1rem 3.75rem 1rem 1.25rem;
2378
+ }
2379
+
2380
+ @media (prefers-reduced-motion: reduce) {
2381
+ .ui-code-tabs .ui-code-tabs__tab {
2382
+ transition: none;
2383
+ }
2384
+ }
2385
+
2103
2386
  .ui-table-wrap,
2104
2387
  .ui-data-table {
2105
2388
  width: 100%;
@@ -2314,6 +2597,16 @@
2314
2597
  color: hsl(var(--brand-solid));
2315
2598
  }
2316
2599
 
2600
+ .ui-sidebar--unstyled {
2601
+ all: revert-layer;
2602
+ }
2603
+
2604
+ .ui-sidebar--unstyled a,
2605
+ .ui-sidebar--unstyled a:hover,
2606
+ .ui-sidebar--unstyled a[aria-current="page"] {
2607
+ all: revert-layer;
2608
+ }
2609
+
2317
2610
  .ui-empty {
2318
2611
  display: grid;
2319
2612
  place-items: center;
@@ -2452,6 +2745,9 @@
2452
2745
  .ui-code--block {
2453
2746
  position: relative;
2454
2747
  overflow: hidden;
2748
+ width: 100%;
2749
+ max-width: 100%;
2750
+ min-width: 0;
2455
2751
  margin: 0;
2456
2752
  border: 1px solid var(--ui-code-border);
2457
2753
  border-radius: var(--ui-radius-lg);
@@ -2601,6 +2897,16 @@
2601
2897
  white-space: pre;
2602
2898
  }
2603
2899
 
2900
+ .ui-code--wrap :where(pre) {
2901
+ overflow-x: hidden;
2902
+ }
2903
+
2904
+ .ui-code--wrap :where(code) {
2905
+ min-width: 0;
2906
+ overflow-wrap: anywhere;
2907
+ white-space: pre-wrap;
2908
+ }
2909
+
2604
2910
  .ui-code--block :where(code span) {
2605
2911
  color: var(--shiki-light, var(--ui-code-token, var(--ui-code-fg)));
2606
2912
  font-style: var(--shiki-light-font-style, var(--ui-code-token-font-style, normal));
@@ -4009,6 +4315,16 @@
4009
4315
  }
4010
4316
  }
4011
4317
 
4318
+ .ui-dialog--fullscreen:not([open]) {
4319
+ transform: none;
4320
+ }
4321
+
4322
+ @starting-style {
4323
+ .ui-dialog--fullscreen[open] {
4324
+ transform: none;
4325
+ }
4326
+ }
4327
+
4012
4328
  .ui-drawer {
4013
4329
  transition:
4014
4330
  opacity var(--ui-duration) var(--ui-ease),
@@ -4584,6 +4900,16 @@
4584
4900
  font-variant-numeric: tabular-nums;
4585
4901
  }
4586
4902
 
4903
+ a.ui-pill {
4904
+ cursor: pointer;
4905
+ text-decoration: none;
4906
+ }
4907
+
4908
+ a.ui-pill:hover {
4909
+ border-color: hsl(var(--brand) / 0.42);
4910
+ color: hsl(var(--brand-solid));
4911
+ }
4912
+
4587
4913
  /* CoverImage hover lift + bottom gradient overlay */
4588
4914
  .ui-cover-image--hover {
4589
4915
  transition: transform 300ms ease;