Skip to content

fix(webui): make every connection and boot failure visible, with a way back - #23

Merged
stevenjj33 merged 1 commit into
webuifrom
fix/webui-error-visibility
Oct 5, 2026
Merged

stevenjj33 merged 1 commit into
webuifrom
fix/webui-error-visibility

Conversation

@stevenjj33

Copy link
Copy Markdown
Collaborator

Change

Roadmap Q-1 (错误可见性收口). Three gaps, closed as one change because they form a single story: something fails → the page says so → the user has an action.

  1. ConnectionStatus existed with zero consumers (verified: the only references to it in src/ were its own interface and component). No stream phase ever reached the page. The shell now mounts it in the main column, under the usage-banner slot, with hideWhenConnected — a healthy connection earns no pixels; 正在重连 / 连接失败 appear on their own. The component gains the two host calls its own docblock had reserved as "the host's layout call": hideWhenConnected and onRetry (retry button renders in the failed state only — reconnecting is the automatic loop mid-attempt).
  2. main.tsx threw before root.render for a missing #webui-root or a missing __WEBUI_CONFIG__; the error boundary mounts inside that render, so both were white pages. Both now render WebuiStartupFallback — one human sentence, the technical cause verbatim, one action (刷新重试 → location.reload()). Rendered into #webui-root when the node exists, or a fresh body node when even the mount point is gone. The happy path stays gated on both preconditions.
  3. The stream loop's recovery ended at refused with no way back. The automatic arm (drop → reconnecting → one resumeSession attempt) was already in stream-loop.ts and works; what was missing was the manual arm after the loop commits refused. createSessionStreamRetry (new session-stream-retry.ts) clears the standing refusal synchronously and re-runs the same attach loop used for server-started turns, anchored on the last applied cursor (or the newest persisted message when no cursor was recorded). One click is one attempt: a still-dead server refuses again through the identical path and the banner returns with the new reason.

Also: the browser fixture's delayEvery/delayNext now also hold sendMessage/resumeSession — the stream auto-answer used to run first and close the transient reconnecting window within a microtask, making it unobservable. No existing spec delayed those two operations (verified), so behaviour elsewhere is unchanged.

Q-2 boundary (not in this PR)

多语言(中英切换)is deliberately not started — the roadmap routes the 接不接 decision through 莫克 first. When picked up: the language entry belongs in the settings page, which overlaps K-zone (izzy's 个性化设置 tab) and P-zone (设置中心, unclaimed). Whichever of K/P lands first should own the tab's unlock; the other must not re-unlock the same tab in a second PR. This PR deliberately adds new zh copy (banner, startup fallback) without touching any settings tab, so it creates no conflict with either zone.

Validation

Real-browser verification (required by the task; not unit-test-only)

  • Wrong WebSocket address (built artifact, config pointed at a dead port, real WebSockets): the page is not white — the rail shows Unable to load projects: WebUI connection failed, the transcript area shows Unable to load messages; after sending a message the connection banner appears: 连接失败 + the transport's own reason (WebUI connection failed) + a 重试连接 button.
  • Temporary throw in a panel (flag-gated throw in WebuiSessionTranscript, built, then reverted before commit): switching sessions renders the boundary — 界面出现错误 + the error message verbatim; clearing the flag and clicking 重试 unmounts the boundary and the transcript of the newly selected session renders. Not console-only.
  • Missing __WEBUI_CONFIG__ (variant page, no config injected): the page paints 页面未能启动 + WebUI runtime configuration is missing (__WEBUI_CONFIG__) + 刷新重试 — not blank.
  • Missing #webui-root (variant page, mount node removed, config present): same surface with WebUI mount node #webui-root is missing.
  • Screenshots of all of the above were taken during verification (kept outside the repo per the publication boundary).

Automated

  • pnpm test:webui — 80 files / 1591 tests passed, including: connection-status.test.tsx +5 cases (hide/attention states, retry-button gating, shell-mount source assertion), new webui-startup-fallback.test.tsx (7), new session-stream-retry.test.ts (3, driven against the real runtime store).
  • npx playwright test (test:webui-browser, chromium, built artifact) — 80/80 passed, including the 2 new connection-status.spec.mjs cases: drop → banner reconnecting visible on the page → cleared when the held resume lands; unresumable drop → banner failed with the transport reason → 重试连接 click reopens the stream and a frame emitted afterwards lands in the transcript.
  • pnpm typecheck:webui-full — passed.
  • pnpm verify — passed, 20 gates on darwin (skips by design: test:webui-browser not applicable on this platform, test:windows, test:release-package).
  • Source inventory regenerated for the 5 new files; pnpm check:source passed.

NOT RUN, platform limitations and live-service boundaries

  • Windows focused contract left to CI (no Windows machine locally).
  • All browser verification ran against the fixture transport or dead-port variants; no live MiniMax-account server was involved, so no live-service acceptance is claimed.

Publication and contribution checks

  • I have permission to contribute these changes under the existing licenses applicable to the changed files/packages; imported material and its provenance are identified and existing notices are preserved.
  • No credentials, account data, real user content, internal source history or private review material is included.
  • Added/removed source files were reviewed before regenerating release/public-source.json; new tests are declared in test/vitest-suites.json where applicable.
  • Shared English/Chinese documentation and capability/verification records are updated where applicable. Mock/offline results are not described as live-service acceptance.

Maintainer handoff

Publication scope or license changes (if any): none.

Shared-source port: not needed.

…y back

Three gaps in error visibility, closed together because they form one
story: something fails, the page says so, and the user has an action.

1. ConnectionStatus existed with zero consumers — no phase ever reached
   the page. The shell now mounts it in the main column under the usage
   banner slot, with hideWhenConnected so a healthy connection earns no
   pixels and 正在重连 / 连接失败 appear on their own. The component
   gains the two host calls its docblock reserved: hideWhenConnected
   (layout) and onRetry (recovery affordance, failed state only).

2. main.tsx threw before root.render for a missing #webui-root or a
   missing __WEBUI_CONFIG__; the error boundary mounts inside that
   render, so both were white pages. Both now render
   WebuiStartupFallback — a human sentence, the technical cause shown
   verbatim, and one action: 刷新重试. Rendered into #webui-root when
   the node exists, or a fresh body node when even the mount point is
   gone. The happy path stays gated on both preconditions.

3. The stream loop's automatic recovery (drop → reconnecting → one
   resumeSession attempt) ended at refused with nothing able to re-enter
   it. createSessionStreamRetry is the manual arm the banner's
   重试连接 button runs: it clears the standing refusal synchronously
   and re-runs the same attach loop used for server-started turns,
   anchored on the last applied cursor (or persisted history when no
   cursor was recorded). One click is one attempt; a still-dead server
   refuses again through the identical path.

The browser fixture's delayEvery now also holds sendMessage /
resumeSession (the auto-answer used to win the race), which is what
makes the transient reconnecting window observable at all.

Tests: connection-status.test.tsx gains the host-call cases plus a
shell-mount source assertion; webui-startup-fallback.test.tsx covers
the surface and the main.tsx wiring; session-stream-retry.test.ts drives
the retry against the real runtime store (cursor anchor, history
anchor, one-attempt refusal). connection-status.spec.mjs proves both
chains in a real browser: drop → 正在重连 on the page → cleared when
the resume lands, and unresumable drop → 连接失败 with the transport's
reason → retry reopens the stream and delivers frames again.
@stevenjj33 stevenjj33 added the bug Something isn't working label Oct 5, 2026
@stevenjj33
stevenjj33 merged commit cd5d982 into webui Oct 5, 2026
9 checks passed
@stevenjj33
stevenjj33 deleted the fix/webui-error-visibility branch October 5, 2026 07:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant