@theme-registry/refract 0.1.6 → 0.1.8

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.
@@ -146,6 +146,56 @@ export interface UsageDescriptor {
146
146
  /** The real recipe identities in this format, so a downstream agent never guesses a name. */
147
147
  readonly recipes: readonly UsageRecipe[];
148
148
  }
149
+ /** How one recipe is marked up in a preview document. `tag` omitted ⇒ the preview picks a sensible element. */
150
+ export interface PreviewMarkup {
151
+ /** The element to render (`"button"`, `"div"`, …). Omit to let the preview choose by group name. */
152
+ readonly tag?: string;
153
+ /** Attributes to set on it — for CSS, `{ class: "<the real class list>" }`. */
154
+ readonly attrs: Readonly<Record<string, string>>;
155
+ }
156
+ /**
157
+ * How to render a **human-facing** preview of an adapter's built output — the audience-flipped sibling
158
+ * of {@link UsageDescriptor} (which serves the machine-facing `llms.txt` guide). Feeds `preview.html`.
159
+ *
160
+ * The build layer can always render *token* plates on its own (they come from the format-neutral DTCG
161
+ * export), so this describes only the half it cannot know: whether the emitted artifacts are **loadable
162
+ * by a browser as-is**, and if so, in what order and with what markup. That is true for CSS and, today,
163
+ * nothing else — SCSS needs compiling, styled-components emits JS modules, JSON has no rendered form.
164
+ * Those adapters return an empty `stylesheets` with an honest {@link PreviewDescriptor.unavailable}
165
+ * message, and the page degrades to tokens-only instead of rendering unstyled boxes.
166
+ *
167
+ * It is passed the **normalized emit plan and the real emitted file names**, because both answers
168
+ * depend on them: `split` has a load-order contract, `subsystem`/`components` name files through a
169
+ * user-supplied function (so names can never be re-derived from the plan alone), and `components`
170
+ * emits self-contained merged rules keyed by a different class than the composition list every other
171
+ * mode uses.
172
+ */
173
+ export interface PreviewDescriptor {
174
+ /**
175
+ * Emitted file names that a browser can load as-is, **in load order** (e.g. `split` puts its
176
+ * variables file first). Empty ⇒ no live rendering is possible for this format; say why in
177
+ * {@link unavailable}. The build layer intersects this with the files actually written, so a
178
+ * stale name can never produce a page pointing at a missing artifact.
179
+ */
180
+ readonly stylesheets: readonly string[];
181
+ /** Mark up one recipe. Omit ⇒ recipes are listed by name only, never rendered. */
182
+ readonly markup?: (recipe: UsageRecipe) => PreviewMarkup | undefined;
183
+ /**
184
+ * Optional grouping key per recipe, used for page layout only — `subsystem` mode groups by
185
+ * subsystem, `components` mode by the file each component was written to. Omit ⇒ one flat section.
186
+ */
187
+ readonly groupBy?: (recipe: UsageRecipe) => string | undefined;
188
+ /**
189
+ * The attribute to set on `<html>` to force an appearance mode (CSS emits `:root[data-theme="…"]`,
190
+ * so the CSS adapter answers `"data-theme"`). Absent ⇒ the preview renders no mode toggle, because
191
+ * it would have no way to switch. The theme's own `prefers-color-scheme` blocks still apply.
192
+ */
193
+ readonly modeAttribute?: string;
194
+ /** Why a live render isn't possible, in the author's own words. Shown when `stylesheets` is empty. */
195
+ readonly unavailable?: string;
196
+ /** Caveats worth surfacing on the page even when rendering does work (e.g. "`variables: false` — …"). */
197
+ readonly notes?: readonly string[];
198
+ }
149
199
  /** The Model-derived context bound alongside the Model. */
150
200
  export interface RenderContext<TBreakpoint extends string = string> {
151
201
  /** Core-built media descriptor (breakpoints → `@media` builder) for responsive rules. */
@@ -186,6 +236,14 @@ export interface BoundSpec<TUnit = string> {
186
236
  * that has neither refract nor its skills. Optional: `defineAdapter` supplies a generic default
187
237
  * (recipe identities via `recipeName`); an adapter overrides it to add format-specific import prose. */
188
238
  describeUsage?(): UsageDescriptor;
239
+ /**
240
+ * Describe how to render a human-facing `preview.html` of this emit — see {@link PreviewDescriptor}.
241
+ * Receives the normalized plan and the file names `emit(plan)` actually produced.
242
+ *
243
+ * **Unlike `describeUsage`, this has NO default**: an absent implementation means "I don't know how
244
+ * to show this in a browser", and the preview falls back to token plates only. That's the correct
245
+ * behavior for a third-party adapter that has never heard of previews, so the contract stays additive. */
246
+ describePreview?(plan: NormalizedEmit, files: readonly string[]): PreviewDescriptor;
189
247
  /**
190
248
  * Build-time emit: self-contained files (adapter vendors its own runtime helpers). `plan` is the
191
249
  * normalized output-shape directive (§9); omitted ⇒ `single`/`theme.css` (back-compat). The
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theme-registry/refract",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "The framework-agnostic, format-neutral theme compiler core — build a ThemeModel from a RawTheme, then lower it with any adapter package (CSS, SCSS, JSON, styled-components).",
5
5
  "author": {
6
6
  "name": "Petyo Stoyanov",
@@ -57,7 +57,7 @@
57
57
  "./package.json": "./package.json"
58
58
  },
59
59
  "peerDependencies": {
60
- "typescript": ">=5.0.0"
60
+ "typescript": ">=5.0.0 <6"
61
61
  },
62
62
  "peerDependenciesMeta": {
63
63
  "typescript": {
@@ -30,6 +30,16 @@ onto the theme. You author an `AdapterSpec<TUnit>` and wrap it with `defineAdapt
30
30
  - **Optional:** `renderToken`, aggregator overrides (`renderAllRecipes`/`…Variables`/`renderAll`),
31
31
  `extend(theme)` (attach runtime glue), `emit(plan?)` (build-time files — switch on `plan.type`
32
32
  and throw a clear error for modes you don't support).
33
+ - **Optional self-description** (each feeds an opt-in build artifact; both are additive, so an
34
+ adapter that implements neither still works):
35
+ - `describeUsage()` → the machine-facing `guide` (`llms.txt` + `manifest.json`). `defineAdapter`
36
+ supplies a generic default; override it to add format-specific consumption prose.
37
+ - `describePreview(plan, files)` → the human-facing `preview.html`. **No default.** Implement it
38
+ only if a browser can load your emitted files *as-is*; otherwise return
39
+ `{ stylesheets: [], unavailable: "why not" }` and the preview renders token plates plus your
40
+ explanation. Take the file names from the `files` argument — in `subsystem`/`components` mode
41
+ `filename` is a user function, so names can't be re-derived from the plan. See
42
+ **docs/extending.md → Writing `describePreview`**.
33
43
 
34
44
  `defineAdapter(spec)` supplies the default aggregators as pure Model walks — override only if the
35
45
  full document isn't a flat concatenation.
@@ -34,8 +34,11 @@ export default defineConfig({
34
34
  ```
35
35
 
36
36
  `ThemeConfig` = `{ raw, targets, media?, units?, baseFontSize? }`. Each `EmitTarget` =
37
- `{ adapter, outDir, name?, emit?, helpers? }`. Resolution order: `theme.config.ts` → `.mjs` →
38
- `.js` (a `.ts` config lazy-loads the optional `typescript` peer).
37
+ `{ adapter, outDir, name?, emit?, helpers?, guide?, preview? }`. Resolution order: `theme.config.ts` → `.mjs` →
38
+ `.js` (a `.ts` config lazy-loads the optional `typescript` peer — it must be **5.x**; a bare
39
+ `npm i -D typescript` resolves to 7.x, whose main entry doesn't expose the compiler API, and the
40
+ build then fails with `Cannot read properties of undefined (reading 'ESNext')`. A `.mjs`/`.js`
41
+ config never loads it).
39
42
 
40
43
  ## Emit modes (`emit` on a target)
41
44
 
@@ -63,6 +66,30 @@ tree-shake, so the CSS-only modes are redundant).
63
66
 
64
67
  Run via `npx refract <cmd>`. `outDir` resolves relative to the config file's directory.
65
68
 
69
+ ## Emitted extras (`guide` / `preview` on a target)
70
+
71
+ Both are **off by default** and land inside the target's `outDir`, so they travel with any
72
+ distribution form. A build with neither set is byte-identical to before.
73
+
74
+ - `guide: true` → `llms.txt` + `manifest.json` — the **machine-facing** consumption guide (real
75
+ class names / export ids / token paths). Object form: `{ packageName?, llmsFile?, manifestFile? }`.
76
+ - `preview: true` → `preview.html` — the **human-facing** rendered specimen. Object form:
77
+ `{ file?, title?, inline? }`; `inline` defaults **true** (one self-contained shareable file), set
78
+ it `false` to emit relative `<link>`s that reflect a rebuilt stylesheet on refresh.
79
+
80
+ **What `preview` shows depends on the adapter, and you should say so rather than over-promise.**
81
+ Token plates (colours, type ramp, spacing, radii, shadows, breakpoints) render from the
82
+ format-neutral token export, so **every** adapter gets them. *Live* recipe plates need output a
83
+ browser can load as-is — **CSS only** today. An SCSS / styled-components / JSON target still renders
84
+ every token and lists every recipe by its real identity, and states why it can't render them live.
85
+ If a user wants a live design-review page from an SC or SCSS theme, tell them to **add a CSS target
86
+ to the same config** — same recipes through the same core, so it's a faithful specimen.
87
+
88
+ The page follows the target's `emit` mode (variables load before styles in `split`; plates group by
89
+ subsystem or by component file), gains a light/dark toggle when the theme declares `modes`, and
90
+ frame-width buttons when it declares `breakpoints`. A theme with **no recipes** (e.g. straight out of
91
+ `refract create`) renders its tokens and says explicitly that recipes are missing.
92
+
66
93
  ## Dark-mode strategy (differs by target)
67
94
 
68
95
  - **CSS / SCSS / JSON** — dark rides the *referenced token's own `modes`* (see **theme-foundations**);
@@ -54,6 +54,11 @@ For AI-assisted consumers, refract can emit a **self-documenting guide** into `o
54
54
  the build target (see **build-config**). Because the guide sits in `outDir`, it travels with any
55
55
  distribution form, package specifier or not.
56
56
 
57
+ Its human-facing sibling is `preview: true`, which writes a `preview.html` specimen into the same
58
+ folder — token plates for any adapter, plus live recipe plates when the output is browser-loadable
59
+ (CSS). Reach for it when a person needs to *see* the theme (design review, a handoff, a sanity check
60
+ after a retune) rather than consume it.
61
+
57
62
  While *authoring* against a live project, the **`@theme-registry/refract-mcp`** server exposes the
58
63
  same information as **live tools + resources** (schema 1) — `getClass` / `resolveToken` / `renderRecipe`
59
64
  and a `refract://manifest.json` resource — answering against the real compiled theme. Prefer it when