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)andgetName(world, traitOrRelation); relation check first ($relationbrand, a relation is also a function), then trait check (function withschemaandid); idempotent and cumulative; atomic validation before any write, throwing withregisterNamesand 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:getNamereturns the name for a trait and for a relation,undefinedfor 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 namesregisterNamesand 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-inIsExcludedname in every world (getNamereturnsIsExcludedwith no registration; registering it asIsExcludedis a no-op, under another name throws, and another trait under the nameIsExcludedthrows). Tests: a name registered in world A isundefinedin world B (004); names surviveworld.reset()(004); built-inIsExcludedname and its two throw cases (004); canary: a trait registered by name but never used is not inworld.traits, andregisterNamesandgetNameleaveworld.traitsunchanged, with the same set of traits before and after (006; thelistEntitiesandinspectEntityhalves 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)(undefinedclears) in its own per-worldWeakMap, 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 whenfnthrows. Tests: label comes fromfn; an empty string,undefined, or a non-string falls back to#<id>; a throwingfnfalls 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)