Merge pull request 'docs: make QA review a standing step in the change checklist' (#29) from fix/logout-customer-reset into main
Reviewed-on: #29
This commit was merged in pull request #29.
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -158,12 +158,67 @@ Gitea Actions runs two workflows on every push to `main` and on pull requests:
|
||||
- Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) syntax: `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `ci:`, `refactor:`, etc.
|
||||
- Branches are automatically deleted after a pull request is merged
|
||||
|
||||
## Shipping a change
|
||||
|
||||
Every change follows the same path. **QA is a required stop, not an optional one** — production is not the place to discover that a deploy is wrong.
|
||||
|
||||
| # | Step | Gate before moving on |
|
||||
| --- | --- | --- |
|
||||
| 1 | Implement on a branch, verify locally | Unit, integration, and e2e suites pass; `tsc --noEmit` and `npm run build` clean |
|
||||
| 2 | Commit and push | `git branch -a --contains <sha>` lists the pushed branch — a commit made after a push is silently left behind otherwise |
|
||||
| 3 | Open a PR and merge to `main` | The merge commit contains the expected commits |
|
||||
| 4 | **Deploy to QA and review it** | The change behaves as intended in a browser, behind authentik |
|
||||
| 5 | Promote the reviewed image to production | Migrations recorded, data intact, deployed bundle is the new one |
|
||||
| 6 | Stop the QA stack | — |
|
||||
|
||||
Steps 4 and 5 are detailed under [QA environment](#qa-environment) and [Production deployment](#production-deployment). Step 5 promotes the *same image* that was reviewed in QA, so what ships is what was tested.
|
||||
|
||||
## Production deployment
|
||||
|
||||
Production runs as a single Docker image (multi-stage build — the frontend is built to static files and served directly by the backend), deployed via Portainer behind Nginx Proxy Manager, with authentik forward-auth gating `/admin`. That infrastructure is homelab-specific and documented separately outside this repo.
|
||||
|
||||
The container applies pending migrations before starting the server, so deployed code can never be ahead of the database schema. A failed migration stops the container rather than letting it serve against a schema it doesn't match — check `docker logs` on the app container if it doesn't come up.
|
||||
|
||||
### Promoting a reviewed change to production
|
||||
|
||||
Only after the change has been reviewed in QA.
|
||||
|
||||
```bash
|
||||
# 1. Back up first — the container migrates the schema on its own.
|
||||
sudo docker exec -t redefined-designs-db-syn pg_dump -U redefined -d redefined \
|
||||
> /volume1/configs/redefined-designs/backup-$(date +%Y%m%d-%H%M).sql
|
||||
ls -lh /volume1/configs/redefined-designs/backup-*.sql # size must be plausible
|
||||
|
||||
# 2. Promote the exact image QA reviewed, rather than rebuilding a second time.
|
||||
sudo docker tag redefined-designs:qa redefined-designs:latest
|
||||
|
||||
# 3. Verify the image before touching the running container.
|
||||
sudo docker inspect --format '{{.Config.Cmd}}' redefined-designs:latest
|
||||
# must print: [sh -c node migrate.js up && node dist/server.js]
|
||||
|
||||
# 4. Recreate the container — restarting it would keep the old image.
|
||||
sudo docker stop redefined-designs-syn
|
||||
sudo docker rm redefined-designs-syn
|
||||
# redeploy the stack in Portainer
|
||||
|
||||
# 5. Verify, in this order.
|
||||
sudo docker logs redefined-designs-syn | head -30
|
||||
# migration output appears BEFORE "listening on 3000",
|
||||
# and "listening on 3000" appears exactly once (repeats = crash loop)
|
||||
|
||||
sudo docker exec -it redefined-designs-db-syn psql -U redefined -d redefined \
|
||||
-c "SELECT name, run_on FROM pgmigrations ORDER BY run_on;"
|
||||
sudo docker exec -it redefined-designs-db-syn psql -U redefined -d redefined \
|
||||
-c "SELECT count(*) FROM items;"
|
||||
|
||||
# 6. Confirm the deployed artifact, not just the source.
|
||||
sudo docker exec redefined-designs-syn sh -c "grep -l 'must have all' /app/public/assets/*.js"
|
||||
```
|
||||
|
||||
Then load the storefront in a private window — aggressive bundle caching has produced false "still broken" reports on this project even after a correct deploy.
|
||||
|
||||
**Rollback:** migrations here have been additive, so the previous image still runs against the migrated schema — retag the old image and redeploy. Restoring the SQL dump is a last resort, not a first move.
|
||||
|
||||
## QA environment
|
||||
|
||||
`docker-compose.qa.yml` defines a disposable QA stack for reviewing merged-but-undeployed changes online. It runs alongside production on the same NAS with its own containers, database, volumes, and host port, and is started only when a review is needed.
|
||||
|
||||
Reference in New Issue
Block a user