Lucentive Labs
Loupe Docs · Lucentive Labs

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.

loupe.config.ts
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).

app/page.tsx
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.

LiveLock a tile in each group
60-second direction
2 of 2 locked
01
Color system
Which palette carries the brand?
02
Headline voice
What does the type say before the words do?

Review & hand off

Confirm your decisions, then hand them to the build pass.

  1. Color systemInk + one signal
  2. Headline voiceBold 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.