@ggui-ai/project-config 0.1.0-rc.1

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.
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Theme loader — resolves `ggui.json#theme` into a validated
3
+ * `LoadedTheme` at boot.
4
+ *
5
+ * The flow:
6
+ *
7
+ * ggui.json.theme (undefined | string | { preset, mode, overrides } | { file, mode })
8
+ * → undefined → default light theme
9
+ * → string OR { preset } → registry lookup via @ggui-ai/design
10
+ * → { file } → readFile → JSON.parse → parseThemeDocument
11
+ *
12
+ * Non-throwing per the blueprint + primitive discovery precedent —
13
+ * one malformed theme should surface as a structured issue, not an
14
+ * exception. `ggui serve` escalates issues to a fatal exit before
15
+ * binding a port.
16
+ *
17
+ * ## cssVariables pre-rendering
18
+ *
19
+ * The loader pre-renders the `:root { --ggui-*: value; }` CSS block
20
+ * via `@ggui-ai/design/themes`: `parseTheme(id, DtcgTheme)` for the
21
+ * preset / default paths (full DTCG with canvas + motion tokens) and
22
+ * `generateCssVariables(ThemeDocument)` for the file path (the
23
+ * duck-typed walker accepts the plain DTCG document without requiring
24
+ * the strict {@link DtcgTheme} shape). Downstream consumers (console,
25
+ * render endpoint, MCP apps iframe) inject the pre-rendered string
26
+ * without re-walking the token tree.
27
+ *
28
+ * ## Mode handling
29
+ *
30
+ * `mode: 'light' | 'dark'` is propagated on every `LoadedTheme`
31
+ * variant so consumers can emit `color-scheme: dark` and pick the
32
+ * matching variant from the registry. For the file path, `mode`
33
+ * is metadata only — the file's tokens ARE the resolved theme,
34
+ * not a switch.
35
+ */
36
+ import { readFileSync, existsSync } from 'node:fs';
37
+ import { dirname, isAbsolute, resolve } from 'node:path';
38
+ import { generateCssVariables, getRawTheme, getTheme, lightTheme, parseTheme, } from '@ggui-ai/design/themes';
39
+ import { parseThemeDocument } from './theme.js';
40
+ /**
41
+ * Load the theme declared in `ggui.json#theme`, or fall back to the
42
+ * shipped default when the field is absent.
43
+ *
44
+ * Non-throwing — single-issue failure mode returned as a tagged
45
+ * result. Throws only for programmer errors (non-absolute
46
+ * `projectRoot`), matching the other discovery helpers.
47
+ */
48
+ export function loadTheme(options) {
49
+ const { projectRoot, manifest } = options;
50
+ if (!isAbsolute(projectRoot)) {
51
+ throw new Error(`loadTheme: projectRoot must be absolute, got ${projectRoot}`);
52
+ }
53
+ const themeConfig = manifest.theme;
54
+ // ── Default path (no `theme` declared) ──────────────────────────
55
+ if (themeConfig === undefined) {
56
+ return {
57
+ ok: true,
58
+ theme: {
59
+ source: 'default',
60
+ mode: 'light',
61
+ document: lightTheme,
62
+ cssVariables: parseTheme('default', lightTheme).cssVariables,
63
+ },
64
+ };
65
+ }
66
+ // ── String shorthand: preset id ─────────────────────────────────
67
+ if (typeof themeConfig === 'string') {
68
+ return loadPreset(themeConfig, 'light', undefined);
69
+ }
70
+ // ── File path ───────────────────────────────────────────────────
71
+ if ('file' in themeConfig) {
72
+ return loadFile(themeConfig.file, themeConfig.mode ?? 'light', projectRoot);
73
+ }
74
+ // ── Preset object ───────────────────────────────────────────────
75
+ return loadPreset(themeConfig.preset, themeConfig.mode ?? 'light', themeConfig.overrides);
76
+ }
77
+ /**
78
+ * Resolve a registered preset (with optional mode + overrides) into
79
+ * a `LoadedTheme`. Returns an issue when the preset id is
80
+ * unregistered.
81
+ *
82
+ * When `overrides` is supplied, each entry is applied as a deep-set
83
+ * onto the resolved raw `DtcgTheme` BEFORE CSS emission. Unknown
84
+ * dot-paths are silently dropped — the console token editor mints
85
+ * valid keys and an accidentally-stale override should not fail
86
+ * manifest parse.
87
+ */
88
+ function loadPreset(preset, mode, overrides) {
89
+ // Resolve the raw token tree first so we can layer overrides
90
+ // before parseTheme walks the leaves.
91
+ const raw = getRawTheme(preset, mode);
92
+ if (!raw) {
93
+ return {
94
+ ok: false,
95
+ issue: {
96
+ path: preset,
97
+ message: `Theme preset "${preset}" is not registered. ` +
98
+ `Use one of the registered ids (e.g. "ggui", "claudic", ` +
99
+ `"premium-zen") or switch to { file: "./..." }.`,
100
+ },
101
+ };
102
+ }
103
+ // No overrides: hand off to the registry's parser directly so we
104
+ // share its cache.
105
+ if (!overrides || Object.keys(overrides).length === 0) {
106
+ const parsed = getTheme(preset, mode);
107
+ if (!parsed) {
108
+ // Should not happen — getRawTheme succeeded above. Defensive.
109
+ return {
110
+ ok: false,
111
+ issue: {
112
+ path: preset,
113
+ message: `Theme preset "${preset}" parsed unexpectedly empty.`,
114
+ },
115
+ };
116
+ }
117
+ return {
118
+ ok: true,
119
+ theme: {
120
+ source: 'preset',
121
+ preset,
122
+ mode,
123
+ document: raw,
124
+ cssVariables: parsed.cssVariables,
125
+ },
126
+ };
127
+ }
128
+ // With overrides: clone the raw tree, deep-set each leaf's $value,
129
+ // then re-parse. We bypass the registry's cache because the
130
+ // override set is per-call and would pollute it.
131
+ const mutated = applyOverrides(raw, overrides);
132
+ const parsed = parseTheme(preset, mutated);
133
+ return {
134
+ ok: true,
135
+ theme: {
136
+ source: 'preset',
137
+ preset,
138
+ mode,
139
+ overrides,
140
+ document: mutated,
141
+ cssVariables: parsed.cssVariables,
142
+ },
143
+ };
144
+ }
145
+ /**
146
+ * Load a DTCG JSON file from a project-relative path. Existing
147
+ * file-loading flow, refactored out of `loadTheme` so the entry
148
+ * point stays small.
149
+ */
150
+ function loadFile(filePath, mode, projectRoot) {
151
+ const absolutePath = isAbsolute(filePath)
152
+ ? filePath
153
+ : resolve(projectRoot, filePath);
154
+ if (!existsSync(absolutePath)) {
155
+ return {
156
+ ok: false,
157
+ issue: {
158
+ path: filePath,
159
+ message: `Declared theme file not found. Resolved to ${absolutePath}; ` +
160
+ `check the path is correct relative to ggui.json.`,
161
+ },
162
+ };
163
+ }
164
+ let raw;
165
+ try {
166
+ raw = readFileSync(absolutePath, 'utf-8');
167
+ }
168
+ catch (cause) {
169
+ return {
170
+ ok: false,
171
+ issue: {
172
+ path: absolutePath,
173
+ message: `Could not read theme file: ${errorMessage(cause)}`,
174
+ cause,
175
+ },
176
+ };
177
+ }
178
+ let decoded;
179
+ try {
180
+ decoded = JSON.parse(raw);
181
+ }
182
+ catch (cause) {
183
+ return {
184
+ ok: false,
185
+ issue: {
186
+ path: absolutePath,
187
+ message: `Theme file is not valid JSON: ${errorMessage(cause)}`,
188
+ cause,
189
+ },
190
+ };
191
+ }
192
+ let document;
193
+ try {
194
+ document = parseThemeDocument(decoded);
195
+ }
196
+ catch (cause) {
197
+ return {
198
+ ok: false,
199
+ issue: {
200
+ path: absolutePath,
201
+ message: `Theme file failed schema validation: ${errorMessage(cause)}`,
202
+ cause,
203
+ },
204
+ };
205
+ }
206
+ return {
207
+ ok: true,
208
+ theme: {
209
+ source: 'file',
210
+ path: absolutePath,
211
+ mode,
212
+ document,
213
+ cssVariables: generateCssVariables(document),
214
+ },
215
+ };
216
+ }
217
+ /**
218
+ * Apply flat dot-path overrides onto a copy of the raw `DtcgTheme`
219
+ * tree. Each override key (`color.primary.500`) walks into the
220
+ * tree; only leaves that already exist as `{ $value, $type }` token
221
+ * objects are mutated — anything else is silently ignored.
222
+ *
223
+ * Returns a deep-cloned tree so the source registry entry is not
224
+ * mutated (the registry's `parsedCache` would otherwise serve stale
225
+ * CSS to subsequent calls).
226
+ */
227
+ function applyOverrides(source, overrides) {
228
+ // Structured clone covers the nested DTCG token tree cleanly —
229
+ // the tree is plain JSON-shaped data (no functions, no Dates).
230
+ const cloned = structuredClone(source);
231
+ for (const [path, value] of Object.entries(overrides)) {
232
+ const segments = path.split('.');
233
+ let cursor = cloned;
234
+ for (let i = 0; i < segments.length - 1; i++) {
235
+ if (cursor === null || typeof cursor !== 'object')
236
+ break;
237
+ cursor = cursor[segments[i]];
238
+ }
239
+ if (cursor === null || typeof cursor !== 'object')
240
+ continue;
241
+ const leafKey = segments[segments.length - 1];
242
+ const leaf = cursor[leafKey];
243
+ if (leaf !== null &&
244
+ typeof leaf === 'object' &&
245
+ '$value' in leaf &&
246
+ '$type' in leaf) {
247
+ // Mutate $value only — keep $type and any $description intact.
248
+ leaf.$value = value;
249
+ }
250
+ }
251
+ return cloned;
252
+ }
253
+ /**
254
+ * Convenience wrapper for the "I have a `ggui.json` path" flow.
255
+ * Resolves the project root from the manifest path.
256
+ */
257
+ export function loadThemeFromGguiJsonPath(manifestPath, manifest) {
258
+ const projectRoot = dirname(resolve(manifestPath));
259
+ return loadTheme({ projectRoot, manifest });
260
+ }
261
+ function errorMessage(cause) {
262
+ return cause instanceof Error ? cause.message : String(cause);
263
+ }
@@ -0,0 +1,311 @@
1
+ /**
2
+ * `ggui.json#theme` → plain DTCG JSON document.
3
+ *
4
+ * The theme file is a plain JSON document matching the Design Tokens
5
+ * Community Group (DTCG) spec. It is one of the open file-format
6
+ * surfaces in the ggui manifest model, alongside
7
+ * `ggui.primitives.json` and `ggui.ui.json`.
8
+ *
9
+ * **Shape parity with the internal `DtcgTheme`.** This v1 schema is
10
+ * deliberately the same shape as `@ggui-ai/design`'s canonical
11
+ * {@link DtcgTheme} — `color`/`font`/`spacing`/`shape`/`motion`/
12
+ * `canvas`/`accessibility`/`zIndex` — so an authored `theme.json`
13
+ * uses the same vocabulary the curated registry themes (light/dark/
14
+ * premium-*) use. Previously this schema was Tailwind-style flat
15
+ * (`typography`/`radius`/`shadow` at root); the v1 schema now mirrors
16
+ * the internal shape to remove the divergence.
17
+ *
18
+ * **External-tool compatibility.** External tooling (Figma Tokens,
19
+ * Style Dictionary, Tokens Studio) commonly emits only a subset of
20
+ * the canonical shape. Only the original required groups (`color`,
21
+ * `font`, `spacing`, `shape`) stay required; `motion`, `canvas`,
22
+ * `accessibility`, `zIndex`, and the new DTCG metadata fields
23
+ * (`$name`, `$description`, `$metadata`) are all OPTIONAL so tools
24
+ * that only emit colors + dimensions still parse cleanly.
25
+ *
26
+ * **BREAKING for old `theme.json` files.** Authors who hand-wrote a
27
+ * `theme.json` against the pre-rc schema must rename:
28
+ *
29
+ * - `typography.fontFamily` → `font.family`
30
+ * - `typography.fontSize` → `font.size`
31
+ * - `typography.fontWeight` → `font.weight`
32
+ * - `typography.lineHeight` → `font.lineHeight`
33
+ * - top-level `radius` → `shape.radius`
34
+ * - top-level `shadow` → `shape.shadow`
35
+ * - top-level `duration` → `motion.duration`
36
+ * - top-level `transition` → `motion.transition`
37
+ *
38
+ * Plus: under `font.family`, `sans` is the only required sub-token
39
+ * (was previously freeform); other family slots (`mono`, …) are
40
+ * optional. The font-family record itself stays open so additional
41
+ * named families parse without a schema change.
42
+ *
43
+ * **Ownership boundary:**
44
+ *
45
+ * - *Where* the theme lives → `ggui.json#theme` (pointer string).
46
+ * - *What* the theme file looks like → this module's schema.
47
+ * - *How* DTCG tokens become CSS variables → `@ggui-ai/design`'s
48
+ * `generateCssVariables` (stays over there; this schema does not
49
+ * emit). The duck-typed walker accepts any DTCG-shaped tree, so
50
+ * the new nested groups (`font.size`, `shape.radius`, …) walk
51
+ * into `--ggui-font-size-md`/`--ggui-shape-radius-md` CSS vars
52
+ * without code changes.
53
+ * - *Built-in default* when `theme` is absent → `@ggui-ai/design`'s
54
+ * shipped `lightTheme`.
55
+ *
56
+ * **Extending rules:**
57
+ *
58
+ * 1. Additive only within `schema: '1'`. New optional fields must
59
+ * default to no-op behaviour so older tooling ignores them.
60
+ * 2. Framework-neutral + host-neutral. No vendor names in enum
61
+ * values, no build-pipeline fields. (`canvas` IS hosting-
62
+ * vendor vocabulary; it is opt-in via the optional field.)
63
+ * 3. Root and per-group objects are strict — unknown keys fail
64
+ * parse. Same discipline as `ggui.json`, `ggui.ui.json`, and
65
+ * `ggui.primitives.json`.
66
+ *
67
+ * **Strictness tradeoff:**
68
+ *
69
+ * Individual DTCG token leaves are validated with a discriminated
70
+ * union over `$type` (color / dimension / fontFamily / fontWeight /
71
+ * shadow / duration / cubicBezier / transition / number). Each
72
+ * variant validates its `$value` shape. `$description` is accepted
73
+ * everywhere as a free-form docstring. Token leaves under newer
74
+ * groups (`canvas`, `accessibility`, `zIndex`) accept a slightly
75
+ * wider token vocabulary because external tools sometimes encode
76
+ * scalar/array values with permissive `$type` strings (`string`,
77
+ * `array`, …); the schema accepts those without enumerating every
78
+ * possible spelling.
79
+ */
80
+ import { z } from 'zod';
81
+ /**
82
+ * Plain DTCG theme document v1. Required groups (`color`, `font`,
83
+ * `spacing`, `shape`) map 1:1 to `@ggui-ai/design`'s
84
+ * `generateCssVariables` walker targets — anything less would mean
85
+ * a partial CSS output worse than falling back to the shipped
86
+ * default. Optional groups (`motion`, `canvas`, `accessibility`,
87
+ * `zIndex`) are the well-known DTCG additives; present → emitted,
88
+ * absent → the design system's built-in fallbacks apply.
89
+ *
90
+ * `$name`, `$description`, and `$metadata` are accepted as optional
91
+ * DTCG-standard metadata. They round-trip cleanly to/from the
92
+ * internal `DtcgTheme` shape but external tools don't always emit
93
+ * them.
94
+ *
95
+ * Strict root: unknown top-level keys fail parse. `$schema`,
96
+ * `$version`, `$name`, `$description`, `$metadata` are all
97
+ * allow-listed (DTCG metadata); anything else is a typo. Additive
98
+ * slices add new fields here under `schema: '1'`.
99
+ */
100
+ export declare const ThemeDocumentV1: z.ZodObject<{
101
+ /** Optional DTCG spec URL. Not validated — the spec hasn't frozen
102
+ * a canonical URL and hand-authored themes often omit it. */
103
+ $schema: z.ZodOptional<z.ZodString>;
104
+ /** Optional author-declared version. Not validated — string-form. */
105
+ $version: z.ZodOptional<z.ZodString>;
106
+ /** Optional human-readable theme name. DTCG metadata; mirrors
107
+ * `DtcgTheme.$name`. */
108
+ $name: z.ZodOptional<z.ZodString>;
109
+ /** Optional human-readable theme description. DTCG metadata;
110
+ * mirrors `DtcgTheme.$description`. */
111
+ $description: z.ZodOptional<z.ZodString>;
112
+ /** Optional metadata bag — font fallback, hosting-philosophy notes,
113
+ * etc. Mirrors `DtcgTheme.$metadata`. */
114
+ $metadata: z.ZodOptional<z.ZodObject<{
115
+ font: z.ZodOptional<z.ZodString>;
116
+ fontUrl: z.ZodOptional<z.ZodString>;
117
+ philosophy: z.ZodOptional<z.ZodString>;
118
+ }, z.core.$strict>>;
119
+ /** Color tokens — palettes + semantic roles. Required. */
120
+ color: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
121
+ $type: z.ZodLiteral<"color">;
122
+ $value: z.ZodString;
123
+ $description: z.ZodOptional<z.ZodString>;
124
+ }, z.core.$strict>, z.ZodRecord<z.ZodString, z.ZodObject<{
125
+ $type: z.ZodLiteral<"color">;
126
+ $value: z.ZodString;
127
+ $description: z.ZodOptional<z.ZodString>;
128
+ }, z.core.$strict>>]>>;
129
+ /** Spacing scale. Required. */
130
+ spacing: z.ZodRecord<z.ZodString, z.ZodObject<{
131
+ $type: z.ZodLiteral<"dimension">;
132
+ $value: z.ZodString;
133
+ $description: z.ZodOptional<z.ZodString>;
134
+ }, z.core.$strict>>;
135
+ /** Font tokens — `family` / `size` / `weight` / `lineHeight`. Required. */
136
+ font: z.ZodObject<{
137
+ family: z.ZodIntersection<z.ZodRecord<z.ZodString, z.ZodObject<{
138
+ $type: z.ZodLiteral<"fontFamily">;
139
+ $value: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
140
+ $description: z.ZodOptional<z.ZodString>;
141
+ }, z.core.$strict>>, z.ZodObject<{
142
+ sans: z.ZodObject<{
143
+ $type: z.ZodLiteral<"fontFamily">;
144
+ $value: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
145
+ $description: z.ZodOptional<z.ZodString>;
146
+ }, z.core.$strict>;
147
+ }, z.core.$strip>>;
148
+ size: z.ZodRecord<z.ZodString, z.ZodObject<{
149
+ $type: z.ZodLiteral<"dimension">;
150
+ $value: z.ZodString;
151
+ $description: z.ZodOptional<z.ZodString>;
152
+ }, z.core.$strict>>;
153
+ weight: z.ZodRecord<z.ZodString, z.ZodObject<{
154
+ $type: z.ZodLiteral<"fontWeight">;
155
+ $value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
156
+ $description: z.ZodOptional<z.ZodString>;
157
+ }, z.core.$strict>>;
158
+ lineHeight: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
159
+ $type: z.ZodLiteral<"number">;
160
+ $value: z.ZodNumber;
161
+ $description: z.ZodOptional<z.ZodString>;
162
+ }, z.core.$strict>, z.ZodObject<{
163
+ $type: z.ZodLiteral<"dimension">;
164
+ $value: z.ZodString;
165
+ $description: z.ZodOptional<z.ZodString>;
166
+ }, z.core.$strict>]>>;
167
+ }, z.core.$strict>;
168
+ /** Shape tokens — `radius` + `shadow`. Required. */
169
+ shape: z.ZodObject<{
170
+ radius: z.ZodRecord<z.ZodString, z.ZodObject<{
171
+ $type: z.ZodLiteral<"dimension">;
172
+ $value: z.ZodString;
173
+ $description: z.ZodOptional<z.ZodString>;
174
+ }, z.core.$strict>>;
175
+ shadow: z.ZodRecord<z.ZodString, z.ZodObject<{
176
+ $type: z.ZodLiteral<"shadow">;
177
+ $value: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
178
+ offsetX: z.ZodString;
179
+ offsetY: z.ZodString;
180
+ blur: z.ZodString;
181
+ spread: z.ZodString;
182
+ color: z.ZodString;
183
+ }, z.core.$strict>]>;
184
+ $description: z.ZodOptional<z.ZodString>;
185
+ }, z.core.$strict>>;
186
+ }, z.core.$strict>;
187
+ /** Motion tokens — `duration` + `transition` (required sub-records),
188
+ * `easing` + `keyframes` (optional sub-records). Optional. */
189
+ motion: z.ZodOptional<z.ZodObject<{
190
+ duration: z.ZodRecord<z.ZodString, z.ZodObject<{
191
+ $type: z.ZodLiteral<"duration">;
192
+ $value: z.ZodString;
193
+ $description: z.ZodOptional<z.ZodString>;
194
+ }, z.core.$strict>>;
195
+ easing: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
196
+ $type: z.ZodLiteral<"cubicBezier">;
197
+ $value: z.ZodString;
198
+ $description: z.ZodOptional<z.ZodString>;
199
+ }, z.core.$strict>>>;
200
+ transition: z.ZodRecord<z.ZodString, z.ZodObject<{
201
+ $type: z.ZodLiteral<"transition">;
202
+ $value: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
203
+ duration: z.ZodString;
204
+ timingFunction: z.ZodString;
205
+ property: z.ZodOptional<z.ZodString>;
206
+ }, z.core.$strict>]>;
207
+ $description: z.ZodOptional<z.ZodString>;
208
+ }, z.core.$strict>>;
209
+ keyframes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
210
+ $type: z.ZodString;
211
+ $value: z.ZodUnknown;
212
+ $description: z.ZodOptional<z.ZodString>;
213
+ }, z.core.$strict>>>;
214
+ }, z.core.$strict>>;
215
+ /** Canvas configuration for the GenerativeCanvas background.
216
+ * Optional — external tools won't emit this. */
217
+ canvas: z.ZodOptional<z.ZodObject<{
218
+ mode: z.ZodObject<{
219
+ $type: z.ZodString;
220
+ $value: z.ZodUnknown;
221
+ $description: z.ZodOptional<z.ZodString>;
222
+ }, z.core.$strict>;
223
+ speed: z.ZodObject<{
224
+ $type: z.ZodString;
225
+ $value: z.ZodUnknown;
226
+ $description: z.ZodOptional<z.ZodString>;
227
+ }, z.core.$strict>;
228
+ colors: z.ZodObject<{
229
+ $type: z.ZodString;
230
+ $value: z.ZodUnknown;
231
+ $description: z.ZodOptional<z.ZodString>;
232
+ }, z.core.$strict>;
233
+ background: z.ZodObject<{
234
+ $type: z.ZodString;
235
+ $value: z.ZodUnknown;
236
+ $description: z.ZodOptional<z.ZodString>;
237
+ }, z.core.$strict>;
238
+ }, z.core.$strict>>;
239
+ /** Accessibility tokens (focus ring, reduced motion, high contrast).
240
+ * Optional. */
241
+ accessibility: z.ZodOptional<z.ZodObject<{
242
+ focusRing: z.ZodOptional<z.ZodObject<{
243
+ color: z.ZodObject<{
244
+ $type: z.ZodLiteral<"color">;
245
+ $value: z.ZodString;
246
+ $description: z.ZodOptional<z.ZodString>;
247
+ }, z.core.$strict>;
248
+ width: z.ZodObject<{
249
+ $type: z.ZodLiteral<"dimension">;
250
+ $value: z.ZodString;
251
+ $description: z.ZodOptional<z.ZodString>;
252
+ }, z.core.$strict>;
253
+ offset: z.ZodObject<{
254
+ $type: z.ZodLiteral<"dimension">;
255
+ $value: z.ZodString;
256
+ $description: z.ZodOptional<z.ZodString>;
257
+ }, z.core.$strict>;
258
+ }, z.core.$strict>>;
259
+ reducedMotion: z.ZodOptional<z.ZodObject<{
260
+ duration: z.ZodObject<{
261
+ $type: z.ZodLiteral<"duration">;
262
+ $value: z.ZodString;
263
+ $description: z.ZodOptional<z.ZodString>;
264
+ }, z.core.$strict>;
265
+ }, z.core.$strict>>;
266
+ highContrast: z.ZodOptional<z.ZodObject<{
267
+ borderWidth: z.ZodObject<{
268
+ $type: z.ZodLiteral<"dimension">;
269
+ $value: z.ZodString;
270
+ $description: z.ZodOptional<z.ZodString>;
271
+ }, z.core.$strict>;
272
+ textColor: z.ZodObject<{
273
+ $type: z.ZodLiteral<"color">;
274
+ $value: z.ZodString;
275
+ $description: z.ZodOptional<z.ZodString>;
276
+ }, z.core.$strict>;
277
+ backgroundColor: z.ZodObject<{
278
+ $type: z.ZodLiteral<"color">;
279
+ $value: z.ZodString;
280
+ $description: z.ZodOptional<z.ZodString>;
281
+ }, z.core.$strict>;
282
+ linkColor: z.ZodObject<{
283
+ $type: z.ZodLiteral<"color">;
284
+ $value: z.ZodString;
285
+ $description: z.ZodOptional<z.ZodString>;
286
+ }, z.core.$strict>;
287
+ }, z.core.$strict>>;
288
+ }, z.core.$strict>>;
289
+ /** Z-index scale. Optional. */
290
+ zIndex: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
291
+ $type: z.ZodLiteral<"number">;
292
+ $value: z.ZodNumber;
293
+ $description: z.ZodOptional<z.ZodString>;
294
+ }, z.core.$strict>>>;
295
+ }, z.core.$strict>;
296
+ /** Static TypeScript type derived from the v1 schema. */
297
+ export type ThemeDocumentV1 = z.infer<typeof ThemeDocumentV1>;
298
+ /** Canonical type alias used everywhere else. */
299
+ export type ThemeDocument = ThemeDocumentV1;
300
+ /**
301
+ * Parse a raw JSON value into a validated {@link ThemeDocument}.
302
+ * Throws a `ZodError` with human-readable issues on invalid input.
303
+ */
304
+ export declare function parseThemeDocument(raw: unknown): ThemeDocument;
305
+ /**
306
+ * Safe-parse variant — returns a discriminated `z.safeParse` result.
307
+ * Prefer this inside CLI tooling where you want to render the issue
308
+ * list without try/catch.
309
+ */
310
+ export declare function safeParseThemeDocument(raw: unknown): ReturnType<typeof ThemeDocumentV1.safeParse>;
311
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA2PxB;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,eAAe;IAC1B;kEAC8D;;IAG9D,qEAAqE;;IAGrE;6BACyB;;IAGzB;4CACwC;;IAGxC;8CAC0C;;;;;;IAG1C,0DAA0D;;;;;;;;;;IAG1D,+BAA+B;;;;;;IAG/B,2EAA2E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAG3E,oDAAoD;;;;;;;;;;;;;;;;;;;IAGpD;mEAC+D;;;;;;;;;;;;;;;;;;;;;;;;;;;IAG/D;qDACiD;;;;;;;;;;;;;;;;;;;;;;;IAGjD;oBACgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAGhB,+BAA+B;;;;;;kBAE/B,CAAC;AAEH,yDAAyD;AACzD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,iDAAiD;AACjD,MAAM,MAAM,aAAa,GAAG,eAAe,CAAC;AAE5C;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,OAAO,GACX,UAAU,CAAC,OAAO,eAAe,CAAC,SAAS,CAAC,CAE9C"}