Desklight
I designed and built Desklight, an AI marketing platform, solo. Allie takes a brief through brand-aware planning, copy, imagery, video and audio into an editable campaign and publishing workflow. Underneath: an agent with persistent memory, a scene-graph graphics engine, model orchestration and metered billing. Live, invite-only.
- Role
- Founder · solo product design & engineering
- When
- May 2026 — present
- Built with
- React · TypeScriptNode / ExpressSupabase · pgvectorClaude tool-useCanvas · HarfBuzzOKLCHStripe

A small business needs a consistent creative direction and a working content operation. A blank prompt box still leaves the owner acting as strategist, art director, editor and scheduler.
Founder, product designer and engineer. I built the brand, React application, Node backend, agent, memory system, creative editor, Lumen design system and billing. I own the product decisions and the production code.
A live, invite-only platform that carries brand context from brief to campaign: planning, generation, editing, scheduling and publishing. Text, imagery, video and audio share one workspace; the owner can refine the work and decide what goes out.
See the work. Try the workflow.
Desklight is live and invite-only. Explore the public campaigns and Lumen, or request a walkthrough of the working product.
- 01 Claim An atomic Postgres claim, so two workers never answer one message. Email and chat share the path.
- 02 Gather Eight sources in parallel: brand rules, memories, learned skills, recent work, reference images.
- 03 Reason Claude tool-use with 33 tools. Read-only tools run together; anything that changes state runs in order.
- 04 Charge Paid tools open a chargeGuard gate first and settle once, for the model that actually ran.
- 05 Remember Facts and outcomes are extracted after the reply. Engram compresses them overnight.
Generic AI gets you generic.
At my last role I looked after several brands. The big marketing pushes took the time, and the smaller design work for those brands kept slipping. Desklight is what I built for that work. A brand needs a photographic register, a typographic voice and a point of view that survives the next brief, so Allie carries that context from email or chat into her tools and brings the work back to an editor and a calendar. The owner changes the copy, refines the design and picks the schedule. I designed the workflow and built every layer under it, so a problem in the interface can become a change to the system.
One product identity. Many creative voices.
Desklight’s identity is the Spark D, a solid D with a four-point sparkle cut through it, set with an Ember accent and a Syne wordmark. Lumen’s Contour mark belongs to the same family. I carried it through the public site, the app and the design system’s documentation. Inside the product the job flips: every customer brand needs its own direction. Noon, Marrow, Forma and Loam are sample brands with their own photography, colour, type and tone, and their campaigns below show the range.
Spend the model call where judgment matters.
Email and chat enter the same agent pipeline. An atomic claim in Postgres keeps two workers from answering one message, then eight context sources load in parallel: brand rules, memories, learned skills, recent work and reference images. The calendar and the account used to load on every message too; now they arrive only when the message needs them. Allie has 33 tools. Read-only tools run together, and anything that changes state runs in sequence. In chat, a deterministic reflex layer handles approvals and scheduling commands without a model round-trip. I once handed the turn after an image to Haiku for speed. That turn also judges the render, may re-render and targets the calendar, and the smaller model re-rendered posts nobody asked for and praised its own work. Every turn now runs on the workspace’s Sonnet.
Send the model only what changed.
Allie was paying to re-read context she had already seen. Cache writes were 59% of her model bill, because the clock and the latest headlines sat ahead of the conversation and changed with every message. I reordered her prompt by how often each part changes. Tools and instructions come first, cached for an hour and shared by every workspace. The brand follows, and anything that changes per message rides inside the message itself.
Then I put Jev, a small judgment model from TypeSafe, in front of her. It reads each message in about 150 ms and answers four yes-or-no questions: does this need the calendar, the brand profile, the help center or the account? When it’s sure, the server runs that lookup before Allie starts, so she can answer in one call instead of two. On 45 real asks, 28 of its 30 confident calls matched the lookup she would have made. Jev only chooses and never writes, so a wrong guess costs one extra lookup, never a wrong fact. On the same real chat, a warm message now costs 39% less.
- −53%
- tokens paid for again
- 6 → 4
- model calls
- 150ms
- for Jev to read one
- Shared, cached
- This chat, cached
- Paid for again · 25× a cached read
The clock lived in the brand block, so brand, history and message were paid for again every time.
Only the new message is paid for. The clock, memories and Jev’s lookups ride inside it.
- Before
- After
- Remind me how our brand voice is supposed to sound. 3.9¢ 2.7¢
- How do I connect our Instagram account? 3.0¢ 1.6¢
- Give me three headline ideas… 2.7¢ 1.8¢
- What time is it for me right now? 2.2¢ 1.1¢
A useful memory also needs to forget.
Engram is the three-level memory I built for Allie. L0 stores facts and corrections. L1 compresses dense, brand-scoped clusters into reusable heuristics, and a weekly L2 pass synthesizes strategy, flags drift and switches off guidance the evidence has outgrown. Reprocessing the same evidence shouldn’t multiply the same rule, so when an existing heuristic already covers 60% of a candidate’s source memories, the system reinforces that rule instead of writing a new one. Older memories lose weight daily. At recall, brand, sender and age filters keep another brand’s direction, a teammate’s private preferences and stale instructions out of the current brief.
Encode taste without washing it out.
Each brand’s visual DNA (photo register, named references, banned visuals, type rules) loads with every message. When Allie writes a photo brief that hits the banned list, it goes back to her for a rewrite before any paid model runs. Colour gets the same care. The OKLCH engine turns a primary and an accent into tonal scales and semantic tokens. A scraped accent is often the greige wall behind the product, so low-chroma yellow-browns are rebuilt from the brand’s own hue, and monochrome brands keep a separate path so black and white never pick up a made-up green. Text and surface pairs are checked against 4.5:1; for accents, the selector tries the brand’s own colour first, then its tonal stops, and reports the contrast it measured.
The editor needed a different foundation.
The editable templates nearly made me stop. I came close to paying for a white-labeled editor instead of finishing my own. The first version described a design in HTML and CSS and measured it in the browser, so the server and the editor could disagree about the same post. After about two and a half rewrites I changed the foundation: a typed scene graph and one pure layout function that takes the scene, its resources and a time tick. HarfBuzz shapes the text, the canvas paints the resolved layout, and a still image is a video with one frame. It lives in eleven engine packages, with a gate suite that checks shaping, per-glyph placement and colour against the layout’s own guarantees. On September 30 I deleted the old HTML renderer: every graphic now bakes through the engine, and a layout it can’t draw refuses instead of falling back.
A retry must not become a second charge.
Model calls cost real money, and a request can be retried while the first run is still alive. chargeGuard is the one door to paid work: check the expected spend before generating, then settle against the model that actually ran. It replaced eleven hand-rolled charge sites, and nothing outside billing calls the charge functions directly. Request-keyed routes claim a settlement position before the wallet moves. That position is built from the resource, a stable charge slot and a counter local to the gate, so a replay rebuilds the same identity and dedups instead of charging twice. A failed settlement is recorded for reconciliation, because the credit balance has to stay understandable when a provider fails.
Live, invite-only.
Desklight went live on September 26, 2026. It supports planning, creation, editing, scheduling and downloads. LinkedIn and X connect today, and the other six platform integrations open as each platform approves the app. The campaigns here use sample brands and real product output. I’m glad to walk you through the working app, the decisions behind it and the code.
The AI is the negative space.

The four-point sparkle has become the shorthand for AI. In the Spark D it’s cut out of the letter as a real opening, so it takes on whatever ground the mark sits on. The mark is drawn on a 64-unit grid: a 48-unit silhouette with a single-radius bowl. The wordmark is Syne Semibold, outlined, with its ascenders set flush to the top of the D. Favicons, app icons and the share card come from the same vector source.
One mark, every surface.
Where the decisions become code.
export const layout = ( sceneInput: DeepReadonly<Scene>, resources: DeepReadonly<LayoutResources>, at: TimeTick,): LayoutResult => { const parsed = safeParseScene(sceneInput); if (!parsed.success) return { ok: false, errors: [{ code: "INVALID_SCENE", path: "$", message: parsed.error.message }] }; const scene = parsed.data; const timeError = validateTime(scene, at); if (timeError !== null) return { ok: false, errors: [timeError] }; The one layout function. The scene is validated at the boundary, time is an input, and failure comes back as typed data that the editor and the server read the same way.
settleOrdinal += 1;const settlePos = { resource, chargeSlot, ordinal: settleOrdinal };if (!chargeModelId) return { ok: true, charged: 0, skipped: 'no_paid_model_ran' };let fenceUnverified = false;if (idemKey) { const fence = await claimSettle(idemKey, settlePos, tenantId); if (fence.claimed === false) { return { ok: true, charged: 0, deduped: true }; } if (fence.unavailable) fenceUnverified = true;}const charge = await chargeForGeneration({ tenantId, resource, modelId: chargeModelId, preferredModelId: modelId, costOpts: actualCostOpts === undefined ? costOpts : actualCostOpts, metadata: fenceUnverified ? { ...metadata, fence_unverified: true } : metadata,}); chargeGuard builds a repeatable settlement position before charging the model that ran. A claimed position returns without charging again; an unavailable fence is flagged for reconciliation. Excerpt from settle(); comments and diagnostic logging omitted.
const candidateSet = new Set(sourceIds);let dup = null;for (const existing of eligible) { const existingSet = new Set(existing.source_memory_ids || []); if (existingSet.size === 0) continue; let intersect = 0; for (const id of candidateSet) if (existingSet.has(id)) intersect++; const overlap = intersect / Math.max(candidateSet.size, 1); if (overlap >= 0.6) { dup = { skill: existing, overlap }; break; }} Engram deduplicates by evidence coverage, within the same brand and domain. Dividing by the candidate set is deliberate: a smaller cluster already covered by a larger rule should not create a duplicate. Excerpt; comments omitted.
const isMonochrome = pri.c < 0.025 && acc.c < 0.045;const isMuddyAccent = !isMonochrome && ( acc.c < 0.04 || (acc.c < 0.085 && acc.h >= 50 && acc.h <= 100));// …} else if (isMuddyAccent) { let popH = pri.c >= 0.015 ? pri.h : (acc.h + 150) % 360; if (popH >= 50 && popH <= 100) popH = 38; effAcc = { l: 0.62, c: ACCENT_TARGET_C, h: popH };} A scraped brand colour is often the wall behind the product. Low-chroma yellow-browns get no usable hue, so the engine builds a vivid accent from the brand’s own primary; true monochrome brands skip this path entirely. Excerpt.
Lumen — the product’s working design system.
I built Lumen alongside the application. Its September refresh brings spatial recipes, semantic surfaces, component states, motion, response and recovery into a public studio. The spacing workbench exposes how inset and nested radii relate; the agent studio makes scope, autonomy and permission visible. Change the controls and inspect the resulting behavior below.
Desklight · live, invite-only
Off your desk.On brand.
The closing section from desklight.ai: native SVG curves that draw themselves in and bend around your pointer.