Comments
No comments yet.
No comments yet.
This blog's deploy hardening, security headers, lint cleanup, admin-console split, and atoms / molecules / organisms component structure were completed as a sequence of small changes.
Treating a broad refactor as one rewrite mixes structural work with behavior changes and makes failures difficult to localize.
This article is not a change log. It records six guardrails for preserving observable behavior.
The important metric is not the raw number of tests.
It is whether each test names a contract, whether each gate runs at the correct boundary, and whether a failed step has a defined recovery path.

*Diagram: lock the external behavior first, then repeat a small change and focused verification for one root cause.*
When the admin console was split, logic rewrites and component-boundary changes did not share a commit.
State and handlers moved into use-admin-console.ts; posts, tags, analytics, settings, and operations moved into panels; the editor moved into editor-panel.tsx.
The current admin-console.tsx is a thin orchestrator that obtains the controller and composes those panels.
The sequence is explicit:
Ideas discovered during the move become separate slices.
Keeping the tempting “while I am here” cleanup out of a structural diff makes the transplant reviewable and keeps a regression's search area small.
The most dangerous automation exits as if it succeeded without proving the expected state.
After building and uploading the no-routes Worker configuration, this blog's deploy helper resolves the uploaded version ID and runs wrangler versions deploy at 100%.
It then reads wrangler deployments list --json and proves that the new version is active at 100% traffic.
If that state cannot be verified, the helper throws instead of reporting success.
Human-readable wrangler deploy output is not the only source for the version ID.
If parsing it fails, the helper compares wrangler versions list --json before and after upload and accepts only one unambiguous new version.
An ambiguous result stops deployment.
The same principle applies to Turnstile configuration.
A missing site key produces a bounded configuration error on both server and client rather than leaving a quiet blank state that might look protected.
A static analyzer or AI review generates investigation leads, not verdicts.
For example, SQLite's MAX(likes - 1, 0) may look suspicious if it is mistaken for aggregate MAX().
The SQLite documentation defines max(X,Y,...) with multiple arguments as a scalar function, so the expression is a valid lower bound at zero.
For every report, check the official specification, the current source, and a minimal reproduction before changing production code.
“Fixing” a false positive can create a real bug.
When a report is valid, a failing regression test records the evidence for reviewers and future changes.
Wrapping setState in setTimeout may hide a setState-in-effect finding, but it does not explain the extra update started by a synchronous effect.
This blog models the hydration boundary with useSyncExternalStore: an explicit server snapshot and client snapshot replace a mount-state effect.
That hook is not a universal replacement.
Values derived from props or state should be calculated during render, and React-only state belongs in useState or useReducer.
An external store or browser API needs an immutable snapshot plus a real subscribe / unsubscribe contract.
A stable no-op subscription is appropriate only for a hydration value that will not change again in the same document.
If a lint disable is genuinely required, scope it to one line and put the reason beside it.
A zero-lint result is not a scoring game; it should mean the data source and update path are explainable.
D1 migration history is forward-only.
Editing an applied migration or manually rewriting d1_migrations separates the real schema from Wrangler's history.
Local .down.sql fixtures are not commands to run directly against the remote database.
For a partial failure where later writes must survive, create a new forward migration that repairs schema or data while preserving history.
For a severe incident where the entire database may return to the pre-migration state, stop writes, verify the saved bookmark with another reviewer, and use D1 Time Travel.
After restore, the incident migration is pending again, so migration automation must remain paused until a reviewed recovery plan is proven on a local copy.
The Next.js canary package and eslint-config-next are exact-pinned to the same version for the same reason.
Upgrade them in an isolated slice with a testable version delta and a known way back.
A recovery path is not a dangerous command kept nearby; it is a prior decision about which data must survive and which recovery mechanism fits the incident.
Running every expensive gate manually during each debugging step slows feedback and obscures which root cause failed.
Each slice starts with the focused RED-to-GREEN cycle and stages only its related diff.
The current pre-commit gate runs format, typecheck, the unit test suite, Gitleaks on the staged diff, and Clawpatch status.
The pre-push gate runs verify:push: format, typecheck, unit tests, admin coverage, the ad audit, real-server E2E, the full dependency audit, and Clawpatch CI; it then runs Gitleaks history.
Lint remains a separate zero-error, zero-warning command and is checked before release.
This layering provides fast focused evidence first and one repository-wide proof at the push boundary.
When a structural test fails because a source path or invariant changed, updating that test contract is part of the same root cause as the production move.
The six guardrails are:
A huge final test is not what makes a broad refactor safe.
The useful unit is one root cause, one focused test, one reviewable small commit, followed by the full gate at the release boundary.