Problem
The RemoteClawApp Lit class at ui/src/ui/app.ts:108 is missing required field initializers for three members of the LifecycleHost, GatewayHost, and ToolStreamHost interfaces:
connectGeneration (LifecycleHost.connectGeneration: number, app-lifecycle.ts:25)
serverVersion (LifecycleHost.serverVersion: string | null, app-lifecycle.ts:31; GatewayHost.serverVersion, app-gateway.ts:91)
chatStreamSegments (ToolStreamHost.chatStreamSegments: Array<{ text: string; ts: number }>, app-tool-stream.ts:33)
Severity by field
| Field |
Severity |
Symptom |
connectGeneration |
Fatal |
app-lifecycle.ts:46 does const connectGeneration = ++host.connectGeneration. When host.connectGeneration is undefined, ++undefined → NaN. Then app-lifecycle.ts:55 does if (host.connectGeneration !== connectGeneration) return; — NaN !== NaN is always true in JS (the only value not equal to itself), so connectGateway(host) is never called → no WebSocket → Health Offline / Version n/a / "Disconnected from gateway." chat banner. |
chatStreamSegments |
Latent crash |
app-tool-stream.ts:441: host.chatStreamSegments = [...host.chatStreamSegments, { text: ..., ts: ... }]. Spreading undefined throws TypeError: chatStreamSegments is not iterable when a tool call interrupts streaming text mid-chat. |
serverVersion |
Cosmetic |
app-gateway.ts:197 passes host.serverVersion (undefined) to resolveControlUiClientVersion, which uses optional chaining (params.serverVersion?.trim() at line 112) — no crash, but the client identifies as an incorrect version in the gateway handshake. |
Root cause
Partial-sync of upstream v2026.3.7 (9ef18943dc) brought in app-lifecycle.ts changes that require these fields on the LifecycleHost type, but did not bring in the companion app.ts change that initializes them on the class. The companion change silently conflicted with the OpenClawApp → RemoteClawApp rebrand and was dropped during merge resolution. The as unknown as Parameters<typeof handleConnected>[0] cast pattern in app.ts (21 occurrences) erased the structural type-check that would otherwise have flagged the missing fields.
(Detailed investigation: [internal, summarized in commit message].)
Fix
Add three field initializers to RemoteClawApp in ui/src/ui/app.ts, near the existing declarations (lines 132–143 for @state() fields; lines 306–321 for private bookkeeping):
private connectGeneration = 0;
@state() serverVersion: string | null = null;
@state() chatStreamSegments: Array<{ text: string; ts: number }> = [];
connectGeneration is non-reactive (lifecycle bookkeeping, not rendered) → matches existing private chatHasAutoScrolled = false style. The other two are reactive (feed rendered Version pill / chat streaming view) → match existing @state() chatStream: string | null = null style.
Acceptance criteria
Commit message
fix(ui): restore connectGeneration/serverVersion/chatStreamSegments on RemoteClawApp — sync regression silently broke gateway connect
Follow-ups (separate issues)
- [Issue B]: audit and remove
as unknown as Parameters<typeof X>[0] casts in ui/src/ui/ so TypeScript catches future regressions of this class.
- [Issue D]: add a smoke test that instantiates
RemoteClawApp and asserts every required host-interface field is initialized.
- [Issue H]: audit other host-shape interfaces (Polling/Chat/Settings/Scroll/Compaction) for missing initializers.
Problem
The
RemoteClawAppLit class atui/src/ui/app.ts:108is missing required field initializers for three members of theLifecycleHost,GatewayHost, andToolStreamHostinterfaces:connectGeneration(LifecycleHost.connectGeneration: number,app-lifecycle.ts:25)serverVersion(LifecycleHost.serverVersion: string | null,app-lifecycle.ts:31;GatewayHost.serverVersion,app-gateway.ts:91)chatStreamSegments(ToolStreamHost.chatStreamSegments: Array<{ text: string; ts: number }>,app-tool-stream.ts:33)Severity by field
connectGenerationapp-lifecycle.ts:46doesconst connectGeneration = ++host.connectGeneration. Whenhost.connectGenerationisundefined,++undefined → NaN. Thenapp-lifecycle.ts:55doesif (host.connectGeneration !== connectGeneration) return;—NaN !== NaNis always true in JS (the only value not equal to itself), soconnectGateway(host)is never called → no WebSocket → Health Offline / Version n/a / "Disconnected from gateway." chat banner.chatStreamSegmentsapp-tool-stream.ts:441:host.chatStreamSegments = [...host.chatStreamSegments, { text: ..., ts: ... }]. SpreadingundefinedthrowsTypeError: chatStreamSegments is not iterablewhen a tool call interrupts streaming text mid-chat.serverVersionapp-gateway.ts:197passeshost.serverVersion(undefined) toresolveControlUiClientVersion, which uses optional chaining (params.serverVersion?.trim()at line 112) — no crash, but the client identifies as an incorrect version in the gateway handshake.Root cause
Partial-sync of upstream v2026.3.7 (
9ef18943dc) brought inapp-lifecycle.tschanges that require these fields on theLifecycleHosttype, but did not bring in the companionapp.tschange that initializes them on the class. The companion change silently conflicted with theOpenClawApp → RemoteClawApprebrand and was dropped during merge resolution. Theas unknown as Parameters<typeof handleConnected>[0]cast pattern inapp.ts(21 occurrences) erased the structural type-check that would otherwise have flagged the missing fields.(Detailed investigation: [internal, summarized in commit message].)
Fix
Add three field initializers to
RemoteClawAppinui/src/ui/app.ts, near the existing declarations (lines 132–143 for@state()fields; lines 306–321 forprivatebookkeeping):connectGenerationis non-reactive (lifecycle bookkeeping, not rendered) → matches existingprivate chatHasAutoScrolled = falsestyle. The other two are reactive (feed rendered Version pill / chat streaming view) → match existing@state() chatStream: string | null = nullstyle.Acceptance criteria
127.0.0.1:{gateway_port}/chatconnects to gateway on first load (Health=Online, Version populated, chat input enabled).document.querySelector('remoteclaw-app').connectGenerationreturns a number (not NaN) in a fresh browser tab.pnpm test,pnpm tsgo,pnpm lint).app-lifecycle.node.test.ts/app-lifecycle-connect.node.test.ts(they already have these fields hand-set; the problem was the production class, not the tests).Commit message
Follow-ups (separate issues)
as unknown as Parameters<typeof X>[0]casts inui/src/ui/so TypeScript catches future regressions of this class.RemoteClawAppand asserts every required host-interface field is initialized.