Technical changelog
Same changes, with file paths, environment variables and the traps found along the way.
The engineering view of What's new. Newest first.
September 2026
Admin login rebranded
apps/backend/src/admin/widgets/login-branding.tsx
Medusa 2.19 has no supported way to replace the login route — it is compiled
into @medusajs/dashboard and the copy comes from that package's i18n bundle,
not from config. What it does expose is a login.before injection zone, which
renders inside the login column between the heading and the form.
So the widget supplies AUTOXSPARE branding and hides Medusa's, rather than replacing a page. It finds Medusa's markup by walking up from its own ref to the login shell and hiding every sibling of the form wrapper, instead of matching Tailwind class names that change between releases.
The screen is pinned to light. That is not taste: the wordmark is black on
transparent and vanishes on the dark theme, and the admin follows the OS by
default. Colours are scoped to the login shell rather than written to
medusa_admin_theme, so the rest of the admin keeps the user's own choice.
Admin served same-origin
MEDUSA_BACKEND_URL=https://api.autoxspare.com
The admin bundle had backend-production-86af.up.railway.app baked in while
being served from api.autoxspare.com. CORS happened to permit it, so nothing
was visibly broken, but the value is inlined at build time — a redeploy is
required for any change to take effect.
Curlec live
apps/backend/src/modules/curlec/
- Amounts are sent in sen (
× 100). Curlec settles MYR only and the provider refuses any other currency outright. - Signature is
HMAC-SHA256overorder_id|payment_id. - FPX requires
callback_urlwithredirect: true; the return lands onapps/storefront/src/app/[countryCode]/(checkout)/checkout/curlec-return/route.ts, which accepts both POST and GET and answers with a 303. - Mode is chosen by
CURLEC_MODE(test|live), which selects betweenCURLEC_TEST_KEY_IDandCURLEC_LIVE_KEY_IDinmedusa-config.ts.
MYR enforced at the store level
apps/backend/src/scripts/enforce-myr.ts (dry run by default, MYR_APPLY=1 to write)
The seed left supported_currencies as EUR + USD. The admin offered those when
creating price lists and promotions, so a price entered in EUR would save and
then never apply to anything.
Vehicle fitment
Vehicle tags were normalised (apps/backend/src/scripts/normalise-vehicles.ts)
and the storefront gained a sticky garage in src/modules/garage/, persisted to
localStorage. Category pages fall back to unfiltered results when a vehicle
filter would return nothing, rather than showing an empty grid.
updateProductTags(id, { metadata }) does not work — the DTO only carries
value. Use updateProductTagsWorkflow from @medusajs/core-flows.
Gateway mode widget
apps/backend/src/admin/widgets/gateway-mode.tsx +
apps/backend/src/api/admin/gateway-mode/route.ts
Read-only by design. A control that flipped live and test keys would be one stray click from either halting every payment or putting a real customer through a test gateway, and neither failure surfaces until settlements go missing. The endpoint returns the mode word only — never a key id, never a secret.
August 2026
Shopify migration
Products, variants, images and stock status imported. Weights for 4,404
variants via apps/backend/src/scripts/import-weights.ts.
product.thumbnail is stored separately from product.images. Updating images
alone leaves the storefront showing the old host. Clear it with '', not null.
Shipping setup
apps/backend/src/scripts/setup-shipping-my.ts — creates the Jitra, Kedah stock
location, service zone covering Malaysia, shipping profile and option.
Shipping options come back empty even with a valid cart and address unless the
sales channel is linked to the stock location with
linkSalesChannelsToStockLocationWorkflow. The script is step-wise idempotent
because the first version bailed out early and left the setup half-built.
Deployment traps
Collected because each one cost a failed build or a wrong diagnosis:
| Trap | Detail |
|---|---|
medusa develop skips typecheck | Backend type errors only fail the Railway build. Run npm run build in apps/backend first. |
| Vercel needs a team scope | Bare vercel --prod returns "Not authorized"; pass --scope vigor-digital-solution. |
| Vercel deploys from repo root only | Running vercel inside apps/storefront creates a junk project. |
vercel env add defaults to sensitive | A sensitive NEXT_PUBLIC_* is unreadable at build time and silently uses the fallback. |
| Railway CLI token | ~/.railway/config.json has both token (null) and accessToken. Reading token yields Not Authorized, which looks like a permissions problem. |
Custom domain targetPort | Can be created as null, in which case Railway's edge returns Application not found and the ACME challenge can never validate. Compare against a working domain. |
| Admin renders blank | npm hoists React 19 over the admin's React 18; fixed by a Vite alias in medusa-config.ts. |
Keeping these docs current
These docs are part of the work, not a write-up afterwards. When a change lands:
- Add a plain-language entry to What's new — what someone running the shop would notice, not what the code does.
- Add the engineering detail here, including any trap worth not rediscovering.
- Update the API reference if a route, payload or environment variable changed.
- Update the user guide if a screen or workflow moved.
A change that alters behaviour but ships no docs is unfinished. If a fix later turns out to be wrong, correct the original entry rather than appending a contradiction — a changelog that argues with itself is worse than none.