-
Notifications
You must be signed in to change notification settings - Fork 705
Taste is the spec
"Taste" is one of those words that makes engineers uncomfortable. It implies subjectivity, gut feeling, things that can't be measured. And yet, when you look at a diagram that's almost on-brand, you know. You can't articulate why, but you know.
A really tempting framing for this post would've been "How we automated design with AI." But after some meditation on the matter, that's not what happened. What happened is we figured out that taste—the thing everyone treats as ineffable—is actually a finite set of decisions. And decisions can be written down. And things written down can be enforced by a machine.
At Lago, we ship blog posts, diagrams, slide decks, whitepapers, and launch videos. Same brand across all five. We're a small team, no dedicated designer policing every output. For a while we had the usual system: a brand guidelines PDF. Hex codes, font names, a note that says "please use the warm off-white, not pure white." You know how that ends. Everyone reads it once, everyone drifts.
So we started encoding our taste into instruction files that Claude Code reads before producing an output. We call them skills. One per format. And the interesting part isn't the automation—it's the extraction process. Figuring out what your taste actually is.
Let's start with an easy one. Our background color is #f5f5ef. It's a warm off-white. Not #ffffff. Every single person who touches this for the first time wants to "correct" it to pure white. The difference is subtle enough that they genuinely think it's a mistake.
In a brand guidelines PDF, this is a line item people skip. In a skill file, it's CANVAS_FILL = "#f5f5ef" and there's no override. The output cannot be white. Same with corner radius: rx="4", four pixels on every rectangle. The skill checks every <rect> in the SVG.
Now, I know what you're thinking—colors and corner radii aren't exactly groundbreaking. Any design system has those. And you're right. The interesting constraints aren't the ones you design upfront. They're the ones you discover when things break.
We publish diagrams on a blog that renders images at roughly 75% of their source width. I did not think about this when I published the first branded diagram. The arrow labels were 10px in the source SVG. That's 7.5px in the blog. Unreadable. On my own screen.
So now the skill has a font floor:
Arrow labels: ≥ 12.5px (renders ~9.4px at 75%)
Box titles: ≥ 16px (renders ~12px)
Nobody thinks about CMS scale factors when designing a brand system. But after you ship one unreadable diagram, you add it to the spec and it never happens again.
Here's another one. We had text labels sitting on top of arrow paths in a flowchart. Looked fine in isolation. Published, the letters blurred into the line beneath them. The fix was putting an invisible rectangle behind each label, filled with the canvas color:
<rect x="588" y="185" width="95" height="18" rx="2" fill="#f5f5ef"/>
<text x="636" y="199" font-size="13" fill="#646464">clean merge</text>I call these "pills." They're invisible to the reader. They matter more than the color palette.
And then there was the forty-minute debugging session where arrow labels rendered as empty squares in a PNG export. The → character in "staging QA → cloud" looked fine in the SVG, fine in the browser, tofu in cairosvg. The fix was replacing → with the word "then." Forty minutes for a find-and-replace. That's now a rule in the skill: no Unicode arrows.
These constraints are unglamorous. CMS scale factors, invisible rectangles, Unicode substitutions. But they're the difference between output that looks almost right and output that looks right.
Let's talk about the fifth format, because this is where "taste as spec" went from a nice idea to an actual engineering system.
Linear's launch videos raised the bar for the whole industry. Beautiful, cinematic, makes you want to ship features just so you can announce them. We wanted something at that level for every feature release. With no motion designer. With no After Effects timeline.
The skill uses Remotion and is 145 lines of instructions. But underneath there's a motion token system, a narrative framework, validation scripts, a quality scorecard, and about 3,000 lines of scene templates. It is, frankly, the most over-engineered thing we've built that isn't billing.
The motion tokens are where taste-as-spec gets interesting, because you're encoding feel as physics:
export const motion = {
duration: {
tap: 4,
click: 7,
stateChange: 14,
whip: 16,
readableHold: 75,
denseContentHold: 90,
},
spring: {
pop: {damping: 14, stiffness: 190, mass: 0.7},
snap: {damping: 18, stiffness: 220, mass: 0.6},
reaction: {damping: 15, stiffness: 220, mass: 0.7},
},A state change is 14 frames. A readable hold is 75 frames at 30fps. A "snap" spring is damping: 18, stiffness: 220, mass: 0.6. When we first built this, the snap spring had mass: 0.8 and the transitions felt floaty. We dropped it to 0.6 and suddenly everything felt right. That's a taste decision encoded as a physics constant.
Before production starts, every video needs a single sentence:
Because the user does X, Lago changes Y, producing Z business outcome.
Every shot must advance X, Y, or Z. Shots that advance nothing get deleted. There's a comprehension test: show the silent animatic to someone who hasn't read the feature docs, ask them to state X, Y, and Z in their own words. If they can't, the storyboard is wrong. Not the viewer. The storyboard.
Camera moves must reveal information, connect cause to effect, or reframe the payoff. Decorative movement is "prohibited" in the skill file—not "discouraged," not "avoid when possible." The quality scorecard has 100 points across seven categories. Delivery gate is 92. Below that, the video doesn't ship.
Here's a real scene from a PO number launch video:
const stamp = spring({
frame: frame - T_STAMP, fps,
config: { damping: 13, stiffness: 240, mass: 0.7 },
});
const stampScale = interpolate(stamp, [0, 1], [2.4, 1]);
const stampRot = interpolate(stamp, [0, 1], [-16, -7]);Those spring values aren't the snap preset. They're a per-scene override, and there's a documented justification for why. If you want a sixth spring preset, you write down why you need it.
Our blog header generator is the simplest skill. Two inputs (a label and a title), one output (1920x1080 image). The gap between label and title is 64px. Text sits 104px from the bottom edge.
Both are "configurable": false. I did that deliberately, because every time spacing is adjustable, someone tweaks it. "This one looks better at 48." "This one needs 80." After six months every header has different spacing and none of them look related.
"configurable": false might be the most important taste decision in the entire system. It's a decision about decisions: some choices should only be made once.
The slide deck skill works the same way. Nine recipes: title, content, cards, stats, quote, divider, chart, list, closing. Content that fits no recipe gets mapped to the nearest one. There is no custom layout option. I expected this to feel restrictive. It doesn't. Nobody was making creative layout choices before—they were agonizing over whether something should be a card or a bullet point.
The total constraint surface across five skills is maybe 500 lines. Most of those are boring: colors, fonts, spacing constants. The ones that matter are the ones I didn't know I needed until something shipped wrong.
Each time something looks off in an output now, I don't fix that output. I add a constraint. Next time, the problem can't exist. The constraint file grows slowly—maybe 500 lines over four months—but each line represents a specific moment where something was almost right and I had to figure out specifically what was wrong.
#f5f5ef. rx="4". damping: 18. readableHold: 75. gap: 64. "configurable": false.
Taste is a finite number of decisions. Fewer than you'd think.