Skip to content

fix(webui): a null deadline must not read as 'time is up', and a comment that denied its own liveness - #28

Merged
modacker merged 2 commits into
webuifrom
docs/connection-status-two-signals
Oct 5, 2026
Merged

modacker merged 2 commits into
webuifrom
docs/connection-status-two-signals

Conversation

@modacker

@modacker modacker commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Two review findings on #25 / #26 / #27, and one comment that was telling the next reader the opposite of the truth.

1. The ConnectionStatus comment denied its own liveness

Its header read:

Deliberately not a transport-level liveness probe: nothing in the client exposes one, so the honest signal is the turn stream's own phase

That was the reason the component sat unmounted for two releases. It is now false — #26 added connection-health, the long-lived watchEvents socket marking itself healthy on an ack and degraded on close, and the component merges the two signals. A reader who trusts that comment concludes the liveness signal cannot exist, which is exactly the conclusion that kept the component off screen.

It now says which signal answers which question — the projection function is untouched and still reads only the turn stream's phase — and what the channel signal does not cover: no heartbeat, so detection is close-edge only and a half-open link is invisible; and a watcher that was never accepted stays "unknown", so a host already unreachable at load time still reads as connected.

2. A null or NaN deadline rendered as "time is up"

#25 tightened the goal auto-reply window to purpose === 1 && expiresAt !== undefined, which is right about which payloads count. The guard still let a non-numeric value through, and the countdown clamps to zero:

expiresAt before after
null ⏱ 0s — "time is up" no countdown
NaN ⏱ NaNs — the literal string in the panel no countdown
0 ⏱ 0s ⏱ 0s (a real past deadline)
a future time ⏱ 12s ⏱ 12s

goalAutoReplyDeadline in the terminal client guards with typeof expiresAt === "number" && Number.isFinite(expiresAt), and this panel's comment already points at that function as its reference. It takes the same pair now.

No current producer emits null or NaN there, so this is a parity gap rather than a live regression. It is still the "missing data displayed as a valid state" class, and NaNs rendering into the panel is worth not shipping.

3. The MCP status suite's premise was wrong, and hid a real gap

The suite header said the runtime "already classifies every server" and that the gap was only that the panel rendered none of it. That is not the plugin page's path.

LocalMcpPublicServerStatus does classify every server — but that is the MCP tool surface (LocalMcpPublicFacade). The plugin page calls listMcpServers → listConfiguredServers → configuredSummary, which returns only name / enabled / transport / description / endpoint / configJson, with configJson hardcoded to "{}". No status, no error, not even available. Fed the real shape, every row reads 未知状态 — the badge is inert.

The tests all fed self-made {status: "error", …} rows, which is why they were green.

The real fix is upstream (configuredSummary has to carry the connection state), so this does not pretend to close that gap. What it does is pin today's honest answer with the server's actual shape, so the day the field arrives, this test goes red — the change becomes visible instead of silent.

The two indexOf source assertions stay: the rows load through an async effect, so no static render exercises them. The file now says plainly that they cannot prove the badge works, which is what the new contract test and the browser spec are for.

Verification

node scripts/verify.mjs --profile full on the committed tree: 20/20 gates, exit 0.

The browser suite was not run locally for this change — port 4179 is hardcoded and was held by another agent's live run. CI's test:webui-browser is linux-only and runs the whole suite there, so that is where the 88 specs get checked.

probe added 2 commits October 5, 2026 21:15
The header said the component is "Deliberately not a transport-level
liveness probe: nothing in the client exposes one, so the honest signal is
the turn stream's own phase". That was the reason the component stayed
unmounted for as long as it did, and it is now false: #26 added
`connection-health`, the long-lived `watchEvents` socket marking itself
healthy on an ack and degraded on `close`, and the component merges the two
signals. The projection function itself is untouched and still reads only
the turn stream's phase.

A reader who trusts this comment concludes the liveness signal cannot
exist, which is exactly the conclusion that kept the component off screen
for two releases. It now says which signal answers which question, and
what the channel signal does not cover: no heartbeat, so detection is
close-edge only and a half-open link is invisible; and a watcher that was
never accepted stays "unknown", so a host that is already unreachable at
load time still reads as connected until something else moves.
#25 tightened the goal auto-reply window to `purpose === 1 && expiresAt
!== undefined`, which matches what the runtime actually acts on. The
guard still let a non-numeric `expiresAt` through, and the countdown
clamps to zero: `null` rendered as "⏱ 0s" and `NaN` rendered the literal
string "NaNs" into the panel. Both claim a deadline the payload never
carried.

`goalAutoReplyDeadline` in the terminal client guards with
`typeof expiresAt === "number" && Number.isFinite(expiresAt)`, and this
panel's comment already points at that function as its reference. Take
the same pair. Zero and a past timestamp stay valid — they are real
deadlines that have passed.

Also corrects the premise in the MCP status suite. `LocalMcpPublicServerStatus`
does classify every server, but that is the MCP tool surface; the plugin
page's `listConfiguredServers` → `configuredSummary` returns no `status` at
all and hardcodes `configJson` to `"{}"`, so every real row reads 未知状态
and the badge is inert until that path carries the field. The suite now
pins today's honest answer with the real server shape, so the day the field
arrives the change is visible rather than silent.
@modacker
modacker merged commit ab9d5b8 into webui Oct 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant