From 65c9f3325cd1da27be93a4e7370835bda5aa7513 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Mon, 14 Sep 2026 21:30:50 -0400 Subject: [PATCH] test(mobile): share the recorder's fixture helper instead of copying it into eight adapters The seam forbids one adapter importing another, not an adapter importing the engine, and the adapters already take `hookMount` and `observableModel` from there. So the eight byte-identical copies of `mountFixture` bought nothing: eight doc comments and eight cast suppressions for one four-line function that has no per-domain part. Export it from `recorder-fixture-shape.ts`, next to the type it checks against, and leave one suppression instead of nine. `adapter-seam.test.ts` 7/7 and `pnpm --dir mobile typecheck` stay clean. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb --- .../task-item-checks-status-mount-adapters.ts | 13 +----------- .../task-item-conversation-mount-adapters.ts | 13 +----------- ...ask-item-hosted-metadata-mount-adapters.ts | 13 +----------- .../adapters/task-list-mount-adapters.ts | 13 +----------- .../task-project-board-load-mount-adapters.ts | 13 +----------- ...task-project-row-comment-mount-adapters.ts | 13 +----------- .../task-project-row-field-mount-adapters.ts | 13 +----------- .../task-project-row-merge-mount-adapters.ts | 13 +----------- .../rpc-recording/recorder-fixture-shape.ts | 20 ++++++++++++++----- 9 files changed, 23 insertions(+), 101 deletions(-) diff --git a/mobile/src/test-support/rpc-recording/adapters/task-item-checks-status-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/task-item-checks-status-mount-adapters.ts index ec26718992b..e6c96f7bb9c 100644 --- a/mobile/src/test-support/rpc-recording/adapters/task-item-checks-status-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/task-item-checks-status-mount-adapters.ts @@ -2,7 +2,7 @@ import { hookMount, performHookAction } from '../hook-mount' import { observableModel, projectObservable } from '../observable-model' import type { MountAdapter, MountContext, MountedOperation } from '../recording-scenario' import type { operationModuleLoader } from '../operation-module-loader' -import type { PartialRecorderFixture } from '../recorder-fixture-shape' +import { mountFixture } from '../recorder-fixture-shape' const REPO_ID = 'repo-1' @@ -141,17 +141,6 @@ type ModelHookSpec = { readonly state: (model: Record) => Record } -/** - * The recorder supplies only the members the mounted action reads; completing the fixture into a - * full domain object would invent data no scenario observes. `NoInfer` makes the target the - * parameter's type rather than the fixture's, so a member the real type does not have is an error - * here instead of a silently wrong recording. - */ -function mountFixture(value: PartialRecorderFixture>): T { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: checked as a deep subset of T above; the recorder supplies every member the action reads. - return value as T -} - function mountModelHook( context: MountContext, spec: ModelHookSpec diff --git a/mobile/src/test-support/rpc-recording/adapters/task-item-conversation-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/task-item-conversation-mount-adapters.ts index a576f43a22e..d4b2794a5e5 100644 --- a/mobile/src/test-support/rpc-recording/adapters/task-item-conversation-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/task-item-conversation-mount-adapters.ts @@ -2,7 +2,7 @@ import { hookMount, performHookAction } from '../hook-mount' import { observableModel, projectObservable } from '../observable-model' import type { MountAdapter, MountContext, MountedOperation } from '../recording-scenario' import type { operationModuleLoader } from '../operation-module-loader' -import type { PartialRecorderFixture } from '../recorder-fixture-shape' +import { mountFixture } from '../recorder-fixture-shape' const REPO_ID = 'repo-1' @@ -161,17 +161,6 @@ type ModelHookSpec = { readonly state: (model: Record) => Record } -/** - * The recorder supplies only the members the mounted action reads; completing the fixture into a - * full domain object would invent data no scenario observes. `NoInfer` makes the target the - * parameter's type rather than the fixture's, so a member the real type does not have is an error - * here instead of a silently wrong recording. - */ -function mountFixture(value: PartialRecorderFixture>): T { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: checked as a deep subset of T above; the recorder supplies every member the action reads. - return value as T -} - function mountModelHook( context: MountContext, spec: ModelHookSpec diff --git a/mobile/src/test-support/rpc-recording/adapters/task-item-hosted-metadata-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/task-item-hosted-metadata-mount-adapters.ts index 6d19a5cfd5e..bd932050411 100644 --- a/mobile/src/test-support/rpc-recording/adapters/task-item-hosted-metadata-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/task-item-hosted-metadata-mount-adapters.ts @@ -2,7 +2,7 @@ import { hookMount, performHookAction } from '../hook-mount' import { observableModel, projectObservable } from '../observable-model' import type { MountAdapter, MountContext, MountedOperation } from '../recording-scenario' import type { operationModuleLoader } from '../operation-module-loader' -import type { PartialRecorderFixture } from '../recorder-fixture-shape' +import { mountFixture } from '../recorder-fixture-shape' const REPO_ID = 'repo-1' @@ -159,17 +159,6 @@ type ModelHookSpec = { readonly state: (model: Record) => Record } -/** - * The recorder supplies only the members the mounted action reads; completing the fixture into a - * full domain object would invent data no scenario observes. `NoInfer` makes the target the - * parameter's type rather than the fixture's, so a member the real type does not have is an error - * here instead of a silently wrong recording. - */ -function mountFixture(value: PartialRecorderFixture>): T { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: checked as a deep subset of T above; the recorder supplies every member the action reads. - return value as T -} - function mountModelHook( context: MountContext, spec: ModelHookSpec diff --git a/mobile/src/test-support/rpc-recording/adapters/task-list-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/task-list-mount-adapters.ts index 77132657072..683b2d75916 100644 --- a/mobile/src/test-support/rpc-recording/adapters/task-list-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/task-list-mount-adapters.ts @@ -2,7 +2,7 @@ import { hookMount, performHookAction } from '../hook-mount' import { observableModel, projectObservable } from '../observable-model' import type { MountAdapter, MountContext, MountedOperation } from '../recording-scenario' import type { operationModuleLoader } from '../operation-module-loader' -import type { PartialRecorderFixture } from '../recorder-fixture-shape' +import { mountFixture } from '../recorder-fixture-shape' const REPO_ID = 'repo-1' @@ -28,17 +28,6 @@ type ModelHookSpec = { readonly state: (model: Record) => Record } -/** - * The recorder supplies only the members the mounted action reads; completing the fixture into a - * full domain object would invent data no scenario observes. `NoInfer` makes the target the - * parameter's type rather than the fixture's, so a member the real type does not have is an error - * here instead of a silently wrong recording. - */ -function mountFixture(value: PartialRecorderFixture>): T { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: checked as a deep subset of T above; the recorder supplies every member the action reads. - return value as T -} - function mountModelHook( context: MountContext, spec: ModelHookSpec diff --git a/mobile/src/test-support/rpc-recording/adapters/task-project-board-load-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/task-project-board-load-mount-adapters.ts index d2e8d334c30..46f762b05cd 100644 --- a/mobile/src/test-support/rpc-recording/adapters/task-project-board-load-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/task-project-board-load-mount-adapters.ts @@ -2,7 +2,7 @@ import { hookMount, performHookAction } from '../hook-mount' import { observableModel, projectObservable } from '../observable-model' import type { MountAdapter, MountContext, MountedOperation } from '../recording-scenario' import type { operationModuleLoader } from '../operation-module-loader' -import type { PartialRecorderFixture } from '../recorder-fixture-shape' +import { mountFixture } from '../recorder-fixture-shape' const REPO_ID = 'repo-1' @@ -120,17 +120,6 @@ type ModelHookSpec = { readonly state: (model: Record) => Record } -/** - * The recorder supplies only the members the mounted action reads; completing the fixture into a - * full domain object would invent data no scenario observes. `NoInfer` makes the target the - * parameter's type rather than the fixture's, so a member the real type does not have is an error - * here instead of a silently wrong recording. - */ -function mountFixture(value: PartialRecorderFixture>): T { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: checked as a deep subset of T above; the recorder supplies every member the action reads. - return value as T -} - function mountModelHook( context: MountContext, spec: ModelHookSpec diff --git a/mobile/src/test-support/rpc-recording/adapters/task-project-row-comment-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/task-project-row-comment-mount-adapters.ts index 23104d05be8..137d927c5c3 100644 --- a/mobile/src/test-support/rpc-recording/adapters/task-project-row-comment-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/task-project-row-comment-mount-adapters.ts @@ -2,7 +2,7 @@ import { hookMount, performHookAction } from '../hook-mount' import { observableModel, projectObservable } from '../observable-model' import type { MountAdapter, MountContext, MountedOperation } from '../recording-scenario' import type { operationModuleLoader } from '../operation-module-loader' -import type { PartialRecorderFixture } from '../recorder-fixture-shape' +import { mountFixture } from '../recorder-fixture-shape' const REPO_ID = 'repo-1' @@ -120,17 +120,6 @@ type ModelHookSpec = { readonly state: (model: Record) => Record } -/** - * The recorder supplies only the members the mounted action reads; completing the fixture into a - * full domain object would invent data no scenario observes. `NoInfer` makes the target the - * parameter's type rather than the fixture's, so a member the real type does not have is an error - * here instead of a silently wrong recording. - */ -function mountFixture(value: PartialRecorderFixture>): T { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: checked as a deep subset of T above; the recorder supplies every member the action reads. - return value as T -} - function mountModelHook( context: MountContext, spec: ModelHookSpec diff --git a/mobile/src/test-support/rpc-recording/adapters/task-project-row-field-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/task-project-row-field-mount-adapters.ts index c7253c37cb3..36e10d4a949 100644 --- a/mobile/src/test-support/rpc-recording/adapters/task-project-row-field-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/task-project-row-field-mount-adapters.ts @@ -2,7 +2,7 @@ import { hookMount, performHookAction } from '../hook-mount' import { observableModel, projectObservable } from '../observable-model' import type { MountAdapter, MountContext, MountedOperation } from '../recording-scenario' import type { operationModuleLoader } from '../operation-module-loader' -import type { PartialRecorderFixture } from '../recorder-fixture-shape' +import { mountFixture } from '../recorder-fixture-shape' const REPO_ID = 'repo-1' @@ -126,17 +126,6 @@ type ModelHookSpec = { readonly state: (model: Record) => Record } -/** - * The recorder supplies only the members the mounted action reads; completing the fixture into a - * full domain object would invent data no scenario observes. `NoInfer` makes the target the - * parameter's type rather than the fixture's, so a member the real type does not have is an error - * here instead of a silently wrong recording. - */ -function mountFixture(value: PartialRecorderFixture>): T { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: checked as a deep subset of T above; the recorder supplies every member the action reads. - return value as T -} - function mountModelHook( context: MountContext, spec: ModelHookSpec diff --git a/mobile/src/test-support/rpc-recording/adapters/task-project-row-merge-mount-adapters.ts b/mobile/src/test-support/rpc-recording/adapters/task-project-row-merge-mount-adapters.ts index 03cb729845f..4641b468972 100644 --- a/mobile/src/test-support/rpc-recording/adapters/task-project-row-merge-mount-adapters.ts +++ b/mobile/src/test-support/rpc-recording/adapters/task-project-row-merge-mount-adapters.ts @@ -2,7 +2,7 @@ import { hookMount, performHookAction } from '../hook-mount' import { observableModel, projectObservable } from '../observable-model' import type { MountAdapter, MountContext, MountedOperation } from '../recording-scenario' import type { operationModuleLoader } from '../operation-module-loader' -import type { PartialRecorderFixture } from '../recorder-fixture-shape' +import { mountFixture } from '../recorder-fixture-shape' const REPO_ID = 'repo-1' @@ -150,17 +150,6 @@ type ModelHookSpec = { readonly state: (model: Record) => Record } -/** - * The recorder supplies only the members the mounted action reads; completing the fixture into a - * full domain object would invent data no scenario observes. `NoInfer` makes the target the - * parameter's type rather than the fixture's, so a member the real type does not have is an error - * here instead of a silently wrong recording. - */ -function mountFixture(value: PartialRecorderFixture>): T { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: checked as a deep subset of T above; the recorder supplies every member the action reads. - return value as T -} - function mountModelHook( context: MountContext, spec: ModelHookSpec diff --git a/mobile/src/test-support/rpc-recording/recorder-fixture-shape.ts b/mobile/src/test-support/rpc-recording/recorder-fixture-shape.ts index 7c32785a223..d315fe28423 100644 --- a/mobile/src/test-support/rpc-recording/recorder-fixture-shape.ts +++ b/mobile/src/test-support/rpc-recording/recorder-fixture-shape.ts @@ -4,11 +4,10 @@ * type. That is what a mount fixture actually is — deliberately partial, because it carries only * what the mounted hook reads, yet still a subset of the real thing. * - * Type-only, and here rather than outside the recorder because `mobile/scripts/rpc-recording.mts` - * fences every path under `mobile/src` except this directory, so a file outside it fails recording - * as an unpinned product source. It sits in the engine rather than under `adapters/` because the - * mount helper that uses it is copied per module and the seam forbids one module importing another, - * so this is what stops eleven copies of a recursive conditional type from existing. + * Here rather than outside the recorder because `mobile/scripts/rpc-recording.mts` fences every + * path under `mobile/src` except this directory, so a file outside it fails recording as an unpinned + * product source. In the engine rather than under `adapters/` because the seam forbids one adapter + * importing another, and every adapter may import the engine. * * Functions pass through whole: a fixture stub like `async () => 0` stands in for a callback, and * making its parameters optional would accept a stub the hook cannot call. @@ -26,3 +25,14 @@ export type PartialRecorderFixture = T extends (...args: never[]) => unknown : T extends object ? { readonly [Key in keyof T]?: PartialRecorderFixture | null } : T + +/** + * The recorder supplies only the members the mounted action reads; completing the fixture into a + * full domain object would invent data no scenario observes. `NoInfer` makes the target the + * parameter's type rather than the fixture's, so a member the real type does not have is an error + * here instead of a silently wrong recording. + */ +export function mountFixture(value: PartialRecorderFixture>): T { + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: checked as a deep subset of T above; the recorder supplies every member the action reads. + return value as T +}