PURPOSE

Controlled React editor for a list of UniformDecl entries used by the VFX/weapon-workbench shader component schema. Renders one row per uniform with a type dropdown and doc input, plus a draft row for adding a new uniform with name validation and duplicate-name guarding.

OWNS

  • Local draft state via useState: draftName (string), draftType (UniformKind, default 'float'), and error (string | null).
  • The VALID_NAME regex ^[a-zA-Z_][a-zA-Z0-9_]{0,63}$ enforcing C-style identifiers up to 64 chars.
  • The UNIFORM_KINDS list: 'float', 'int', 'vec2', 'vec3', 'vec4', 'sampler2D'.
  • Three local mutators: setUniform (patches one row by index), addUniform (validates then appends), deleteUniform (filters by name).
  • Inline styling for the uniforms block: dark panel rows on #14141e, monospace name cells, #44ffcc add button, #442222 delete button, #ff7777 error text.

READS FROM

  • value: UniformDecl[] prop — the current uniform list rendered as rows.
  • UniformDecl and UniformKind type imports from @engine/vfx-workbench/component-schema.
  • Local draftName, draftType, error state for the add-row UI.

PUSHES TO

  • onChange(next: UniformDecl[]) prop — invoked from setUniform (patched copy), addUniform (appended entry with { name, type, doc: '' }), and deleteUniform (filtered copy).
  • setDraftName('') after a successful add; setError(null) at the start of an add attempt and setError('invalid name') / setError('name already exists') on validation failure.

DOES NOT

  • Persist uniforms — has no store, file, or network access; lifts all changes through onChange.
  • Edit uniform names after creation — the existing-row name cell is a read-only <span>; renaming requires delete + re-add.
  • Validate uniform values, defaults, ranges, or shader compatibility — only the name regex and duplicate-name check.
  • Provide reordering, drag-and-drop, or undo.
  • Coerce doc content — accepts any string verbatim and writes doc: '' for new entries.
  • Render a header label beyond the UNIFORMS caption; no count, no help text.

Signals

  • data-testid="uniform-row-${name}" — one per existing uniform row.
  • data-testid="uniform-type-${name}" — type <select> per row.
  • data-testid="uniform-doc-${name}" — doc <input> per row.
  • data-testid="delete-uniform-${name}" — row delete button.
  • data-testid="new-uniform-name" — draft name input.
  • data-testid="new-uniform-type" — draft type select.
  • data-testid="add-uniform" — draft add button.
  • data-testid="uniform-editor-error" — error message div, only rendered when error is non-null.

Entry points

  • Default export: none. Named export UniformSchemaEditor({ value, onChange }).
  • Props interface Props { value: UniformDecl[]; onChange: (next: UniformDecl[]) => void }.
  • Consumed by parent weapon-workbench shader/component editor screens that own the persisted UniformDecl[].

Pattern notes

  • Pure controlled component: parent owns the array, child owns only the draft inputs and transient error.
  • Validation happens at add-time only; existing rows assume the prop list is already well-formed.
  • Duplicate-name check uses value.some((u) => u.name === draftName) against current props, not against the draft history.
  • Type cast e.target.value as UniformKind trusts the <select> options to enumerate the full UniformKind union.
  • Row keying uses u.name directly, which relies on names being unique — guaranteed by the add-time duplicate check.
  • Styling is inline and self-contained; there is no CSS module or theme hook.