Skip to content

Commit e8755e6

Browse files
committed
refactor(layering): split the Wave 6 cutover rows into a sibling module
Second review P2 on #2021. `runtime-command-cutover-table.ts` had reached 1,325 lines, past the point where one read covers it. Wave 6's eight rows move to `runtime-command-cutover-table-wave6.ts` and are spread back in, leaving the table at 1,095 lines. The split is by wave because that is how these rows are retired: a wave's rows are deleted together once the ADR declares its commands' migrations closed, and deleting a whole file is a cleaner end than excising a run of literals from the middle of a larger one. `retiredDispatchProjectionProof` moves to the shared extensions module, since both tables now use it — the main table for `snapshot`/`diff`, the sibling for its own eight. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RpfS12XApXqasuAWZJaEX
1 parent 7521dc3 commit e8755e6

5 files changed

Lines changed: 218 additions & 201 deletions

File tree

packages/platform-android/src/runtime.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,21 +236,19 @@ export function createAndroidPlatformRuntime(host: PlatformRuntimeHost): Platfor
236236
* the device is up, and admission would otherwise pay an adb round trip per request.
237237
*/
238238
const clipboardShell = new Map<string, Promise<boolean>>();
239-
const clipboardShellSupported = async (device: DeviceInfo): Promise<boolean> => {
239+
const clipboardFact = async (device: DeviceInfo): Promise<RuntimeOperationFact> => {
240+
if (device.kind === 'simulator') return clipboardShellUnavailable;
240241
let probe = clipboardShell.get(device.id);
241242
if (!probe) {
242243
probe = probeAndroidClipboardShell(host, device);
243244
clipboardShell.set(device.id, probe);
244245
}
245-
return await probe;
246+
return (await probe) ? available : clipboardShellUnavailable;
246247
};
247248
const inspectFacts = async (device: Parameters<typeof appLogs.inspectFacts>[0]) => {
248249
const logs = await appLogs.inspectFacts(device);
249250
const deployment = androidAppDeploymentFacts(device);
250-
const clipboardCell =
251-
device.kind === 'simulator' || !(await clipboardShellSupported(device))
252-
? clipboardShellUnavailable
253-
: available;
251+
const clipboardCell = await clipboardFact(device);
254252
return Object.freeze({
255253
device: logs.device,
256254
operations: {

scripts/layering/runtime-command-cutover-extensions.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { retiredDispatchProjectionViolations } from './runtime-command-cutover-descriptor.ts';
12
import { parseSync } from 'oxc-parser';
23
import { propertyName, visitAst, type ProductionSource } from './cutover-policy-ast.ts';
34
import { lineOf } from './runtime-command-cutover-ast.ts';
@@ -11,6 +12,17 @@ export {
1112
runtimeLifecycleRouteBindingViolations,
1213
} from './runtime-command-cutover-lifecycle.ts';
1314

15+
/**
16+
* Every migrated row proves the same thing about its own command: the retired dispatch projection
17+
* is gone. One factory carries the command, so a row states its name once in the row literal
18+
* rather than adding another identically-shaped wrapper beside the table.
19+
*/
20+
export function retiredDispatchProjectionProof(
21+
command: string,
22+
): (sources: ReadonlyMap<string, string>) => UnruledViolation[] {
23+
return (sources) => retiredDispatchProjectionViolations(sources, command);
24+
}
25+
1426
type AstNode = Record<string, unknown>;
1527

1628
const DEVICES_HANDLER_FILE = 'src/daemon/handlers/session-inventory.ts';
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import type { MigratedCommandCutover } from './runtime-command-cutover-model.ts';
2+
import { retiredDispatchProjectionProof } from './runtime-command-cutover-extensions.ts';
3+
4+
/**
5+
* Wave 6's rows (#1739): the seven named command units that left `platformExecution: legacy` for
6+
* request-bound runtimes, plus `react-native`.
7+
*
8+
* They live beside `runtime-command-cutover-table.ts` rather than inside it because that file had
9+
* grown past the point where one read covers it. The split is by wave, which is how the tracker
10+
* retires these rows: a wave's rows are deleted together once this ADR declares its commands'
11+
* migrations closed, and a whole-file deletion is a cleaner end than excising a run of literals
12+
* from the middle of a larger table.
13+
*/
14+
export const WAVE_6_COMMAND_CUTOVERS: readonly MigratedCommandCutover[] = [
15+
{
16+
rule: 'R55 clipboard-runtime-cutover',
17+
command: 'clipboard',
18+
subject: 'device clipboard',
19+
tier: 'request-scoped',
20+
execution: 'device-runtime',
21+
legacyRetirement: {
22+
// The dispatch-table arm and its `core/dispatch.ts` handler. The daemon route function was
23+
// renamed to `handleSessionClipboardCommand` when it moved out of the over-budget
24+
// `handlers/session.ts`, so this name is now genuinely absent from production rather than
25+
// shadowed by a surviving namesake.
26+
routeNames: ['handleClipboardCommand'],
27+
},
28+
admissionMember: {
29+
forms: ['computed-property'],
30+
files: ['src/platforms/apple/plugin.ts'],
31+
message: 'Apple plugin retains a legacy clipboard support or hint closure',
32+
},
33+
runtimeTypeNames: ['ClipboardRuntimeOperations'],
34+
operations: { names: ['readClipboard', 'writeClipboard'] },
35+
singularExecution: {
36+
// `clipboard` is action-selected (R35's lesson): the session route resolves exactly one of
37+
// the two operations per request, binds once, and never both together.
38+
routes: ['resolveBoundClipboardRuntime'],
39+
operations: ['readClipboard', 'writeClipboard'],
40+
operationOwners: {
41+
readClipboard: ['executeClipboardRead'],
42+
writeClipboard: ['executeClipboardWrite'],
43+
},
44+
},
45+
extensions: [retiredDispatchProjectionProof('clipboard')],
46+
},
47+
{
48+
rule: 'R56 app-switcher-runtime-cutover',
49+
command: 'app-switcher',
50+
subject: 'app switcher reveal',
51+
tier: 'request-scoped',
52+
execution: 'device-runtime',
53+
legacyRetirement: {
54+
// The dispatch-table arm; `app-switcher` had no dedicated named handler function to retire
55+
// (its legacy body lived inline in the `DISPATCH_HANDLERS` literal). It also leaves the
56+
// HarmonyOS overlay that granted it a capability bucket the descriptor never listed.
57+
staticCommandSets: ['HARMONYOS_SUPPORTED_COMMANDS'],
58+
},
59+
admissionMember: {
60+
forms: ['computed-property'],
61+
files: ['src/platforms/apple/plugin.ts'],
62+
message: 'Apple plugin retains a legacy app-switcher support or hint closure',
63+
},
64+
runtimeTypeNames: ['AppSwitcherRuntimeOperations'],
65+
operations: { names: ['appSwitcher'] },
66+
singularExecution: {
67+
routes: ['dispatchGenericCommand'],
68+
operations: ['appSwitcher'],
69+
operationOwners: { appSwitcher: ['executeAppSwitcher'] },
70+
},
71+
extensions: [retiredDispatchProjectionProof('app-switcher')],
72+
},
73+
{
74+
rule: 'R57 trigger-app-event-runtime-cutover',
75+
command: 'trigger-app-event',
76+
subject: 'app-event delivery',
77+
tier: 'request-scoped',
78+
execution: 'device-runtime',
79+
legacyRetirement: {
80+
// The dispatch-table arm and its `core/dispatch.ts` handler, plus the session route's
81+
// last capability-gate-then-`dispatchCommand` thunk: with `trigger-app-event` bound, every
82+
// leaf on that route supplies a bind-and-execute thunk instead. The daemon route function
83+
// is `handleAppEventCommand` now, so the retired name is genuinely absent rather than
84+
// shadowed by a surviving namesake.
85+
routeNames: ['handleTriggerAppEventCommand', 'legacySessionDispatchExecute'],
86+
},
87+
runtimeTypeNames: ['AppEventRuntimeOperations'],
88+
operations: { names: ['triggerAppEvent'] },
89+
singularExecution: {
90+
routes: ['resolveBoundAppEventRuntime'],
91+
operations: ['triggerAppEvent'],
92+
operationOwners: { triggerAppEvent: ['executeAppEvent'] },
93+
},
94+
extensions: [retiredDispatchProjectionProof('trigger-app-event')],
95+
},
96+
{
97+
rule: 'R58 settings-runtime-cutover',
98+
command: 'settings',
99+
subject: 'device settings mutation',
100+
tier: 'request-scoped',
101+
execution: 'device-runtime',
102+
legacyRetirement: {
103+
// `settings` was the last `DISPATCH_HANDLERS` arm, so this row retires the legacy command
104+
// dispatcher whole — its table, its three entry points, its name enumerator, and the
105+
// request-router fallback that reached for it when no runtime route claimed a command.
106+
// Every one of these names is now absent from production rather than shadowed.
107+
routeNames: [
108+
'dispatchCommand',
109+
'dispatchWithInteractor',
110+
'dispatchKnownCommand',
111+
'DISPATCH_HANDLERS',
112+
'listRegisteredDispatchCommandNames',
113+
'executeGenericPlatformCommand',
114+
],
115+
// Settings also leaves the HarmonyOS overlay that granted it a bucket membership the
116+
// descriptor never listed; the set still exists for `perf` and must no longer name it.
117+
staticCommandSets: ['HARMONYOS_SUPPORTED_COMMANDS'],
118+
},
119+
admissionMember: {
120+
forms: ['computed-property'],
121+
files: ['src/platforms/apple/plugin.ts'],
122+
message: 'Apple plugin retains a legacy settings support or hint closure',
123+
},
124+
runtimeTypeNames: ['SettingsRuntimeOperations'],
125+
operations: { names: ['setSetting'] },
126+
singularExecution: {
127+
routes: ['handleSettingsCommand'],
128+
operations: ['setSetting'],
129+
operationOwners: { setSetting: ['executeSetSetting'] },
130+
},
131+
extensions: [retiredDispatchProjectionProof('settings')],
132+
},
133+
{
134+
rule: 'R59 alert-runtime-cutover',
135+
command: 'alert',
136+
subject: 'native alert handling',
137+
tier: 'request-scoped',
138+
execution: 'device-runtime',
139+
legacyRetirement: {
140+
// `alert` never had a dispatch-table arm: its daemon route called the Apple runner, the
141+
// macOS helper and the Android alert module directly, and owned their poll and retry
142+
// windows itself. Those four names are what R59 retires, and with the Apple closure gone
143+
// the per-AppleOS capability table lost its last reader and went too.
144+
routeNames: [
145+
'handleNativeAlertCommand',
146+
'waitForNativeAlert',
147+
'handleNativeAlertAction',
148+
'supportsAlertSurface',
149+
],
150+
modulePaths: ['src/platforms/apple/capabilities.ts'],
151+
},
152+
admissionMember: {
153+
forms: ['computed-property'],
154+
files: ['src/platforms/apple/plugin.ts'],
155+
message: 'Apple plugin retains a legacy alert support or hint closure',
156+
},
157+
runtimeTypeNames: ['AlertRuntimeOperations'],
158+
operations: { names: ['readAlert', 'awaitAlert', 'acceptAlert', 'dismissAlert'] },
159+
singularExecution: {
160+
// Action-selected (R35's lesson): the snapshot route resolves exactly one of the four legs
161+
// per request, binds once, and never two together.
162+
routes: ['resolveBoundAlertRuntime'],
163+
operations: ['readAlert', 'awaitAlert', 'acceptAlert', 'dismissAlert'],
164+
operationOwners: {
165+
readAlert: ['executeReadAlert'],
166+
awaitAlert: ['executeAwaitAlert'],
167+
acceptAlert: ['executeAcceptAlert'],
168+
dismissAlert: ['executeDismissAlert'],
169+
},
170+
},
171+
extensions: [retiredDispatchProjectionProof('alert')],
172+
},
173+
{
174+
rule: 'R61 react-native-runtime-cutover',
175+
command: 'react-native',
176+
subject: 'React Native overlay dismissal',
177+
tier: 'request-scoped',
178+
execution: 'device-runtime',
179+
legacyRetirement: {
180+
// The command's device work moved onto a bound `tapPoint` with R48; what R61 retires is the
181+
// capability gate that still stood in front of it (proved by this table's own admission
182+
// check) and the resolve-then-execute shape that gate implied. The dismissal function was
183+
// renamed to `executeReactNativeOverlayDismiss` to record that: it no longer resolves
184+
// anything, so the old name is genuinely absent rather than shadowed by a namesake.
185+
routeNames: ['dismissReactNativeOverlayTarget'],
186+
},
187+
runtimeTypeNames: ['BoundTouchRuntime'],
188+
operations: { names: ['tapPoint'] },
189+
singularExecution: {
190+
// Admission happens once, before the observing capture; the dismissal reuses that binding
191+
// rather than admitting a second time after the overlay is known.
192+
routes: ['executeReactNativeOverlayDismiss'],
193+
operations: ['tapPoint'],
194+
operationOwners: { tapPoint: ['createTapTouchExecutor'] },
195+
},
196+
extensions: [retiredDispatchProjectionProof('react-native')],
197+
},
198+
];

0 commit comments

Comments
 (0)