PURPOSE

Controlled React editor for the AnchorMap of a weapon-workbench component. Lets the designer add, rename via kind change, parameterize, and delete named attachment points (pivot / edge / radial) that other components or weapons snap to when building composite effects.

OWNS

  • Local UI draft state: draftName, draftKind, error.
  • Validation gate canAdd derived from draftName length, isValidAnchorName, and uniqueness against current anchors.
  • Per-row controls: kind select, optional radius input (radial only), delete button (disabled for center).
  • Inline error display fed by thrown errors from the anchor mutators.

READS FROM

  • Props anchors: AnchorMap and onChange: (next: AnchorMap) => void.
  • @engine/vfx-workbench/anchor-types for addAnchor, removeAnchor, updateAnchor, isValidAnchorName, and types AnchorMap, AnchorSpec, AnchorKind.

PUSHES TO

  • Parent via onChange(next) with the result of addAnchor, removeAnchor, or updateAnchor. The component is fully controlled; it never mutates state in place.

DOES NOT

  • Persist anchors. Persistence is the parent’s responsibility.
  • Resolve anchor positions in world space — purely a metadata editor.
  • Render a preview of the anchor on the component.
  • Allow deletion of the reserved center anchor.
  • Allow renaming an existing anchor (only kind/radius edits and add/delete).
  • Validate radius bounds beyond parseFloat of the input value.

Signals

  • onChange(next: AnchorMap) after every successful add / delete / kind change / radius change.
  • Errors thrown by mutator functions are caught and surfaced via the local error string (red text under the editor).

Entry points

  • Default kind defaults applied on add: edge gets direction: 'forward'; radial gets radius: 16.
  • data-testid hooks: anchor-kind-<name>, anchor-radius-<name>, delete-anchor-<name>, new-anchor-name, new-anchor-kind, add-anchor.
  • Imported by parent screens in src/starship-survivors/screens/weapon-workbench/ that own a component’s AnchorMap.

Pattern notes

  • Pure controlled component — single onChange upward, no internal copy of anchors.
  • All mutations go through the engine helpers (addAnchor / removeAnchor / updateAnchor) so invariants (name validity, center protection, uniqueness) live in one place and throw on violation.
  • Error handling catches thrown Error objects and falls back to String(e) so the UI never blanks on a non-Error throw.
  • Add button is gated by canAdd to prevent invalid submissions; kind dropdown drives spec shape (radius input appears only for radial).
  • Inline styles only; no external CSS dependency. Fonts: Cal Sans for the header label, Space Grotesk for help copy, monospace for anchor names.