The codebase is in good shape. Lint, unit tests, and worker tests all pass. The architecture is clean (shared API contracts, separated routes / queries / commands), SQL is parameterized everywhere, and idempotency is handled carefully. The main theme of this review: several things are intentionally "development-grade" and are currently enabled in the beta environment. They are fine for a beta behind Cloudflare Access, but each one is a blocker before a public or production launch.
shared/order-contracts.ts is the single source of truth for both client and worker — statuses, limits, and types stay in sync by construction.worker/orders/commands.ts:18): unique idempotency key, duplicate-handling on batch conflict, and safe retry of the initiate call.worker/orders/commands.ts:229): INSERT ... ON CONFLICT ... RETURNING avoids counter races.worker/routes/customer-orders.ts:72) and again before presign submission (worker/uploads.ts:82).worker/identity.ts:19 · worker/routes/system.ts:33
assertLocalIdentityEnabled allows APP_ENV === "beta", and POST /api/local-session
accepts any seeded user id with no credential — anyone can become any staff member by posting a user id, or by simply
setting the copy_shop_local_user cookie themselves (it is not signed).
Action: acceptable while the beta is locked behind Cloudflare Access, but production must replace this with real
authentication (e.g., Access service tokens / OIDC with signed sessions) and flip LOCAL_IDENTITY_ENABLED off.
This is the single biggest pre-launch item.
worker/routes/customer-orders.ts:22, 38, 86
/api/orders/initiate, /api/uploads/:token, and /api/orders/:id/complete have no
rate limiting. Anyone can create unlimited pending orders (each advertising up to 1 GB of files) and anyone who learns
an order id or public id can call complete on it.
Action: before public exposure add rate limiting (Cloudflare WAF rules or a Workers rate-limit binding)
and consider requiring the upload token / idempotency key on complete so the endpoint can't be called with a
guessed public id.
worker/index.ts:11 · scripts/generate-beta-worker.mjs:99, 54
The ctx.access check exists twice: once in worker/index.ts and again in the generated beta
worker. If one copy changes, the other silently drifts. Additionally, the generator embeds every built asset as base64
inside a single TypeScript file — roughly 33% size inflation with no compression — which will eventually hit Worker
size limits as the app grows.
Action: prefer the standard Workers Static Assets setup (like wrangler.jsonc uses) for beta
instead of the code-generated bundle, which removes both the duplication and the inlining. If the generated approach must
stay, extract the Access check into one shared function.
worker/orders/commands.ts:41 · worker/routes/customer-orders.ts:44
Pending orders and their files rows expire after one hour, but nothing ever deletes them. Customers who
abandon checkout will accumulate dead rows in D1 and unreferenced objects in R2 forever.
Action: add a scheduled handler (Cron Trigger) that deletes orders still
pending past their upload expiry, cascading their files rows and R2 objects.
worker/orders/queries.ts:98-125
listOrders loads every matching order and returns total: rows.length. Fine at seed volume,
but a few thousand orders will slow the workspace and bloat every response. The counts query also ignores the active
filters, so tab badges may not match the filtered list the user sees.
Action: add LIMIT/OFFSET (or keyset) pagination and decide whether counts should respect
the department/query filters.
worker/order-validation.ts:94 · worker/orders/commands.ts:223 · src/app/core/format.ts
"America/Chicago" appears in at least five places across worker and client, and shopToday() is
implemented twice (worker and UI). If the shop changes location or DST rules handling needs adjustment, there are many
places to update.
Action: move the timezone constant and shopToday() into shared/order-contracts.ts
(or a new shared/ module) and import from both sides.
src/app/customer/customer-order.component.ts:92
Progress callbacks match files by file.name. If a customer attaches two files with the same name, both
progress bars jump together and either could be marked complete early. (The upload itself is correctly matched by index in
order-api.service.ts:36 — only the progress display is affected.)
Action: key progress by upload.fileId from the initiate response instead of by name.
worker/orders/queries.ts:186) — a customer named 100% Best will match unexpectedly. Escape % and _.readJson parses bodies of unbounded size (worker/http.ts:3) — consider capping the JSON body length before parsing./api/uploads/:token) — acceptable, but they will show in any server/proxy logs; rotate-on-use would tighten this.archive/ and .openai/ can be deleted; .DS_Store and tsconfig.tsbuildinfo are properly gitignored but could be removed locally.db:seed:beta seeds fake local users into the beta D1 (package.json:28) — intentional for now, but make sure production uses a different seed or none.formatBytes(0) returns "0 KB" (src/app/core/format.ts:2) — cosmetically should probably be "0 bytes".| Step | Item | Why this order |
|---|---|---|
| 1 | Pending-order cleanup cron | Small, self-contained, prevents data growth in beta right now. |
| 2 | Pagination + count consistency | Protects the staff workspace as real orders accumulate. |
| 3 | Rate limiting on public endpoints | Needed before the beta link leaves your control. |
| 4 | Replace identity simulator | Biggest change; do it once the data model is stable. |
| 5 | Drop generated beta bundle for standard assets | Removes the drift risk and size ceiling; can pair with step 4. |
| 6 | Timezone consolidation + low items | Refactor polish, any time. |