Abdulkader Safi

Specs before code: the plan behind a two-day build

The first GitBasedDocs commit was almost all plan: research notes, feature specs and task cards. How that plan was shaped, and the four places reality corrected it.

5 min read

Share
GitBasedDocs docs page rendering the project's own documentation

The first commit in the GitBasedDocs repo is 58 files and 2,978 added lines, and almost none of it is app code. It is a bare Next.js scaffold plus the plan: 5 research notes, 8 feature specs, 18 task cards and an AGENTS.md with the commands and board rules. It landed at 15:19 on September 9. The last feature commit landed at 19:02 on September 10, and the repo now has 55 commits.

Research notes first

Each research note answers one question and ends with a takeaway.

  • How git-based CMS tools work, and why this app reads GitHub at request time instead of at build time.
  • How to read a private repo. A fine-grained token scoped to one repo won over a GitHub App, a deploy key and OAuth user tokens. The GitHub App is written down as phase two.
  • How to sync without GitHub Actions: webhook first, a Sync now button for repairs, and a timed recheck as the safety net.
  • How to render Markdown in Next.js: frontmatter with fallbacks, remark and rehype, sanitize before the browser, cache HTML per blob sha.
  • Auth and data: the roles, the Drizzle tables and the access check every read path runs.

A sixth note came mid-build. At 14:53 on day two I added a ranked list of the ten Markdown extensions that matter most for a vault that also lives on GitHub, with a pick and a reason for each. The five commits that implemented it followed between 14:56 and 15:10.

Specs with the same skeleton

Every feature spec has the same sections: purpose, users, behavior, data, UI, edge cases and "out of scope for v1".

Some specs carry more than behavior. The access control spec lists the tests that have to pass before the build counts as done: viewer A cannot open a page, an image or a search result in project B, and all three return 404. A removed member loses access on the next request without signing out.

The specs index also sets a build order: 01, 03, 02, 06, 04, 05, 07, 08. That is sign-in, then the GitHub connection, then projects, then sync, and only then the page viewer. The commit history follows it. Nothing renders a page until there is a synced index to render from.

Cards that say when they are done

The task board is a folder of Markdown files, one per card, grouped by area: setup/, auth/, github/, sync/ and so on. Each card has YAML frontmatter for status, priority and order, a Spec: line pointing at the feature file it implements, and a "Done when" checklist.

The checklist is the useful part. The webhook card says done means a bad signature returns 401 with no sync, and a push to another branch returns 200 with a log line. After the build, the card got a table of seven cases tested over HTTP against the running server, with the response for each.

The board started with 18 cards and ended with 30. The extra 12 came from work the plan didn't list: the design system and the reader home, admin navigation, two rounds of UI review with phone layout fixes, the Markdown extensions, the account page, and commit info on pages. All 30 are marked done.

Where the plan was wrong

The plan was wrong in a few places, and the fix each time was to change the spec in the same commit as the code.

The specs were first written against Auth.js v5 patterns. The second commit, 17 minutes after the first, moved the specs, the board and AGENTS.md to next-auth v4, since the v5 adapter packages don't work with v4.

The auth research picked database sessions so an admin could revoke access at once. next-auth v4 only allows email and password sign-in with JWT sessions. The fix commit switched to JWT and kept revoke working another way: the session callback reloads the user row on every call, so a deactivated user fails on their next request.

The GitHub connection spec assumed docs sit in a docs/ folder inside a code repo. My vault is the whole repo. The sync commit made that folder setting optional and empty by default, and let a project own the repo root.

The navigation spec said a folder's index.md sets the folder's label. In practice docs/index.md renamed the docs folder to "Index". The fix made folders keep their real names, as Obsidian does, and rewrote that line of the spec.

Not every document kept up. The auth research note still recommends database sessions, and the admin settings spec still lists AUTH_SECRET and a Postgres deploy, while the app reads NEXTAUTH_SECRET and runs on SQLite only.

The design spec came later

At 17:30 on day one, after the login and the connection form already worked, I wrote a page-by-page design spec. It covers every screen, what goes on it, and the exact theme tokens from globals.css. It opens with "Build this in Figma first, then we match the code to it". Its strongest rule is a radius of 0: every corner sharp, on cards, buttons, inputs, badges and dialogs.

Last updated 10 Sep 2026 · filed under Tools, web application, claude code, agents

Every entry on this project

19 build notes, in order

Including the ones where nothing worked. You are on part 4.

Read the series →

FAQ

Frequently asked questions

What went into the first GitBasedDocs commit?

58 files and 2,978 lines: a bare Next.js scaffold, 5 research notes, 8 feature specs with a build order, 18 task cards and an AGENTS.md with the commands and board rules. App code started in the next commits.

What happened when a spec turned out to be wrong?

The spec was changed in the same commit as the code fix. Examples: sessions moved from database to JWT because next-auth v4 only allows email and password sign-in with JWT, and folders kept their real names after an index.md renamed the docs folder to Index.

Written by

Abdulkader Safi

Software Engineer

Lead engineer at DSRPT, from Lebanon and based in Kuwait. I write about the tools and bugs from real client work, with the numbers I measured.

About me → GitHub LinkedIn

Need this kind of work done on your project?

Start a project →

Keep reading

More from GitBasedDocs

All 19 entries →
  • The GitBasedDocs search palette showing results for a partial word
    GitBasedDocs

    · part 19 of 19

    Search that quotes whatever you type

    GitBasedDocs search moved from LIKE over every page body to SQLite FTS5 kept in step by triggers. Word prefixes and accent folding came with the tokenizer. The real work was quoting input, choosing a stable order, and a backup detail about rowids.

  • A GitBasedDocs doc page with a Linked from list under the article
    GitBasedDocs

    · part 18 of 19

    Backlinks that use the renderer's own link rules

    GitBasedDocs pages now show which pages link to them, resolve Obsidian aliases, open embedded PDFs and list recent changes. The backlinks reuse the functions that turn links into hrefs, so a page appears under Linked from exactly when its link works.

  • A docker-compose.yml with BACKUP_ENABLED set to true and a folder of dated backup archives
    GitBasedDocs

    · part 17 of 19

    Backups behind one flag in docker compose

    GitBasedDocs now has a Dockerfile, a compose file and backups you turn on with BACKUP_ENABLED. Each archive is a VACUUM INTO copy plus the asset files, and the copy is edited before it is packed so a restored app catches up on its own.