PURPOSE
Right-pane editor for a selected VFX component inside the Weapon Workbench. Renders the live WebGL preview, exposes shape/palette/uniform/anchor controls for baked components, hosts the GLSL editor for live_shader components, and ships changes either to the runtime atlas (bake) or to disk as a JSON definition.
OWNS
- Local editor state:
params(uniform values),anchors,palette,live(LiveSection for live_shader),layers+selectedLayerId(schema v2 layer stack),shapePickerOpen,timelineT,showAnchors,baking,bakeStatus. - Refs:
canvasBoxRef(DOM mount for preview canvas),surfaceRef(PreviewSurfaceinstance),rafRef(animation frame handle),startTimeRef,scrubTRef(null = auto-play, number = scrub time). - Lifecycle of the
PreviewSurfaceWebGL context: construction, template selection, uniform pushes, the rAF render loop, and destruction on unmount/component switch. - Bake and save-to-disk request orchestration including in-flight status string.
READS FROM
useWorkbenchStore—selectedComponentId.SEED_COMPONENTS(./fixtures) — looked up by id to resolve the active component definition.TEMPLATESregistry from@engine/vfx-workbench/shader-templates/registry— providesuniformSchemaand template id for the shape section, shape knobs, draggable handles, and surface template selection.DEFAULT_PALETTEfrom@engine/vfx-workbench/palette— fallback when component has no palette.- Component fields read defensively via
any:kind,name,category,id,baked.params,baked.palette,baked.layers,baked.shader,anchors,live.
PUSHES TO
POST /__dev/live-shader-write— body{ componentId, name, category, fragGlsl, vertGlsl, uniformSchema, previewSampleInputs, blendMode, estCostMs, anchors }for live_shader bake.POST /__dev/atlas-write— body{ category, componentId, pngBase64, manifestEntry }for baked-shader bake; PNG produced bybakeComponent+bytesToBase64.POST /__dev/vfx-save— body{ kind: 'component', id, content }wherecontentis JSON-stringified component definition with mergedparams/palette/anchors.PreviewSurfaceinstance —useTemplate,setUniforms(params,u_paletteviaflattenPalette),render(t),destroy.- Child components via props:
LiveShaderEditor(value,onChange),AnchorEditor(anchors,onChange),PaletteWidget(value,onChange),ShapePicker(currentTemplateId,onPick),TimelineScrubber(currentT,onScrub,onAutoResume),AnchorOverlay(anchors,size,visible),DraggableHandleOverlay(canvasSize,uniforms,values,onChange),LayerStackEditor(layers,selectedLayerId,onSelect,onChange).
DOES NOT
- Mutate the
useWorkbenchStore; the editor is a read-only consumer ofselectedComponentId. - Modify
SEED_COMPONENTSin memory — edits live in local React state until baked or saved. - Implement Clone: the button is rendered but the
onClickbody is an empty placeholder comment. - Render anything for components whose
kindis neitherbakednorlive_shader. - Handle per-layer parameter editing for schema v2 stacks — caption explicitly defers that to “Phase 4b.”
- Persist UI prefs (overlay toggle, scrub state) across component switches; they reset in the id-change effect.
Signals
setBakeStatusstrings:'Shipping live shader…',`Shipped ✓ at ${data.shippedAt}`,`Ship failed: ${e.message}`,'Baking…',`Baked ✓ layer ${data.layerIndex} at ${data.bakedAt}`,`Bake failed: ${e.message}`,'Saved ✓',`Save failed: ${text}`.data-testidattributes exposed for tests:component-editor-empty,clone-button,shape-section,change-shape-button,component-editor-preview,anchor-overlay-toggle,bake-button,save-to-disk-button.aria-labelon each uniform range input is the uniformname.timelineTstate machine:'auto'= rAF loop ownst; numeric = scrubber ownstand rAF is cancelled untilhandleAutoResume.
Entry points
- Default export: none. Named export
ComponentEditor()React function component. - Constants: module-local
PREVIEW_SIZE = 192,SECTION_HEADERandSECTION_DIVIDERCSS objects. - Handlers:
handleBake,handleSaveToDisk,handleShapePick,handleScrub,handleAutoResume— all closures; not exported. - Mounted by the Weapon Workbench right pane; no router entry.
Pattern notes
- Branches early on
component.kind: empty placeholder →live_shaderreturns a minimal editor (header +LiveShaderEditor+AnchorEditor) → non-baked fallback → full baked editor. - Three effects keyed off
component?.id: state reset, preview surface mount + rAF loop teardown, plus two effects that pushparamsandpaletteinto the live surface on change. - The shape picker rebuilds defaults from
template.uniformSchema(only numeric defaults), and re-pushes them plus the palette to the surface viauseTemplate+setUniforms. DraggableHandleOverlay’sonChangeflattens array-valued uniforms intoname[i]keys before merging intoparams.- Uniform sliders read
range[0..1]with0..1fallback and a(max - min) / 100step. - Save-to-disk merges
params,palette, andanchorsinto the existingbakedblock (or justanchorsfor live_shader) before JSON-stringifying. - Layer stack section renders only when
bc.baked.layersis a non-empty array; selection defaults to the first layer. - Component switching cancels rAF, destroys the WebGL surface, and clears refs in the cleanup function.