# Migration runbook — apparel-designer → goods-editor module Step-by-step instructions for executing the module extraction. This document is intentionally minimal-fluff: do each step in order, verify the marker at each checkpoint, move to the next. ## Prerequisites Verify both repos exist as siblings: ``` /Users/khalid/Documents/Claude-Desktop/ ├── apparel-designer/ (the host app, in working state) └── goods-editor-module/ (the empty module repo we just scaffolded) ``` ## Phase 1 — Module scaffolding (already done) These were created during the planning session and are ready to use: ``` goods-editor-module/ ├── package.json ├── vite.config.js (library mode) ├── vite.server.config.js (Node server entry) ├── vitest.config.js ├── .gitignore ├── README.md ├── src/ │ ├── ApparelDesigner.jsx (~30KB orchestration absorbed from App.jsx) │ ├── index.js (public API barrel) │ └── server/ │ ├── index.js │ └── mountEditorApi.js (Express middleware factory) ├── examples/dev-host/ (standalone dev playground) └── scripts/ ├── release.sh └── post-migrate-patches.mjs ``` **Verification:** `ls goods-editor-module/src/index.js` should succeed. ## Phase 2 — File migration Copy all the editor source from the host into the module. ```bash cd /Users/khalid/Documents/Claude-Desktop bash apparel-designer/scripts/migrate-to-module.sh ``` **What this does:** Copies utils, hooks, constants, components, styles, fonts, stickers, tests from `apparel-designer/src/` (and related directories) into `goods-editor-module/src/`. Source is left intact in `apparel-designer/` — nothing is deleted yet. **Verification:** `ls goods-editor-module/src/components/canvas/` should show DesignCanvas.jsx, ElementToolbar.jsx, etc. ## Phase 3 — Post-copy patches The copied files import constants like `SHIRT_COLORS` directly. Inside the module, those need to come from props. Run the patch script: ```bash cd /Users/khalid/Documents/Claude-Desktop/goods-editor-module node scripts/post-migrate-patches.mjs ``` **What this does:** Modifies a handful of files (`ShirtOptionsPanel`, `Sidebar`, `UploadTab`) to accept their data as props instead of importing constants. **Verification:** Search the module source for `SHIRT_COLORS` — should return 0 hits in component files (still present as a `constants/shirt.js` default that's no longer imported by components). ## Phase 4 — Module install + build ```bash cd /Users/khalid/Documents/Claude-Desktop/goods-editor-module npm install npm run build ``` **Verification:** `ls dist/` should show: - `goods-editor.es.js` - `goods-editor.cjs.js` - `style.css` - `server/index.js` - `server/index.cjs` If the build fails: - **"Cannot find module 'X'"** — the migration may have missed a file. Check `goods-editor-module/src/` for the missing path and copy it from `apparel-designer/src/` manually. - **"SHIRT_COLORS is not defined"** — patch script didn't run or didn't fully match. Manually search for the symbol in the offending file and replace with the prop. ## Phase 5 — Module dev-host smoke test (optional but recommended) Verify the module works in isolation before wiring it into the host: ```bash cd /Users/khalid/Documents/Claude-Desktop/goods-editor-module npm run dev # open http://localhost:3000 in a browser ``` You should see the editor with the dev host's minimal top bar. If something's broken it's a module issue; fix here before touching the host. The dev host doesn't have an /api/upload endpoint, so uploads will fail — that's expected. Text, stickers, emoji, and templates work without a server. ## Phase 6 — Host cutover Replace the host's monolithic App.jsx + server.js with the new module-consuming versions: ```bash cd /Users/khalid/Documents/Claude-Desktop/apparel-designer bash scripts/cutover-host.sh ``` **What this does:** 1. Renames the monolithic files to `*.monolith.*` (kept for reference) 2. Promotes `App.host.jsx` → `App.jsx`, `server.host.js` → `server.js`, `package.host.json` → `package.json` 3. Deletes the module-owned source from `src/` **Verification:** `ls apparel-designer/src/` should show only: - `App.jsx` - `App.monolith.jsx` (backup) - `main.jsx` - `index.css` - `components/Header.jsx`, `components/PWAInstall.jsx`, `components/OfflineIndicator.jsx` - `styles/Header.css`, `styles/PWAInstall.css` (or however they organize after cleanup) (The host's `src/styles/` and `src/components/` may still have host-specific CSS that wasn't deleted. That's expected — only module-owned files were removed.) ## Phase 7 — Host install + run ```bash cd /Users/khalid/Documents/Claude-Desktop/apparel-designer rm -rf node_modules package-lock.json npm install npm run dev ``` The `file:../goods-editor-module` reference in package.json points at the local module directory. npm install will copy it into `node_modules/goods-editor/`. ### Important: host-owned constants survive cutover The `cutover-host.sh` script preserves three files in `src/constants/`: - `shirt.js` — host's color / size / price catalog - `templates.js` — host's template definitions - `products.js` — host's product configuration These are imported by the new `App.jsx` and passed into `` as props. Other constants (fonts, stickers, elements, etc.) are module-owned and get deleted. If you customize the host's catalog (different shirt colors, new templates), you edit those files — not the module. ### Important: host-required native deps for server-side export `goods-editor/server`'s `mountEditorApi()` accepts the host's `canvas` and `sharp` modules via options: ```js import canvas from 'canvas'; import sharp from 'sharp'; import { mountEditorApi } from 'goods-editor/server'; mountEditorApi(app, { canvas, sharp, exportsDir, fontsDir, ... }); ``` This injection pattern means: - The module declares both as **optional peer dependencies** — listed for tooling visibility but not auto-installed. - Hosts that mount the server install canvas + sharp in their OWN `package.json` (as regular deps) and import them at the top of their `server.js`. - Hosts that DON'T mount the server (editor-only, preview-only) don't install them at all. The React side of `goods-editor` doesn't need them. Why injection rather than `require()` inside the module: when the module is consumed via `file:` (local dev) or `git+ssh://` (release), the module's built server bundle lives in the consuming app's `node_modules`. A `require('canvas')` from inside that bundle would resolve from the MODULE'S directory chain, not the host's. The injection pattern sidesteps this entirely — the host imports canvas from its own `node_modules` and passes the imported object in. **System libs:** `canvas` builds native bindings against cairo/pango. On macOS: ``` brew install pkg-config cairo pango libpng jpeg giflib librsvg pixman ``` If `npm install` fails on the canvas build step, those libs are usually the missing piece. If the host doesn't need server-side export (e.g. editor-only flows), skip the `mountEditorApi(app, ...)` call in `server.js` entirely and don't install canvas/sharp — the editor still works as a pure React component. ### Important: the host's `virtual:sticker-manifest` setup stays in place The module's `src/constants/stickers.js` imports from `virtual:sticker-manifest` — a Vite virtual module that the HOST'S `vite.config.js` provides via `stickerManifestPlugin()`. This is intentional: it makes the host the source-of-truth for which stickers are available. **Don't delete `stickerManifestPlugin()` from the host's `vite.config.js`,** and don't delete `public/stickers/`. The cutover script leaves both alone. If you accidentally remove the plugin, you will see this error when running the editor: ``` Failed to resolve import "virtual:sticker-manifest" from "node_modules/goods-editor/dist/goods-editor.es.js". ``` The fix is to restore the plugin (it's the `stickerManifestPlugin()` function at the top of `vite.config.js`, plus its inclusion in the `plugins:` array). If you want to ship the editor on a host that doesn't use Vite, or that has a fundamentally different sticker layout, you can pass stickers in via props on `` instead and provide a no-op virtual module that exports an empty `STICKER_FILES` array. (That extension point doesn't exist yet — file an issue / add a prop when the second host needs it.) **Verification:** `http://localhost:3000` should show the editor with the host's full Header and the editor mounted below it. Functionality should match the pre-migration version exactly. ## Phase 8 — Final checks Things to verify in the running app: - [ ] Can add text from the Text tab - [ ] Can upload a photo and see it on the canvas - [ ] Can add a sticker / emoji - [ ] Cmd+Z undoes; Cmd+Shift+Z redoes - [ ] Crop an image, Cmd+Z restores pre-crop (the May 22 regression) - [ ] Save button triggers an export (download via the export pipeline) - [ ] Reload the page — designs persist via localStorage - [ ] Production build: `npm run build && NODE_ENV=production npm start` If everything works: 1. `git add -A && git commit -m "Migrate to goods-editor module"` in both repos 2. Cut a release of the module: ```bash cd goods-editor-module ./scripts/release.sh --version=0.1.0-alpha.0 ``` 3. In the host, swap the dep from `file:` to `git+ssh://` once you've pushed to Gitea. ## Rollback If anything goes sideways and you need to undo the cutover: ```bash cd apparel-designer mv src/App.jsx src/App.host.jsx mv server.js server.host.js mv package.json package.host.json mv src/App.monolith.jsx src/App.jsx mv server.monolith.js server.js mv package.monolith.json package.json # Restore deleted module-owned files from git: git checkout -- src/ fonts/ e2e/ playwright.config.js vitest.config.js rm -rf node_modules package-lock.json npm install npm run dev ``` The `goods-editor-module/` repo stays put — rollback only affects the host. You can iterate on the module separately and try the cutover again later. ## Done After successful cutover, future development looks like: - **Editor changes** (canvas behavior, sidebar UX, hooks, utilities): edit in `goods-editor-module/`. Test via `npm run dev` (dev host). When happy, `./scripts/release.sh --version=X.Y.Z` and bump the host's `package.json`. - **Host changes** (homepage, marketing, cart, top bar): edit in `apparel-designer/`. AI tools pointed at this directory only see the host's ~10 files plus the editor's public API surface, not the editor's ~100 internal files. That's the win.