/CLAUDE.md
CLAUDE.md at /CLAUDE.md
Path: CLAUDE.md
CLAUDE.md
Astro 6 website for Takazudo Modular, a Japanese modular synthesizer shop. Content managed via MDX files and JS data modules.
Deployed at https://takazudomodular.com/. Preview URLs use *.takazudomodular.pages.dev (Cloudflare Pages, post-Wave-8 cutover). During dual-deploy, Netlify preview URLs *--takazudomodular.netlify.app remain active.
Local Setup
After cloning, copy .env.example to .env, fill in values, then run:
pnpm setup-local
This creates local symlinks (imgs/ → Dropbox image storage, photos/ → Dropbox photo storage for the uploader pipeline, image-stash/ → capture repo) based on paths in .env. The imgs/ symlink is required for the image pipeline (pnpm convimgs:upload). The photos/ symlink is optional — it’s only useful for locally inspecting user-uploaded originals; the Photo Uploader app itself writes directly to R2. Build and dev server work without either symlink (production images are served from R2 CDN).
Generated files: src/data/generated/ contains capture-manifests.json and transcript-manifests.json (gitignored). These are auto-generated by pnpm run build:capture-manifest, which runs as part of _prepare (called by pnpm dev). If TypeScript complains about missing modules in src/data/generated/, run pnpm run build:capture-manifest to generate them.
Rules & Restrictions
- rm -rf: NEVER use absolute paths. Always
rm -rf ./relative/path - Git: No force push. No
git commit --amend(unless user explicitly permits). No reusing branch names. Default merge strategy: regular merge (not squash) unless user requests squash - console.log: Prohibited in source code (CI fails). Use
console.errorfor error handling. CI excludes test files and commented lines - Temp files: Always save to
__inbox/(gitignored). Never save temp files to repo root - Stdout warnings: Do NOT ignore warnings printed to stdout/stderr when running commands (
pnpm dev,pnpm build, etc.). Read the output carefully. If you see warnings, either investigate and fix them, or ask the user about them. This is especially important for dev server startup messages, build warnings, and health checks
Git Worktree
There are two scenarios when working with worktrees:
Scenario A: Session launched from repo root, referencing worktrees
When the session starts at the repo root and you cd into /worktrees/ to read files for reference:
- NEVER do git operations (commit, push, checkout) inside
/worktrees/directories. You may havecd’d there to read code, but git ops would affect that worktree’s branch, not the branch you’re working on in the main repo. - Worktrees are read-only references in this scenario. Read files, then navigate back to repo root before any git operations.
- If your current path contains
/worktrees/, navigate to repo root before any git operations.
Scenario B: Session launched from a worktree directory
When the user launches the CLI session directly from a /worktrees/<name>/ directory, that worktree IS your working directory. You should:
- Make file changes, run commands, and develop normally in that worktree
- Git operations (commit, branch, etc.) are fine here — the user intentionally launched from this worktree to work on its branch
General worktree rules
- Setup:
pnpm run init-worktree <name>(creates worktree + symlinks .env files) - Always pull the base branch before creating a worktree to include all merged changes
- Working directory is where user launched CLI, not where previous sessions worked
WIP Article Base Branch
base/wip-articles is a long-lived accumulation branch for YouTube guide article drafts — not a feature branch. AI can draft articles fast, but humans finalize slowly, so drafts accumulate here before being shipped one episode at a time.
Workflow
-
Draft — New article MDX files are ported into
base/wip-articles(or a theme-scoped variant; see below). -
Finalize — When an episode is ready to publish, run:
/l-youtube-guide-writer --finalize <mdx-path>See
.claude/skills/l-youtube-guide-writer/SKILL.mdfor full usage. -
Ship — Each finalized episode gets its own small PR targeting
maindirectly. Never mergebase/wip-articles→mainwholesale.
Branch naming
| Branch | Purpose |
|---|---|
base/wip-articles | Default accumulation branch for all guide article drafts |
base/wip-articles-<theme> | Theme-scoped epic work (e.g., base/wip-articles-docs for documentation work) |
Theme branches follow the same workflow: drafts accumulate there, finalized episodes get individual PRs to main.
Placement rules (series & base branch)
When starting a new guide draft, decide which series it belongs to and which base branch it sits on using this precedence — check from top to bottom, stop at the first match.
| # | Rule | Series | Base branch |
|---|---|---|---|
| 1 | Existing series continuation — a guide series already exists for the target product (on main or any WIP branch) | Add as next -ep{N+1} of that series. Update src/data/guide-series.mjs | Continue the active themed sub-base if one exists, otherwise base/wip-articles directly |
| 2 | User-specified series — user names a series in the invocation (e.g., --series=<slug>) | Add to the named series | Same rule as 1 |
| 3 | Tiny-module single article — one-off short video (e.g., 2-minute brand overview of a small utility), no existing series | Single article, no sub-series. File: {product}-guide.mdx (standalone, no ep-number) | base/wip-articles directly (no themed sub-base) |
| 4 | Multi-article epic / porting — bulk work such as ~5+ episodes, modernizing stale drafts, or porting from an old branch | New series per product as usual | New themed sub-base base/wip-articles-<theme> (epic PR targets base/wip-articles) |
Rule of thumb: do not invent a new themed sub-base for a single tiny article. Sub-bases are for epic-scale work. A single short-video draft ships fastest when it lives directly on base/wip-articles and finalizes straight to main.
Canonical reference
The OXI Coral series EP.1–5 on main represents the canonical finalized shape for guide articles. Use it as a reference when checking formatting, frontmatter, and structure.
See also: doc/src/content/docs/writing/guide-article-writing.md for the full writing guide.
Port Mapping
| Port/Domain | Service | Start Command |
|---|---|---|
| zmod.localhost | Next.js Dev | pnpm dev |
| zmod.localhost | Next.js + API (local) | pnpm dev:full |
| 34434 | Next.js Prod | pnpm serve |
| 9999 | Netlify Functions | pnpm functions:serve |
| localhost | zudo-doc Docs | cd doc && pnpm dev |
| localhost | zmdpreview (Mercari preview) | cd sub-packages/zmdpreview && pnpm dev |
| localhost | photo-uploader (mobile photo upload) | pnpm photo-uploader:dev |
| localhost | photo-uploader-worker (Cloudflare Worker, preview env) | pnpm worker:dev |
| localhost | zmod-preorder-worker (Cloudflare Worker, preview env) | pnpm --filter zmod-preorder-worker dev |
| localhost | image-stash-viewer | cd sub-packages/image-stash-viewer && pnpm tauri:dev |
| localhost | photo-manager (Tauri admin for Photo Uploader dataset) | pnpm photo-manager:dev |
| localhost | wip-assets-viewer | cd sub-packages/wip-assets-viewer && pnpm tauri:dev |
Sub-package ports: see each sub-package’s CLAUDE.md.
Preview Deploy URLs
Post-Wave-8 (Cloudflare Pages authoritative):
| Branch | URL |
|---|---|
preview | https://preview.takazudomodular.pages.dev |
expreview/* | https://expreview-{name}.takazudomodular.pages.dev |
Doc site uses a custom subdomain (CNAME required, user-action A17 in cutover checklist):
| Site | URL |
|---|---|
| Doc | https://doc.takazudomodular.com |
| Styleguide | https://zmod-styleguide.pages.dev (internal tool, no custom domain) |
During dual-deploy (pre-cutover), Netlify preview URLs remain active:
| Branch | URL |
|---|---|
preview | https://preview--takazudomodular.netlify.app |
expreview/* | https://expreview-{name}--takazudomodular.netlify.app |
Branch name length rule: Cloudflare Pages limits subdomain to 63 chars. With project name takazudomodular (15 chars) + . (1) = 16, branch slug must be ≤ 47 chars. For expreview/ branches: the prefix uses 10 chars (as expreview-), leaving up to 37 chars for the topic name.
URL to File Mapping
zmod.localhost:34434/notes/[slug]->/src/mdx/notes/,/src/mdx/highlights/zmod.localhost:34434/s/[slug]->/src/mdx/s/(standalone pages: about, discord)zmod.localhost:34434/products->/src/data/products.mjslocalhost:32342/docs/->/doc/src/content/docs/
Port 34434 is auto-killed before pnpm dev. For others: lsof -ti:[PORT] | xargs kill -9.
Image Pipeline
/imgs/is a symlink to Dropbox (/Users/takazudo/Dropbox/takazudoModular/_web-img-storage), excluded from git- Processed images output to
/static/images/p/[slug]/(symlinked to/public/images/p/) - Formats: WebP (600w-2000w),
mercari.png,metadata.json, and conditionallyogp.jpg(see OGP convention below) - Production images served from Cloudflare R2 via Netlify proxy redirects
- NEVER put files directly into
static/images/p/. Always use the pipeline: original →/imgs/→convimgs→r2:upload - Use
pnpm convimgs:uploadto process and upload in one step - Run
pnpm build:metadataafterpnpm convimgsor when creating new worktrees. Required beforepnpm devif images changed - Build does NOT require
/imgs/or local images — production uses R2 CDN URLs - Fresh clone: run
pnpm r2:downloadto get processed images for local dev - Image health warnings during
pnpm dev: Thecheck:imagesstep (runs automatically before dev server) warns when MDX frontmatter references image slugs not inmetadata-db.json, or when source images in/imgs/haven’t been processed. Look forWARNING: N slug(s) not found in metadata-db.jsonorWARNING: N source image(s) not processedin stdout. If you see these, ask the user whether to runpnpm r2:downloadorpnpm convimgs:upload. Do not push while these warnings are present — the same “slug not inmetadata-db.json” condition surfaces in production as a black-square blurhash placeholder on home-page cards and an empty<ImgsGrid>on the detail page (this is exactly how thezudo-3u-to-1uregression manifested on 2026-05-02). Treat the warning as a release blocker, not a “fix later” note - Run
pnpm r2:check-orphansto detect R2 images without originals in/imgs/ metadata-db.jsonis tracked in git (one-record-per-line, sorted by slug for merge-friendly diffs). Regenerate withpnpm build:metadataafter image changes. Do not reformat this filepnpm build:metadatais non-destructive by default. The committedmetadata-db.jsonis loaded as a baseline, the localstatic/images/p/tree is scanned, and the output is the union — locally-scanned slugs override the baseline, baseline-only slugs are preserved. This is what makespnpm convimgs:upload && pnpm build:metadatasafe to run inside a worktree (wherestatic/images/p/is empty by default) without nuking ~1900 production entries. Pass--prune(alias:--strict) only when you really do want the legacy “scan-only” behavior — i.e. you’ve truly retired slugs and your localstatic/images/p/is intentionally complete (runpnpm r2:downloadfirst)metadata-db.jsonregeneration diffs — additions look noisy, commit them anyway. Deletions of unrelated slugs — never commit.- Additions / refreshes: a single new product can produce a 100+ line
+/-diff because (a) the new slug entries land mid-file in sort order, shifting nothing visually but registering as inserts, and (b) any other entries whoseblurhashDataUriwas deferred since the last regeneration also refresh in the same pass. The result looks like “many unrelated changes” and triggers an instinct to drop it from the commit. Do not. Ship the file as-is via/l-metadata-update. - Deletions of slugs unrelated to your current change: NEVER commit these. They mean your local
static/images/p/was sparse and an old (pre-merge-mode)pnpm build:metadatadropped baseline entries. With the merge-mode default this should not happen at all — if you see it, you almost certainly ranpnpm build:metadata --pruneby mistake. Restore the file withgit checkout metadata-db.jsonand re-run without--prune. Slugs missing frommetadata-db.jsonship to production as black-square blurhash placeholders / empty<ImgsGrid>s on real product pages (thezudo-3u-to-1uregression of 2026-05-02 hit exactly this path).
- Additions / refreshes: a single new product can produce a 100+ line
OGP Image Convention (__og / __ogonly suffixes)
The image pipeline uses __ (double underscore) as a delimiter for conversion rules. Three file types control OGP generation:
| Source file | Slug | Generates |
|---|---|---|
foobar.heic | foobar | WebP + mercari + metadata (NO ogp.jpg) |
foobar__og.heic | foobar__og | WebP + mercari + metadata + ogp.jpg |
foobar__ogonly.heic | foobar__ogonly | ogp.jpg ONLY |
- No suffix — regular image, OGP skipped for faster processing
__og— full processing plus OGP. Use for product thumbnails that also serve as social sharing images__ogonly— OGP only, no WebP or mercari variants. Use for dedicated OGP images (e.g., brand OGP for site-wide default)- No slug stripping — the slug IS the filename without extension (e.g.,
foobar__og.heic→ slugfoobar__og) - The
--force-ogpCLI flag can force OGP generation on regular files
When no ogp.jpg exists for a slug, no og:image meta tag is emitted.
MDX Frontmatter Image Fields
| Field | Purpose | Notes |
|---|---|---|
imgThumb | Display image slug (hero, thumbnails, cards) | Preferred field name |
heroImgUrl | Legacy alias for imgThumb | Still works, imgThumb takes precedence |
imgOgp | Optional OGP override slug | When set, OGP uses this instead of imgThumb |
When imgOgp is absent, OGP falls back to imgThumb/heroImgUrl (existing behavior).
Use imgOgp when a page needs a different image for social sharing vs. display (e.g., standalone pages with __ogonly images).
Merge driver
metadata-db.json has a custom git merge driver (metadata-db-jsonl) that treats the file as a key-value map keyed by slug rather than a sequence of lines. Disjoint slug additions on two branches auto-merge without conflict.
Setup is automatic: pnpm setup-local registers the driver in the local git config. The .gitattributes line metadata-db.json merge=metadata-db-jsonl activates it for this file.
Limitation: GitHub’s server-side merge does NOT run client merge drivers. If the GitHub UI reports a conflict on metadata-db.json, resolve it by pulling locally (the driver re-merges cleanly), then pushing — GitHub will then merge trivially.
Fallback: a contributor who skipped pnpm setup-local falls back to git’s default text merger — the same manual-conflict experience as before the driver was introduced. No regression.
Photo Pipeline
Parallel to the product-image pipeline above, the Photo Uploader app (epic #1473, super-epic #1470) manages user-uploaded photos with their own storage layout and metadata DB. Keep the two pipelines conceptually and physically separate — they share only the R2 bucket.
R2 layout
The zmodmedia bucket carries three top-level prefixes:
| Prefix | Purpose |
|---|---|
images/p/{slug}/... | Product images (existing pipeline) |
photos/originals/{YYYY}/{MM}/{slug}.{ext} | User-uploaded originals (HEIC/JPEG/PNG), date-sharded |
photos/variants/{slug}/{400w,800w,1600w}.webp | Generated multi-res WebP variants |
Photos do not get mercari.png or ogp.jpg. Three widths only.
Canonical store: Cloudflare D1, owned by the Worker (epic #1592 cutover)
Photo metadata is canonical in Cloudflare D1 (table photos, schema in schema/photos.sql). Since the Workers cutover (epic #1592), the Cloudflare Worker at sub-packages/photo-uploader-worker/ owns all D1 access via its native D1 binding — there is no REST client and no Cloudflare API token involved at runtime. The Worker’s commit handler upserts into D1 directly, and the build pipeline reads from D1 indirectly by hitting the Worker’s GET /photos.json endpoint. The build script then regenerates photo-metadata-db.json from those rows ∪ filesystem variant scan. The historical photo-metadata-db.json file at the repo root is a build artifact — kept in git as a snapshot, a seed source for scripts/seed-d1-photos.mjs, and as the local-dev fallback target when no BUILD_AUTH_TOKEN is configured. Regenerate it with pnpm photos:build.
After the cutover, Netlify holds zero Cloudflare credentials — the deleted CLOUDFLARE_* env vars are no longer needed by any build- or runtime-path on the Netlify side. All Cloudflare auth lives either as Worker runtime secrets (wrangler secret put) or as GitHub Actions deploy-time secrets.
pnpm build runs pnpm photos:build between build:metadata and build:search-index, so every Netlify build picks up the latest captions / hashtags from D1 (via the Worker). The build script is defensive: a Worker outage logs the error and falls back to the filesystem snapshot rather than failing the build. Build logs print one of:
photos:build: reading from Cloudflare Worker /photos.json— Worker was reachable.photos:build: filesystem fallback (no BUILD_AUTH_TOKEN)— local dev / no token.photos:build: filesystem fallback (Worker unreachable)— Worker attempted, failed, fell back.
Operator setup, the secrets runbook (Worker runtime secrets via wrangler secret put + GitHub Actions deploy secrets), the D1 binding configuration, schema evolution policy, and the rollback procedure all live in sub-packages/photo-uploader-worker/CLAUDE.md. The Netlify side (netlify/functions/) is a thin proxy layer now — netlify/functions/photo-uploader-{login,sign,commit}.ts are tiny shim Functions that forward to the Worker. Client URLs stay stable. (The shim Functions exist instead of [[redirects]] because Netlify silently drops rewrite rules whose from starts with /.netlify/ — that prefix is reserved for the Functions / Edge Functions / Blobs platform.)
photo-metadata-db.json (build artifact / local-dev fallback)
Tracked in git at the repo root, one JSON object per line, sorted by slug ascending — same merge-friendly convention as metadata-db.json, but wrapped in [] (array) instead of {} (object-keyed-by-slug) because each record carries its own slug field.
Per-record shape:
{
"slug": "20250101-120000-abc12345",
"takenAt": "2025-01-01T12:00:00Z",
"uploadedAt": "2025-01-15T09:30:00Z",
"variants": {
"400w": "/photos/variants/20250101-120000-abc12345/400w.webp",
"800w": "/photos/variants/20250101-120000-abc12345/800w.webp",
"1600w": "/photos/variants/20250101-120000-abc12345/1600w.webp"
},
"dimensions": { "w": 4000, "h": 3000 },
"aspectRatio": 1.3333,
"orientation": "landscape"
}
- Slug format:
YYYYMMDD-HHMMSS-{8-char hex}(derived fromtakenAt+ random suffix) orientation=landscape|portrait|square, derived fromdimensionsaspectRatio=w / h, rounded to 4 decimalstakenAt/uploadedAtare ISO 8601 UTC (Zsuffix)
Consumers import via lib/utils/photo-url.ts — it reads the virtual module virtual:photo-metadata-db under Astro/Vite and falls back to a direct fs.readFileSync in plain Node contexts (for scripts/).
Phase 1 status
Epic #1473 is multi-phase. Phase 1 (this commit) freezes the contract with a fixture photo-metadata-db.json and placeholder variant WebPs in static/photos/variants/ so Epic D can render against stable data. The uploader UI, HEIC conversion, R2 upload scripts, and auth all land in Phase 2.
Commands Reference
# Core
pnpm dev # Next.js dev server only (no API)
pnpm build # Production build (~3-4 min)
pnpm serve # Serve production build
pnpm clean # Clean Next.js cache
# Dev with API (3 environments)
pnpm dev:full # Next.js + local functions (file-based blobs)
pnpm dev:full:preview # Next.js + preview remote API
pnpm dev:full:prod # Next.js + production remote API
# Code quality
pnpm check # Run all checks (typecheck + lint + format)
pnpm check:fix # Fix all auto-fixable issues
pnpm typecheck # TypeScript only
pnpm lint / pnpm lint:fix # ESLint only
pnpm format / pnpm format:fix # Prettier only
# Testing
pnpm test # Unit + critical e2e
pnpm test:all # All tests including full e2e
pnpm test:unit # Unit tests only
# Data & images
pnpm build:metadata # Build image metadata DB (run after convimgs)
pnpm convimgs:upload # Process images + auto-upload to R2
pnpm r2:upload # Upload all images to R2 (incremental)
pnpm r2:download # Download all images from R2
pnpm r2:check-orphans # Detect orphaned images on R2
# Photos (epic #1473 — Photo Uploader)
pnpm photos:download # Download user-uploaded originals from R2
pnpm photos:build # Generate WebP variants + refresh photo-metadata-db.json
pnpm photos:upload-variants # Upload photo variants to R2 (incremental)
pnpm photos:check-orphans # Report R2 photo objects with no DB entry
Pre-Push Checklist
Always run pnpm check before pushing. Fix issues with pnpm check:fix. For significant changes, also run pnpm test. For comprehensive pre-merge verification: ./scripts/pre-merge-check.sh.
Key Files
src/data/product-master-data.mjs— MASTER DATA: product catalog (specs, images, metadata)src/data/products.mjs— Processed products with utility functions (imports from master data)src/data/brands.mjs— Brand information and SVG mappingslib/data/taxonomy.ts— Tag and category label mappingsastro.config.ts— Astro configurationsrc/astro/— Astro pages, layouts, and componentssub-packages/design-system/— Tailwind design tokens and theme
Commit Messages
Start with a scope prefix in brackets, then a short description:
[web]- main website (src/astro/, components/, lib/, src/, styles, config)[content]- MDX content and translations (src/mdx/)[data]- data files (src/data/, product-master-data, brands, guide-series)[doc]- documentation site (doc/)[zpreorder]- pre-order sub-package (sub-packages/zpreorder/)[photo-uploader]- photo uploader sub-package (sub-packages/photo-uploader/) + its Netlify functions (netlify/functions/photo-uploader-*)[products-viewer]- products viewer (sub-packages/products-viewer/)[mercari-viewer]- Mercari viewer (sub-packages/mercari-viewer/)[image-processor]- image pipeline tools (sub-packages/image-processor/, image-mixer/, product-photo-maker/)[design-system]- design tokens (sub-packages/design-system/)[claude]- Claude Code config (.claude/, CLAUDE.md)[misc]- CI, dependencies, general config, other
Examples: [web] Add EN brand pages, [content] Translate power guide series, [misc] Update dependencies
Code Style
- Prettier: 100-char line width, single quotes, trailing commas
- File naming: Always kebab-case (e.g.,
brand-block.tsx). Never PascalCase for files - Imports: Absolute where possible, organized by type
- Naming: camelCase for variables/functions, PascalCase for components/classes
- Tailwind: No inline styles. Numeric Tailwind classes prohibited (e.g.,
p-2,m-4,gap-8). Use semantic tokens:p-vgap-*,p-hgap-*,gap-vgap-*. Seecomponents/CLAUDE.mdfor full styling guide - No CSS Modules: Do not use
.module.cssfiles. Use Tailwind utility classes for styling. For complex CSS that can’t be expressed as utilities (animations, clip-paths), use plain CSS files with unique class prefixes (e.g.,hamster-animation.csswith.hamster-*classes) imported as side effects - GitHub CLI: Use
ghfor PR/issue interaction (gh pr view,gh api repos/Takazudo/zmodular/pulls/<N>/comments)
Dependency Version Locks
- cookie: 0.7.0 (security fix)
- undici: >=7.24.0 (security fix, via pnpm override)
Never auto-merge Dependabot PRs. Run full test suite before updating dependencies.
Deployment
Post-cutover: hosted on Cloudflare Pages (zmod project) with automatic deployments. Search uses MiniSearch (built at build time, served via Pages Function functions/api/search.ts).
Documentation is auto-synced: pushes to main trigger sync-main-to-doc.yml which merges main into the doc branch. Post-cutover, doc-pages-deploy.yml then deploys to https://doc.takazudomodular.com. Direct pushes to doc also trigger doc-pages-deploy.yml.
Styleguide: pushes to styleguide branch trigger styleguide-pages-deploy.yml which deploys to https://zmod-styleguide.pages.dev.
Pre-cutover (dual-deploy): Netlify remains authoritative. Netlify deploy workflows (main-deploy.yml, preview-deploy.yml, preview-branch-deploy.yml) continue to fire. CF Pages workflows fire only after bigbang-move to zudolab/zzmod.
Dual-Deploy State — Cloudflare Pages Migration (zzmod epic #1798)
Status as of Wave 8.1 (sub-issue #1817): Repo-side cutover edits have landed. Awaiting operator §4.A items (CF project creation, D1 databases, secrets, service bindings) and the DNS flip.
Netlify remains the authoritative host until the DNS flip. After the flip, Cloudflare Pages is authoritative. The Netlify project must be kept intact (do not delete) as the rollback path through the soak window (Wave 9).
Files added in Waves 2–8:
wrangler.toml— CF Pages project config + service bindings (PHOTO_UPLOADER, PREORDER_WORKER).github/workflows/pages-deploy.yml— Build + deploy to CF Pages (dormant until bigbang-move: guards ongithub.repository == 'zudolab/zzmod')_redirects-pages— CF Pages-format redirect rules. Wave 8 update:/.netlify/functions/*API entries removed;/api/*served by Pages Functions.scripts/gen-cf-pages-redirects.mjs— Regenerates_redirects-pages. Wave 8 update: skips/.netlify/functions/*entries.functions/api/notify-signup.ts,functions/api/reservation.ts,functions/api/admin/[[adminPath]].ts— Wave 8 preorder proxy Pages Functions.functions/api/_shared/preorder-proxy.ts— Shared proxy helper for zmod-preorder-worker.
The pages-deploy.yml workflow copies _redirects-pages to dist/_redirects after pnpm build and before wrangler pages deploy. It does NOT touch static/_redirects, which remains for Netlify.
Wave 9 cleanup (post-soak): remove netlify/ directory, netlify.toml, @netlify/* deps, and Netlify workflow secrets.
Sub-Packages
26 sub-packages in sub-packages/. Most have their own CLAUDE.md with ports, architecture, and testing details.
Web apps (with dev servers)
zpreorder— Pre-order admin panel (Vite + React, connects to Netlify Functions)photo-uploader— Mobile photo upload app for the Photo Uploader epic (Vite + React, localhost). Calls the photo-uploader-worker via the Netlify rewrite at/.netlify/functions/photo-uploader-*for auth + presigned R2 PUTmercari-viewer— View/edit Mercari CSV data (zmercari.localhost)products-viewer— Editproduct-master-data.mjs(zproducts.localhost)zmdpreview— Mercari product description previewer (localhost)keyword-viewer— Tag/keyword browser (localhost)styleguide— Component styleguide for Preact components (localhost)styleguide-v2— Component styleguide, Astro-based (localhost)
Tauri apps
addac-order— ADDAC System price calculator, standalone Tauri v2 + React/Vite (localhost)image-stash-viewer— Preview generated images in image-stash/ (localhost)wip-assets-viewer— Browse WIP asset files (SVG diagrams etc.) for the Synth Diagram System; configurable watched dir, SVG/raster rendering, copy-path-to-clipboard (localhost)photo-manager— Tauri admin for the Photo Uploader dataset (epic #1651). HTTP-only shell over the photo-uploader-worker/admin/*endpoints. Local-only, baked admin bearer token (no UI login). Vite dev: localhost. Usepnpm photo-manager:devfor the Vite-only browser dev loop, orpnpm photo-manager:tauri:devfor the full desktop window.imgs-viewer— Browse, preview, and process product images (Tauri v2 with custom Rust backend, localhost)tauri-doc— Tauri wrapper for doc dev server (localhost)tauri-keyword-viewer— Tauri wrapper for keyword-viewer (localhost)tauri-mercari-viewer— Tauri wrapper for mercari-viewer (localhost)tauri-zmdpreview— Tauri wrapper for zmdpreview (localhost)tauri-zpreorder-prev— Tauri wrapper for zpreorder, preview env (localhost)tauri-zpreorder-prod— Tauri wrapper for zpreorder, prod env (localhost)
See sub-packages/TAURI-WRAPPERS.md for the common Tauri wrapper architecture pattern.
CLI tools
image-processor— Image pipeline: WebP conversion, OGP generation, metadataimage-mixer— Composite image layers for product photosorange-calibrator— Normalize orange fabric background color across product photosproduct-photo-maker— Product photo generation (bg removal, shadow, texture compositing)product-md-sync— Sync product data to markdown filessynth-svg— SVG diagram generators for modular synth tutorial seriesyt-tools— YouTube video processing tools for guide article creation
Libraries
design-system— Shared Tailwind CSS design tokens and themedesign-token-lint— Lint Tailwind class names against design system tokensogp-debug-panel— Configurable OGP debug panel for inspecting Open Graph meta tags
Cloudflare Workers
photo-uploader-worker— Cloudflare Worker backing the Photo Uploader app (epic #1592 cutover). Owns the four photo-uploader API routes (/photo-uploader-{login,sign,commit}+/photos.json), accesses D1 via native binding, signs R2 PUT URLs withaws4fetch. Local dev:pnpm worker:dev(port 8787). Deploys viapnpm worker:deploy:{preview,prod}or thedeploy-photo-uploader-worker.ymlGitHub Actions workflow. See its CLAUDE.md for the full secrets runbook and operator procedure.
Markdown/MDX formatting uses @takazudo/mdx-formatter (npm package, invoked via pnpm dlx).