# 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
`<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:

```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 `<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:

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.
