Skip to content
Carlos Lima

Changelog

acceptance-gate is a public portfolio monorepo whose subject is its own pipeline. Every pull request — including the ones that build the repository itself — passes the same gate: lint, typecheck, build, test, format, health, and an acceptance suite written in Gherkin and run by Playwright.

Each release below says what earned its version, corrects the numbers the previous one got wrong, and names what did not land.

v1.3.0

the console gets a manual, generated from what it already tests

Seventeen PRs since v1.2.0 (#409#427), telling one story: the visual-diff console now has an end-user manual, and it cannot drift from what it describes.

What earns the minor

LayerPRs
@gate/manual — three pages (console, report, sample) parsed at build time from the acceptance suite's own .feature files#414
The step renderer, drawn on the board first#415, #416
StepList promoted to a packages/ui molecule, shared by the manual and the console#417
"snapshot" retired from both surfaces' own words#413, #418
Wired to its deployed origin, linked from the console, a stable Thumbnail before images arrive#419, #420, #421
Page views counted on the same terms as the blog and the console#423, #424
The glossary, and why a cached test cannot answer for the confinement guard#409, #412
A UI fix — ds-table's card layout#410
apps/blog gets a robots.txt naming its sitemap#425
report.html retired — the console renders every report itself now#426
Phase 5 board item: the console says what a run is, what A/B mean, and what a verdict means#427

The deployed console's sample mode was already a real demo — PR #242's regression, seen backwards through the differ. What #427 actually closed was three sentences of narration, plus a worked overlap/margin example and a link to one representative Sandcastle-authored PR (#426) in the README, as the evidence the README had promised without pointing at an instance of it.

Ten manifests, not nine

apps/manual is new this cycle. The reason for lockstep is the one 1.0.0 stated and every release since has kept: nothing publishes to npm, every workspace is private, so the version is a coherence marker rather than a distribution fact. Every internal dependency is workspace:*, so pnpm-lock.yaml is unaffected and --frozen-lockfile still resolves.

The index, measured rather than adjusted

PROJECT_INDEX.md was stamped 2026-08-28 and had already drifted, in both directions and by omission:

ClaimCorrected to
apps/manualmissing entirely → 6 files / 69 tests, three published pages
packages/ui 34 / 492, 27 components35 / 512, 28 components (StepList, the sixth molecule)
apps/blog 18 / 37118 / 378 (375 passed, 3 skipped)
packages/visual-diff 11 / 33410 / 327 (report-html.mjs and its test file are gone)
apps/storybook 5 / 125, 18 docs pages5 / 128, 19 docs pages

apps/visual-diff-ui (53/829) and the orchestrator's own suite (39/654) had not drifted. README's own status table carried the same omission — no apps/manual row — corrected alongside it.

One known gap, carried forward rather than hidden

Phase 5's design-board update did not land this cycle: the pencil MCP could not see an open file this session, so designs/acceptance-gate.pen's Board 04 was not updated before the console copy in #427. Reconciling the board with that copy is a follow-up.

Full changelog: https://github.com/climaa/acceptance-gate/compare/v1.2.0...v1.3.0

Release notes for v1.3.0 on GitHub

v1.2.0

the apps report their own errors

Ten PRs since v1.1.0 (#394#407), telling one story: both apps went from swallowing production errors to reporting them.

What earns the minor

LayerPRs
The error boundaries neither app had#400
@gate/logger — silent in production, error() always forwards to a pluggable reporter#402
The boundaries become its first call sites#405
The Bugsink adapter behind @gate/logger/bugsink#406
Page views on the production deployment only#394, #397, #398
The 404 a streamed cache miss cannot set#399
next-env.d.ts untracked#401
The written half — the post, plus Error Tracking / Logging / Web Analytics#407

The tracker is live, not dormant: the DSN lives in Vercel's project settings, and .env.example ships both variables empty on purpose, so local runs, CI and the sandbox stay indifferent to the wiring. The story is written up in The research said GlitchTip. The decision was Bugsink., with the runbook in Error Tracking.

Nine manifests, not eight

packages/logger is new this cycle. The reason for lockstep is the one 1.0.0 stated: nothing publishes to npm, every workspace is private, so the version is a coherence marker rather than a distribution fact. Every internal dependency is workspace:*, so pnpm-lock.yaml is unaffected and --frozen-lockfile still resolves.

The one code change: .fallow/churn.bin untracked

Cutting the tag is what made the case for it. It is fallow's churn cache, rewritten as a side effect of fallow health — which is pnpm health:check, one of the eight required gate jobs. The tracked copy was therefore dirtied by every local gate run and by every agent that ran the gate before staging, and it entered the history as a side effect of PRs about something else. Two of those raced in #405/#406: the first merged, the second went CONFLICTING with auto-merge armed and stranded after the orchestrator had exited, and a binary conflict needs a human.

Nothing reads the committed copy — every run regenerates it from git history — so, exactly as with next-env.d.ts in #401, there was no correct value to commit.

The index, measured rather than adjusted

ClaimCorrected to
apps/blog 18 / 35918 / 371 (3 skipped, one per draft post)
apps/visual-diff-ui 53 / 82553 / 829
apps/storybook 5 / 1215 / 125
42 acceptance scenarios44
a one-scenario local lane2
Storybook 16 docs pages18

EXPECTED_SCENARIOS and EXPECTED_LOCAL_SCENARIOS already said 44 and 2 — the same drift as last release, so both lines now name the constant that has to agree.

Full changelog: https://github.com/climaa/acceptance-gate/compare/v1.1.0...v1.2.0

Release notes for v1.2.0 on GitHub

v1.1.0

the visual-diff console is stable

The console is the product now. v1.0.0 drew a line under the MVP — the design system, the blog, the Storybook, the acceptance suite, the differ and the orchestrator. This tag is about the surface that reviews what the differ produces: apps/visual-diff-ui has taken 65 of the 93 commits since, and it has stopped moving.

Nothing here publishes to npm — every workspace is still private: true — so the version remains a coherence marker rather than a distribution fact. All eight manifests carry 1.1.0, not eight numbers that drift.

What changed

The visual-diff console. 17 feat, 20 fix, 10 refactor. The job system grew a lock, a streaming log, a dismissable current job and guarded mutations; the report grew tier sections, a review loop, an accessibility treatment that leads rather than trails, and a three-up viewer behind a comparison modal. Client-side JSON now parses against the same zod schemas the server answers with (#385), the four routes share one mutation guard and one containment check instead of four apiece (#358, #359), and the compare ask carries a counter so a repeat press from another tab still lands (#389).

The accept tab is gone (#356). It spawned promote, which writes a gitignored __baselines__ that no CI job reads — a review gesture that looked like it accepted a baseline and accepted nothing. Accepting now happens where it always really happened: the accept-baselines workflow a person dispatches, or accept in the pinned container. ModeTabs is two modes, capture and compare, and says why in its own header.

The accept loop closed (#330) — the Docker wrapper, accept-baselines.yml and the nightly determinism job that compare.mjs's STRICT_ENV was built for. That was the last of Wave 4's tail.

packages/ui gained the eight components the console needed — TriStateCheckbox, SegmentedControl, BucketChip, Thumbnail, Dialog with its bottom-sheet variant, Table, and the rest — taking the system to 27 in four tiers, still token-only, still layered by eslint-plugin-boundaries.

By the numbers

  • 150 test files / 2,690 tests — 790 console, 654 orchestrator, 490 ui, 334 visual-diff, 307 blog, 115 storybook
  • 42 Gherkin scenarios blocking in gate.needs, plus a one-scenario local lane that never runs in CI
  • 158 committed visual baselines, up from 106, still captured in a pinned container
  • 72 of the 94 commits since v1.0.0 carry a model co-author trailer — 236 of 284 all-time
  • 94 pull requests merged, 32 issues closed

The tag found its own bug

The first CI run of the release PR went red on a diff of version strings and markdown. GET /api/env reached dockerAvailable(), which blocks on docker info for up to three seconds, inside a unit suite whose timeout is five — latent since the endpoint was written, invisible on any laptop with a daemon running, and three of that block's four tests were equally exposed. lib/docker.ts had already declared the seam that prevents it; the suite had never set it (#396).

It is in these notes rather than quietly squashed into them because it is the argument this repo makes: a gate that only ever runs where the author sits is not a gate.

Known issues

Both are upstream, both are labelled and blocked, and neither has a workaround worth taking:

  • #68 — ESLint 10 is unreachable: eslint-config-next@16 breaks on it, two blockers deep.
  • #47 — TypeScript 7 blocked: Next cannot resolve tsconfig paths under the native compiler.

#264, the orchestrator's zero-diff-PR bug that led v1.0.0's known issues, is closed.

What's next

The project board is still the live, story-pointed roadmap. Wave 5's remaining work is three items, all verified still open at this tag rather than assumed: ds-table's card layout has not taken #302's measured auto-fit grid, the runner suite's confinement guard can still be answered by a turbo cache replay, and .fallow/churn.bin is tracked with nothing ignoring it.

Release notes for v1.1.0 on GitHub

v1.0.0

the MVP is complete

The MVP is complete. Every piece the repo set out to build is in and running against itself: the design system, the blog that consumes it, the Storybook that publishes it, the acceptance suite, the visual differ, and the autonomous orchestrator that merges work through all of them. This tag is the line under that.

Work continues in public — the project board is the live, story-pointed roadmap, and the place to watch what happens next.

First tagged release. Nothing here publishes to npm — every workspace is private: true, so the version is a coherence marker rather than a distribution fact: the repo is the product, and it carries one version across all seven manifests.

What this is

A public portfolio monorepo where every pull request — including the ones that build the repo itself — walks through the same gate:

text
lint · typecheck · build · test · format · health · sandcastle · e2e  (parallel)  →  gate

gate is the only required status check, so a new job joins gate.needs rather than anyone editing branch protection. The ruleset itself is committed as JSON (.github/rulesets/main.json): PR required, squash-only, no bypass actors.

visual-diff is the one deliberate exception. It runs on every PR, posts a sticky report comment, and fails its own job loudly on a real diff — but it never joins gate.needs. A numeric pixel threshold stops being a safety net once change volume is high, and approving a baseline has to cost the same as reviewing a code diff: a person, in the PR.

What's in it

Surface
.sandcastle/The autonomous orchestrator — plans, implements in a Docker sandbox, reviews with a second agent, opens a PR, squash-merges once gate is green. 27 modules, guarded by its own hermetic suite.
blogNext.js 16 App Router + MDX — posts, tags, RSS, sitemap, OG images, Cache Components and Partial Prefetching enabled.
StorybookEvery component rendered in isolation, plus the docs pages that describe the system: System Design, Atomic Design, QA, DevOps, Skills.
packages/uiThe design system — all 19 components of the normative inventory in four tiers, token-only styling, layering enforced by eslint-plugin-boundaries.
packages/visual-diffThe self-built visual-regression CLI: Playwright capture, pixelmatch compare, axe scan per shot, self-contained HTML report. No SaaS.
apps/e2eplaywright-bdd acceptance suite. .feature files are product requirements and are never edited to make a test pass.

By the numbers

  • 86 test files / 1,613 tests — 629 orchestrator, 308 ui, 286 blog, 291 visual-diff, 99 storybook
  • 9 Gherkin scenarios across smoke, blog and axe accessibility, blocking in gate.needs
  • 106 committed visual baselines, captured in a pinned container so a font-hinting difference between a laptop and CI can never read as a UI change
  • 163 of 189 commits on main carry a model co-author trailer
  • 161 merged pull requests, 91 closed issues

Known issues

  • #264 — the orchestrator's merge phase can open a zero-diff PR that still carries Closes #<ID>, which auto-closed a still-blocked issue once. The sandcastle-noop-issues protection exists at classification time but is bypassed once a branch reaches the merge prompt with a real PR. Open, labelled bug, and queued as the first work after this tag.

What's next

The project board carries the roadmap past this tag — Backlog → In Progress → Done, story-pointed, burn-up per wave. It is a window onto the pipeline, never a control surface: the work still arrives as issues, and the issues still walk the gate.

Not in scope yet

TypeScript 7 (#47) and ESLint 10 (#68) are both blocked upstream and tracked as such — Dependabot ignores those majors on purpose rather than opening a PR that cannot pass.

Release notes for v1.0.0 on GitHub