docs: make QA review a standing step in the change checklist

Records the two-environment layout and a numbered checklist every change
follows, with QA review as a required stop rather than a judgement call.
Production is not where a bad deploy should be found, which is what
happened with the categories/tags release.

Production promotion now retags the image QA reviewed rather than
rebuilding, so what ships is exactly what was tested, and the README
carries the full command sequence with a verification gate at each step.

Also records the two gates that have already failed here: confirming a
pushed commit is actually on the branch, and that a schema/code ordering
problem cannot be caught by any local suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 14:28:33 -05:00
co-authored by Claude Opus 5
parent a37270f9bb
commit c546bd5141
2 changed files with 93 additions and 0 deletions
+38
View File
@@ -4,6 +4,23 @@
This is a **self-hosted** application running on a **Synology NAS**, deployed and managed via **Portainer** (Docker Compose stacks through Portainer's UI, not raw `docker compose` CLI on the host). This is a homelab project, not a cloud-hosted SaaS — deployment is manual, there's no CI/CD pipeline to production, and infrastructure decisions are shaped by NAS constraints (Synology's permission model, limited CPU/RAM, no exposed Postgres port, etc.).
There are **two environments**, both on the NAS:
| | Production | QA |
| --- | --- | --- |
| Hostname | `redefined-designs.bermudalamb.synology.me` | `qa-redefined-designs.bermudalamb.synology.me` |
| App / DB container | `redefined-designs-syn` / `redefined-designs-db-syn` | `redefined-designs-qa-syn` / `redefined-designs-qa-db-syn` |
| Database | `redefined` | `redefined_qa` |
| Image tag | `redefined-designs:latest` | `redefined-designs:qa` |
| Host port | 32750 | 32751 |
| Config paths | `/volume1/configs/redefined-designs/` | `/volume1/configs/redefined-designs-qa/` |
| Portainer stack | `redefined-designs` | `redefined-designs-qa` |
| authentik gating | `^/(admin\|api/admin)` only | the whole site (`location /`) |
| Data | Real | Disposable fixtures; no production data is ever copied |
| Restart policy | `unless-stopped` | `"no"` — up only during a review |
QA's stack lives in `docker-compose.qa.yml`; production's is managed in Portainer's UI and is not in this repo. QA runs `DEMO_MODE=true` with no PayPal credentials and no SMTP configuration, so it cannot reach live PayPal or email anyone.
**Repo**: `https://gitea.bermudalamb.synology.me/bermudalamb/redefined-designs` (self-hosted Gitea, also on the NAS)
**Production URL**: `https://redefined-designs.bermudalamb.synology.me`
**Container names**: `redefined-designs-syn` (app), `redefined-designs-db-syn` (Postgres)
@@ -70,6 +87,26 @@ A storefront for one-of-a-kind items (quantity 1 per item — once sold, it's go
- **A failed request must never render as an empty result.** `App.tsx` tracks a `failed` flag separately from `loading`, and shows an error with a Retry rather than falling through to `Empty`. Saying "no items yet" when the server is broken hides an outage and reads to a customer as an empty shop — this is exactly how the 2026-08-17 incident presented. `fetchItems`/`fetchFilterOptions` throw on a non-OK response rather than returning the parsed error body, which would otherwise be set as the item list and crash the grid on `.map`. Covered by `tests/e2e/storefront-errors.spec.ts`.
- **antd import style is inconsistent across the codebase.** New files use deep imports (`import Drawer from 'antd/lib/drawer'`) per the user's standing rule; most older files still use barrel imports from `'antd'`. Don't churn existing files just to convert them.
## Shipping a change — the standing checklist
**Every change goes through QA before production. This is not optional and not a judgement call — treat it as an action item on every single change, and surface it as one without being asked.** Production is not where a bad deploy should be discovered; that already happened once (see the 2026-08-17 incident below) and it is what the QA stack exists to prevent.
| # | Step | Gate before moving on |
| --- | --- | --- |
| 1 | Implement on a branch, verify locally | Unit + integration + e2e pass; `tsc --noEmit` and `npm run build` clean |
| 2 | Commit and push | `git branch -a --contains <sha>` lists the pushed branch |
| 3 | Open a PR and merge to `main` | The merge actually contains the expected commits |
| 4 | **Build and deploy to QA, and review it in a browser** | The change does what it claims, behind authentik |
| 5 | Promote the reviewed image to production | Migrations recorded, data intact, deployed bundle is the new one |
| 6 | Stop the QA stack | — |
Step 5 promotes the **same image** QA reviewed (`docker tag redefined-designs:qa redefined-designs:latest`) rather than rebuilding, so what ships is exactly what was tested. Full commands for steps 4 and 5 are in the README.
Two gates worth stating separately, because both have already gone wrong here:
- **Step 2 is not a formality.** A commit made *after* a branch was pushed was silently left out of PR #24 and ended up dangling, recoverable only through the reflog. `git log` on the branch looked normal because the commit simply was not on it.
- **Step 4 catches the class of bug that only appears when built and deployed.** The categories/tags release passed every local suite and still took production down, because the failure was a schema/code ordering problem that no local run could expose.
## Deployment workflow — read this before touching the NAS
This is where nearly every real time-sink in this project's history happened. The process:
@@ -181,6 +218,7 @@ sudo docker exec -it redefined-designs-db-syn psql -U redefined -d redefined -c
- Local dev/editing happens in **VS Code**, pushed via **PowerShell** `git` — the NAS-side `gitc` workflow is *only* for pulling already-merged code down to deploy, never for authoring changes.
- **Thom does the pushing.** Commit locally and stop; don't `git push` on his behalf.
- **When work comes from a Gitea issue, post every clarifying question and its answer back to that issue as a comment** — including the options considered and why the rejected ones were rejected. The issue is the durable record; decisions made in a chat session are invisible to anyone reading it later. Post each round as the answers come in rather than batching everything to the end.
- **Every change is deployed to QA and reviewed there before it goes to production** — raise it as an action item on every change, unprompted. See the standing checklist above.
- **Design specs live in `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md`** and are committed before implementation starts.
- **`.superpowers/` stays gitignored, but design artifacts inside it must be lifted out before they are lost.** That directory is scratch state belonging to the brainstorming tool and contains a session token, PID files, and absolute local paths — none of which belong in the repo. The mockups it holds *are* worth keeping, so copy them into `docs/superpowers/specs/<date>-<topic>-mockups/` and wrap them as standalone pages (they are served as fragments inside a tool-provided frame, so they need its style tokens and `toggleSelect` helper inlined to open on their own). Keep the rejected options, not just the chosen one — the value is in the comparison.