Case Study

Cache

A moodboard tool for the low-stakes moments — when arranging a few references doesn't need a whole design file behind it. Paste anything — an image, a link, a PDF — and it's already on the board. No account, no "create new project" dialog, no auto layout to fight with.

RoleDesign + build
StackNext.js, CSS modules
StatusWorking prototype
⌘v
01 — Where this actually started

Moodboards, before there was a product

Cache didn't start as a product idea. It started as a habit I already had — I've been making moodboards since I was a kid, long before I had a name for what that was. Project planning, visual intelligence, brainstorming, mapping out a feeling before I can explain it in words — it's always run through images first. That's not backstory for the case study. It's the actual point of origin.

The clearest version of that habit today lives on my phone, not in any design tool. I keep two widgets on my homescreen — a daily art widget and Pinterest — and every so often they land on the same feeling without knowing about each other. When that happens I screenshot it. I've been calling that parallel universes: two feeds that share no data, no algorithm, no curator in common, occasionally agreeing anyway.

Parallel universes

Not a curated pairing — an accidental one. The daily art widget doesn't know what's on my Pinterest boards, Pinterest doesn't know what painting loaded that morning. When the two rhyme anyway — same palette, same shape logic, same mood coming from two completely unrelated places — that's the moment worth keeping. It's evidence that the thing I'm drawn to is a real pattern, not a mood I talked myself into.

a screenshot pairing a daily-art widget showing Monet's Summer, 1874, with a pinterest-style saved grid of obsolete tech and instrument objects — circuit boards, a spaceball input device, sculptural panel devices
Monet's Summer, 1874 × a saved grid of worn instrument objects
a screenshot pairing Jin Nong's ink painting Plum Blossoms, 1757, with a pink Sony AIBO Pal memory stick captioned 'pov: when your widgets match your vibe'
Jin Nong's Plum Blossoms, 1757 × a pink Sony AIBO memory stick
a screenshot pairing Leonardo da Vinci's sepia drawing The Head of the Virgin in Three-Quarter View with a bejeweled pink and white toy robot figure
Da Vinci's Head of the Virgin, 1510–13 × a bejeweled pink robot figure
a screenshot pairing Maria Oakey Dewing's painting Iris at Dawn, 1899, with a hand holding a paper flower craft whose center lights up red like a soft circuit
Maria Oakey Dewing's Iris at Dawn, 1899 × a paper flower circuit lighting up in a hand
a screenshot pairing Vincent van Gogh's painting Daubigny's Garden, 1890, with a rendering of a pale green sci-fi medical scanner pod
Van Gogh's Daubigny's Garden, 1890 × a pale green sci-fi scanner pod
a screenshot pairing László Moholy-Nagy's Bauhaus photomontage Olly and Dolly Sisters, 1925, with a small speaker wired to an exposed circuit board mounted on a white panel
Moholy-Nagy's Olly and Dolly Sisters, 1925 × a speaker and circuit sculpture on a white panel
a screenshot pairing Henri Martin's pointillist painting Spring, 1903, with a pastel compact-shaped e-reader held open in a hand, displaying text
Henri Martin's Spring, 1903 × a pastel compact e-reader held open
a screenshot pairing Edward Burne-Jones's painting The Mirror of Venus, 1877, with a pixel-art virtual pet game room featuring mirrors, potted flowers, and a small green creature
Burne-Jones's The Mirror of Venus, 1877 × a pixel-art pet room with mirrors and flowers
a screenshot pairing Edward Okuń's tapestry-like painting Street at Night, c. 1900, with a densely wired circuit backplane photographed from above
Edward Okuń's Street at Night, c. 1900 × a densely wired circuit backplane

Look at the first pairing above and the correlation isn't subtle: Monet's Summer — soft yellow field, muted sky, a figure half-sunk into the grass — sitting right next to a saved grid of a bare circuit board, a Spaceball input device, sculptural instrument panels, a green synth-looking control box, a "prototype" object shot on cracked concrete. Different centuries, different mediums, same instinct underneath: worn, warm, slightly obsolete things, shot plainly, without irony. That instinct is exactly what Electrocute Lab is built on, and Cache is the tool I built to actually act on it fast.

This is also, honestly, the gap in Cache today. The paste-first workflow is built for the moment I'm actively assembling something — pulling references onto one patch, on purpose. The parallel universes habit is the opposite motion: passive, noticed in the moment, on a phone, with no board open at all. Right now that half still lives in screenshots and text threads — the grid above is a running collection, not a one-off, and it'll keep growing as new ones get caught. More on what closing that gap could look like in the roadmap below.

02 — The idea, and the why

Moodboards aren't the killer feature

Don't get me wrong — Figma is my main design tool, I genuinely love it. But there's a specific moment where it's the wrong tool for the job: I just want to drag a few references together, low stakes, nothing structural, and opening a full design file for that is like using a table saw to cut a piece of tape. The actual want underneath that is simple: collect things fast, arrange them loosely, move on.

The reframe that made the product make sense: it's not a moodboard tool, it's a room. A moodboard implies a finished artifact you're building toward. A room is a space you keep walking back into. So the entry point isn't "create a new board" — it's opening the app to nothing, hitting cmd+v, and it's just there.

Why it should exist

Most tools in this space pick a side: dead simple with almost no styling control, or fully styleable but heavier to open than a quick reference-drop deserves. Cache is trying to hold paste-anything simplicity and real, Figma-grade styling control at the same time — that combination is the actual gap, and it's the reason this kept going past the point of "just for me."

screenshot: the full patch UI — toolbar, canvas, and a real patch in progress, at real size
03 — How it actually works

The happy path

Not a click-through wireframe deck — this is the actual working app. Every step below is something you can do right now on the live link.

01
Open to nothing, paste something

No landing page, no "new project" button. Copy any image or a URL and hit cmd+v directly on the patch — it appears immediately as a card.

02
Arrange and style it

Drag to move, drag the corner dot to resize, double-click text to edit. Select a piece and a style panel floats in — it isn't a permanent sidebar, so the patch keeps the full page to itself until something's actually selected. Fill color uses a custom in-app picker instead of the browser's native color dialog; text pieces get a font picker (six presets plus your own uploaded font file); every piece gets corner radius, rotation, opacity, and layer order.

03
Paste a link or drop a file

Paste a raw URL and it unfurls into a real card (title, preview image, domain) via a small server route. Drop a PDF and it becomes an openable file card — both get an explicit "open" button so opening them never fights with dragging them.

screenshot: a link card and a pdf file card on the patch
04
Undo, delete, done

Cmd/ctrl+z steps back through a real history (one undo per gesture, not per pixel). Delete/backspace removes the selected piece. Export as PNG, a self-contained HTML file (links and files stay genuinely clickable), or PDF (with real clickable link annotations, not just a flat picture).

04 — The landscape

Where it sits

This space is more crowded than it looks from the outside. Are.na, Cosmos, Milanote, mymind, PureRef, and Figma's own FigJam are all circling the same territory — "visual, freeform, collect-anything." Most of the obvious wedges are already taken.

The gap that was actually open: paste-anything simplicity + real, Figma-grade styling control, held at the same time. Plenty of tools nail one side of that or the other — dead simple to start but little to no control over how things look, or fully styleable but heavier to open than the task deserves. This is trying to hold both at once.

ToolIs for
PinterestDiscovery
Are.naArchives
CosmosCollecting
MilanotePlanning
Figma / FigJamCollaboration
CachePersonal visual thinking — paste in, style it, done
05 — Naming as a decision, not an afterthought

Patches, stashes, cached

Every tool in this category names its unit of work differently — Figma has files, Are.na has channels, Notion has pages, Cosmos has clusters. Picking a vocabulary on purpose, instead of defaulting to "board" and "save," was worth the extra round of naming work.

Board / canvas
Patch
Folder
Stash
Saved
Cached

The product name went through a real shortlist first — found gems, swatch, patch, trinket — before landing on cache: short, and it does double duty as both "a stash of something valuable" and literally what a computer does to store things for fast retrieval. That it also folded neatly into "cached" as the save-state verb was a bonus, not the plan.

The computer-science meaning isn't just a pun sitting on top of the name, either. A real cache is a small, fast store that sits between you and the thing you actually want, so the next time you reach for it, it's already close at hand instead of being re-fetched from wherever it originally lived. A patch works the same way — the references you just pulled together stay right where you left them, arranged, instead of needing to be found again each time you want to look at them together.

There's a well-known line in computer science: there are only two hard problems — cache invalidation and naming things. This project ran straight into the second one, directly above. It mostly dodges the first — nothing in a patch needs to expire or get evicted the way a real cache does. The detail worth keeping, though: "cache" comes from the French cacher, to hide. The actual visual identity here does the opposite on purpose — a blank tag instead of a hidden logo. The name kept its function and inverted its origin.

06 — Visual direction

Three passes to get here

The first pass leaned into my usual pastel poetronics palette — blush, matcha, wisteria, running-stitch borders. It looked fine and it looked like every other craft-adjacent tool in the space, Cache included. The second pass just turned the saturation up. Also fine, also still generic.

The direction that actually stuck came from naming references outside the usual moodboard aesthetic entirely: Dieter Rams, for the belief that color and ornament should only show up where they're doing real work, not decorating it — and Margiela, for treating the absence of a logo as the identity itself. Translating that kind of restraint into actual interface decisions, instead of quoting either one literally, is what got this away from looking like every other tool in the category.

Pass 01
Pastel, running-stitch borders. Reads as craft-tool default.
Pass 02
Same palette, more saturated. Same problem, louder.
Final direction
Dark chrome, bone paper canvas, sharp corners, one pastel accent per function.

Two details carry most of the identity now. Selection is a viewfinder-bracket mark instead of a soft dashed outline — sharp, technical, doubles as a pattern-cutting crop-mark reference. And every patch carries a small blank tag in the corner, standing in for a logo the way Margiela's four-stitch blank tag stands in for one — deliberately arbitrary, so it never reads as a version number or a real system.

The screen, at full fidelity

screenshot: the full patch UI — toolbar, canvas, and a real patch in progress, at real size
CACHEText
ImageLinkFile

The moodboard behind this direction was built inside Cache itself — pasting in product shots, the Margiela tag, and instrument-panel references directly onto a patch, rather than moodboarding this in Figma. Partly to stay consistent with what the tool is actually for, partly because there's no better way to show a paste-first tool's taste than to use it that way.

screenshot: the actual moodboard patch, exported from cachescreenshot: the actual moodboard patch, exported from cachescreenshot: the actual moodboard patch, exported from cache

(Yes — this moodboard was made entirely inside Cache. Paste, arrange, export! :))

Dieter Rams — Braun products
Color and ornament only where they do real work — the one pastel accent per function (lavender = selection, butter = the primary action, pink = resize) comes from this.
Margiela — the blank tag
Absence as identity. The patch mark carries no logo — just white fabric, black stitching, and a small embroidered ⌘v as the one wink at what the tool does.
Instrument panels / HUD readouts
The corner readout, the viewfinder-bracket selection marks, and the rotation control itself — a real dial you turn, not a slider wearing a costume — all come from the same place: technical displays, not soft craft-tool UI.
07 — Four bugs worth naming

What actually broke, and why

A case study that only shows the final state skips the part that's actually evidence of how someone builds. Four real ones, in the order they surfaced — including one that took three wrong guesses before the actual fix.

Sliders wouldn't dragReact
"I'm not able to drag the sliders to make adjustments, it's a bit finicky"

The style panel's component was defined inside the parent component's function body. Every slider onChange triggered a re-render, which gave React a brand-new component identity, which tore down and rebuilt the whole panel — including the native range input — mid-drag. The browser's own drag tracking never survived a single tick.

const StylePanelContent = () => ( ... ) // recreated every render function StylePanelContent({ selected, updateElement, ... }) { ... } // moved to module scope, props passed explicitly

Moving it to module scope with explicit props fixed it structurally — not a CSS fix, not a browser quirk, an actual React anti-pattern with a specific name.

Pasted images "wouldn't move"State
"Images when pasted are overlapping and I can't move them, they snap together"

They weren't stuck — every pasted image landed at the exact same board-center coordinates, with zero offset. A second paste stacked precisely on top of the first, so any card you dragged away just got covered again by the next paste landing in that same spot. The fix was a cascading offset shared across paste, text-add, and color-block-add, so nothing spawns exactly on top of anything else.

Exporting a link card could break the whole PNGCanvas / CORS

Link cards fetch a preview image from whatever site got pasted. Drawing an external, cross-origin image onto a canvas without the right handling "taints" the canvas — the whole export throws a security error, not just that one card. Setting img.crossOrigin = "anonymous" up front means a source without proper CORS headers just fails to load cleanly (falls back to a blank card) instead of silently poisoning the entire download.

Background removal — three wrong guesses before the real oneNext.js / webpack
"Background removal failed" — every time, on every browser, on both localhost and the deployed site.

First guess: the library needs SharedArrayBuffer, which needs two response headers to cross-origin-isolate the page. Added them, verified they actually landed with curl -I against a real production server. No change.

Second guess, and a real bug this time: every image here is a data: URL, and the library's own check for "is this an absolute path" uses a regex requiring // right after the scheme — matches http://, not data:. It was reading every pasted image as a relative path and mangling it. Found by installing the exact package version locally and grepping its source for every .replace() call until the actual line turned up. Fixed it by converting to a blob first. The exact same error came back anyway.

The real one: the crash was inside a different library entirely — onnxruntime-web, the ML runtime behind background removal — in a function called RelativeURL, trying to locate its own wasm file. Webpack was rewriting that function's internal import.meta.url reference into something that isn't a plain string anymore, and the library's own url-building code broke on it. That's exactly the subsystem the library's own docs warn about: "currently only Next.js 15 is supported." This was running on 14.

The fix, after upgrading: load the library from a CDN URL with a webpackIgnore comment instead of importing it locally. Webpack never touches a webpack-ignored import, so it never rewrites anything inside it — the bug simply has nothing left to attach to.

08 — Stack

What's built, what's roadmap

Frontend
Next.js, React, plain CSS modules — no Tailwind, no build-time class generation.
Canvas
Hand-rolled drag/resize/select logic in this prototype; the planned swap is tldraw — purpose-built for freeform shapes with pan/zoom/undo for free, instead of reinventing canvas primitives by hand.
Sharing
Currently a base64 snapshot packed into the URL hash — zero backend, but it doesn't scale past small boards. Real persistence is a guest-editable board ID in Postgres (Supabase), same no-account-required UX, actual database underneath.
Links
One small server route (/api/unfurl) fetches og:title/og:image server-side — the one place this "no backend" app actually needs a sliver of one, since a browser can't fetch an arbitrary third-party page directly.
Export
One shared canvas-rendering function powers both PNG and PDF export; PDF adds real clickable link annotations on top via jsPDF (MIT licensed). HTML export embeds everything — images, files, custom fonts — as a single self-contained file.
Fonts
Six presets self-hosted via next/font/google, plus custom font upload (woff/woff2/ttf/otf) injected as real @font-face rules. Canvas exports call document.fonts.load() before drawing so the export always matches what's on screen.
09 — Edge cases and what's next

The honest roadmap

Not yet built, on purpose: selecting an arbitrary subset of pieces — right now selection is all-or-one (select-all, or click a single piece), with no shift-click or rubber-band way to grab just three specific pieces out of ten. Also missing: live group-dragging — a full selection can be copied and pasted back as a group, keeping every piece's position relative to the others, but there's no way yet to grab several selected pieces and drag them together with the mouse in real time; dragging still only ever moves one piece. Beyond that: stashes, the folder layer above a single patch, once the core loop has been used by more than one person; connections and flows between pieces, which stay explicitly out of scope until the basic loop is proven; and moving sharing off the URL hash and onto real storage once boards start carrying real files.

Mobile — capturing parallel universes

The desktop app is built for the moment you're actively assembling a patch. It's not built for the moment described in section 01 — spotting a rhyme between two things you weren't even looking for a rhyme between, on your phone, with no board open. That's a different interaction shape entirely: not "open Cache, paste, arrange," but "screenshot two things, drop them next to each other, done" — closer to a share-sheet target than a full editor. No layout decisions, no styling panel, just two (or three) images landing side by side the instant you catch the correlation, so it's saved before the feeling passes. The patches it produces would be small and fast on purpose — the point is capturing the instinct, not composing a board.