Quickstart
Author a typed config, render it, and read back a deterministic export brief — in five steps.
The shortest path from a design question to a locked, machine-readable answer. Five steps; the last one is live on this page.
Author a config
A Config is groups → options → specimens. Each group is one decision; each option is a choice; each specimen is what the choice looks like. Mark one option per group recommended to set the default pick.
import type { Config } from "@lucentive-labs/loupe-schema";
export const config: Config = {
version: 1,
title: "My direction",
groups: [
{
id: "color",
title: "Color system",
prompt: "Which palette carries the brand?",
options: [
{
id: "ink",
label: "Ink + signal",
recommended: true,
specimen: { kind: "palette", colors: ["#0c1a1c", "#11272b", "#8499ab", "#2fd4c4"] },
},
{
id: "amber",
label: "Warm amber",
specimen: { kind: "palette", colors: ["#14110e", "#f6a13c", "#ffc879", "#f7efe3"] },
},
],
},
],
};Validate it
parseConfig throws on structural errors (bad crop rects, missing fields). validateConfig returns human-readable semantic errors — duplicate ids, missing asset references, dangling preview bands. Run both before you render.
tsx -e "import('@lucentive-labs/loupe-schema').then(async m => {
const { config } = await import('./loupe.config.ts');
const cfg = m.parseConfig(config);
const errs = m.validateConfig(cfg);
if (errs.length) { console.error('INVALID:\n- ' + errs.join('\n- ')); process.exit(1); }
console.log('config valid');
})"Render it
Pick the adapter that fits. In a React app, mount <Loupe /> and import the canonical styles once. For a portable, dependency-free artifact, run the generator (see the agent method).
import { Loupe } from "@lucentive-labs/loupe-react";
import "@lucentive-labs/loupe-dom/styles.css";
import { config } from "./loupe.config";
export default function Page() {
return <Loupe config={config} />;
}Lock your picks
Click a tile to lock it. The sticky preview recomposes, the "your picks" thumbnails update, and the progress counter advances. Single-select per group; click the same tile again does nothing, and Clear unlocks a group back to open.
Read the brief
The export brief is the deterministic handoff. The Copy brief button copies the markdown; both forms are also available from core:
tsx -e "Promise.all([
import('@lucentive-labs/loupe-core'),
import('@lucentive-labs/loupe-schema'),
import('./loupe.config.ts'),
]).then(([core, schema, m]) => {
const cfg = schema.parseConfig(m.config);
const sel = core.recommendedSelections(cfg);
const b = core.selectExportBrief(cfg, sel);
console.log(b.markdown);
})"See it run
The same flow, live. Lock a tile and the brief on the tutorial page updates with it.
Keep loupe.config.ts as the single source of truth. The preview and the brief are both derived from the same selections, so they never drift.