Lucentive Labs · design-tooling
Loupe
ActiveLock a visual-design decision by clicking it, not describing it. Loupe turns a visual-design decision into clickable option tiles — real image crops, palettes, type, motion, layout mocks. Locking a tile recomposes a live preview and keeps a deterministic export brief in sync: the ground truth a human or an AI build pass consumes.
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.
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.
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.
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
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.
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.
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.
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.
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.
Portable artifact or live React
Generate a single self-contained index.html that runs from a file path, or drop <Loupe/> into a React 19 app. Both render identical structure, classes, and ARIA from the same source — SSR- and hydration-safe.
@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
Zero-dependency headless core: SSR-safe store, deterministic preview and brief derivations, crop math, ARIA prop-getters.
@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(): bundles a self-contained index.html with JS and CSS inlined and assets copied. Output is deterministic.
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.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.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 self-contained artifact. generate() validates, copies assets, and inlines everything into one index.html that runs anywhere.
// 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 ONE self-contained 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.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
Three real configs, three different brands
The same machinery, re-skinned three ways — from photographic art direction to a control-plane product surface to a brand system from zero. Each is a runnable config in the repo.
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.
View the config on GitHub →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.
View the config on GitHub →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.
View the config on GitHub →Adoption
Two ways to run it
Ship a portable artifact
Run generate() in a build step and get a single self-contained index.html. It opens from a file path, drops onto any static host, and reviews in a browser with no install — the way design partners and agents pick up a decision lock.
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.
Used by
In production at Lucentive
IAS — Intuitive Agent System
The control-plane product surface is decided with a Loupe lock; the exported brief is the ground truth the IAS build pass consumes.
Lucentive agent harnesses
Build agents read the JSON-Schema contract to author and validate a config, then act on the deterministic brief instead of free-text direction.
Human Today
The “Soft Data Body” art direction is captured as a Loupe config (the human-today example fixture), ready to drive its next production pass.
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.