Lucentive Labs

Lucentive Labs · design-tooling

Loupe

Active

Loupe is a config-driven decision-lock tool for visual or strategic choices. It renders typed options, records locked selections, and exports deterministic Markdown and JSON build briefs for the next human or agent pass.

Live · running hereA real <Loupe /> from @lucentive-labs/loupe-react, themed Night Atlas. Lock a tile in each group; the preview composes below.
Loupe — live in labs-site
3 of 3 locked
01
Ground
Pick the canvas the system sits on.
02
Accent
Pick the load-bearing accent.
03
Display
Pick the display voice.

Review & hand off

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

  1. GroundAtlas Night
  2. AccentAperture Turquoise
  3. DisplayBricolage Grotesque
Raw brief · markdown for the build pass

The wedge

Config in. Live preview. Deterministic brief out.

A visual decision should end as something a build can reproduce, not a paragraph someone has to interpret. Loupe runs the decision through three steps and stops the drift.

01

Author the config

One typed file lists the decisions: groups of option tiles built from real image crops, palettes, type specimens, motion presets, and layout mocks. A Zod schema validates it.

02

Lock tiles in a live preview

Click a tile and a sticky preview recomposes from your picks — the same crop engine, theme, and type the real build will use. You are choosing against what you will ship, not a swatch in isolation.

03

Hand off a deterministic brief

Every lock keeps an export brief in sync: human-readable markdown and a machine-readable JSON of the same decisions. No timestamps, no absolute paths — the same picks always produce the same brief.

The change

From a decision log to a locked artifact

Before · text-first

A decision log nobody can rerun

  • Direction lives in a doc: prose notes, hex codes, and screenshots that drift from the build.
  • “Make it warmer” means something different to the person who wrote it and the agent that reads it.
  • Re-litigated next week, because nothing recorded which option won or why.
  • The build pass guesses at the crop, the weight, the spacing — and guesses differently each time.
After · click-to-lock

A brief the next pass reproduces

  • Direction is a locked artifact: each decision is a tile you can see, click, and compose against the others.
  • “Warmer” becomes a specific palette tile with exact colors, captured the moment it is chosen.
  • The brief is the record of what won — a human or an agent reads the same ground truth.
  • The exported crop, weight, and layout are exact values, so the next pass reproduces them.

What it is

Small surface, exact behavior

Five packages, one contract

Schema, headless core, vanilla DOM renderer, React adapter, and a static-HTML generator. Take only the layer you need; they all read the same config.

loupe-schema · core · dom · react · generator

Exact crop engine

Specimens carry a normalized crop rectangle (fractions of the intrinsic image). The core computes exact cover math from the rect plus intrinsic dimensions, so the tile, the preview, and the export all show the same pixels.

deterministic · framework-free core

Bring your own brand

A theme is a flat map of semantic tokens — background, surface, primary, ring, type. Pass it on the config or the component and the whole picker wears your skin. The three examples ship three different brands off the same machinery.

--loupe-* token contract

Agent-native contract

The config is a Zod schema, and one call emits its JSON Schema. An agent can author a valid decision lock the same way a person does, and a semantic validator catches missing assets, duplicate ids, and broken preview references before render.

toJsonSchema() · validateConfig()

Portable artifact or live React

Generate a portable static artifact, or drop <Loupe/> into a React 19 app. Asset-light configs can be one index.html; referenced assets are copied beside it. Both paths render the same structure, classes, and ARIA from the same source.

portable artifact · React 19 adapter

@lucentive-labs/loupe-schema

The Zod config contract a human or an agent fills, plus the JSON Schema emitter and a semantic validator.

@lucentive-labs/loupe-core

Framework-free headless core: SSR-safe store, deterministic preview and brief derivations, crop math, ARIA prop-getters. Its only runtime dependency is the schema package.

@lucentive-labs/loupe-dom

The canonical vanilla browser renderer — mount(), renderToString(), the crop engine, and styles.css. No framework.

@lucentive-labs/loupe-react

The React 19 adapter: <Loupe/> and useLoupe(), a thin useSyncExternalStore view over the core store.

@lucentive-labs/loupe-generator

Node-only generate(): emits deterministic index.html with JS and CSS inlined, copying referenced assets beside it when needed.

In code

Author it, render it, hand it off

The config is the contract. A person writes it by hand; an agent writes it from the JSON Schema. Either way, the same file drives the live React adapter and the portable artifact.

1 · Author a typed config. One file lists the groups, the option tiles, and your brand theme. Zod validates it.

loupe.config.tsts
// loupe.config.ts
import type { Config } from "@lucentive-labs/loupe-schema";

export const config: Config = {
  version: 1,
  title: "Northwind · Brand System",
  // No image assets in this lock — palette / type specimens only.
  assets: {},
  // Bring your own brand: semantic tokens, kebab keys, no --loupe- prefix.
  theme: {
    "color-bg": "#14110e",
    "color-primary": "#f6a13c",
    "color-ring": "#f6a13c",
    "font-sans": "Manrope, system-ui, sans-serif",
  },
  groups: [
    {
      id: "color",
      title: "Color system",
      prompt: "Which palette carries the brand?",
      options: [
        {
          id: "amberInk",
          label: "Amber on ink",
          caption: "Warm, premium, high-contrast",
          recommended: true,
          specimen: {
            kind: "palette",
            colors: ["#14110e", "#f6a13c", "#ffc879", "#f7efe3"],
          },
        },
        {
          id: "monoSignal",
          label: "Mono + one signal",
          specimen: {
            kind: "palette",
            colors: ["#101216", "#3a4048", "#9aa3ad", "#f6a13c"],
          },
        },
      ],
    },
  ],
  // What the build pass must never do — travels with the brief.
  banned: ["Generic SaaS gradient blobs.", "Cold corporate blue as primary."],
};

2 · Drop the React adapter into an app. <Loupe /> is a thin view over the headless core; import the shared stylesheet once.

app/decide/page.tsxtsx
// app/decide/page.tsx
"use client";

import { Loupe } from "@lucentive-labs/loupe-react";
import "@lucentive-labs/loupe-dom/styles.css";
import { config } from "./loupe.config";

export default function DecidePage() {
  return (
    <Loupe
      config={config}
      // Optional per-mount theme override.
      theme={{ "color-primary": "#2fd4c4" }}
      // Fires after every lock / clear / reset with the live selections.
      onLockChange={(selections) => console.log(selections)}
    />
  );
}

3 · Or generate a portable artifact. generate() validates, copies assets, and inlines JS and CSS into index.html. Referenced assets are copied beside it.

generate.tsts
// generate.ts — Node, build-time
import { generate } from "@lucentive-labs/loupe-generator";
import { config } from "./loupe.config";

// Validates (structural + semantic), copies referenced assets, and inlines
// JS + CSS into index.html. Throws on an invalid config.
const { htmlPath, assets } = await generate(config, {
  outDir: "out/decision-lock",
  assetsDir: "design/boards",
});

console.log(`Artifact: ${htmlPath}`);
console.log(`Copied ${assets.length} asset(s) — open the file anywhere.`);

And the agent path: hand a model the JSON Schema, parse what it returns, and run the semantic validator before render.

agent-authoring.tsts
// agent-authoring.ts — an agent fills the same contract
import {
  toJsonSchema,
  parseConfig,
  validateConfig,
} from "@lucentive-labs/loupe-schema";

// 1. Hand the JSON Schema to the model as the authoring contract.
const schema = toJsonSchema();

// 2. The model returns a config object; parse enforces structure.
const config = parseConfig(modelOutput);

// 3. Semantic checks beyond shape: missing assets, duplicate ids,
//    broken preview references. Empty array = ready to render.
const problems = validateConfig(config);
if (problems.length) throw new Error(problems.join("\n"));

Worked examples

One canonical example, two catalogue fixtures

Brand-starter is public and runnable in Loupe's canonical repository. Human-today and ias-control-plane are maintained in the private Labs repository, so they are shown here as catalogue orientation rather than public source links. A fixture demonstrates the tool and is not production-adoption proof.

examples/human-today

Photographic art direction

The image-heavy “Soft Data Body” lab, reproduced from a hand-built decision page as a typed config. Real moodboard crops drive imageCrop, layoutMock, and motion specimens.

imageCrop9 groupsivory / teal
Labs catalogue fixture · Private source

examples/ias-control-plane

A product surface, not a brand

The decisions a control plane actually makes: how a run is drawn, surface density, telemetry feel, status color. Asset-light by design, so the artifact is one tiny file that reviews fast.

asset-light9 groupssignal cyan
Labs catalogue fixture · Private source

examples/brand-starter

A brand system from zero

Color, type, layout rhythm, and motion before any imagery exists. A warm amber-on-ink “Northwind” theme re-skins the same machinery — the template for deciding a system, not a photo.

asset-free5 groupsamber / ink
Canonical Loupe example · View on GitHub →

Adoption

Two ways to run it

Ship a portable artifact

Run generate() in a build step and get a portable index.html plus any copied assets. It opens from a file path or static host and carries the decision interface and export brief without a runtime package dependency.

Embed the live React adapter

Mount <Loupe/> inside an existing React 19 app for an always-current lab. It is a thin view over the headless core, so selections, the composed preview, and the export brief stay in sync with the same config the artifact uses. The demo above is this adapter, running here.

Verified use

Publicly observable today

Interactive Loupe demonstration on labs.lucentive.io

Public demo; no production-runtime claim is inferred from it.

Get started

Lock your next visual decision

Read the docs for the install and the config schema, or read the source. MIT-licensed and on GitHub.