Porting www.babanuj.com from Next.js 15 (Vercel) to classic Shopify Hydrogen — React Router 7 + Vite — hosted on Vercel, in a new repo, with a parity-gated cutover.
v0.6 — OAuth spike running · two admin actions will need MoeDeciding Hydrogen client vs bespoke port (prior: bespoke, for session continuity) · callback-URI registration + test customer are Moe's to unlock
24 / 44 tasks
Reviewer
Claude (Fable 5)plans, reviews every PR, maintains this board
Executor
Codex · gpt-5.6 @ mediumcodex exec, one task per PR, never merges
Merge authority
Delegated to reviewerMoe's standing instruction Jul 11; local squash as Moe Ghashim + DCO; Moe retains override
Locked decisions
Target
Classic Hydrogen v2026.4
React Router 7 + Vite, Storefront API pinned 2024-10 for parity
Hosting
Vercel, not Oxygen
vercelPreset() + custom server entry; adapter is spike v0.1-t2
Repo
~/babanuj-hydrogen
new sibling repo; production repo untouched
Cutover
Parallel run + parity gate
noindexed staging, automated audit, attribution rehearsal, then DNS
URLs
1:1 path parity
no Shopify/PostHog/Meta config changes needed at cutover
Checkout
checkout.babanuj.com
invariant — cart.checkoutUrl is never rewritten
Cutover gate
DNS does not move until every step is green, in order.
1URL-parity audit (full sitemap + redirects)2Lighthouse: SEO 100, perf within 5 pts3Cart smoke (checkout host asserted)4Attribution rehearsal (real refunded order → PostHog)5Runbook dry-run + rollback proven
Milestones
v0.1
Scaffold & platform risk spikes
5/5 ✓ done
Skeleton builds and deploys to Vercel; the three platform risks — deployment adapter, caching without workerd, edge rewrites — proven before any product code.
t3Attribution rehearsal — real refunded order through PostHogPlanned
t4Cutover runbook + proven rollbackPlanned
t5Post-cutover cleanup after 2-week soakPlanned
Risk register
#
Risk
Severity
Mitigation
R1
No first-party Vercel adapter for classic Hydrogen — the glue is ours
Retired
Spike PASSED Jul 11: live Storefront SSR + streaming proven on a Vercel deployment. One residual: Hydrogen's POST Storefront proxy hits a Node duplex bug — tracked for v0.7 consent decision
R2
Losing tag-based revalidation → stale catalog
Mitigated
TTL matrix proven on CDN (s-maxage 900 + SWR 86400); /api/revalidate path preserved; final tuning in v0.8-t6. Found & fixed: Hydrogen's tracking-cookie default made responses uncacheable
R3
SEO regression at cutover (canonicals, redirects, sitemap)
Attribution chain breaks (PostHog revenue, Meta CAPI)
High
Byte-level HMAC test + full rehearsal with a real refunded order
R5
Hydrogen calver breaking changes quarterly
Medium
Pin exact versions; upgrades are explicit post-cutover tasks
R6
Shopify+Vercel's official direction is the new framework-agnostic Hydrogen (Next.js starter) — this port swims against that current
Medium
Acknowledged at sign-off; toolkit path documented as R1's fallback
Activity log
v0.6 breakdown approved — OAuth spike started. Four tasks: the auth decision spike (Hydrogen's client vs the bespoke 751-line port; reviewer prior is bespoke for session continuity at cutover), auth routes + guard, orders views, profile/address mutations. Two items only Moe can unlock: registering the preview callback URI in Shopify admin, and a test customer for live-login evidence — everything else is machine-verified and proceeds without waiting.
🏁 v0.5 milestone closed and merged to main (6f2e0f8) — the port is a browsable store. PR 025 landed the content routes with precedence proven live, exact FAQ JSON-LD parity on both SEO landings, and even a 404 verified as production parity (no shipping policy exists in the shop). All 17 public pages now serve at production-identical URLs with CDN-cached, cookie-free documents. v0.6 (customer accounts) breakdown drafting. PR #25
PR 024 merged — brand pages at parity. All 8 brand ids live; the facts strip's Lines value derives to the same '24 products' as production; identical grid; the seed-mode empty-grid quirk verified identical in the old repo before acceptance. Task 006 (content routes — the v0.5 closer, with route-precedence tests guarding the CMS catch-all) started. PR #24
PR 023 merged — search at contract-level parity; halfway point reached. /api/search diffed live against production: identical 17-field shape, byte-identical first result, same cache header; /search renders the same 68 products and identical filtered sets; the nav typeahead restored from old-source evidence (debounced suggestions with keyboard selection). Task 005 (brands) started. PR #23
PR 022 merged — card quick-add restored. The hover overlay is line-for-line vs the old card with byte-identical positioning vs production's live DOM; click adds optimistically and opens the drawer, exactly as production does. Clean useOptionalCart() keeps provider-less contexts graceful. Task 004 (search + typeahead) started — the /api/search JSON gets diffed field-for-field against production's live endpoint. PR #22
PR 021 merged — collections at parity; card quick-add fix fired. Same 7 live products/order/prices as production; legacy 308s verified for both mapping types. Accepted the task-specified improvement (valid-empty collections render empty rather than the old seed-fallback quirk). The reviewer escalated the missing card hover quick-add — present in production, unblocked since v0.4 — as a dedicated fix before the remaining grids land. PR #21
PR 020 merged — PDP at parity. Loader order mirrors legacy (stale 308 → lookup → 404), both redirect target types verified live; gallery/facts/tabs/recommendations visually match production; the variant picker's absence matches production's own behavior for the same products — parity preserved, not 'fixed'. Task 003 (collections) started. PR #20
PR 019 merged — the home page is live at full parity. Wholesale section ported with byte-identical mailto CTAs; home documents CDN-cached cookie-free (MISS→HIT); cart hydrates client-side. This PR also banked the port's most important fix: RR7 single-fetch responses were inheriting the public cache policy, publicly caching user cart JSON — caught live, fixed with a headers() export + regression test. Task 002 (PDP) started. PR #19
PR 019 (home) reviewed — one blocker, and a serious catch banked. The runner's live verification found RR7 single-fetch responses inheriting the public cache policy — the CDN was caching a user's cart JSON (privacy-grade bug; fixed with a headers() export + module test). Visual parity reached across hero/categories/carousels/brands/promo/how-it-works after three fix rounds. Reviewer then caught the missing wholesale-catalog section (the /#wholesale anchor target) — port in flight. PR #19
v0.5 breakdown approved — task 001 (home) started. Six route tasks with the Q1 refactor bound to home: cart moves to client hydration so catalog documents ship cookie-free with CDN caching (MISS→HIT re-proof required on the home page itself). Every route PR carries a same-URL visual side-by-side against production.
🏁 v0.4 milestone closed and merged to main (5fe6cf7). The cart works end-to-end on the live store: click-through smoke passed every step (add → drawer → badge → qty ± → remove → checkout.babanuj.com landing), attribution proven via direct Storefront query, and the live smoke caught a real nav wiring bug unit tests missed. Q1 answered: the cart moves to client fetch in v0.5-t1 so catalog documents stay CDN-cacheable. v0.5 (all 17 routes) breakdown drafting. PR #18
PR 017 merged — attribution proven on a live cart. A synthetic PostHog cookie posted to /api/cart/add became a posthog_distinct_id attribute readable via a direct Storefront query — the first link of the orders/paid revenue chain, verified end-to-end. Response contract byte-identical to production. Task 004 (drawer + cart page + full click-through smoke) started — the v0.4 closer. PR #17
PR 016 merged — cart actions live-verified; open question Q1 filed. Full round-trip on the deployment: add (cookie at exact legacy attributes) → update (totals recompute) → remove → 302 to the real checkout.babanuj.com permalink. Reviewer filed Q1 in dev/open-questions.md: cart-in-root-loader forces documents private, conflicting with v0.5's CDN-cached catalog pages — recommendation is client-fetched cart, decided at v0.5 kickoff. Task 003 (/api/cart/add attribution endpoint) started. PR #16
PR 015 merged — cart context live. The codebase's single useOptimistic sits in the provider with a CI guard test scanning the source; the Bag badge reads useCart(); deferred cookie-cart reads never set cookies (deployment-verified). Task 002 (the /cart action: add/update/remove/checkout with userErrors surfaced and checkoutUrl untouched) started. PR #15
v0.4 breakdown approved — task 001 (cart context) started. Four tasks: the single-useOptimistic provider fed by a deferred cookie cart, intent-based /cart actions with checkout-host protection, the /api/cart/add compatibility endpoint with PostHog attribution stamping (JSON contract pinned key-for-key), and the drawer/cart page with a REQUIRED live add-to-cart smoke against the real store.
🏁 v0.3 milestone closed and merged to main (c1e9642). The port now looks like babanuj: exact-parity styling foundation, computed-style-identical primitives, full chrome with the newsletter's checkout-host invariant live-tested, and a DOM-verified portal mobile menu pixel-equivalent to production (with real HTTP 404 error surfaces). PR 014 closed the milestone. v0.4 (cart) breakdown drafting. PR #14
PR 013 merged after the cat-bar fix. The verbatim 9-item production list restored with active-link boundary matching and route-change centering via a keyed useMountEffect child (repo effect-ban honored); new SSR test pins the list and semantics. Task 004 (mobile menu portal, branded 404/error boundary with real HTTP 404 status, deferred Sonner toaster) started — the v0.3 closer. PR #13
PR 013 reviewed — changes requested on the cat-bar. The chrome passed rigorous parity checks (nav computed styles byte-identical, footer text programmatically equal, newsletter posting field-for-field to the literal checkout host with a live 200), but the reviewer caught the cat-bar rendering raw CATEGORIES data where production hardcodes a curated 9-item list (Shop All → /search … Wholesale → /#wholesale). Codex is applying the verbatim port now. PR #13
PR 012 merged — primitives computed-style-identical. Product card compared against the live /search grid property-by-property (radius, border, chip pill, typography, price weight) — all identical; Hydrogen Image emits proper Shopify CDN srcsets; zero new dependencies; Judge.me stars correctly left as the v0.7 seam. Task 003 (chrome + newsletter with the checkout-host invariant) started. PR #12
PR 011 merged — styling foundation at exact parity. The 938-line globals.css diffs EMPTY against production after normalization; all 7 font files hash-identical; browser verification confirmed Bricolage/DM Sans/DM Serif and the full token palette match www.babanuj.com, no FOUT. New /dev/tokens route is the visual-parity harness for the rest of v0.3. Task 002 (shared primitives) started. PR #11
v0.3 breakdown approved — task 001 (styling foundation) started. Four tasks: Tailwind v4 + the 938-line token sheet + self-hosted fonts, shared primitives (photo → Hydrogen Image), the site chrome with the newsletter's checkout-host invariant, and the portal-based mobile menu + branded error surfaces. Every UI PR now requires a visual comparison against production.
🏁 v0.2 milestone closed and merged to main (1997279). The entire data layer is ported and proven: /dev/data ran all 13 live checks OK against the real store (11 client reads, adapter with GID preservation, full cart cycle with the checkout.babanuj.com assertion). Notable catch this task: the vitest glob was silently skipping .tsx tests — fixed, protecting every future component test. v0.3 (design system & chrome) breakdown drafting. PR #10
PR 009 merged — adapter boundary + seed fallback in. shopifyProductToBabanuj and the reverse bridge differ from production only in import paths (zero logic hunks); every env-fallback branch mapped 1:1; the no-network guarantee is enforced by a fetch-stubbed test. Task 005 (/dev/data health route — the milestone's live proof) started. PR #9
PR 008 merged — Storefront client ported, live-tested. All 16 legacy exports carried (cookie ownership moved to callers as designed); live integration tests against the real store passed: hidden-product filtering, menu/policies, and a real cart round-trip preserving the variant GID and the checkout.babanuj.com host. Reviewer independently confirmed cookie-attribute parity against production source. Task 004 (adapter boundary + seed fallback) started. PR #8
PR 007 merged — all GraphQL documents byte-identical. 22 template literals verified equal to production by an independent scanner; 19 exported documents pinned to a SHA-256 fingerprint recomputed from the old repo's own sources — any future query edit fails CI. Task 003 (the 647-line Storefront client, with an explicit cookie seam and env-gated live integration tests incl. the checkout-host assertion) started. PR #7
PR 006 merged — domain data ported with byte-parity. All seven pure modules verified byte-identical to production after quote normalization (data.ts 636 lines, redirects, shipping, contact, constants, type-guards); sole deviation is a one-line Next type substitution in utils.ts. Three-ID rule now enforced by unit tests in CI. Task 002 (GraphQL documents, byte-identical with a SHA-256 fingerprint test) started. PR #6
v0.2 breakdown approved — task 001 (domain data) started. Five task files on feat/v0.2-data-layer match PLAN §9 with criteria intact and the v0.1 learnings baked in (bespoke-client boundary, CACHE_NONE discipline). Task 001 ports the pure domain layer — brands, categories, vendor mappings, redirect data, seed products — value-for-value, with a unit test enforcing the brand/category three-ID rule.
🏁 v0.1 milestone closed and merged to main (d23b909). PR 005 (CI + guardrails) merged after its own CI ran green and the pre-push hook proved it blocks tracked .env files. DoD walked line by line: adapter ✓, caching ✓, edge routing ✓, test floor ✓, invariants preserved, open questions drained. v0.2 (data layer) breakdown now being drafted by Codex on feat/v0.2-data-layer. PR #5
Edge-routing spike passed — PR 004 merged (287f881); task 005 started. PostHog /ingest proxies live (real PostHog responses, SDK served first-party), redirects fire before RR7 routes, apex→www rule staged for cutover. Live-caught defect: Vercel ':path*' misses trailing slashes — /ingest/e/ 404'd until switched to '(.*)' captures. Task 005 (CI workflow, husky pre-push, Vitest + smoke floor) closes v0.1.
Merges delegated; PR 003 merged; task 004 started. Moe: "Merge and don't wait for me" — recorded in the Decisions log. PR 003 squash-merged locally as 95759d1 under Moe Ghashim <mohanadgh@gmail.com> (GitHub API squash stamps the wrong identity, so merges are local from now on). Task 004 is the last platform spike: PostHog /ingest reverse proxy + representative redirects (incl. apex→www rule) coexisting with React Router on Vercel.
Caching spike passed — PR 003 approved. CDN caching proven cookie-free on Vercel (MISS→HIT, identical timestamps). Real find: Hydrogen 2026.4 forwards Shopify tracking cookies onto every document by default, silently defeating CDN caching — disabled (matches production behavior; revisit pinned to v0.7). TTL matrix: catalog 15-min staleness + 24h SWR; cart private/no-store; /api/revalidate stays at its path as a secret-guarded no-op. Awaiting merge. PR #3
PR 002 merged (by Claude, on Moe's instruction) — task 003 started. Caching spike: named Cache-Control policies (catalog TTL/SWR within the ≤15-min staleness budget, cart private/no-store), a cache-demo route to prove MISS→HIT on Vercel's CDN, the TTL matrix mapping the old cache tags, and the /api/revalidate disposition decision.
Vercel adapter spike PASSED — PR 002 approved. A Vercel deployment of the new project server-rendered a live product ("Zaitoune Baklava With Honey 500g") from the Storefront API 2024-10 and streamed the deferred chunk ~228ms later. R1 retired. One finding carried to v0.7: Hydrogen's built-in POST Storefront proxy fails on Node (duplex) — documented with remedies. Awaiting Moe's merge. PR #2
PR 001 merged by Moe — task 002 started. The Vercel adapter spike is running: Codex wires vercelPreset() + createHydrogenContext(), then the runner creates the new babanuj-hydrogen Vercel project (separate from production) and must prove live Storefront SSR + streaming on a preview URL. This is the R1 abort-criterion task.
PR 001 approved. Codex applied both review fixes (branch-naming scheme corrected to the hyphen form in AGENTS.md, node engines widened to ^22 || ^24); re-verified on origin, typecheck + build still green. Mergeable — waiting on Moe to squash-merge, then task 002 (Vercel adapter spike) begins. PR #1
PR 001 opened and reviewed — changes requested. Scaffold verified independently (build, typecheck, dev-server 200; Hydrogen 2026.4.4 + RR 7.16 pinned; no Oxygen config; commits authored Moe Ghashim + DCO). Two findings sent back to Codex: AGENTS.md documented a git-impossible branch scheme (slash under an existing branch ref → amended to hyphen), and node engines should allow ^24. PR #1
v0.1 breakdown approved; task 001 started. Codex produced 5 task files matching PLAN.md §9 with acceptance criteria intact — approved after independent verification (commits authored Moe Ghashim + DCO, protected paths untouched). Repo pushed to GitHub. Notes: executor model is gpt-5.6-terra (gpt-5.6/sol rejected on this ChatGPT plan; Decisions log updated), and Codex's sandbox can't write .git, so the runner performs commits/PRs with Codex-authored content verbatim.
Phase 2 handoff fired. Private repo moeghashim/babanuj-hydrogen created on GitHub; Codex (gpt-5.6 @ medium) invoked to generate the v0.1 task breakdown under dev/milestones/. It stops for review before any code.
Plan signed off by Moe. All defaults locked (bespoke shopifyFetch, API 2024-10, @vercel/og, CDN-TTL caching, Vercel-alias staging, 2-week soak). PLAN.md status → LOCKED.
Plan drafted. Codebase inventoried (17 pages, 8 handlers, 51 components), Hydrogen-on-Vercel research returned conditional GO, 44 tasks across 9 milestones written to PLAN.md. Awaiting Moe's sign-off — nothing handed to Codex yet.