Problem
ui/src/ui/app.ts uses as unknown as Parameters<typeof X>[0] 21 times. ui/src/ui/app-lifecycle.ts uses the same pattern 10+ times. The double-cast through unknown is regular TypeScript syntax (not a @ts-ignore directive, so lint doesn't flag it), but it deliberately erases the structural type check that would otherwise verify the source class satisfies the target interface.
This pattern is the structural enabler of the gateway-disconnect regression (see Issue A). Every time upstream adds a required field to LifecycleHost / GatewayHost / ToolStreamHost / PollingHost / ChatHost / SettingsHost / ScrollHost / CompactionHost, and the fork-side RemoteClawApp class doesn't get the corresponding initializer, TypeScript cannot detect the divergence. The field is undefined at runtime; the bug surfaces only when consumed.
Fix strategy
Two-phase:
Phase 1: Replace as unknown as Parameters<typeof X>[0] with as Parameters<typeof X>[0] (drop the unknown) throughout ui/src/ui/app.ts and ui/src/ui/app-lifecycle.ts. This will surface a cascade of type errors — each error is a real structural divergence the unknown was hiding.
Phase 2: Resolve each surfaced error by adding missing fields to RemoteClawApp (or whichever source class). No new casts allowed — each error is the type system telling the truth.
Scope
grep -rnE 'as unknown as Parameters' ui/src/ui/
Known hotspots:
ui/src/ui/app.ts — 21 occurrences (lines 318, 329, 333, 337, 342, 346, 351, 358, 368, 372, +11 others)
ui/src/ui/app-lifecycle.ts — 10+ occurrences (lines 48, 50, 51, 52, 58, 60, 62, 65, 70, 76, …)
Acceptance criteria
Edge cases to document
Some casts may be legitimate (e.g., crossing a type-system boundary the type system cannot model — manual deserialization, test partial objects). For those cases, document inline:
// TYPE-ERASURE: {reason, e.g., "Zod-validated payload, runtime assertion below"}
const x = raw as unknown as T;
Expected count after refactor: 0 legitimate cases in ui/src/ui/app.ts / app-lifecycle.ts. All 31+ current casts are bridging class to host interface — the fix is to make the class satisfy the interface, not to erase the check.
Commit message
refactor(ui): remove as unknown as Parameters<typeof X>[0] casts — restore structural type-checking
Impact
Prevents the regression class described in Issue A's investigation (partial-sync dropping class-side changes) from recurring invisibly.
References
- Related: Issue A (dependent fix — must land first)
- Related: Issue D (smoke test adds runtime defense-in-depth)
- Related: Issue H (audit catches missing fields surfaced by this refactor)
Problem
ui/src/ui/app.tsusesas unknown as Parameters<typeof X>[0]21 times.ui/src/ui/app-lifecycle.tsuses the same pattern 10+ times. The double-cast throughunknownis regular TypeScript syntax (not a@ts-ignoredirective, so lint doesn't flag it), but it deliberately erases the structural type check that would otherwise verify the source class satisfies the target interface.This pattern is the structural enabler of the gateway-disconnect regression (see Issue A). Every time upstream adds a required field to
LifecycleHost/GatewayHost/ToolStreamHost/PollingHost/ChatHost/SettingsHost/ScrollHost/CompactionHost, and the fork-sideRemoteClawAppclass doesn't get the corresponding initializer, TypeScript cannot detect the divergence. The field isundefinedat runtime; the bug surfaces only when consumed.Fix strategy
Two-phase:
Phase 1: Replace
as unknown as Parameters<typeof X>[0]withas Parameters<typeof X>[0](drop theunknown) throughoutui/src/ui/app.tsandui/src/ui/app-lifecycle.ts. This will surface a cascade of type errors — each error is a real structural divergence theunknownwas hiding.Phase 2: Resolve each surfaced error by adding missing fields to
RemoteClawApp(or whichever source class). No new casts allowed — each error is the type system telling the truth.Scope
grep -rnE 'as unknown as Parameters' ui/src/ui/Known hotspots:
ui/src/ui/app.ts— 21 occurrences (lines 318, 329, 333, 337, 342, 346, 351, 358, 368, 372, +11 others)ui/src/ui/app-lifecycle.ts— 10+ occurrences (lines 48, 50, 51, 52, 58, 60, 62, 65, 70, 76, …)Acceptance criteria
as unknown as Parameters<typeof X>[0]inui/src/ui/(grepreturns nothing).pnpm tsgopasses.pnpm check(format + typecheck + lint) passes.pnpm testpasses.@ts-ignore/@ts-expect-error/as unknown ascasts added to silence the surfaced errors — every error resolved at the source class.Edge cases to document
Some casts may be legitimate (e.g., crossing a type-system boundary the type system cannot model — manual deserialization, test partial objects). For those cases, document inline:
Expected count after refactor: 0 legitimate cases in
ui/src/ui/app.ts/app-lifecycle.ts. All 31+ current casts are bridging class to host interface — the fix is to make the class satisfy the interface, not to erase the check.Commit message
Impact
Prevents the regression class described in Issue A's investigation (partial-sync dropping class-side changes) from recurring invisibly.
References