Skip to content

fix(ui): restore connectGeneration/serverVersion/chatStreamSegments on RemoteClawApp — gateway connect silently broken #2493

@alexey-pelykh

Description

@alexey-pelykh

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:

  1. connectGeneration (LifecycleHost.connectGeneration: number, app-lifecycle.ts:25)
  2. serverVersion (LifecycleHost.serverVersion: string | null, app-lifecycle.ts:31; GatewayHost.serverVersion, app-gateway.ts:91)
  3. 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

  • Chat dashboard at 127.0.0.1:{gateway_port}/chat connects to gateway on first load (Health=Online, Version populated, chat input enabled).
  • document.querySelector('remoteclaw-app').connectGeneration returns a number (not NaN) in a fresh browser tab.
  • Existing tests pass (pnpm test, pnpm tsgo, pnpm lint).
  • No change to synthetic-host fixtures in 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

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.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions