Phase 0 Notes: Medusa Spike Findings¶
Date: 2026-04-21 Author: D1
This file captures observations from the Phase 0 spike — running Medusa v2 locally under Docker Compose and exploring the admin UI. These notes inform Phase 1 decisions and flag anything worth remembering.
What surprised us¶
The Medusa starter already ships fully Docker-configured. The cloned medusajs/medusa-starter-default repo included a complete docker-compose.yml, Dockerfile, start.sh, .env, and a medusa-config.ts already patched for Docker (SSL disabled on Postgres, Vite HMR configured for in-container use). Zero scaffolding work was needed — the spike was almost entirely just docker compose up.
Medusa uses Yarn Berry v4 internally, not Yarn v1. The project ships with .yarn/releases/yarn-4.12.0.cjs and .yarnrc.yml, using nodeLinker: node-modules for compatibility. Yarn v1.22.x on the host is irrelevant — the bundled binary handles everything inside the container.
Podman, not Docker, is the container runtime. The host uses Podman with a Docker-compatible CLI shim. Running docker compose up works fine, but the Podman socket (/run/user/1000/podman/podman.sock) must be active first — systemctl --user start podman.socket. This is not automatic on login; it needs to either be enabled (--now) or started manually before each session.
Redis URL warning is a non-issue. The logs showed Medusa falling back to a fake Redis because REDIS_URL set in the environment: block of docker-compose.yml takes precedence over the env_file: entry. The fake Redis is fine for a local spike — but the real Redis container was unused. For Phase 1, verify the environment variable resolution order if Redis session storage matters.
What felt right¶
The product/variant model is the correct foundation for TCG SKUs. Medusa's variant system handles the core dimensions (title, type) and the metadata extension hook is where TCG-specific attributes will live: condition, foil, grading, set, language, serialized vs fungible. Nothing in the base model contradicts the PRD's product shape.
Stock locations map cleanly. Medusa's location model (named locations, stock-by-location, movement APIs) aligns directly with the warehouse / event / store topology in the PRD. No bending required.
The order backbone is solid. Order state machine, line items, fulfillment records, and returns are all present out of the box. The connector layer in Phase 3 will ingest into this structure; the shape is right.
Migrations ran cleanly. First boot ran ~150 migrations across all Medusa modules (product, inventory, pricing, order, fulfillment, auth, etc.) with no errors. The schema is complete and consistent.
What felt wrong¶
Medusa admin is not the operator UI — and it will never be. This is the most important observation from Phase 0. The admin UI at http://localhost:9000/app is a generic commerce administration interface. It shows products, variants, inventory, orders, and stock locations — all correct foundations — but it has no concept of:
- TCG metadata (condition, grading, foil, set, language)
- Serialized vs fungible inventory
- Import batches or DCA costing
- Shopee / Lazada channel attribution
- Event lifecycle or POS workflows
- Finance ledger or profitability reporting
- Multi-tier pricing or partner buyers
This is not a gap in Medusa — it is the correct design. Medusa is the commerce engine, not the operations platform. The day-to-day operator interface is the custom Next.js admin dashboard that Phase 5+ builds. Medusa admin will be used narrowly: product creation, location setup, and occasional order inspection. Operators will not live in it.
The phrase "looks basic and useless" is accurate for our use case — because all the useful parts have not been built yet. That is Phase 1 through Phase 8.
Things to remember for Phase 1¶
- Medusa's value is the API and workflow engine, not the admin. The order state machine, inventory reservation system, fulfillment tracking, and workflow orchestration primitives save months of engineering. They are invisible in the admin UI but central to the architecture.
- TCG metadata: use Module Link, not a cross-DB SKU join. The right approach is a
TCGVariantMetadatamodel insrc/modules/tcg/linked toproductVariantviadefineLink(). This lives in the Medusa database, is queried withfields: ["*", "tcg_variant_metadata.*"], and requires no cross-DB reference. The original plan's "sidecar in App DB linked via SKU" is the more complex option and has been superseded. - Multi-tier pricing (Partner, Streamer, Bulk): Medusa already handles it. Medusa's Pricing module supports customer-group-based pricing rules and quantity tiers natively. Create Medusa Customer Groups per tier; the custom engine only computes DCA-suggested prices and writes them to Medusa's Pricing API.
- Sales Channels are native in Medusa. Create one Medusa Sales Channel per external channel (Shopee, Lazada, Telegram, POS, Manual). Every ingested order carries
sales_channel_id— no App DB duplication needed for channel attribution. - FastAPI vs TypeScript — resolved: TypeScript-only. Medusa's workflow hooks, subscribers, and module container are TypeScript-only and in-process. A FastAPI layer could only call Medusa via HTTP — it could not hook into
createOrderWorkflow.hooks.orderCreatedor use Medusa's transactional rollback. Based on this Phase 0 finding, the project plan was updated to drop the separate FastAPI service: all custom orchestration lives in Medusa TypeScript modules (src/modules/,src/api/,src/subscribers/,src/jobs/) in a single codebase, single deployable, single database. See Project Plan → System Architecture → Architectural principle. - Medusa admin can be extended with custom widgets and UI routes (Medusa v2 supports this). If there is a Phase 1+ case where showing TCG context inside the Medusa admin is genuinely useful, custom UI routes are available. But the default plan is a separate Next.js dashboard.
- Podman socket must be running before any
docker composecommands. Consider addingsystemctl --user enable --now podman.socketto the dev environment setup docs.
Re-validated 2026-05-09 — observation still holds
Re-checked against current Medusa v2.14 docs and the Medusa AI bot during Phase 5 architecture lock-in. The Medusa v2 docs section "What You Can't Customize in the Medusa Admin" (Customize Admin page) explicitly directs heavy-customization cases to a custom dashboard built on the Admin API: "If your use case requires heavy customization of the admin dashboard, you can build a custom admin dashboard using Medusa's Admin API routes." The Phase 0 observation "Medusa admin is not the operator UI — and it will never be" remains correct, and is now formalised in Phase 5 Plan as the Path B architecture decision (separate Next.js app at apps/dashboard/, not Medusa admin extensions).