Repository navigation
Conversation
…romised a release that does not happen Found by reviewing the 23 merged PRs that carry no review at all (14 of them self-submitted and self-merged) and then clicking through the real UI. None of the four below is caught by a test; three of them were found by hand, and one of those three took the whole app down. ## 1 · `attachments` was the one field the message normalizer missed (#17) `normalizeWebuiClientMessage` strips and re-adds ten fields so its `typeof` / `Array.isArray` guards are authoritative. `attachments` was in neither list, so it rode through `...remainder` and `projectMessageAttachments` ran a bare `for...of` over it. Measured on `f56b26f`: attachments={"a":1} -> TypeError: attachments is not iterable attachments=42 -> TypeError: attachments is not iterable attachments=true -> TypeError: attachments is not iterable #17's title claims the malformed-field crash class is gone. It was gone for ten fields and present in the eleventh. ## 2 · `listing?.entries.length` — the `?.` stopped one level too early `WebuiWorkspaceDirectoryBrowser` read `listing?.entries.length`. The `?.` guarded `listing`; `.entries` was bare. A body with no `entries` threw `Cannot read properties of undefined (reading 'length')`. The `?.` looked correct, which is why the render read as safe. The real fix belongs at the door: `browseWorkspaceDirs`'s result is now normalised before it reaches state, so a bad body degrades to an empty listing instead of an error page. ## 3 · The sign-in panel was stored with no validation at all `getSigninPanel()`'s resolved value went straight into `setSignin({ panel })`, and `SigninProgress` does `[...days].sort(...)` unguarded. A body without `days` threw `days is not iterable`. Here the server does validate — `validateSigninPanel` throws `Invalid sign-in panel` — so this is defence at the boundary, not a known live shape. It is worth fixing anyway: the blast radius is the entire UI, and a malformed panel should cost you the seven-day strip, not the page. Both crashes above share one root: the transport resolves the server body verbatim (`resolve(frame.body as T)` in `transport.ts`), so every consumer inherits the server's shape with nothing in between. The three fixes above normalise at each consumer rather than at the transport, because only the consumer knows what a missing field should degrade to. ## 4 · The forced reconnect never reported the channel down (#26) `reconnectWhenAvailable` — tab back into view, network back — cleared `socket` and then called `previous.close()`. The close handler guards on `socket !== ws`, which is therefore always true on that path, so `markWebuiEventWatcherDown` was skipped and the banner read 已连接 for the whole reconnect window: the exact silent lie the watcher exists to remove. The path had no test at all — this file had zero `dispatchEvent` calls. ## 5 · The npm preview package has not been publishing, and the docs said it was `gh secret list` is empty — the repository has no Actions secret named `NPM_TOKEN` — so `HAS_NPM_TOKEN` in `webui-npm-preview.yml` is `false` and the publish step never runs. `docs/releasing-webui-npm.md` still promised 「Publishing is fully automatic: every push to the `webui` branch … publishes a new preview version」. The job is also not a merge gate (branch protection is 404), and #15 relaxed the step from failing to a `::warning::`, so "no credential" and "published" are the same colour. The doc now says what is true and what turning it back on involves. ## 6 · The browser fixture could not open three surfaces `fixture.mjs` answered every unknown operation with `return {}`. Three real user paths therefore crashed instead of rendering, which is why 93 specs passed while a human could not get through the app: - `getSigninPanel` — no case at all, so the sign-in row threw on every open - `browseWorkspaceDirs` — no case, so the workspace picker threw on every open - `setPermissionMode` — a no-op write, while `getPermissionMode` always answered `"default"`. `SessionComposer.changePermissionMode` writes and then reads back to verify; with this pair every mode but `default` raised 「授权模式未能保存,请重试」. That read-back is the correct pattern and is left alone — the fixture was the thing that could not satisfy it. ## Tests - `message-parts.test.ts`: a non-array `attachments` no longer throws, and a real attachments array still renders - `connection-health.test.ts`: a forced reconnect now marks the channel down, driven through the `online` listener Verified locally on `f56b26f` + this branch: `typecheck:webui` (server / client / test / root), `test:webui`, `test:capabilities`, `test:status-contract`, and the 93-spec Playwright suite. The three surfaces that used to crash were also clicked through by hand in a real browser.
…ntory `check:source` fails on an unregistered path, which is what turned the three platform failures on #30 red: `Added: test/webui-browser/recovered-surfaces.spec.mjs`. The verification job is only the gate that requires the matrix, so it went red with them. One line, alphabetical, next to the other browser specs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Base:
f56b26f. Found by reviewing the 23 merged PRs that carry no review at all (14 of them self-submitted and self-merged), then clicking through the real UI.None of the four code fixes is caught by a test. Three were found by hand, and each one replaced the whole app tree with the error page.
The crashes
1 ·
attachmentswas the one field the message normalizer missed (#17)normalizeWebuiClientMessagestrips and re-adds ten fields so its guards are authoritative.attachmentswas in neither list, so it rode through...remainderandprojectMessageAttachmentsran a barefor...ofover it. Measured onf56b26f:#17's title claims the malformed-field crash class is gone. It was gone for ten fields and present in the eleventh.
2 ·
listing?.entries.length— the?.stopped one level too earlyThe
?.guardedlisting;.entrieswas bare. A body with noentriesthrewCannot read properties of undefined (reading 'length'). It reads as safe, which is why it survived review. Fixed at the door instead of the use site: thebrowseWorkspaceDirsresult is normalised before it reaches state.3 · The sign-in panel was stored with no validation at all
getSigninPanel()'s resolved value went straight intosetSignin({ panel })andSigninProgressdoes[...days].sort(...)unguarded. A body withoutdaysthrewdays is not iterable. The server does validate here (validateSigninPanelthrows), so this is defence at the boundary — worth having because the blast radius is the whole UI, not one row.4 · The forced reconnect never reported the channel down (#26)
reconnectWhenAvailable— tab back into view, network back — clearedsocketthen calledprevious.close(). The close handler guards onsocket !== ws, always true on that path, somarkWebuiEventWatcherDownwas skipped and the banner read 已连接 for the whole reconnect window. That file had zerodispatchEventcalls before this.The doc that was lying
gh secret listis empty — the repo has noNPM_TOKENsecret — soHAS_NPM_TOKENisfalseand the publish step never runs.docs/releasing-webui-npm.mdstill promised 「Publishing is fully automatic」. The job is also not a merge gate (protection is 404) and #15 relaxed the step from failing to a::warning::, so "no credential" and "published" are the same colour. The doc now says what is true and what turning it back on costs.The fixture could not open three surfaces
fixture.mjsanswered every unknown operation withreturn {}, which is why 93 specs passed while a human could not get through the app. AddedgetSigninPanel,browseWorkspaceDirsand a realsetPermissionMode— the last one becausechangePermissionModewrites and then reads back to verify, and no-op write + constant read meant every mode butdefaultraised 「授权模式未能保存,请重试」. That read-back is the correct pattern and is left untouched; the fixture was what could not satisfy it.Tests
message-parts.test.ts— a non-arrayattachmentsno longer throws; a real array still rendersconnection-health.test.ts— a forced reconnect marks the channel down, driven through theonlinelistenerrecovered-surfaces.spec.mjs(new, 3 browser specs) — the sign-in row, the workspace picker and the permission-mode write all reach the page without tripping the error boundaryVerified locally on
f56b26f+ this branchtypecheck:webuiserver / client / test + roottest:webuitest:capabilitiestest:status-contractBuilt before the browser run, per #14's own finding that the harness only proves the process is right, never the artifact.
Not in this PR
Deliberately left, because they are not bugs in the same sense and each needs a call:
configuredSummaryreturns nostatus/error, so it is permanently 未知状态. Wiring that means changing the local-runtime-v2 contract, not fixing a client guard.SessionComposer.tsx:1811) — a real defect, but the fix touches session-identity bookkeeping.target: ES2024is ahead of esbuild'ses2022. Restoring them will turn the gate red again.