DEV Community

Mxhlix
Mxhlix

Posted on

My coding agent always stopped at 80%. The spec I gave it was why.

I run about 600 browser-based web tools, each shipped in five locales, all built by Claude Code. For a long time, adding a new one meant handing the agent a spec: design conventions, component rules, forbidden patterns. It would read the spec, produce something plausible, and stop at roughly 80% quality. The last 20% took rounds of me pointing at things.

"Roughly 80%" was a feeling, not a measurement. I only got a number after I changed the setup.

Two things changed. The agent stopped reading specs and started copying production code. And the "build a new tool" skill got split from the "fix a broken tool" skill. Below is what that actually looks like in the repo, with the files and commits it came from.

What replaces the spec

A spec starts drifting from reality the moment you write it. Every small decision made during implementation — this pattern gets that padding, this one case is an exception — never makes it back into the document. Nobody keeps that up. So the agent fills in the unwritten parts its own way, differently every time.

Production code has all of those decisions baked in. So the skill file became a thin wrapper, and the real content moved into a catalog: 263 lines indexing 15 production references across 5 implementation patterns.

The catalog opens with a decision tree. Three questions, plus one for the specialized case:

Q1. Is the output PNG or SVG?
  ├─ SVG only          → §6 Pure SVG
  └─ PNG (primary)     → Q2

Q2. Does the preview composite a user-supplied image (a photo, etc.)?
  ├─ Yes → §3 html2canvas (background image + text overlay)
  │        * If objects are dragged / rotated / edited individually,
  │          use §2 Canvas 2D (ita-bag-badge-layout-calculator) instead
  └─ No  → Q3

Q3. Is text rendering precision decisive (typography-critical)?
       (name badges, price tags, labels, certificates, address cards,
        vertical Japanese text, print offset)
  ├─ Yes (1px of glyph drift is visible) → §2 Canvas 2D direct draw
  └─ No  (just image-ify the DOM)        → §4 dom-to-image-more

Q4 (Specialized). QR / barcode?
  └─ Yes → §5 dedicated library (QRCode.js / JsBarcode)
Enter fullscreen mode Exit fullscreen mode

Whichever branch it lands on has a table of production references. Canvas 2D has nine. Three rows of it:

Reference Template path Strengths DL handler line Draw core line
shugibukuro-address-template templates/visual/shugibukuro-address-template.html vertical text / rotated glyphs / auto line-size 202 227
wedding-place-card-maker templates/visual/wedding-place-card-maker.html honorifics / font auto-fit / multi-page A4 173 214
ita-bag-badge-layout-calculator templates/calc/ita-bag-badge-layout-calculator.html drag & drop editing / tiling / coverage ratio / one renderer shared by editor and PNG export 994 387

The agent picks two: one to use as the base, one to diff against. It doesn't read either file end to end — the table says where the download handler is and where the render core starts, so it reads around those line numbers. Then it copies the base template as a file and reshapes it around the new tool's data.

Did it work

One tool has before/after numbers, because I instrumented it while validating the approach. The measurement is the gap between where text sits in the DOM preview and where it lands in the downloaded PNG:

broken state:
  scaleFactor: 0.397
  hasJustifyCenter: true
  Canvas vs DOM drift (text top): event 0px / name -7px / role -6px / org -11px

skill applied:
  scaleFactor: 0.733
  hasJustifyCenter: false
  Canvas vs DOM drift: event +0.8px / name +2.5px / role +1px / org +0.8px
Enter fullscreen mode Exit fullscreen mode

Worst-case drift went from 11px to 2.5px. That's the specific class of defect I used to catch by eye and send back.

The honest caveat: this was measured while repairing an existing tool, not while building a new one. The handoff note from that week lists "validate the skill on a from-scratch build" as untested, and it stayed that way. My claim that new builds got better is operational — the correction rounds mostly stopped — not instrumented.

One side effect worth mentioning: the catalog records convergence. Five patterns exist, but 9 of the 15 production references are Canvas 2D. The line in the catalog that says "when in doubt, pick Canvas 2D" is writable only because that 60% is sitting right above it.

The split that lasted 94 minutes

The second decision was separating new construction from repair. I used to explain it as "one skill would bloat." What actually happened is simpler, and the commit timestamps tell it better than I can.

The prototype was a single 83-line skill with build steps and repair steps living together. At 21:11 I split it in two: 71 lines of build, 85 lines of repair. A clean bisection.

At 22:45, 94 minutes later, the repair skill I had just created was demoted. Its whole SKILL.md became qa-audit/references/visual.md, one leaf under a new skill that routes across six symptom categories.

The reason it collapsed that fast is that the two sides branch on different axes. Build branches on implementation pattern: Canvas 2D, html2canvas, dom-to-image, dedicated library, pure SVG. Repair branches on symptom category:

Reference Open when symptom matches Status
references/layout.md LayoutPatternValidator flagged, fullscreen button overlap, download / reset / copy button position wrong ✅ Implemented
references/visual.md Image-export tool: preview ≠ downloaded image, clipped text, filename ----.png for CJK input, scaler off-center ✅ Implemented
references/i18n.md FR / PT translation reads as machine-translated, hardcoded JA/EN UI strings ⏳ Stub
references/interactive.md Reset button doesn't reset, button state stuck, tap feedback missing on mobile ⏳ Stub

Those axes are orthogonal. Layout breakage and bad translations happen regardless of whether the tool draws with Canvas 2D or html2canvas. And "how to build a Canvas 2D tool" has no entry point until someone reports a symptom, which is exactly what never happens on a new build. Keep both in one skill and every read costs the agent a pass over the axis it doesn't care about. The build side is 82 lines today. The repair side is 495 (85 in SKILL.md, 410 across six reference files). Merged, every new tool would have walked past all of it.

Unbuilt categories are stubs that point at the canonical doc instead of describing a procedure, so the agent can't invent one that doesn't exist. The promotion rule got written into the same commit: a stub becomes a real reference when the same bug recurs across two or more tools and the existing pointer doesn't already contain the fix.

Skills advise. Hooks enforce.

A skill is a document. It has no power. The agent skims.

So the violations that must never ship are blocked at write time. Here's one. If flex-1 or min-h-[Npx] survives on a preview element, it silently overrides the height JS sets on that element. It shows up two ways: empty checkerboard padding above and below the preview, or the inner card punching out through the bottom of the tool container. Same root cause, and from the symptom you would not guess they're related.

Writing "delete this class" in the skill did not stop it. It came back on a second tool. Then this went in:

# [P-068] preview-stage / preview-wrapper has flex-1 or min-h-[N] while JS controls height
# Past recurrences: conference-name-badge-maker (Wave 9), perspective-grid-generator (2026-05-27).
if echo "$CONTENT" | grep -qE 'id="(preview-stage|preview-wrapper|preview-area|preview-container)"[^>]*(flex-1|min-h-\[)'; then
  VIOLATIONS="${VIOLATIONS}\n⚠ P-068: ... Canonical class is 'relative w-full overflow-hidden' and let JS own the height."
fi
Enter fullscreen mode Exit fullscreen mode

(Warning text trimmed. The recurrence list stays in the comment because it's the evidence for promoting this from advice to a block.)

One grep. That stopped it. The build skill also grew a line telling the agent to strip the class if it finds it in the reference it's copying from, because references are not automatically clean.

"It's in the skill but the agent ignored it" is not a writing problem. It's what happens when you expect enforcement from advice.

What it costs

Reference quality is the output ceiling. You still have to build the first one properly, by hand, with the taste applied. That is more up-front work than writing a spec, not less.

The catalog needs maintenance. Line numbers drift when a template gets reworked, so the catalog declares a tolerance: ±5 lines, update only after a major rewrite. That doesn't save you from the other failure. While pulling quotes for this post I found that the skill file still summarizes Canvas 2D as having 8 references while the catalog itself lists 9. I added a reference, updated the catalog, and never touched the summary table in the skill.

Which is the part I'd push back on if someone else wrote this post. Production code doesn't rot while it keeps running. The index pointing at it rots like any other document. What reference-driven buys you is that the rot is now visible and cheap — a stale line number sends the agent five lines off target, instead of a stale rule sending it down a design that no tool has used in months.

And this only pays off because I'm mass-producing. There's no reason to build a catalog for a project with one tool in it. Somewhere around the third one, when you catch yourself thinking "I wrote this already," is when it's worth naming the first one a reference.


Verified April–June 2026. Environment: Claude Code with custom skills, running 600+ web tools across 5 locales. Line counts and code excerpts taken from the live files in July 2026.

Top comments (0)