Retargets the build and the playbook off rootful Docker on monoco, following the
conventions already in place on emily's other services: Quadlet units in
~/.config/containers/systemd, per-service data in ~/podman-data, and a shared
Caddy that every service registers itself with — a network drop-in plus a
marked block in its Caddyfile. Nothing is published on the host; Caddy reaches
the containers by name over mozimo_net.
- Makefile: podman build + push instead of docker buildx, PLATFORMS=linux/arm64
(both hosts are aarch64). podman has no built-in QEMU the way buildx's builder
image does, so a foreign-platform failure now points at qemu-user-static.
- deploy.sh: pass COMMIT= to make, which the Makefile actually reads; it was
passing TAG=, so the tag silently came from `git rev-parse` instead.
- ansible: hosts emily, become false, XDG_RUNTIME_DIR for rootless podman and
systemctl --user. Docker modules become Quadlet templates with user-service
handlers, and the network role now ships mozimo.network plus the Caddy drop-in
rather than calling docker_network.
- UserNS=keep-id maps the container's uid 1000 (node) and root (vince) onto the
host user, so PocketBase and Vince write host-owned files instead of
subuid-owned ones; the play asserts the pinned uid still matches the host.
- ansible/README.md: the unit layout, the service-registration convention, and
the monoco -> emily migration (PocketBase 23 MB + Vince 119 MB, DNS cutover,
rollback).
Verified by a check-mode rehearsal against emily (13 ok / 8 changed / 0 failed)
and a disposable Quadlet unit on that host proving the uid mapping, the
EnvironmentFile and the isolated network. The real build and cutover have not
been run.
The AFK half of the wayfinder ticket "Seed the dev store from the live
catalogue". Products, metafields and images still come from the CSV export —
the CSV re-hosts images for free — but collection membership cannot: the
Collection column is import-only, takes one collection per product, and this
catalogue runs 1.6 memberships per product across 16 collections.
- scripts/seed-dev-store.mjs (`npm run seed:dev-store`): creates each missing
collection with its members in one call and publishes it. Uses
CollectionCreateInput.sources because collectionAddProducts is deprecated at
2026-10, and publishes because collectionCreate leaves a collection
unpublished — invisible to the Storefront API the dev store exists to serve.
Idempotent, never deletes, reports handles it cannot find (which is how a
partial CSV import shows up), and is a dry run unless given --apply.
- assets/dev-store-seed.json: the baseline the seed is checked against and the
membership map it consumes — 61 products with option values, weights and
product types, 16 collections with 100 memberships, and the 10 metafield
definitions that must exist before the import.
- The ticket now records what the probes settled: the option shape is readable
from the Storefront API (all 61 use Option1 name=Title; 22 are `Default
Title`, 39 carry real values, so the import must not normalise), and the
repo's production credentials are Storefront-scoped only — `products`,
`collections` and `productsCount` are Access denied without read_products —
so the ticket's "Admin API script using the credentials the repo already
mints" was never available. A dev-store custom app is required instead.
The route from "mozimo.in displays Shopify products" to "mozimo.in sells them",
as a plan with a phase spine and gates, plus the open decision tickets that
still shape it and the evidence they rest on.
- .scratch/shopify-headless-commerce/: plan.md (phases and gates), map.md (the
wayfinder map), 10 open decision tickets
- CONTEXT.md: Storefront / Theme store / Cart / Parity vocabulary
- docs/adr/0001-0003: Checkout stays Shopify-hosted, one Variant per product,
the checkout domain stays shop.mozimo.in
- docs/research/ and docs/shopify-catalog-inventory.md: verified store facts,
the Cart API probe, and the catalogue inventory
Raw Shopify doc scrapes under .scratch/shopify-research/ are ignored: they are
working material, not record.
Foundations for the phases that write. The Storefront served a retired API
version (2025-01 was answered as 2025-10) and no product carried a merchandise
ID, so nothing could be carted.
- Pin the Storefront API to 2026-10 and log the version Shopify actually
served, so retired-version drift is visible instead of silent. Drop the
stale predictiveSearch comment.
- ShopProduct carries the Variant GID and per-variant price, replacing the
product-level price range with the Variant as the one source of truth.
Products without a Variant are dropped rather than shown uncartable.
- Send Shopify-Storefront-Buyer-IP from the buyer's request, and omit it when
no request is in scope (builds, scripts).
- Standardise on the minted OAuth token; the static storefront token is a
warned legacy fallback.
- Persist the last known-good payloads to disk beside the token cache, so a
Shopify outage serves stale real commerce data rather than the bundled
Editorial catalog. Verified against an unreachable store: a product absent
from the Editorial catalog still rendered from last-good data.
- Fix a latent 500: a failed OAuth mint threw out of gql (it ran outside the
try). Transport failures now report status 0 and gql fails closed.
- Point the seasonal Rakhi card at the gift collection; its
/products/rakhi-bark-box-200gm handle is dead and no Rakhi product remains.
POST / with a React server-actions header is not a request this app
makes — TanStack server functions POST to /_serverFn/<id>. Left alone
the transform stream sat open until the 120s lifetime timer force-cleaned
it, surfacing as a fatal uncaught exception in Sentry (event e267e264,
release 84dffa8). Now answered 404 in 9ms; normal pages unaffected.
- composed hero stays split ivory/copy + image; bonbons carry the
mustard/oxblood/copper of the r9 direction; Shop Chocolate opens
same-tab now
- preview mock shown and approved before this commit; swap again when
the r9 shoot lands
- collection_rows: hampers-boxes row (seed default + ensure-row migration);
/chocolate shows the button, /collections/hampers-boxes gets the two-tone
hero (last-word script rule) + live Shopify hampers + row blurb as lead
- gifting_page: tabs/hampers/seasonal heading fields (seed + fill-missing
migration, never overwrites admin edits); gifting.tsx consumes them
- site_settings: home seasonal + universe copy keys (seed + ensure-keys
migration); index loader passes them to SeasonalTeaser/UniverseSection
- hero lead on collection pages falls back to the PB row blurb
- www host: /_* and /api/* proxy to pocketbase :8090, everything else to
the TanStack app; redir /_ /_/ for the admin path
- cms host unchanged: redir / -> /_/
- migration: legacy per-host marker blocks are removed before the new
combined block is installed
- cms.ts: PB file urls are now same-origin relative (/api/files/...) —
browsers could never load the old 127.0.0.1:8090 urls; validated the
rendered Caddyfile with caddy:2
- snippet generation moved to a j2 template; per-host pb_root flag
- cms.mozimo.in emits 'redir / /_/' before reverse_proxy (pattern from
overlord's Caddyfile); other hosts unchanged
- snippets now update in place (blockinfile idempotency) instead of
skipping when the desired block changed
- Makefile builds from 'git archive HEAD' (stale-remote-HEAD impossible):
tags registry.tanshu.com/mozimo:<short-commit> + :main; build-check target
verifies both architectures without pushing
- deploy.sh: refuses dirty tracked tree / HEAD != origin/main (--force to
bypass), ff-only pull, passes the commit as image_tag to ansible,
--prune flag for opt-in image cleanup
- ansible: docker_image uses image_tag extra-var (falls back to tag),
prune task keeps the 3 newest mozimo images, mozimo-repo only