Deploying the stack first failed with "pull access denied for
redefined-designs, repository does not exist or may require docker
login". The README had the Portainer stack creation ahead of the build,
so no local redefined-designs:qa image existed and Compose fell back to
pulling from Docker Hub, where the repository does not exist.
The build step now comes first in the one-time setup, and the review
workflow says to rebuild before restarting the stack — the stack builds
nothing itself and would otherwise run whatever was last tagged :qa.
pull_policy: never makes the failure legible: a missing local image now
reports itself as missing rather than as a registry authentication
problem. Noted as safe to remove if the NAS's Compose ever rejects the
key, since it only affects the error message.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A disposable stack for reviewing merged-but-undeployed changes online,
running alongside production on the same NAS.
Every value that could collide with production is changed: container
names, host port (32751), volume paths, database name, and image tag.
The Postgres data directory is the critical one — sharing production's
would mean QA writing into production's database files.
QA deliberately differs from production in three ways. DEMO_MODE=true
with no PayPal credentials, so the full cart and checkout path is
exercisable with no route to live PayPal. No SMTP configuration, so a QA
run cannot email anyone; the mailer already degrades gracefully when
unset. And restart: "no", so a NAS reboot does not quietly bring QA back
up and leave it running.
The stack must be named redefined-designs-qa in Portainer: the stack name
becomes the compose project name, and reusing production's would make
compose reconcile the two against each other and remove the production
containers.
README covers the one-time NAS directory setup, the review workflow, and
how to reset QA data.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The wireframes behind the storefront filter layout decisions lived only in
.superpowers/brainstorm/, which is gitignored, so they were lost to anyone
reading the repo.
That directory stays ignored — it also holds a brainstorming-session token,
PID files, and absolute local paths, none of which belong in the repo. The
mockups themselves are design artifacts, so they are copied into the specs
directory and wrapped as standalone pages: the tool serves them as fragments
inside its own frame, so its style tokens and toggleSelect helper are inlined
to make them open in a browser with no server and no network.
Both rejected layouts and the rejected mobile variant are kept alongside the
chosen ones — the comparison is the part worth preserving.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reported from testing: the item form said "Item added" for a save that
never happened.
saveItem and the other admin calls returned res.json() without checking
res.ok, so a 4xx/5xx resolved normally and every caller reported success
for a write the server had rejected. That is worse than failing outright,
because nothing prompts the user to look for the missing row. All admin
calls now throw on a non-OK response, and the handlers report the error,
keep the form open so entered values survive, and only claim success once
the server has accepted the write. Mark sold/available previously did
nothing visible on failure at all.
Categories can now be created from the item form, as tags already could.
Previously a category that did not exist yet meant abandoning a
half-filled form for the Categories tab. New categories are created at
the top level; nesting stays in the Categories tab.
The control lives in its own component: inline, every keystroke
re-rendered the whole Inventory component and rebuilt the category tree,
which visibly jittered the open popup. It sits above the tree rather than
below it, where a long list both hid it and made its position depend on
the list's measured height. The tree no longer expands everything on
open, which does not scale past a screenful; it has search instead.
The app now honours prefers-reduced-motion by disabling antd transitions,
and the e2e suite runs with that preference set.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The storefront showed no inventory after deploying the categories/tags
release. No data was lost: the code queried categories/item_tags/
items.category_id against a database where the migration had not been
run, and that failure was invisible at every layer.
Three changes, each addressing one layer:
Migrations now run at container start, so deployed code cannot be ahead
of the schema and the easily-forgotten manual `docker exec migrate.js
up` step disappears. migrate.js waits for Postgres to accept
connections first, since the NAS brings the DB container up slower than
the app, and still exits non-zero so a bad migration stops the
container rather than serving a half-migrated schema.
Express 4 does not forward a rejected async handler, and no error
middleware was mounted, so a failing query never responded at all. Async
routes are now wrapped and an error middleware guarantees a 500. A hung
request is indistinguishable from an empty result in the UI, which is
how a schema mismatch came to read as "the store has no items".
The storefront now separates "request failed" from "no items" and offers
a retry. fetchItems/fetchFilterOptions throw on a non-OK response rather
than returning the parsed error body, which would have been set as the
item list and crashed the grid on .map.
Also restores the project-context update from 7fb5764, which was left
out of PR #24 and ended up dangling.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>