Skip to content

Migrate Remix Store to Remix 3 - #251

Draft
brookslybrand wants to merge 61 commits into
mainfrom
v3
Draft

Migrate Remix Store to Remix 3#251
brookslybrand wants to merge 61 commits into
mainfrom
v3

Conversation

@brookslybrand

Copy link
Copy Markdown
Contributor

Purpose

Long-lived integration PR for the Remix 3 migration. Feature and platform PRs continue to target v3; this PR keeps the complete migration visible against production main and gives Oxygen a stable pull request for preview deployments.

Current state

  • Remix 3 and framework-neutral Hydrogen platform skeleton
  • Native Node + Remix Assets development and Fly runtime
  • Vite-based Oxygen adapter
  • Request-scoped Storefront client and streaming SSR
  • Portable unit and Playwright acceptance baseline
  • Continuous Fly staging deployments at https://remix-store.fly.dev

Review and merge policy

Do not merge until storefront parity and cutover checks are complete. Review migration increments through their smaller PRs into v3; this draft updates automatically as those PRs merge.

See REMIX_STORE_MIGRATION_PLAN.md and REMIX_STORE_PARITY_PLAN.md for scope, sequencing, and acceptance criteria.

* Add the native Remix Assets runtime

* Use the standard Remix demo port

* Simplify dual-runtime platform setup

* Remove server filename convention
* Set up continuous Fly deployment

* Use blue-green Fly deployments

* Deploy Fly staging in Dallas

* Finalize the Fly deployment workflow
@shopify

shopify Bot commented Aug 10, 2026

Copy link
Copy Markdown

Oxygen deployed a preview of your v3 branch. Details:

Storefront Status Preview link Deployment details Last update (UTC)
remix-store ✅ Successful (Logs) Preview deployment Inspect deployment August 28, 2026 2:44 PM

Learn more about Hydrogen's GitHub integration.

* Remove stale dependency overrides

* Use Oxlint and Oxfmt

* Narrow release-age exceptions
* Port the storefront shell

* Install Chromium for browser tests
* Port collections catalog and public assets

* Polish collections implementation
* Port product detail pages

* Fix product details after home navigation
* Port cart to Remix 3

* Align cart tests with v3 practices

* Remove unused cart migration code

* Match pending checkout state
* Complete product page parity

* Prevent cold navigation layout shift

* Remove cold navigation workaround

* Render initial product gallery opacity

* Use Remix destination SSR fix

* Return to Remix main preview

* Remove redundant storefront type aliases
* Port Shopify policy and contact pages

* Match policy content spacing and typography

* Preserve policy content structure

* Match policy list and paragraph rhythm

* Fix policy content stacking
* Match migrated storefront styles to production

* Match product description typography

* Add cached Tailwind preflight reset
* Add Shopify compatibility routes

* Fix buyer IP and discount compatibility
Repin the Remix preview to 54982346, the current head of
preview/pr-11688 ("installable build from 0d4a158"), so the app tracks
the latest build of the boundary reconciliation PR.

Revert the Dockerfile pnpm cache mount to `id=pnpm`. The poisoned store
entries were keyed by the old db77511 tarball URL, so the new SHA cannot
collide with them and the id bump is no longer doing anything.

Drop `onlyBuiltDependencies: [remix]`. Global `ignoreScripts: true`
already disables lifecycle scripts, so the allowlist was inert, and it
expressed the opposite of the intent documented directly above it: the
preview ships prebuilt dist and must not be built.

Verified the committed lockfile installs correctly from a cold pnpm
store (`--prod --frozen-lockfile`), yielding remix@3.0.0-beta.5 with
dist, rather than the monorepo root.
pnpm writes one of two shapes for a git dependency that targets a
subdirectory, and which one you get depends on whether the tarball was
already in the local store when the lockfile was written:

  resolution: {gitHosted: true, path: packages/remix, tarball: ...}
  resolution: {gitHosted: true, integrity: sha512-..., tarball: ...}

The second shape drops the subdirectory, so pnpm unpacks the repository
root. For Remix that is remix-the-web@undefined, which ships no dist/,
and it took out install, typecheck, test, the Oxygen build, and the Fly
image in one go. A lockfile regenerated on a cold store reintroduces it
silently, and the working tree still looks fine on a warm machine, so
this is easy to merge by accident.

Check the committed lockfile instead of relying on that. Only entries
whose key requests a subdirectory are considered, so ordinary git
dependencies that legitimately carry an integrity hash still pass.

Wired into `lint` so the existing CI job covers it without a workflow
change. Verified against the real broken lockfile from ea111b3 (all 47
entries flagged) and against main plus a synthetic no-subpath git
dependency (no false positives).
Remove lockfile lint workaround and use the Remix preview PR branch,
while pinning pnpm to 10.33.3.
Match shop.remix.run's image loading experience: show a blurred
low-res (?width=32) preview while the full image loads, then fade
the preview out as the full image transitions from blurred to sharp.

- ProductImageFrame renders a data-preview-image <img> (blur(20px),
  object-fit: cover, scale(1.1)) behind the ShopifyImage
- ref sets data-loaded on load/error; CSS fades the preview opacity
  to 0 and transitions the main image filter to blur(0) over 750ms
- prefers-reduced-motion guards disable the transitions
- test asserts the full image via user-facing alt text rather than
  the internal data-preview-image selector
* Add consent-aware storefront analytics

* Fix pinned Remix installation in Docker

* Isolate the pinned Remix Docker cache

* Stabilize product navigation acceptance test

* Clean up analytics: key by product.id, publish from queueTask, dedupe cart helpers

- Key ProductViewed/CollectionViewed by GID instead of a redundant
  productHandle prop; add route-level keys so product/collection switches
  remount the tracker
- Move ProductViewed/CollectionViewed publishes into handle.queueTask
  instead of publishing during render
- Lift the duplicated pending-check and snapshot-apply blocks into shared
  hasPendingCartWork and createSnapshotApplier helpers in analytics.tsx;
  delete the three local copies in cart.tsx
- Reuse publishCartViewedWhenSettled for the cart page view, removing the
  every-render queueTask and local publishCurrentCartView identity dedupe
- Unexport toAnalyticsCart; drop prevCart from CART_VIEWED payload
- Share the queueTask-flushing createTestComponent across both test files

* Add dev-only console logger for storefront analytics events

Subscribe to every AnalyticsEvent in development and log payloads with
console.debug. Raw subscribers see events before consent gating, so
events show up even before the visitor accepts the consent banner.

* Fix dev analytics logger: gate on import.meta.hot, not import.meta.env

import.meta.env is undefined in the browser entry with this fullstack
Vite setup. import.meta.hot is already used for HMR in this file and is
only present during Vite dev, so it's the correct dev-only gate.

* Poll for analytics bus in dev logger

The analytics bus is created by an inline ShopifyScripts script that
loads asynchronously. The previous one-shot getAnalytics() call raced
with the bus setup and silently no-op'd. Retry every 200ms until the
bus is available, then log a confirmation message.

* Remove dev analytics console logger
* Port store-wide sale experience

* Simplify the store-wide sale port

Keep the sale metafield on the existing navigation query, label only
line-level automatic discounts, and drop the extra GraphQL toolchain,
parsers, and construction-focused tests.
* Add secure storefront subscriptions

* Match sold-out notify form to production layout

Use the same 2/3 + 1/3 email and Notify me row as shop.remix.run,
hide Shop Pay on unavailable variants, and keep newsletter consent
on the dedicated subscribe page.

* Match product typography and add-to-cart pending state

Bold the product title, use production dropdown type, show a
centered green check while add-to-cart is in flight, and keep
discount-code warnings off the product form.

* Match the subscribe page to production layout

Use the compact 2/3 + 1/3 email row, production copy, hidden
consent, and a green check success state. Keep Remix mime on
the HTTPS tarball so CI can install without SSH.

* Remove unused product add-to-cart pending style

* Keep Shop Pay enabled while adding to cart

* Scope product dropdown font-size assertion to the product form

* Drop the e2e dropdown font-size check

The fixture product is Default Title only, so the size menu never
renders. Browser tests already cover the real option menu.

* Drop pixel-layout e2e assertions

Keep journeys and user-visible sale/cart contracts. Do not lock
font sizes, paddings, or button heights in Playwright.
* Add US and Canada market routing

* Drop leftover market nits

Remove unused marketFromPathname, the deleted sale-query test
fallback, and the stale single-market ShopifyScripts comment.
* Add seasonal storefront snow

* Hide snow until the canvas is ready

Size the overlay from the visible field, skip the dense static
blizzard on first paint, and keep static dots for reduced motion.
* Add vendored anti-slop lint rules

* refactor: validate boundaries with Remix data schemas

* fix: resolve anti-slop lint violations

* chore: remove temporary anti-slop tooling

* chore: restore lint tooling baseline
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant