{"slug": "taste-is-the-spec", "title": "Taste Is the Spec", "summary": "Lago, a small team with no dedicated designer, encoded its brand taste into instruction files called skills that Claude Code reads before producing outputs across five formats including blog posts, diagrams, and slide decks. The skills enforce specific constraints such as a warm off-white background (#f5f5ef), 4px corner radius, minimum font sizes for arrow labels (≥12.5px) and box titles (≥16px), invisible rectangles behind text labels, and a ban on Unicode arrows after a rendering bug cost 40 minutes to debug. By codifying these decisions, Lago ensures consistent brand output without manual policing.", "body_md": "-\n[Notifications](/login?return_to=%2Fgetlago%2Flago)You must be signed in to change notification settings -\n[Fork 705](/login?return_to=%2Fgetlago%2Flago)\n\n# Taste is the spec\n\n[1 revision](/getlago/lago/wiki/Taste-is-the-spec/_history)\n\n\"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.\n\nA 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.\n\nAt 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.\n\nSo 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.\n\nLet's start with an easy one. Our background color is `#f5f5ef`\n\n. It's a warm off-white. Not `#ffffff`\n\n. 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.\n\nIn a brand guidelines PDF, this is a line item people skip. In a skill file, it's `CANVAS_FILL = \"#f5f5ef\"`\n\nand there's no override. The output *cannot* be white. Same with corner radius: `rx=\"4\"`\n\n, four pixels on every rectangle. The skill checks every `<rect>`\n\nin the SVG.\n\nNow, 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.\n\nWe 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.\n\nSo now the skill has a font floor:\n\n```\nArrow labels: ≥ 12.5px (renders ~9.4px at 75%)\nBox titles:   ≥ 16px  (renders ~12px)\n```\n\nNobody 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.\n\nHere'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:\n\n```\n<rect x=\"588\" y=\"185\" width=\"95\" height=\"18\" rx=\"2\" fill=\"#f5f5ef\"/>\n<text x=\"636\" y=\"199\" font-size=\"13\" fill=\"#646464\">clean merge</text>\n```\n\nI call these \"pills.\" They're invisible to the reader. They matter more than the color palette.\n\nAnd then there was the forty-minute debugging session where arrow labels rendered as empty squares in a PNG export. The `→`\n\ncharacter in \"staging QA → cloud\" looked fine in the SVG, fine in the browser, tofu in cairosvg. The fix was replacing `→`\n\nwith the word \"then.\" Forty minutes for a find-and-replace. That's now a rule in the skill: no Unicode arrows.\n\nThese 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.\n\nLet's talk about the fifth format, because this is where \"taste as spec\" went from a nice idea to an actual engineering system.\n\nLinear'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.\n\nThe 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.\n\nThe motion tokens are where taste-as-spec gets interesting, because you're encoding feel as physics:\n\n``` js\nexport const motion = {\n  duration: {\n    tap: 4,\n    click: 7,\n    stateChange: 14,\n    whip: 16,\n    readableHold: 75,\n    denseContentHold: 90,\n  },\n  spring: {\n    pop:  {damping: 14, stiffness: 190, mass: 0.7},\n    snap: {damping: 18, stiffness: 220, mass: 0.6},\n    reaction: {damping: 15, stiffness: 220, mass: 0.7},\n  },\n```\n\nA state change is 14 frames. A readable hold is 75 frames at 30fps. A \"snap\" spring is `damping: 18, stiffness: 220, mass: 0.6`\n\n. When we first built this, the snap spring had `mass: 0.8`\n\nand 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.\n\nBefore production starts, every video needs a single sentence:\n\nBecause the user does X, Lago changes Y, producing Z business outcome.\n\nEvery 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.\n\nCamera 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.\n\nHere's a real scene from a PO number launch video:\n\n``` js\nconst stamp = spring({\n  frame: frame - T_STAMP, fps,\n  config: { damping: 13, stiffness: 240, mass: 0.7 },\n});\nconst stampScale = interpolate(stamp, [0, 1], [2.4, 1]);\nconst stampRot = interpolate(stamp, [0, 1], [-16, -7]);\n```\n\nThose spring values aren't the `snap`\n\npreset. 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.\n\nOur 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.\n\nBoth are `\"configurable\": false`\n\n. 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.\n\n`\"configurable\": false`\n\nmight be the most important taste decision in the entire system. It's a decision *about* decisions: some choices should only be made once.\n\nThe 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.\n\nThe 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.\n\nEach 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.\n\n`#f5f5ef`\n\n. `rx=\"4\"`\n\n. `damping: 18`\n\n. `readableHold: 75`\n\n. `gap: 64`\n\n. `\"configurable\": false`\n\n.\n\nTaste is a finite number of decisions. Fewer than you'd think.", "url": "https://wpnews.pro/news/taste-is-the-spec", "canonical_source": "https://github.com/getlago/lago/wiki/Taste-is-the-spec", "published_at": "2026-07-21 20:28:57+00:00", "updated_at": "2026-07-21 20:42:53.147998+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools"], "entities": ["Lago", "Claude Code"], "alternates": {"html": "https://wpnews.pro/news/taste-is-the-spec", "markdown": "https://wpnews.pro/news/taste-is-the-spec.md", "text": "https://wpnews.pro/news/taste-is-the-spec.txt", "jsonld": "https://wpnews.pro/news/taste-is-the-spec.jsonld"}}