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: the furnieditor index.ts barrel (same folder as the existing furni-editor composers)
Step 1: Add headers in OutgoingHeader.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):
import { IMessageComposer } from '../../../../api';
import { OutgoingHeader } from '../OutgoingHeader';
export class FurniEditorUpdateFurnidataComposer implements IMessageComposer<ConstructorParameters<typeof FurniEditorUpdateFurnidataComposer>>
{
private _data: ConstructorParameters<typeof FurniEditorUpdateFurnidataComposer>;
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 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 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<string,unknown> 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):
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: ExportupdateFurnidata, 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).
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 <FurniEditorEditView ... onUpdate=... onDelete=... /> 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<input> 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:
Add local state near the other state (lines ~71–91): const [furniName, setFurniName] = useState('');const [furniDescription, setFurniDescription] = useState('');const [confirmFurnidata, setConfirmFurnidata] = useState(false); and seed furniName/furniDescription from furniDataEntry?.name/?.description (falling back to item.publicName/item.description) in the same useEffect that syncs form (lines ~95–122), re-running when furniDataEntry changes.
Step 4: Diff + confirm modal (mirrors the existing Delete-confirm modal, lines ~353–368). When confirmFurnidata, show a small modal listing old → new:
Copy the exact overlay/panel Tailwind classes from the existing delete-confirmation modal so it looks identical.
Step 5: Typecheck + manual render.cd Nitro-V3 && yarn typecheck (clean). With yarn start running, open the editor on a furni: Classname/Public Name show read-only (monospace, muted), the Furnidata section shows the real display name from furnidata, editing + Save shows the confirm modal, Confirm sends the composer.
Task 4 (client): typography / color refresh (theme tokens)
The chosen direction: replace scattered hardcoded hex with theme tokens, restyle labels for hierarchy, bump input font + focus ring, and render read-only/technical values in monospace on a muted bg.
Files:FurniEditorEditView.tsx (the in-file helper class strings).
Step 1: Update the helper class strings near lines ~209–211:
(Match the real existing inputClass signature/errors variable name; only change the class string + add readonlyClass. text-secondary/focus:ring-primary resolve via tailwind.config.js tokens secondary=#185D79, primary=#1E7295.)
Step 2: Section titles — they already use <Text small bold variant="primary"> (theme #1E7295). Leave as-is (already token-aligned) OR, if a stronger separator is wanted, add border-b border-[#e3e3da] pb-1 to the section header row. Keep minimal.
Step 3: Apply font-mono to technical inline values already covered by readonlyClass (Classname/Public Name from Task 3). Also render the header ID: {id} | Sprite: {spriteId} (line ~223) in font-mono text-[#555] for consistency.
Step 4: Typecheck + visual check — yarn typecheck clean; with yarn start, confirm labels are now uppercase secondary-tinted, inputs larger with a focus ring, classname/public-name monospace on muted bg.
Step 1: Read the existing FurniEditorSearchEvent.handle() (it queries items_base by item_name/public_name LIKE the query). After collecting the DB matches, also scan the in-memory furnidata index for display-name matches and union their item ids:
Get the provider: FurnitureTextProvider p = Emulator.getGameEnvironment().getFurnitureTextProvider();
The provider currently exposes getName(classname) but not a name→classnames search. Add a method to FurnitureTextProvider: public java.util.List<String> findClassnamesByName(String q) that lowercases q and returns classnames whose indexed name contains it (iterate the index map values; cap results e.g. 200). Then map those classnames → items_base.id via a SELECT id FROM items_base WHERE item_name IN (...) and merge with the existing result rows (dedupe by id, keep the existing result row shape).
Keep it bounded (cap added rows) and behind the same ACC_CATALOGFURNI gate.
NO Co-Authored-By. (This task is optional/last — if it balloons, ship Tasks 1–4 first.)
Task 6: final build/verify
Renderer: cd Nitro_Render_V3 && yarn compile:fast clean.
Client: cd Nitro-V3 && yarn typecheck && yarn test --run green (pre-existing unrelated failures noted, not introduced).
Server (if Task 5 done): cd Emulator && mvn -q package -DskipTests=false SUCCESS; deploy jar to Latest_Compiled_Version + restart for manual end-to-end.
Manual acceptance: edit a furni's display name in the editor → confirm modal → live update in catalog/inventory/infostand without refresh; Revert restores; Classname/Public Name read-only; search by display name finds it; audit row written.
Furni editor — furnidata editing UI + typography refresh (Client/Renderer) Implementation Plan
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 existingFurniEditorResult(10044) and live propagation reuses the mergedFurnitureDataReload(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 = 10046readsint itemId+String(JSON{name,description}); incomingFurniEditorRevertFurnidataEvent = 10048readsint itemId; both respond withFurniEditorResultComposer(10044) and broadcastFurnitureDataReloadComposer(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(afterFURNI_EDITOR_DELETE = 10045, ~line 505)Create:
outgoing/furnieditor/FurniEditorUpdateFurnidataComposer.tsCreate:
outgoing/furnieditor/FurniEditorRevertFurnidataComposer.tsModify: the furnieditor
index.tsbarrel (same folder as the existing furni-editor composers)Step 1: Add headers in
OutgoingHeader.ts:(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.)FurniEditorUpdateFurnidataComposer.ts(mirror the existingFurniEditorUpdateComposerin the same folder):Before writing, open the real
FurniEditorUpdateComposer.tsand copy its EXACT structure/imports (theIMessageComposerimport path + thegetMessageArray/getHeader/disposeshape 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 ], headerFURNI_EDITOR_REVERT_FURNIDATA.Step 4: Export both from the furnieditor composers
index.tsbarrel (add the twoexport * 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):
NO
Co-Authored-Bytrailer.Task 2 (client): hook actions
Files: Modify
E:\Users\simol\Desktop\DEV\Nitro-V3\src\hooks\furni-editor\useFurniEditor.tsStep 1: Parse furnidata name/desc into state. Where the detail handler parses
furniDataJsonintofurniDataEntry(lines ~140–152), also derive convenience strings. ThefurniDataEntryisRecord<string,unknown>withname/descriptionkeys. No new state needed — the EditView will readfurniDataEntry?.name/furniDataEntry?.description. (No change required here if the EditView readsfurniDataEntry; otherwise exposefurniDataName/furniDataDescriptionstrings. Choose the minimal path — prefer readingfurniDataEntrydirectly 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):Use the REAL send-composer helper this hook already uses (the exploration shows
updateItemsendsnew FurniEditorUpdateComposer(...)— copy its exact send mechanism, whetherSendMessageComposer(...)or a localsend). Import the two new composers from@nitrots/nitro-renderer. ReusingpendingActionRef.type='update'makes the existingFurniEditorResultEventsuccess handler (lines ~162–210) auto-reload the detail — which is what we want after a furnidata write.Step 3: Export
updateFurnidata,revertFurnidatain 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:
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.tsxandsrc\components\furni-editor\FurniEditorView.tsx.Step 1: Thread props. In
FurniEditorEditViewPropsaddonUpdateFurnidata: (id: number, name: string, description: string) => void;andonRevertFurnidata: (id: number) => void;. InFurniEditorView.tsx(where<FurniEditorEditView ... onUpdate=... onDelete=... />is rendered, ~lines 149–158), passonUpdateFurnidata={ updateFurnidata }andonRevertFurnidata={ revertFurnidata }(destructure them fromuseFurniEditor()).Step 2: Make Classname + Public Name read-only. In the Basic Info section (lines ~232–256): replace the Item Name
<input>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 sharedreadonlyClass(Task 4). Keepform.itemName/form.publicNamein state (soupdateItemstill sends unchanged values harmlessly) but do NOT let them be edited. Example:FurniData.jsonsection (lines ~323–334) with:Add local state near the other state (lines ~71–91):
const [furniName, setFurniName] = useState('');const [furniDescription, setFurniDescription] = useState('');const [confirmFurnidata, setConfirmFurnidata] = useState(false);and seedfurniName/furniDescriptionfromfurniDataEntry?.name/?.description(falling back toitem.publicName/item.description) in the sameuseEffectthat syncsform(lines ~95–122), re-running whenfurniDataEntrychanges.confirmFurnidata, show a small modal listing old → new:Copy the exact overlay/panel Tailwind classes from the existing delete-confirmation modal so it looks identical.
Step 5: Typecheck + manual render.
cd Nitro-V3 && yarn typecheck(clean). Withyarn startrunning, open the editor on a furni: Classname/Public Name show read-only (monospace, muted), the Furnidata section shows the real display name from furnidata, editing + Save shows the confirm modal, Confirm sends the composer.Step 6: Commit:
NO
Co-Authored-By.Task 4 (client): typography / color refresh (theme tokens)
The chosen direction: replace scattered hardcoded hex with theme tokens, restyle labels for hierarchy, bump input font + focus ring, and render read-only/technical values in monospace on a muted bg.
Files:
FurniEditorEditView.tsx(the in-file helper class strings).(Match the real existing
inputClasssignature/errorsvariable name; only change the class string + addreadonlyClass.text-secondary/focus:ring-primaryresolve viatailwind.config.jstokenssecondary=#185D79,primary=#1E7295.)Step 2: Section titles — they already use
<Text small bold variant="primary">(theme#1E7295). Leave as-is (already token-aligned) OR, if a stronger separator is wanted, addborder-b border-[#e3e3da] pb-1to the section header row. Keep minimal.Step 3: Apply
font-monoto technical inline values already covered byreadonlyClass(Classname/Public Name from Task 3). Also render the headerID: {id} | Sprite: {spriteId}(line ~223) infont-mono text-[#555]for consistency.Step 4: Typecheck + visual check —
yarn typecheckclean; withyarn start, confirm labels are now uppercase secondary-tinted, inputs larger with a focus ring, classname/public-name monospace on muted bg.Step 5: Commit:
NO
Co-Authored-By.Task 5 (server): search also matches furnidata display name
Lets the Search box find furni by their real (furnidata) name, not just
item_name/public_name.Files (Arcturus): Modify
Emulator/.../messages/incoming/furnieditor/FurniEditorSearchEvent.java.Step 1: Read the existing
FurniEditorSearchEvent.handle()(it queriesitems_basebyitem_name/public_nameLIKE the query). After collecting the DB matches, also scan the in-memory furnidata index for display-name matches and union their item ids:FurnitureTextProvider p = Emulator.getGameEnvironment().getFurnitureTextProvider();getName(classname)but not a name→classnames search. Add a method toFurnitureTextProvider:public java.util.List<String> findClassnamesByName(String q)that lowercasesqand returns classnames whose indexed name contains it (iterate theindexmap values; cap results e.g. 200). Then map those classnames →items_base.idvia aSELECT id FROM items_base WHERE item_name IN (...)and merge with the existing result rows (dedupe by id, keep the existing result row shape).ACC_CATALOGFURNIgate.Step 2: Build
cd Emulator && mvn -q compile→ SUCCESS.Step 3: Commit (Arcturus repo,
main):NO
Co-Authored-By. (This task is optional/last — if it balloons, ship Tasks 1–4 first.)Task 6: final build/verify
cd Nitro_Render_V3 && yarn compile:fastclean.cd Nitro-V3 && yarn typecheck && yarn test --rungreen (pre-existing unrelated failures noted, not introduced).cd Emulator && mvn -q package -DskipTests=falseSUCCESS; deploy jar toLatest_Compiled_Version+ restart for manual end-to-end.Self-review
updateFurnidata(id,name,description),revertFurnidata(id),onUpdateFurnidata/onRevertFurnidataprops,readonlyClass— consistent across T2/T3/T4.inputClass/errorsnames (T4) by reading the files first.