refactor: route capability bucket through PlatformPlugin + pin supports() closures (Phase 3 step b)#965
Merged
Merged
Conversation
…ts() closures (Phase 3 step b) b.1: isCommandSupportedOnDevice now reads each platform's capability bucket from getPlugin(device.platform).capability.bucket (the PlatformPlugin registry, ADR-0009) instead of the platformDescriptors fold. capabilities.ts registers the builtin plugins at module load (idempotent, lazy closures only) so the admission path populates the registry without depending on core/interactors.ts load order. b.2: the per-command supports()/unsupportedHint() closures stay VERBATIM on the command-descriptor facet; they cannot move to the plugin's per-FAMILY capability.supportsByDefault without flattening their per-command shape (perfect-shape §7). A new table-equivalence parity test pins both the bucket-route swap and the closures byte-for-byte across the full platform x command x device-kind x target matrix.
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
Member
Author
|
Reviewed against plans/perfect-shape.md, plans/phase3-platform-plugin-progress.md, ADR-0003, and ADR-0009. This is a scoped Phase 3 b.1/b.2 slice: capability bucket lookup now flows through PlatformPlugin, while the per-command supports and unsupportedHint closures remain on the command capability facet rather than being flattened into a family default. The parity test pins plugin bucket selection against the old platformDescriptors fold and separately pins the supports/hint behavior across the command/device matrix. I do not see a routing or architecture blocker; checks are green. Marking ready-for-human. |
|
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 3 step (b.1) + (b.2) — parity-gated capability migration onto PlatformPlugin
Routes the per-platform capability bucket through the merged
PlatformPluginregistry (step a / PR #956) and pins the devicesupports()/unsupportedHint()closures, parity-gated and byte-identical. Scope is strictly b.1 + b.2 — no daemon facets (b.3) and no macOS unwind (c).device.platformstaysios/macoson the wire; no leaf code (XCTest synthesis, adb/idb, tvOS focus) touched.What I migrated (b.1 — bucket source unified)
src/core/capabilities.tsisCommandSupportedOnDevicenow selects the per-platform capability bucket viagetPlugin(device.platform).capability.bucket(the single PlatformPlugin registry) instead of thederiveCapabilityForPlatform(platformDescriptors, …)fold. This makes the plugin registry the single bucket source (perfect-shape §5.1'sconst m = cap[plugin.capability.bucket]).selectCapabilityForPlatformhelper and theplatformDescriptors/deriveCapabilityForPlatformimports are removed fromcapabilities.ts.platformDescriptorsitself is kept (per the plan: "keep until proven redundant") — it remains the parity oracle thatplatform-plugin/__tests__/parity.test.tspins the plugin buckets against.capabilities.tsnow callsregisterBuiltinPlatformPlugins()at module load (idempotent, registers only lazy closures — no leaf imports, no cold-start cost). This is required for correctness: the admission path reachesisCommandSupportedOnDeviceviadaemon/handlers/response.tswithout necessarily having loadedcore/interactors.ts(the only other registrar), so the registry must be populated here too. The import chain (capabilities → register-builtins → {plugin, platform-inventory}) is value-cycle-free (those modules only type-importcapabilities).What I deliberately left hand-authored (b.2)
The per-command
supports()/unsupportedHint()closures (src/core/command-descriptor/registry.ts:41-59—isNotMacOs,isMacOsOrAppleSimulator,isIosMobileSimulator,supportsSynthesisGesture,supportsAndroidOrIosNonTv,synthesisGestureUnsupportedHint, plus the inlineclipboard/alert/settingsclosures) stay VERBATIM on the command-descriptor facet.They are per-command predicates; the plugin's
capability.supportsByDefaultis per-platform-family. Moving them onto the plugin would either lose the per-command shape (flatten — forbidden by perfect-shape §7) or require a per-command map on the plugin (breaking the §5.2 command-facet ownership for no benefit). There is no family-wide Apple guard hiding here — different Apple commands gate differently (isNotMacOsvssupportsAndroidOrIosNonTvvssupportsSynthesisGesture), so an apple-pluginsupportsByDefaultwould be a no-op. This realizes the progress-plan's explicitly-sanctioned option 2: "keep them on the command facet and have the platform-level default flow through the plugin" (the bucket route is b.1; the closures stay put). The plugin's reservedsupportsByDefaultis left for the step-(c) AppleOS sub-platform guards.Parity tests (the gate)
New
src/core/__tests__/capability-plugin-routing-parity.test.ts— two independent oracles, proven byte-for-byte BEFORE the fold was removed:BASE_COMMAND_CAPABILITY_MATRIXshape (plus a synthetic web-bearing shape and a sparse shape) × everyPlatform, assertscapability[getPlugin(p).capability.bucket]deep-equalsderiveCapabilityForPlatform(platformDescriptors, capability, p).deriveCapabilityForPlatformis retained as the before-derivation oracle, so a plugin-vs-descriptor disagreement fails the test (incl. thewebbucket).isCommandSupportedOnDeviceover the full{command × device}matrix (the reuseddevice-fixtures.tsfixtures + an exhaustive synthetic platform×kind×target cross-product) equals an independent reference that reproduces the before pipeline (descriptor-fold bucket + verbatim supports closure + kind check) for all non-web platforms.unsupportedHintForDeviceis byte-for-byte equal (string equality) to independent verbatim copies of the hint closures across the full matrix.supports/unsupportedHintclosure is present in the reference map, so the oracle can't silently miss one.webadmission (not covered by the descriptor-fold reference, which lacks the web augmentation) is pinned by the bucket-selection test above plus the unchanged web column ofcapabilities.test.ts.Byte-identical argument
The only change inside
isCommandSupportedOnDeviceis swapping the bucket-selection expression; the web augmentation, thesupportsgate, and the kind check are untouched. Test 1 proves the swapped expression is value-identical to the old fold for every capability shape × platform, and Tests 2-4 prove the full admission + hint surface is unchanged across the exhaustive device matrix. For a typedPlatform,tryGetPluginnever returnsundefined(registry totality is compile-asserted byBuiltinPluginsCoverAllPlatforms), so thereturn falsefall-through is the unreachable counterpart of the old fold's never-default (perfect-shape §5.1).Local verification (worktree)
tsc -p tsconfig.json✓oxlint . --deny-warnings✓oxfmt --check✓rslib build✓fallow audit --base origin/main→ No issues in changed files ✓vitest run --project unit: core (682), platforms/tests (706), the 5 capability/descriptor/plugin parity files (27), request-router-typed-error + args (137) — all green ✓