Skip to content

W-044: ecs-devtools name registry (DEVTOOLS-F01)

Type: feature · Size: S · Epic: E-dnd-m1 · Target: packages/ecs-devtools · Breaking: no Depends: W-026 Refs: ecs-devtools spec DEVTOOLS-001..006 (group DEVTOOLS-F01; L3, L6)

Goal

Implement the per-world name registry in packages/ecs-devtools: registerNames, getName, and setEntityLabel, with the built-in IsExcluded name and the canary that registration leaves world.traits unchanged (DEVTOOLS-006). Headless, no React.

Steps

  • S1 (DEVTOOLS-001, DEVTOOLS-002, DEVTOOLS-003): registerNames(world, names) and getName(world, traitOrRelation); relation check first ($relation brand, a relation is also a function), then trait check (function with schema and id); idempotent and cumulative; atomic validation before any write, throwing with registerNames and the key for a non-trait/relation value, a name used for a different trait or relation, and a trait under a second name (including two keys for one trait in the same call); internal (not exported from the index) ordered-entries accessor in registration order for W-045 (DEVTOOLS-019). Tests: getName returns the name for a trait and for a relation, undefined for an unregistered one (001); the same call twice is a no-op and a second call keeps earlier entries (002); each of the three throw cases names registerNames and the key, and a call with one valid and one invalid entry registers neither (003); files: packages/ecs-devtools/src/registry.ts, packages/ecs-devtools/src/index.ts, packages/ecs-devtools/tests/registry-register.test.ts
  • S2 (DEVTOOLS-004, DEVTOOLS-006): per-world registry in a WeakMap<World, …> (no other module-level state); built-in IsExcluded name in every world (getName returns IsExcluded with no registration; registering it as IsExcluded is a no-op, under another name throws, and another trait under the name IsExcluded throws). Tests: a name registered in world A is undefined in world B (004); names survive world.reset() (004); built-in IsExcluded name and its two throw cases (004); canary: a trait registered by name but never used is not in world.traits, and registerNames and getName leave world.traits unchanged, with the same set of traits before and after (006; the listEntities and inspectEntity halves of 006 are tested in W-045); files: packages/ecs-devtools/src/registry.ts, packages/ecs-devtools/tests/registry-world.test.ts
  • S3 (DEVTOOLS-005; changeset): setEntityLabel(world, fn) (undefined clears) in its own per-world WeakMap, and an internal (not exported) getLabel(world, entity) that W-045 reuses for entities and pair targets: fn(entity) if it returns a non-empty string, otherwise #<entity.id()>, also when fn throws. Tests: label comes from fn; an empty string, undefined, or a non-string falls back to #<id>; a throwing fn falls back to #<id>; setEntityLabel(world, undefined) restores the default; labels are per world; add .changeset/w-044-ecs-devtools-name-registry.md (patch, @workspace/ecs-devtools); files: packages/ecs-devtools/src/entity-label.ts, packages/ecs-devtools/src/index.ts, packages/ecs-devtools/tests/entity-label.test.ts, .changeset/w-044-ecs-devtools-name-registry.md

Log

  • 2026-11-01 created (Triage), split from W-025
  • 2026-11-01 moved from Later to Next, ready (Depends W-026 done)
  • 2026-11-01 steps planned
  • 2026-11-02 moved down in Next: the devtools panel (DND-F10) is last in demo-dnd M1 (Triage)