# Furni editor — furnidata editing UI + typography refresh (Client/Renderer) Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Steps use checkbox (`- [ ]`). **Goal:** Expose the server-side furnidata name/description editing (Plan A, already on Arcturus `main`) in the React furni editor: make Classname/Public Name read-only, add an editable **Furnidata** section (Display Name + Description) with diff-confirm + revert, search by furnidata name, and refresh the editor's typography/colors to the theme tokens. **Architecture:** Renderer (`Nitro_Render_V3`) gains 2 outgoing composers matching the server's incoming headers (update **10046**, revert **10048**); the success result reuses the existing `FurniEditorResult` (10044) and live propagation reuses the merged `FurnitureDataReload` (10047). Client (`Nitro-V3`) adds hook actions + UI. A small server tweak lets search match furnidata display names. **Tech Stack:** React 19 + Vite + TailwindCSS 4 (theme tokens in `tailwind.config.js`), TS, Vitest (client); TS/PixiJS (renderer); Java/Maven (server tweak). Server feature already built (Plan A). **Companion:** spec `Arcturus-Morningstar-Extended/docs/superpowers/specs/2026-06-06-furni-editor-furnidata-names-design.md`; server plan `…/plans/2026-06-06-furni-editor-furnidata-names-server.md`. Exploration of the client (exact file:line) is in this session's history — follow the cited patterns. **Server header contract (already on Arcturus main):** incoming `FurniEditorUpdateFurnidataEvent = 10046` reads `int itemId` + `String` (JSON `{name,description}`); incoming `FurniEditorRevertFurnidataEvent = 10048` reads `int itemId`; both respond with `FurniEditorResultComposer` (10044) and broadcast `FurnitureDataReloadComposer` (10047). --- ## Task 1 (renderer): outgoing composers + headers **Files (in `E:\Users\simol\Desktop\DEV\Nitro_Render_V3\packages\communication\src\messages`):** - Modify: `outgoing/OutgoingHeader.ts` (after `FURNI_EDITOR_DELETE = 10045`, ~line 505) - Create: `outgoing/furnieditor/FurniEditorUpdateFurnidataComposer.ts` - Create: `outgoing/furnieditor/FurniEditorRevertFurnidataComposer.ts` - Modify: the furnieditor `index.ts` barrel (same folder as the existing furni-editor composers) - [ ] **Step 1: Add headers** in `OutgoingHeader.ts`: ```ts public static readonly FURNI_EDITOR_UPDATE_FURNIDATA = 10046; public static readonly FURNI_EDITOR_REVERT_FURNIDATA = 10048; ``` (Match the real declaration style in that file — `public static readonly NAME: number = id;` or the enum/const pattern actually used. Verify 10046/10048 are unused in OutgoingHeader.) - [ ] **Step 2: Create `FurniEditorUpdateFurnidataComposer.ts`** (mirror the existing `FurniEditorUpdateComposer` in the same folder): ```ts import { IMessageComposer } from '../../../../api'; import { OutgoingHeader } from '../OutgoingHeader'; export class FurniEditorUpdateFurnidataComposer implements IMessageComposer> { private _data: ConstructorParameters; constructor(itemId: number, jsonFields: string) { this._data = [ itemId, jsonFields ]; } public getMessageArray() { return this._data; } public dispose() { this._data = null; } public getHeader() { return OutgoingHeader.FURNI_EDITOR_UPDATE_FURNIDATA; } } ``` **Before writing, open the real `FurniEditorUpdateComposer.ts`** and copy its EXACT structure/imports (the `IMessageComposer` import path + the `getMessageArray/getHeader/dispose` shape may differ from the above; match it verbatim, only changing the header constant and that the payload is `[itemId, jsonFields]`). - [ ] **Step 3: Create `FurniEditorRevertFurnidataComposer.ts`** — same pattern, constructor `(itemId: number)`, payload `[ itemId ]`, header `FURNI_EDITOR_REVERT_FURNIDATA`. - [ ] **Step 4: Export both** from the furnieditor composers `index.ts` barrel (add the two `export * from './FurniEditor...Composer';` lines next to the existing furni-editor composer exports). - [ ] **Step 5: Build** — `cd E:\Users\simol\Desktop\DEV\Nitro_Render_V3 && yarn compile:fast` (or the real compile script in package.json). Expected: clean, no TS errors. - [ ] **Step 6: Commit** (renderer repo): ``` git -C "E:/Users/simol/Desktop/DEV/Nitro_Render_V3" add packages/communication/src/messages/outgoing/OutgoingHeader.ts packages/communication/src/messages/outgoing/furnieditor/ git -C "E:/Users/simol/Desktop/DEV/Nitro_Render_V3" commit -m "feat(furnieditor): outgoing composers for furnidata update (10046) + revert (10048)" ``` NO `Co-Authored-By` trailer. --- ## Task 2 (client): hook actions **Files:** Modify `E:\Users\simol\Desktop\DEV\Nitro-V3\src\hooks\furni-editor\useFurniEditor.ts` - [ ] **Step 1: Parse furnidata name/desc into state.** Where the detail handler parses `furniDataJson` into `furniDataEntry` (lines ~140–152), also derive convenience strings. The `furniDataEntry` is `Record` with `name`/`description` keys. No new state needed — the EditView will read `furniDataEntry?.name`/`furniDataEntry?.description`. (No change required here if the EditView reads `furniDataEntry`; otherwise expose `furniDataName`/`furniDataDescription` strings. Choose the minimal path — prefer reading `furniDataEntry` directly in the view.) - [ ] **Step 2: Add actions.** Mirror `updateItem` (lines ~233–239). Add inside the hook body and to the return object (lines ~254–259): ```ts const updateFurnidata = useCallback((id: number, name: string, description: string) => { pendingActionRef.current = { type: 'update', id }; setLoading(true); SendMessageComposer(new FurniEditorUpdateFurnidataComposer(id, JSON.stringify({ name, description }))); }, []); const revertFurnidata = useCallback((id: number) => { pendingActionRef.current = { type: 'update', id }; setLoading(true); SendMessageComposer(new FurniEditorRevertFurnidataComposer(id)); }, []); ``` Use the REAL send-composer helper this hook already uses (the exploration shows `updateItem` sends `new FurniEditorUpdateComposer(...)` — copy its exact send mechanism, whether `SendMessageComposer(...)` or a local `send`). Import the two new composers from `@nitrots/nitro-renderer`. Reusing `pendingActionRef.type='update'` makes the existing `FurniEditorResultEvent` success handler (lines ~162–210) auto-reload the detail — which is what we want after a furnidata write. - [ ] **Step 3: Export** `updateFurnidata`, `revertFurnidata` in the hook's return object. - [ ] **Step 4: Typecheck** — `cd E:\Users\simol\Desktop\DEV\Nitro-V3 && yarn typecheck`. Expected: no new errors (pre-existing renderer-SDK TS2307 in a sandbox without the renderer are acceptable, but here the renderer IS present so it should be clean for these files). - [ ] **Step 5: Commit:** ``` git -C "E:/Users/simol/Desktop/DEV/Nitro-V3" add src/hooks/furni-editor/useFurniEditor.ts git -C "E:/Users/simol/Desktop/DEV/Nitro-V3" commit -m "feat(furni-editor): updateFurnidata/revertFurnidata hook actions" ``` NO `Co-Authored-By`. --- ## Task 3 (client): EditView — read-only classname/public_name + editable Furnidata section + props **Files:** Modify `src\components\furni-editor\views\FurniEditorEditView.tsx` and `src\components\furni-editor\FurniEditorView.tsx`. - [ ] **Step 1: Thread props.** In `FurniEditorEditViewProps` add `onUpdateFurnidata: (id: number, name: string, description: string) => void;` and `onRevertFurnidata: (id: number) => void;`. In `FurniEditorView.tsx` (where `` is rendered, ~lines 149–158), pass `onUpdateFurnidata={ updateFurnidata }` and `onRevertFurnidata={ revertFurnidata }` (destructure them from `useFurniEditor()`). - [ ] **Step 2: Make Classname + Public Name read-only.** In the Basic Info section (lines ~232–256): replace the **Item Name** `` with a read-only display, relabel to **"Classname"**, and render the value in monospace on a muted background (see Task 4 classes). Same for **Public Name** (label it "Public Name (DB fallback)"). Use a shared `readonlyClass` (Task 4). Keep `form.itemName`/`form.publicName` in state (so `updateItem` still sends unchanged values harmlessly) but do NOT let them be edited. Example: ```tsx
{ form.itemName }
{ form.publicName }
``` - [ ] **Step 3: New editable Furnidata section.** Replace the read-only `FurniData.json` section (lines ~323–334) with: ```tsx
setFurniName(e.target.value) } maxLength={ 256 } />