Skip to content

Issue #34: tech stack audit + forward research

  • Issue: #34
  • Started: 2026-04-28
  • Completed:

Goal

Two artefacts shipped on feat/issue-34:

  1. 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.
  2. 1–3 RFCs at docs/rfc/NNNN-slug.md for 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:

  1. Audit current — read package.json, docker-compose.yml, prisma.config.ts, prisma/schema.prisma, the src/ import graph, and the existing architecture pages. Produce a single reference doc with per-layer tables. Flag deps in package.json that are unused in src/ (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.
  2. 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.
  3. 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.md using _template.md. Each RFC ends with a recommendation and an explicit "needs MASTER signoff" callout, not a decision.
  4. Wire the RFCs into the docs site — register each new RFC in mkdocs.yml nav: and link them from docs/rfc/index.md (the index currently has a placeholder note saying no RFCs exist yet — update it). Register the new reference doc in mkdocs.yml and docs/reference/index.md. Strict-build will fail otherwise.
  5. 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 engines field in package.json, no node version pin, eslint flat-config under eslint-config-next 16.2.4, BullMQ + ioredis listed but zero imports in src/, only @radix-ui/react-slot actually 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 engines pin — flag), npm + package-lock.json lockfile, 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-slot used), class-variance-authority + clsx + tailwind-merge.
    • Backend / API — Next.js route handlers, Prisma 7.8 + @prisma/adapter-pg 7.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 build for docs, bash scripts/build-docs.sh, GitHub Actions docs.yml, Cloudflare Pages auto-deploy.
    • Test — Vitest 4.1, @vitest/ui. 7 spec files in tests/. No E2E layer.
    • Lint / format — ESLint 9 flat config with eslint-config-next 16.2.4. No Prettier configured (Tailwind 4 has its own formatting expectations — flag as gap).
    • Observability — none today. Flag.
  • Each row: package + semver constraint + lockdown (caret floats in deps, dev-deps mostly caret too — flag the few oddballs like react: 19.2.4 exact-pin, next: 16.2.4 exact-pin, eslint-config-next: 16.2.4 exact-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, cmdk for the Cmd+K palette item from REBUILD-V1.md); others are dead weight (zustand was rejected per REBUILD-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 engines pin in package.json is 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 Agent tool-use batch (per superpowers: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 in docs/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.json already includes BullMQ + ioredis, docker-compose.yml already 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_trgm is 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.ts interface 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: under nav: Proposals (RFC): add the three new files in numerical order; under nav: Reference: add tech-stack.md (alphabetical between prisma-models.md and personas.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: add tech-stack.md to 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-verify or 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/search if the diff stays readable.
  • Option B (preferred if total diff > ~500 lines): two commits — docs(reference): add tech stack audit (#34) then docs(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 status clean against feature branch; git log shows 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.yml nav: — 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.