# Production Furni Assets Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Make every furni import write the icon, Nitro bundle, and FurnitureData entry into the directories served by the production client under `/var/www/Gamedata`. **Architecture:** Extend the central furni asset resolver with a separate Gamedata layout while preserving CMS-local and Nitro-Files targets. Both import paths consume the same resolved targets; FurnitureData resolves the same Gamedata root independently so metadata and binary assets stay aligned. **Tech Stack:** TypeScript 7, Node.js filesystem APIs, Vitest 4, Next.js 16. ## Global Constraints - Preserve all existing `nitro_files_root`, `furni_*_dir`, and `furni_data_mirror_path` behavior. - Auto-detect `/var/www/Gamedata` only when the directory exists; allow `gamedata_root` to override it. - Do not copy source SWFs into the Gamedata tree. - Report live mirror failures in the import warnings. - Use test-first development and run the production build before completion. --- ### Task 1: Resolve the production Gamedata layout **Files:** - Create: `src/lib/services/furni-asset-dirs.test.ts` - Modify: `src/lib/services/furni-asset-dirs.ts` - Modify: `src/lib/services/furni-data.ts` - Modify: `src/app/admin/settings/cms-settings-config.ts` **Interfaces:** - Produces: `getGamedataRoot(): Promise`. - Produces: Gamedata mirror entries from `getFurniAssetWriteTargets()` with `/icons` and `/bundled/furniture`. - Consumes: `siteSettings.get("gamedata_root", "")` and `existsSync(DEFAULT_GAMEDATA_ROOT)`. - [ ] **Step 1: Write failing resolver tests** Mock `siteSettings.get` and `existsSync`, then assert that a configured Gamedata root produces: ```ts expect(targets.mirrorDirs).toContainEqual({ swfDir: targets.swfDir, iconDir: path.join(gamedataRoot, "icons"), nitroDir: path.join(gamedataRoot, "bundled/furniture"), }); ``` Also assert that an absent unconfigured root adds no Gamedata mirror and that a duplicate primary destination is de-duplicated. - [ ] **Step 2: Run the resolver test and verify RED** Run: `pnpm exec vitest run --coverage=false src/lib/services/furni-asset-dirs.test.ts` Expected: FAIL because `gamedata_root` is not read and the Gamedata mirror is absent. - [ ] **Step 3: Implement the Gamedata resolver** Add `DEFAULT_GAMEDATA_ROOT`, `getGamedataRoot()`, and a pure Gamedata mapping that uses the primary `swfDir` while mapping icons and Nitro bundles to the live locations. Merge this candidate with the existing Nitro-Files candidate and remove identical directory triples. - [ ] **Step 4: Extend FurnitureData and the settings form** Make `getFurnitureDataWritePaths()` include `/config/FurnitureData.json`, after any explicit `furni_data_mirror_path`. Add a documented `gamedata_root` text setting with `/var/www/Gamedata` as the placeholder. - [ ] **Step 5: Run resolver tests and verify GREEN** Run: `pnpm exec vitest run --coverage=false src/lib/services/furni-asset-dirs.test.ts` Expected: PASS. - [ ] **Step 6: Commit Task 1** ```text fix: map furni imports to production gamedata ``` ### Task 2: Mirror manual Nitro uploads **Files:** - Create: `src/lib/services/upload-import.test.ts` - Modify: `src/lib/services/upload-import.ts` **Interfaces:** - Consumes: `getFurniAssetWriteTargets(): Promise`. - Produces: manual upload copies at every unique `mirrorDirs[].iconDir` and `mirrorDirs[].nitroDir`. - [ ] **Step 1: Write a failing manual-upload test** Mock the database and metadata dependencies, provide temporary primary and Gamedata directories, call `uploadSingleFurni`, and assert: ```ts await expect(fs.readFile(path.join(liveNitroDir, "chair.nitro"))).resolves.toEqual(nitroBuffer); await expect(fs.readFile(path.join(liveIconDir, "chair_icon.png"))).resolves.toEqual(iconBuffer); ``` - [ ] **Step 2: Run the upload test and verify RED** Run: `pnpm exec vitest run --coverage=false src/lib/services/upload-import.test.ts` Expected: FAIL because manual uploads currently write only to the primary CMS directories. - [ ] **Step 3: Implement manual mirroring** Resolve all write targets, create their directories through the existing `ensureDirectories()`, and copy the successfully written primary Nitro and optional icon files to each unique mirror. Catch each copy error separately and append a warning containing the destination path. - [ ] **Step 4: Run the upload test and verify GREEN** Run: `pnpm exec vitest run --coverage=false src/lib/services/upload-import.test.ts` Expected: PASS. - [ ] **Step 5: Run focused and full verification** Run: ```text pnpm exec vitest run --coverage=false src/lib/services/furni-asset-dirs.test.ts src/lib/services/upload-import.test.ts src/lib/services/furni-import.test.ts pnpm typecheck pnpm test pnpm build ``` Expected: every command exits with status 0. - [ ] **Step 6: Commit Task 2** ```text fix: mirror manual furni uploads to live assets ```