feat: registry-driven exhaustive command dispatch — Phase 1 step 5#910
Merged
Conversation
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
Member
Author
|
iOS smoke (booted iPhone 17 Pro, source build of this tip): |
This was referenced Jun 27, 2026
7eecb1d to
9cffb43
Compare
ce9217a to
bae3034
Compare
thymikee
added a commit
that referenced
this pull request
Jun 27, 2026
…lify #909) — Phase 1 step 7 The command-descriptor registry is now `as const` (#910), so each entry keeps its literal `name` and literal `batchable`. Derive StructuredBatchCommandName from it via `Extract<…, { batchable: true }>['name']` instead of the 43-member hand-authored union from #909, and delete the now-tautological exhaustive Record membership assertion in parity.test.ts (type and value now derive from the same `batchable: true` entries). Strictly behaviorless: the derived union is the identical 43-member set (confirmed bidirectionally assignable to the old hand union, member count unchanged), the runtime allowlist value is unchanged, and the public BatchCommandName re-export is structurally identical (consumer switches on specific batch command names still typecheck).
9cffb43 to
7479e03
Compare
bae3034 to
9be0b60
Compare
thymikee
added a commit
that referenced
this pull request
Jun 27, 2026
…lify #909) — Phase 1 step 7 The command-descriptor registry is now `as const` (#910), so each entry keeps its literal `name` and literal `batchable`. Derive StructuredBatchCommandName from it via `Extract<…, { batchable: true }>['name']` instead of the 43-member hand-authored union from #909, and delete the now-tautological exhaustive Record membership assertion in parity.test.ts (type and value now derive from the same `batchable: true` entries). Strictly behaviorless: the derived union is the identical 43-member set (confirmed bidirectionally assignable to the old hand union, member count unchanged), the runtime allowlist value is unchanged, and the public BatchCommandName re-export is structurally identical (consumer switches on specific batch command names still typecheck).
7479e03 to
6f5f872
Compare
9be0b60 to
b3dd13c
Compare
thymikee
added a commit
that referenced
this pull request
Jun 27, 2026
…lify #909) — Phase 1 step 7 The command-descriptor registry is now `as const` (#910), so each entry keeps its literal `name` and literal `batchable`. Derive StructuredBatchCommandName from it via `Extract<…, { batchable: true }>['name']` instead of the 43-member hand-authored union from #909, and delete the now-tautological exhaustive Record membership assertion in parity.test.ts (type and value now derive from the same `batchable: true` entries). Strictly behaviorless: the derived union is the identical 43-member set (confirmed bidirectionally assignable to the old hand union, member count unchanged), the runtime allowlist value is unchanged, and the public BatchCommandName re-export is structurally identical (consumer switches on specific batch command names still typecheck).
Part A (enabler): make commandDescriptors `as const` so each entry keeps its literal `name`, and export `Command` — the literal command-name union. Part B: replace the dispatchKnownCommand switch with a `Record<DispatchCommand, DispatchHandler>` lookup table. The Record type forces every dispatch command to have a handler (a missing entry is now a COMPILE error, replacing the runtime `default: throw` as the coverage net); an `Object.hasOwn` guard preserves the identical INVALID_ARGS error for unknown commands. DispatchCommand is hand-authored to match the switch cases verbatim (it is not the registry `generic` route, and `swipe-preset`/`read` aren't registry names). Strictly behaviorless: same routing, same handler args, same results.
b3dd13c to
0c56eb2
Compare
thymikee
added a commit
that referenced
this pull request
Jun 27, 2026
…lify #909) — Phase 1 step 7 The command-descriptor registry is now `as const` (#910), so each entry keeps its literal `name` and literal `batchable`. Derive StructuredBatchCommandName from it via `Extract<…, { batchable: true }>['name']` instead of the 43-member hand-authored union from #909, and delete the now-tautological exhaustive Record membership assertion in parity.test.ts (type and value now derive from the same `batchable: true` entries). Strictly behaviorless: the derived union is the identical 43-member set (confirmed bidirectionally assignable to the old hand union, member count unchanged), the runtime allowlist value is unchanged, and the public BatchCommandName re-export is structurally identical (consumer switches on specific batch command names still typecheck).
|
thymikee
added a commit
that referenced
this pull request
Jun 27, 2026
…lify #909) — Phase 1 step 7 The command-descriptor registry is now `as const` (#910), so each entry keeps its literal `name` and literal `batchable`. Derive StructuredBatchCommandName from it via `Extract<…, { batchable: true }>['name']` instead of the 43-member hand-authored union from #909, and delete the now-tautological exhaustive Record membership assertion in parity.test.ts (type and value now derive from the same `batchable: true` entries). Strictly behaviorless: the derived union is the identical 43-member set (confirmed bidirectionally assignable to the old hand union, member count unchanged), the runtime allowlist value is unchanged, and the public BatchCommandName re-export is structurally identical (consumer switches on specific batch command names still typecheck).
thymikee
added a commit
that referenced
this pull request
Jun 27, 2026
…lify #909) — Phase 1 step 7 (#912) The command-descriptor registry is now `as const` (#910), so each entry keeps its literal `name` and literal `batchable`. Derive StructuredBatchCommandName from it via `Extract<…, { batchable: true }>['name']` instead of the 43-member hand-authored union from #909, and delete the now-tautological exhaustive Record membership assertion in parity.test.ts (type and value now derive from the same `batchable: true` entries). Strictly behaviorless: the derived union is the identical 43-member set (confirmed bidirectionally assignable to the old hand union, member count unchanged), the runtime allowlist value is unchanged, and the public BatchCommandName re-export is structurally identical (consumer switches on specific batch command names still typecheck).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1 step 5 of the command-descriptor migration (ADR 0008). Stacked on #909.
What
Part A — enabler. In
src/core/command-descriptor/registry.ts, thecommandDescriptorstable is nowas const(RAW_COMMAND_DESCRIPTORS … as const satisfies readonly Omit<CommandDescriptor, 'mcpExposed'>[], with theas constflowing through the.mapso each entry keeps its literalname). Exports the literal command-name union:The closure/function fields (
supports,unsupportedHint,allowSessionlessDefaultDevice, …) are fine underas const;tscstays clean.defineCommandDescriptoris no longer needed in the map (it widenednametostring) and is dropped from the registry's usage.Part B — exhaustive dispatch map. Replaces the
dispatchKnownCommandswitch (command) { case …: return handle*Command(…); default: throw … }with aconst DISPATCH_HANDLERS: Record<DispatchCommand, DispatchHandler>lookup. Every command routes to the identical handler with the identical arguments and return value the switch used; the inline cases (close/back/home/rotate/app-switcher) are preserved verbatim as inline arrows.How exhaustiveness is enforced
The
Record<DispatchCommand, DispatchHandler>type forces every dispatch command to have a handler — a missing entry is now a COMPILE error (verified: deleting thebackhandler yieldsTS2741: Property 'back' is missing … but required in type 'Record<DispatchCommand, DispatchHandler>'). This replaces the runtimedefault: throwas the coverage safety net. A runtime guard remains:Object.hasOwn(DISPATCH_HANDLERS, command)gates the lookup and throws the sameINVALID_ARGSUnknown command: ${command}error for unknown commands — and prevents inherited keys (e.g.toString) from resolving to a prototype method.DispatchCommandis hand-authored to match the switch cases verbatim. It is deliberately NOT derived from the registry'sgenericdaemon route: that set is both narrower (noopen/type/read/etc.) and includesgesture(which dispatch never handled), andswipe-preset/readare not registry command names at all. The mismatch is noted in a doc comment on the type.Behaviorless
Same routing, same handler args, same results, same error for unknown commands. No runtime behavior changed.
Verification
tsc -p tsconfig.json --noEmit→ exit 0; missing-handler probe → compile error (TS2741);Commandliteral-union probe → invalid name rejected.oxfmt --write+oxlint --deny-warningson changed files → clean.vitest run dispatch core/dispatch dispatch-interactions daemon core/command-descriptor→ 109 files, 992 tests passed.git grep→ empty.iOS e2e smoke pending reviewer.