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.
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" ] },
},
],
},
],
}; 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');
})" 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} />;
} 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.
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);
})"
The same flow, live. Lock a tile and the brief on the tutorial page updates with it.
Live Lock a tile in each group
01 Color system
Which palette carries the brand?
Clear Rec Ink + one signal Restrained, system-like Warm amber Premium, high-contrast Something else — type your own
02 Headline voice
What does the type say before the words do?
Clear Rec Lucid futures, built.
Bold sans Operational Direction · System · Proof
Quiet operational Something else — type your own
Live preview 2 /2 ›Composed preview Live from your picks
2 / 2Review & hand off Confirm your decisions, then hand them to the build pass.
Hand off & continue → Copy brief Reset
Color system Ink + one signal Headline voice Bold sans Banned ✕ Generic SaaS gradient blobs. ✕ Cold corporate blue as the brand. Raw brief · markdown for the build pass
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.