Skip to content

refactor: relocate Apple supports()/unsupportedHint() closures onto the plugin — Phase 3 b.2 (#973)#993

Merged
thymikee merged 1 commit into
mainfrom
phase3-b2-supports-closures
Jul 1, 2026
Merged

refactor: relocate Apple supports()/unsupportedHint() closures onto the plugin — Phase 3 b.2 (#973)#993
thymikee merged 1 commit into
mainfrom
phase3-b2-supports-closures

Conversation

@thymikee

@thymikee thymikee commented Jul 1, 2026

Copy link
Copy Markdown
Member

Phase 3 step b.2 — relocate the Apple supports() / unsupportedHint() closures onto the PlatformPlugin

Closes #973 (part of #972). Moves the per-command supports() / unsupportedHint() device closures VERBATIM off the command-descriptor facet onto the owning plugin's capability.supportsByDefault / unsupportedHintByDefault (perfect-shape §7 / ADR-0009: relocate, never flatten). Behaviorless, parity-gated, no leaf code touched.

What moved (verbatim)

The closures at src/core/command-descriptor/registry.ts:41-59 (isNotMacOs, isMacOsOrAppleSimulator, isIosMobileSimulator, supportsSynthesisGesture, supportsAndroidOrIosNonTv, synthesisGestureUnsupportedHint) plus the inline clipboard / alert / settings closures now live on the Apple plugin in src/core/platform-plugin/register-builtins.ts, keyed by command in APPLE_SUPPORTS_BY_DEFAULT / APPLE_UNSUPPORTED_HINT_BY_DEFAULT. Bodies are byte-for-byte identical — only ownership moved. The synthesisGestureUnsupportedHint macOS-coordinate-pinch / tvOS-no-touch / physical-iOS strings are preserved exactly.

Why the Apple plugin (the faithfulness argument)

supportsByDefault is per-family; the closures are per-command — the tension PR #965 flagged when it deferred this step. The resolution: every one of these closures is a no-op on non-Apple devices — it returns true for android/linux that pass the bucket+kind check (or the command's non-Apple bucket already rejects the device before supports runs), and the hint closures return undefined off Apple. So the discriminating logic is entirely Apple-family. Consulting the closure only for the family that owns device.platform (i.e. Apple) is therefore byte-for-byte identical to the old command-facet gate. This is verified exhaustively (0 mismatches over the full platform×kind×target cross-product) before any hand site was deleted.

To keep the per-command shape without flattening to data, the plugin carries the closures as a command-keyed map of closures (Readonly<Record<string, (device) => boolean>>), not a boolean table — the device-shaped logic stays as closures.

Wiring

  • src/core/capabilities.ts: isCommandSupportedOnDevice reads plugin.capability.supportsByDefault?.[command]; unsupportedHintForDevice reads tryGetPlugin(device.platform)?.capability.unsupportedHintByDefault?.[command]. supports / unsupportedHint are removed from the CommandCapability type.
  • src/core/command-descriptor/registry.ts: closure defs deleted, supports: / unsupportedHint: stripped from every entry, unused DeviceInfo import dropped. The facet now carries platform/kind buckets only.
  • src/core/platform-plugin/plugin.ts: capability.supportsByDefault re-typed from the reserved per-family method to the per-command map, plus a new unsupportedHintByDefault counterpart.

Parity gate (before deleting the hand sites)

src/core/__tests__/capability-plugin-routing-parity.test.ts (independent VERBATIM oracle, unchanged copies of the closures):

  • (b.1) end-to-endisCommandSupportedOnDevice over the full {command × device} matrix still equals the before-pipeline reference (descriptor-fold bucket + verbatim supports + kind). Proves admission is unchanged.
  • (b.2) hintunsupportedHintForDevice is string-equal to the verbatim hint reference across the full matrix.
  • (b.2) the Apple plugin carries exactly the relocated supports/hint closures — the plugin maps' key sets equal the verbatim reference (nothing dropped/added); ios and macos share one map.
  • (b.2) the relocated Apple closures are byte-for-byte the verbatim originals — every relocated closure returns identical boolean / identical hint string vs the verbatim copy for every command × device-fixtures.ts sample-device.
  • (b.2) no non-Apple family carries a relocated supports/hint closure — guards the faithfulness precondition (android/linux/web have no per-command gate).

command-descriptor/__tests__/parity.test.ts updated (capability entries are now selectable by platform bucket alone).

Local verification (worktree, binaries direct)

  • tsc -p tsconfig.json
  • oxlint . --deny-warnings ✓ (exit 0)
  • oxfmt --write && --check src test
  • node scripts/layering/check.ts ✓ (635 files, R1/R2/R3)
  • rslib build
  • vitest run --project unit ✓ — 296 files / 2885 tests, no android flake this run
  • fallow audit --base origin/main ✓ — no issues in 7 changed files

Deferred / notes

  • Nothing from this scope deferred — every command-facet supports() / unsupportedHint() closure was relocated and parity-proven.
  • This supersedes PR refactor: route capability bucket through PlatformPlugin + pin supports() closures (Phase 3 step b) #965's "option 2" deferral (keep closures on the command facet). The capability-plugin-routing-parity.test.ts header comment claiming the closures "cannot move to the plugin's per-family supportsByDefault without flattening" is rewritten accordingly — the no-op-off-Apple property is what makes the move faithful.
  • Human-review-only. Do not merge without review.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.4 MB 1.4 MB +282 B
JS gzip 452.1 kB 452.2 kB +75 B
npm tarball 550.1 kB 550.2 kB +104 B
npm unpacked 1.9 MB 1.9 MB +282 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 28.5 ms 27.6 ms -0.9 ms
CLI --help 48.5 ms 48.0 ms -0.5 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/9722.js +1.3 kB +341 B
dist/src/1986.js -991 B -266 B

@thymikee

thymikee commented Jul 1, 2026

Copy link
Copy Markdown
Member Author

Reviewed current head 1ea4b86 against #973, ADR 0009, and plans/perfect-shape.md. I do not see blockers.

The PR moves the Apple supports() / unsupportedHint() closures out of the command-descriptor facet and onto the Apple plugin as command-keyed closures. That keeps the daemon/public descriptor surface to platform/kind buckets while preserving the device-shaped predicate bodies instead of flattening them into data. isCommandSupportedOnDevice and unsupportedHintForDevice now consult the owning platform plugin, which matches the platform-boundary direction.

The parity coverage is the important part here: it keeps independent verbatim references, checks the Apple plugin key set, checks relocated closure behavior across sample devices, checks hint strings, and confirms non-Apple plugins did not grow accidental gates. Checks are green and the size report is negligible.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Jul 1, 2026
…he plugin (#973)

Phase 3 step b.2. Move the per-command supports() / unsupportedHint() device
closures VERBATIM off the command-descriptor facet onto the owning
PlatformPlugin's capability.supportsByDefault / unsupportedHintByDefault
(perfect-shape §7 / ADR-0009: relocate, never flatten). Bodies are byte-for-byte
identical; only their ownership moves to the Apple plugin, the family that owns
every discriminating device (macOS-coordinate-pinch, tvOS-no-touch, physical-iOS,
two-finger-synthesis).

The relocation is faithful because every closure is a no-op (returns true /
undefined) on non-Apple devices, so consulting it only for the Apple family
leaves admission unchanged across the full device matrix. isCommandSupportedOnDevice
and unsupportedHintForDevice now read the closure off getPlugin(device.platform);
the command facet carries platform/kind buckets only, and supports/unsupportedHint
are removed from the CommandCapability type.

Parity gate (byte-for-byte, before deleting the hand sites): independent VERBATIM
oracle in capability-plugin-routing-parity.test.ts pins (a) production admission +
hint output unchanged across the {platform x command x kind x target} matrix, and
(b) the relocated Apple-plugin closures are behaviorally identical to the originals
across the device-fixtures sample matrix, with a guard that no non-Apple family
grew a gate.
@thymikee thymikee force-pushed the phase3-b2-supports-closures branch from 1ea4b86 to 0fd2f95 Compare July 1, 2026 10:31
@thymikee

thymikee commented Jul 1, 2026

Copy link
Copy Markdown
Member Author

Rebased onto main (now includes #988/#990/#991/#992). Resolved 2 conflicts:

Re-verified after rebase (all green): tsc, oxlint --deny-warnings, oxfmt --check, layering guard, full unit suite (2897), fallow. The b.2 parity, d.2 tvOS, and b.3 appLog tests all coexist.

Optional follow-up (not done, to keep the rebase faithful): the relocated closures still use device.target !== 'tv'; d.2 canonicalized that as isTvOsDevice — behaviorally identical, could be aligned for consistency.

@thymikee thymikee merged commit 3454ff1 into main Jul 1, 2026
21 checks passed
@thymikee thymikee deleted the phase3-b2-supports-closures branch July 1, 2026 10:38
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-07-01 10:38 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 3 b.2: relocate Apple supports()/unsupportedHint() closures onto the plugin

1 participant