<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Umar Gora</title><description>Umar Gora is a senior software engineer at NearForm, creator of the sdx spec-driven development toolchain, and founder of Deen Developers.</description><link>https://umar.codes</link><item><title>Spec-driven development</title><link>https://umar.codes/spec-driven-development</link><guid isPermaLink="true">https://umar.codes/spec-driven-development</guid><description>Spec-driven development means writing the decisions down before the code: what is being built, what deliberately isn&apos;t, and how you&apos;ll know it worked — precisely enough that an AI agent can implement it without inventing the missing parts. This page documents the methodology as I actually practise it: the two-document structure, the judgement rules, the verification gates, and the places it still breaks down.</description><pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Spec-driven development means writing the decisions down before the code: what is being built,
what deliberately isn’t, and how you’ll know it worked — precisely enough that an AI agent can
implement it without inventing the missing parts. This page is the methodology as I actually
practise it: the documents, the judgement rules, and the places it still breaks down.&lt;/p&gt;
&lt;p&gt;I made the argument for working this way in &lt;a href=&quot;/vibe-coding&quot;&gt;Why I’ll never go back to vibe
coding&lt;/a&gt;, on the NearForm blog. This page is the &lt;em&gt;how&lt;/em&gt;, and it’s a living
document — the revision log at the bottom records how it changes as the practice does.&lt;/p&gt;
&lt;h2 id=&quot;the-core-loop&quot;&gt;The core loop&lt;/h2&gt;
&lt;p&gt;The loop has four artifacts, and the discipline is that each one exists before the next:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;A design spec&lt;/strong&gt; — the decisions, locked. What we’re building, what we’re not, which
alternatives were rejected and why.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An implementation plan&lt;/strong&gt; — the design decomposed into tasks small enough that each one
states its files, its code, and its expected output.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution with gates&lt;/strong&gt; — an agent (or I) work the plan phase by phase. Each phase has a
done check, and nothing moves forward until it passes. One commit per phase, named for its
outcome.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A verification report&lt;/strong&gt; — pass/fail against the spec’s own criteria, with evidence.
Not “it works”; the actual command output.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Alongside the loop run two logs. &lt;code&gt;DECISIONS.md&lt;/code&gt; records every judgement call made during
execution — what the spec said, what was done instead, why. &lt;code&gt;BLOCKERS.md&lt;/code&gt; records anything the
spec couldn’t anticipate: write the blocker, skip the task, keep going. The run never stops to
wait for a human unless it hits a &lt;em&gt;gate&lt;/em&gt; — and a good spec has very few of those. The spec that
rebuilt this website had exactly one (the framework choice) across ten phases, and the
execution produced twelve dated decision entries without a single mid-run question beyond it.&lt;/p&gt;
&lt;p&gt;The split between spec and plan matters. The spec is small and dense — decisions per square
inch. The plan is large and mechanical; the biggest I’ve run to completion was 1,484 lines,
four stacked pull requests of three to six tasks each, every task ending in a test. They fail
differently: a bad spec produces the wrong thing correctly, a bad plan produces the right
thing slowly. Keeping them separate means you can tell which one to fix.&lt;/p&gt;
&lt;h2 id=&quot;what-belongs-in-a-spec&quot;&gt;What belongs in a spec&lt;/h2&gt;
&lt;p&gt;Six things earn their place. Everything else is padding that an agent will either ignore or,
worse, obey.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Locked decisions, with the rejected alternatives named.&lt;/strong&gt; Not “we will use approach A” but
“A over B and C, because B violates seam discipline and C is over-engineering.” When an agent
mid-task discovers a reason to prefer B, the spec has already had that argument, and the agent
can see it lost. My design specs carry these as a table; one recent migration locked eight
decisions this way, another twelve.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Non-goals.&lt;/strong&gt; The highest-leverage section, and the one most specs omit. The spec for this
site ends with five things it deliberately does not do — no per-page markdown twins, no CMS,
no newsletter — each with the reasoning. Agents are eager; non-goals are the fence.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Verbatim constants.&lt;/strong&gt; Strings that must appear byte-identical everywhere: canonical URLs,
entity descriptions, API names. Marked explicitly as &lt;em&gt;use this verbatim, do not paraphrase&lt;/em&gt;.
Anything not marked verbatim is direction, and direction invites judgement — which is exactly
what you want, as long as the boundary is explicit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pinned facts.&lt;/strong&gt; Package versions, environment values, deadlines. A table the implementation
must match, so “it drifted during the run” becomes a checkable claim.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Done checks, per phase.&lt;/strong&gt; Each phase ends with a test the phase must pass — “&lt;code&gt;pnpm build&lt;/code&gt;
succeeds”, “every URL in the discovery doc resolves or 301s”, “all ten verification rows
pass.” A phase without a done check isn’t a phase; it’s a hope.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Escape hatches.&lt;/strong&gt; The &lt;code&gt;DECISIONS.md&lt;/code&gt; / &lt;code&gt;BLOCKERS.md&lt;/code&gt; contract from the loop above, written
into the spec itself: exercise judgement and record it; when blocked, log and continue. This
is what lets a run survive contact with reality without either stopping constantly or
improvising silently.&lt;/p&gt;
&lt;h2 id=&quot;what-stays-out&quot;&gt;What stays out&lt;/h2&gt;
&lt;p&gt;Everything the implementer — human or agent — should own. File layout, function names,
internal structure, library micro-choices. Over-specifying these produces worse results than
under-specifying them, for a reason that took me a while to accept: an exhaustive prescription
is always partly wrong, and an agent will follow the wrong parts as faithfully as the right
ones. A judgement rule plus a decision log beats a prescription.&lt;/p&gt;
&lt;p&gt;The clearest example from this site’s rebuild: the spec said “Astro 5 with the content layer.”
By execution time, current stable was Astro 7. Because the spec’s contract was &lt;em&gt;record
deviations in &lt;code&gt;DECISIONS.md&lt;/code&gt;&lt;/em&gt; rather than &lt;em&gt;follow versions literally&lt;/em&gt;, the run took Astro 7,
wrote down why, and kept moving. A stricter spec would have either halted or — worse —
dutifully installed an outdated major.&lt;/p&gt;
&lt;p&gt;The other thing that stays out is ceremony. Artifacts that no one reads back are cost without
control: I’ve dropped QA gate files in favour of inline review notes, and I keep working plans
out of the repo entirely when they’re execution scaffolding rather than durable record.&lt;/p&gt;
&lt;h2 id=&quot;verification-is-a-document-not-a-feeling&quot;&gt;Verification is a document, not a feeling&lt;/h2&gt;
&lt;p&gt;The end of a run is a report with a row per claim and evidence per row — actual Lighthouse
scores, actual command output, the actual logged JSON line proving the crawler logger fired.
Two phrases are banned from it: “should work” and “probably passes.” If the proof wasn’t run
fresh, it isn’t proof.&lt;/p&gt;
&lt;p&gt;The reason for the paranoia is that the most dangerous test result is a &lt;strong&gt;vacuous green&lt;/strong&gt;.
I’ve been burned by these repeatedly, in ways a spec now has to anticipate: a resume check
that “passed” because it reduced to &lt;code&gt;includes(&amp;#39;&amp;#39;)&lt;/code&gt; against an empty environment variable; a
layout assertion suite that passed with every rectangle at zero size because the test runner
never loaded the CSS. Both were green. Neither tested anything. Done checks now come with a
guard against emptiness — assert the count is greater than zero before asserting the
properties of the members.&lt;/p&gt;
&lt;p&gt;Verification also catches what the spec itself got wrong. This site’s spec permitted a
page-load fade; verification showed it delayed first contentful paint badly enough to zero out
the metric in backgrounded tabs. The fade is gone, and the reason is in the decision log —
which is the system working, not failing.&lt;/p&gt;
&lt;h2 id=&quot;where-it-breaks-down&quot;&gt;Where it breaks down&lt;/h2&gt;
&lt;p&gt;Honest failure modes, all from my own logs:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Specs that specify impossible states.&lt;/strong&gt; One acceptance criterion — and its integration test
— asserted a state the system could not reach, because the role in question didn’t hold the
required permission. Both spec and test agreed with each other and disagreed with reality.
Lesson: acceptance criteria need the same “can this actually occur” review as code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hypotheses dressed as decisions.&lt;/strong&gt; A performance spec confidently attributed a layout-shift
problem to a specific banner. Measurement disproved it — lab CLS was already near zero — and
the honest move was reshaping the work into a field-measurement task first. A related epic
died entirely the same week, pull request closed, when the data showed its target metric
wasn’t the real problem. Specs encode beliefs; some beliefs are wrong, and the spec has to
lose gracefully.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Drift.&lt;/strong&gt; Implementations move after the spec is approved — every long-lived spec is slowly
becoming fiction. The countermeasure is making drift &lt;em&gt;detectable&lt;/em&gt; rather than pretending it
won’t happen: contract-drift checks in CI (warn-only at first, blocking once trusted), and
staleness thresholds on the specs themselves.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The overhead is real, and mostly unmeasured.&lt;/strong&gt; The industry discourse on spec-driven
tooling runs almost entirely on personal anecdotes about token costs and spec sizes. I got
frustrated enough to build a benchmark harness — eight fixed tasks, deterministic acceptance
suites, and a &lt;em&gt;null adapter&lt;/em&gt; that establishes the floor any real tool must beat. First
measured result: one popular SDD tool completed a brownfield task at a median cost of $0.57,
132 lines of spec, a 0.75 acceptance pass rate. Spec-driven development trades immediate
feedback for consistent output; that trade should be measured, not vibed.&lt;/p&gt;
&lt;h2 id=&quot;relationship-to-sdx&quot;&gt;Relationship to sdx&lt;/h2&gt;
&lt;p&gt;Everything above is discipline, and discipline erodes. &lt;a href=&quot;/sdx&quot;&gt;sdx&lt;/a&gt; — published on npm as
&lt;code&gt;specdx&lt;/code&gt; — is the toolchain I’m building to make the enforceable parts of this page machine-
checked instead of remembered. It gives specs a formal schema (nine document types, each with
required sections), lints them (thirteen rules, from missing frontmatter to vague language),
packs the relevant ones into token-budgeted context for an agent session, and statically
checks implementation drift after the fact. Its &lt;code&gt;ready&lt;/code&gt; command is the gate between planning
and building, run in CI.&lt;/p&gt;
&lt;p&gt;One design decision in sdx mirrors this whole page: the core pipeline makes &lt;strong&gt;no LLM calls&lt;/strong&gt;.
Validation is deterministic, keyed to run in CI, because the agent already lives in your
editor — the spec tooling’s job is to keep it honest, not to be clever. The spec is the
interface; the tools just make lying to yourself harder.&lt;/p&gt;</content:encoded></item><item><title>I built the same site in Next.js, Remix, and Astro</title><link>https://umar.codes/three-frameworks</link><guid isPermaLink="true">https://umar.codes/three-frameworks</guid><description>I built this site three times — in Next.js, Remix, and Astro — and served each build from its own subdomain. What differed was the server model and the JavaScript shipped by default; what didn&apos;t was the HTML, the CSS, or the content. This essay is what that experiment taught me, and why the production site is now Astro.</description><pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I built this site three times — once each in Next.js, Remix, and Astro — and served them on
their own subdomains. The frameworks differed almost entirely in how they treat the server;
they barely differed in what visitors received. For a content site, the deciding factor was how
much JavaScript each one ships by default.&lt;/p&gt;
&lt;h2 id=&quot;why-i-built-the-same-site-three-times&quot;&gt;Why I built the same site three times&lt;/h2&gt;
&lt;p&gt;The honest reason: I wanted to form opinions from building, not from reading other people’s
benchmark threads. A personal site is the perfect test subject — small enough to rebuild in a
weekend, real enough to expose each framework’s defaults. So for a while, &lt;code&gt;next.umar.codes&lt;/code&gt;,
&lt;code&gt;remix.umar.codes&lt;/code&gt;, and &lt;code&gt;astro.umar.codes&lt;/code&gt; all served the same content from three different
codebases.&lt;/p&gt;
&lt;p&gt;That experiment is over, and this essay absorbs it. The subdomains now redirect here — three
identical sites competing for the same search index entry turned out to be a good way to learn
about frameworks and a bad way to run a website.&lt;/p&gt;
&lt;h2 id=&quot;what-differed-the-server-model&quot;&gt;What differed: the server model&lt;/h2&gt;
&lt;p&gt;Each framework has a different answer to “where does your code run?”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next.js&lt;/strong&gt; wants to be everywhere: server components, client components, static generation,
incremental regeneration — several rendering models in one framework, each with its own rules.
The pages-router version of this site used exactly one of them (static generation), which meant
most of the framework was surface area I carried but didn’t use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remix&lt;/strong&gt; is the most opinionated: everything is a server request, loaders feed routes, forms
post back. It’s the cleanest mental model of the three — and the least necessary one for a site
where every page could be built once at deploy time. Running a server so that unchanging HTML
can be re-rendered per request is architecture without a requirement behind it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Astro&lt;/strong&gt; treats pages as templates that run at build time and components as islands that only
hydrate if you ask. The default is zero client JavaScript, and that default is the whole
argument.&lt;/p&gt;
&lt;h2 id=&quot;what-didnt-differ&quot;&gt;What didn’t differ&lt;/h2&gt;
&lt;p&gt;The HTML was the same. The CSS was the same. The content was identical by design. Lighthouse
scores converged in the high 90s on all three once images were handled properly — for a small
static site, any of these frameworks can be made fast. What differed was how much work “made
fast” took: in Astro it was the default, in Next.js and Remix it was a discipline.&lt;/p&gt;
&lt;h2 id=&quot;what-the-production-build-looks-like-now&quot;&gt;What the production build looks like now&lt;/h2&gt;
&lt;p&gt;The version of this site you’re reading is the Astro rebuild. The full production build is
940 KB on disk, and 608 KB of that is self-hosted fonts. It contains ten HTML pages,
one CSS file, and zero JavaScript bundles — the only scripts on any page are two inline
snippets (theme persistence and a toggle handler) and two JSON-LD blocks. A production build
takes about three seconds on my machine.&lt;/p&gt;
&lt;p&gt;The React versions couldn’t get near that floor, because shipping React at all costs roughly
45 KB gzipped before the first line of my own code.&lt;/p&gt;
&lt;h2 id=&quot;which-one-id-pick-by-project&quot;&gt;Which one I’d pick, by project&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Content site, blog, docs, marketing:&lt;/strong&gt; Astro, without hesitation. The zero-JS default and
the content-collection layer fit the problem exactly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product with heavy interactivity and sessions:&lt;/strong&gt; Remix’s loader/action model, or Next.js if
the team already lives in the Vercel ecosystem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The real lesson:&lt;/strong&gt; pick by what the framework makes &lt;em&gt;default&lt;/em&gt;, not by what it makes
&lt;em&gt;possible&lt;/em&gt;. Everything is possible in all three. You will ship the defaults.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Why I&apos;ll never go back to vibe coding</title><link>https://umar.codes/vibe-coding</link><guid isPermaLink="true">https://umar.codes/vibe-coding</guid><description>Vibe coding — steering an AI with vague prompts and vibes — produces context collapse and accumulating rework. Spec-driven development trades immediate feedback for consistent output, and for anything beyond a throwaway script that trade is worth it. I made the full case, including the BMAD method, on the NearForm blog.</description><pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Vibe coding — steering an AI with vague prompts and hoping — feels fast until the context
collapses and the rework starts compounding. My case for the alternative: write the
specification first, precise enough that the agent can’t invent the missing parts.
Spec-driven development trades immediate feedback for consistent output, and for anything
beyond a throwaway script that trade wins.&lt;/p&gt;
&lt;p&gt;I made the full argument — including a walkthrough of the BMAD method and the honest
tradeoffs — on my employer’s blog:
&lt;a href=&quot;https://nearform.com/digital-community/why-ill-never-go-back-to-vibe-coding-a-developers-case-for-spec-driven-development/&quot;&gt;read the full piece at NearForm →&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The practical, evolving version of this methodology lives on this site as
&lt;a href=&quot;/spec-driven-development&quot;&gt;spec-driven development&lt;/a&gt;, and the toolchain it grew into is
&lt;a href=&quot;/sdx&quot;&gt;sdx&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Stop losing filter state: shareable URLs for AG Grid</title><link>https://umar.codes/ag-grid-url-sync</link><guid isPermaLink="true">https://umar.codes/ag-grid-url-sync</guid><description>ag-grid-url-sync is an open-source library I built that turns AG Grid filter state into clean, human-readable query strings, so a filtered view can be shared as a URL instead of a screenshot. It supports all 26 native AG Grid filter operations. The full write-up was published on the NearForm blog.</description><pubDate>Wed, 30 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every team I’ve watched work with AG Grid eventually hits the same wall: someone builds the
perfect filtered view, and the only way to share it is a screenshot and a list of instructions.
I built &lt;a href=&quot;https://www.npmjs.com/package/ag-grid-url-sync&quot;&gt;ag-grid-url-sync&lt;/a&gt; to fix that — it
serialises filter state into clean, human-readable query strings, covering all 26 native AG
Grid filter operations, so a view is just a URL you paste into Slack.&lt;/p&gt;
&lt;p&gt;I wrote up the design, the API, and the workflow problems that motivated it on my employer’s
blog: &lt;a href=&quot;https://nearform.com/digital-community/stop-losing-filter-state-shareable-urls-for-ag-grid-with-ag-grid-url-sync&quot;&gt;read the full piece at NearForm →&lt;/a&gt;&lt;/p&gt;</content:encoded></item></channel></rss>