11 KiB
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.
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:
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
cd /Users/khalid/Documents/Claude-Desktop/goods-editor-module
npm install
npm run build
Verification: ls dist/ should show:
goods-editor.es.jsgoods-editor.cjs.jsstyle.cssserver/index.jsserver/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 fromapparel-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:
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:
cd /Users/khalid/Documents/Claude-Desktop/apparel-designer
bash scripts/cutover-host.sh
What this does:
- Renames the monolithic files to
*.monolith.*(kept for reference) - Promotes
App.host.jsx→App.jsx,server.host.js→server.js,package.host.json→package.json - Deletes the module-owned source from
src/
Verification: ls apparel-designer/src/ should show only:
App.jsxApp.monolith.jsx(backup)main.jsxindex.csscomponents/Header.jsx,components/PWAInstall.jsx,components/OfflineIndicator.jsxstyles/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
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 catalogtemplates.js— host's template definitionsproducts.js— host's product configuration
These are imported by the new App.jsx and passed into
<ApparelDesigner> 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:
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 theirserver.js. - Hosts that DON'T mount the server (editor-only, preview-only)
don't install them at all. The React side of
goods-editordoesn'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 <ApparelDesigner> 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:
git add -A && git commit -m "Migrate to goods-editor module"in both repos- Cut a release of the module:
cd goods-editor-module ./scripts/release.sh --version=0.1.0-alpha.0 - In the host, swap the dep from
file:togit+ssh://once you've pushed to Gitea.
Rollback
If anything goes sideways and you need to undo the cutover:
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 vianpm run dev(dev host). When happy,./scripts/release.sh --version=X.Y.Zand bump the host'spackage.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.