Issue #34: tech stack audit + forward research¶
- Issue: #34
- Started: 2026-04-28
- Completed: —
Goal¶
Two artefacts shipped on feat/issue-34:
docs/reference/tech-stack.md— per-layer inventory of every dependency, runtime, and tool currently in the repo, with versions, purpose, lockdown status, and a gaps/risk section.- 1–3 RFCs at
docs/rfc/NNNN-slug.mdfor the forward decisions that have real ambiguity: hosting target, background jobs, search backend.
No code changes. No ADRs (those happen after MASTER picks an option). mkdocs build --strict passes. One atomic commit (or a small audit→RFCs series) on the branch — no push, no PR.
Out of scope¶
- Changing any code, config, or
package.json. Audit only flags issues; it does not fix them. - Writing ADRs. The point of this pass is to surface options, not lock in choices.
- Implementing the RFC recommendations. The RFCs propose; MASTER signs off later.
- Image handling RFC and observability RFC. Image handling has weak ambiguity (Cloudflare R2 is the obvious pair for the existing CF Pages docs site and the Cloudflare-edge architecture diagram); observability is too early — there's nothing in the stack yet to instrument. Both can become RFCs later when the hosting decision concretises them.
- Background-job scheduling shape (cron syntax, retry policies). The RFC picks the runtime; specifics belong to the implementation ticket (#18).
- Cost ceilings, scaling targets, or SLOs. These are MASTER inputs. If an RFC needs one of these to make a recommendation, the RFC flags it as an open question rather than guessing.
Approach¶
Foundation ticket → docs only. Pattern documented in Doing your first ticket § Research / foundation tickets:
- Audit current — read
package.json,docker-compose.yml,prisma.config.ts,prisma/schema.prisma, thesrc/import graph, and the existing architecture pages. Produce a single reference doc with per-layer tables. Flag deps inpackage.jsonthat are unused insrc/(already spotted:zustand,recharts,cmdk,ioredis,bullmq,zod, 7 of the 8@radix-ui/*packages). Flag the Next.js 16 + Prisma 7 + React 19 + Tailwind 4 cluster of fresh-and-recent versions as a supply-chain/EOL/breaking-changes risk band. - Pick the RFC topics — the prompt offers four candidates (hosting, jobs, search, images). Pick the three with the most real ambiguity: hosting (#19), background jobs (BullMQ vs alternatives, given we have Redis already), search backend (pairs with #39). Drop image handling — see Out of scope.
- Research forward — for each RFC, dispatch one subagent per candidate option to research in isolation (capabilities, cost shape on Lebanese-market scale, ops burden, lock-in, migration cost). Synthesise each subagent batch into one RFC at
docs/rfc/NNNN-slug.mdusing_template.md. Each RFC ends with a recommendation and an explicit "needs MASTER signoff" callout, not a decision. - Wire the RFCs into the docs site — register each new RFC in
mkdocs.ymlnav:and link them fromdocs/rfc/index.md(the index currently has a placeholder note saying no RFCs exist yet — update it). Register the new reference doc inmkdocs.ymlanddocs/reference/index.md. Strict-build will fail otherwise. - Gate — run
mkdocs build --strict. Fix any broken refs. Commit. Stop.
Steps¶
Order matters — the audit informs which gaps need RFCs, and the nav registrations happen after the files exist so strict build is run once at the end.
- Step 1: Confirm context, lock RFC slate
- Re-read the audit findings already gathered: unused deps list, Next 16 / Prisma 7 / React 19 / Tailwind 4 freshness band, Postgres 17 + Redis 7 in compose, no
enginesfield in package.json, no node version pin, eslint flat-config undereslint-config-next16.2.4, BullMQ + ioredis listed but zero imports insrc/, only@radix-ui/react-slotactually imported (other 7 radix packages dead). - Decide RFC slate: hosting, jobs, search. Drop image handling and observability — see Out of scope.
-
Verification: each gap and each RFC topic above maps to a section of the deliverable.
-
Step 2: Write
docs/reference/tech-stack.md - Frontmatter:
title,description,status: active, tags[reference, stack, deps]. - Sections:
- Runtime — Node 22 (no
enginespin — flag), npm +package-lock.jsonlockfile, TypeScript 5, Prisma 7. - Frontend — Next.js 16.2.4 (App Router, RSC), React 19.2.4, Tailwind 4, lucide-react, motion (Framer's successor lib), Radix primitives (only
react-slotused), class-variance-authority + clsx + tailwind-merge. - Backend / API — Next.js route handlers, Prisma 7.8 +
@prisma/adapter-pg7.8, undici 7. - Database — Postgres 17 (alpine) on host port 5433 (ADR-0001), Prisma migrations with seed via
tsx. - Cache / queue — Redis 7 (alpine), BullMQ + ioredis listed but unused in
src/— queue infra is M2 (#18). - Scraper — cheerio, undici, custom
src/scrapers/{core,sites}/. nanoid for click tokens. - Build / deploy — Next.js build,
mkdocs buildfor docs,bash scripts/build-docs.sh, GitHub Actionsdocs.yml, Cloudflare Pages auto-deploy. - Test — Vitest 4.1,
@vitest/ui. 7 spec files intests/. No E2E layer. - Lint / format — ESLint 9 flat config with
eslint-config-next16.2.4. No Prettier configured (Tailwind 4 has its own formatting expectations — flag as gap). - Observability — none today. Flag.
- Runtime — Node 22 (no
- Each row: package + semver constraint + lockdown (caret floats in deps, dev-deps mostly caret too — flag the few oddballs like
react: 19.2.4exact-pin,next: 16.2.4exact-pin,eslint-config-next: 16.2.4exact-pin while everything else floats). - Gaps subsection: deps in package.json unused in src/ —
zustand,recharts,cmdk,ioredis,bullmq,zod,@radix-ui/{react-checkbox,react-dialog,react-dropdown-menu,react-select,react-slider,react-tabs,react-tooltip}. Some are intentional pre-installs for upcoming work (BullMQ + ioredis for #18,cmdkfor the Cmd+K palette item fromREBUILD-V1.md); others are dead weight (zustandwas rejected perREBUILD-V1.md— URL-driven state instead). Note each one's status (intentional vs dead) and recommend pruning the dead ones. - Cross-cutting risks: Next 16 + Prisma 7 + React 19 + Tailwind 4 are all very recent — a single supply-chain incident or breaking patch in any one would cascade. Mitigation: lockfile is committed; CI doesn't yet auto-bump. EOL: Postgres 17 supported through 2029, Redis 7 through 2027, Node 22 LTS through Apr 2027. No
enginespin inpackage.jsonis a real risk — a contributor on Node 18 would silently get a different runtime. -
Verification:
mkdocs serve(or just file inspection) — every section is present, every flagged gap has a one-line rationale, no broken cross-references to ADRs/issues. -
Step 3: Dispatch parallel research subagents for the three RFCs
- One Agent per option per RFC, all in a single
Agenttool-use batch (persuperpowers:dispatching-parallel-agents). - Hosting RFC subagents (3): Vercel + Neon + Upstash; Cloudflare Pages + Hyperdrive + Workers KV / D1 mismatch (call out that CF Pages runs Pages Functions on Workers — Next.js 16 RSC/server-actions support is the load-bearing question); Hetzner VPS + self-managed Postgres + self-managed Redis. Skip bits.lb beta (insufficient public info; flag as "needs MASTER input" in the RFC).
- Background jobs RFC subagents (3): BullMQ on the existing Redis; pg-boss on the existing Postgres (no new infra); GitHub Actions scheduled workflows +
npm run scrape(no queue at all — fits the "24 jobs/day" scale called out indocs/architecture/deployment.md). - Search RFC subagents (3): Postgres FTS +
pg_trgm(no new infra); Meilisearch (self-hosted Docker); Typesense (self-hosted or Typesense Cloud). - Each subagent prompt: research the option for a Lebanese-market-scale product (low-thousands of products, low-thousands of monthly users in the foreseeable horizon), focused on (a) capabilities for our use, (b) cost shape, © ops burden, (d) lock-in / migration cost, (e) how the option meshes with the rest of our stack (Postgres 17, Next.js 16, Cloudflare Pages docs already live). Return a tight markdown brief — facts, not narrative.
-
Verification: nine subagent reports collected. Each names the option, capabilities, cost shape, ops burden, lock-in.
-
Step 4: Write
docs/rfc/0001-hosting-target.md - Frontmatter using
_template.md:title: RFC-0001 — Hosting target for the web app and workers,status: new, tags[rfc, infra, deployment]. - Body sections from the template: Summary, Motivation (#19 is blocked, deployment.md is a stub), Proposal (recommend the synthesised option), Trade-offs, Alternatives (the two non-recommended subagent options + bits.lb-as-unknown), Open questions (cost ceiling? ops appetite? Lebanese-host political weight?), Implementation plan (linked back to #19), Out of scope.
- Status set to
Draft. Top of body has explicit "Decision: needs MASTER signoff" callout. -
Verification: file passes mkdocs strict (every link resolves), template structure preserved.
-
Step 5: Write
docs/rfc/0002-background-jobs.md title: RFC-0002 — Background job runtime, tags[rfc, infra, scrapers].- Motivation:
package.jsonalready includes BullMQ + ioredis,docker-compose.ymlalready runs Redis, but #18 hasn't shipped and no code imports either. Decide what we actually want before wiring it up. - Proposal: synthesise the three subagent briefs into one recommendation. Likely shape (validate with subagent output): pg-boss is the best fit at this scale (no new infra, transactionally consistent with the data we operate on, scrape volume is ~24 jobs/day per
docs/architecture/deployment.md); BullMQ is correct only if we expect concurrency / fanout we don't currently have; GitHub Actions cron is correct only if we accept losing observability of in-flight work and the 6-hour outer time budget. - Trade-offs: dropping BullMQ means dropping ioredis from deps too (audit gap closed).
- Open questions: do we need at-least-once or exactly-once semantics for scrape jobs? (At-least-once is fine — listings upsert by
(retailerId, url).) -
Verification: strict build clean.
-
Step 6: Write
docs/rfc/0003-search-backend.md title: RFC-0003 — Search backend for product browse and build picker, tags[rfc, search, frontend].- Motivation: pairs with #39. Today browse is a category filter — there is no search. Need to decide where search lives before we add it, because retrofitting is expensive.
- Proposal: synthesise the three subagent briefs. Likely shape (validate): Postgres FTS +
pg_trgmis the M1 answer at our scale (~thousands of products, brand+model+specs are short text, fuzzy match on Arabic + English needed). Meilisearch / Typesense earn their keep above ~50k records or when typo-tolerance + facets become user-visible features. - Trade-offs: FTS in Postgres is harder to expose via RSC streaming patterns later if we want autocomplete; the migration to Meili/Typesense is non-trivial but bounded if we hide the search behind a
lib/search.tsinterface today. - Open questions: is Arabic SERP a hard requirement at M1 (tied to #35 personas)? If yes, FTS needs
unaccent+ a custom dictionary; Meili/Typesense have it built in. -
Verification: strict build clean.
-
Step 7: Wire the three RFCs and the new reference doc into nav
mkdocs.yml: undernav: Proposals (RFC):add the three new files in numerical order; undernav: Reference:addtech-stack.md(alphabetical betweenprisma-models.mdandpersonas.md— index pages already exist).docs/rfc/index.md: replace the "No RFCs yet" placeholder with a numbered table listing the three.docs/reference/index.md: addtech-stack.mdto the "Available" table; remove it from the "To write next" list (it was not previously listed; just verify).-
Verification: each RFC and the reference doc reachable from the rendered site.
-
Step 8: Run the gate
mkdocs build --strict. Expected: zero warnings, zero broken refs, clean exit.- If any ref breaks: fix in place, re-run. Do not
--no-verifyor relax--strict. -
Verification: command exits 0.
-
Step 9: Commit (one or two tightly-scoped commits)
- Option A: one atomic commit
docs(reference): tech stack audit + RFCs for hosting/jobs/searchif the diff stays readable. - Option B (preferred if total diff > ~500 lines): two commits —
docs(reference): add tech stack audit (#34)thendocs(rfc): add hosting / jobs / search RFCs (#34). Plan + index + nav changes split across the two commits in whichever lands them with the most coherent diff. - Conventional commit. No
Closes #34(foundation work needs MASTER review of the RFCs before close). -
Verification:
git statusclean against feature branch;git logshows the new commits at HEAD; no push, no PR. -
Step 10: Stop and report
- Report shape: commit SHA(s); one paragraph on stack state + top 3 risks/decisions; one-line summary per RFC; explicit deferrals (image handling, observability, ADR work); reviewer-attention items (the open questions inside each RFC).
Risks¶
| Risk | Likelihood | Mitigation |
|---|---|---|
| Subagent reports diverge in depth, leaving the RFC unbalanced | med | Synthesise from facts only; if one option's brief is thin, name the gap inside the RFC's Open Questions rather than papering over it |
mkdocs build --strict flags a cross-link in nav or RFC index that I missed |
med | Run the build at Step 8 specifically; fix in-place is fast; don't relax strict |
| Audit reveals an actually-EOL or unmaintained dep (the prompt's stop-and-ask trigger) | low | None of Next 16, Prisma 7, React 19, Tailwind 4, BullMQ 5, undici 7, cheerio 1.2 are EOL — they're the opposite, all very fresh. If one turns out abandoned during research, stop and ask MASTER |
| Picking 3 RFCs is too many for one foundation pass; the depth suffers | low | Each RFC is scoped to recommendation + alternatives + open questions, not full implementation. The implementation plans land on the corresponding tickets (#19, #18, #39) |
gh issue view 34 is unauthenticated on this box, so I am not reading the issue body verbatim |
low | The prompt itself enumerates scope and acceptance criteria; the issue body would only add framing. Note in the report as an uncertainty for reviewer attention |
Tests¶
Foundation ticket — no unit tests. The gate is mkdocs build --strict. Strict build is the test.
Doc updates¶
-
docs/reference/tech-stack.md(new) -
docs/rfc/0001-hosting-target.md(new) -
docs/rfc/0002-background-jobs.md(new) -
docs/rfc/0003-search-backend.md(new) -
docs/rfc/index.md(replace placeholder list) -
docs/reference/index.md(add tech-stack to Available) -
mkdocs.ymlnav:— register new files - ADRs: none in this pass — RFCs only, per ticket constraint
Rollback¶
git reset --hard origin/master on the feature branch (or just git checkout master). All work is docs-only and isolated to feat/issue-34. No prod side-effects.