refactor: shared webview IPC helpers with enforced exhaustiveness#911
refactor: shared webview IPC helpers with enforced exhaustiveness#911
Conversation
781bd7a to
8c6204a
Compare
df4afc3 to
abfbedc
Compare
4e7be71 to
973e57a
Compare
abfbedc to
f890b00
Compare
973e57a to
83a1ca1
Compare
f890b00 to
30594e4
Compare
83a1ca1 to
1ce0401
Compare
30594e4 to
6647e51
Compare
ae111a5 to
7b451c4
Compare
6647e51 to
9c3ac9d
Compare
7b451c4 to
26fc3d2
Compare
9c3ac9d to
f260351
Compare
26fc3d2 to
04e4c55
Compare
f260351 to
76a78b6
Compare
04e4c55 to
13ee0b6
Compare
76a78b6 to
30e9c13
Compare
9fbd977 to
bfb8ce3
Compare
30e9c13 to
47ed5e2
Compare
|
/coder-agents-review |
There was a problem hiding this comment.
Solid infrastructure PR. The buildCommandHandlers / buildRequestHandlers pattern is a strong approach to eliminating silently-dropped messages at compile time. The README is well-written reference documentation with the error-semantics table, concrete code examples, and the "no dropped events" explanation. The chat shim decomposition into named functions (toIframe / handleFromIframe / handleFromExtension / showRetry) is a clear improvement over the monolithic handler. The migration is systematic: all three panels moved to the shared helpers, tests updated to match the new wire format.
1 P0, 1 P2, 11 P3, 1 P4, 4 Nit.
The P0 is a regression: the speedtest ready command calls window.postMessage (DOM global) instead of the VS Code IPC because the postMessage import was removed but one call site was not migrated. The chart is blank on initial open. Fix: sendCommand(SpeedtestApi.ready).
The P2 is architectural: the chat shim's switch statements are raw JS inside a template literal, outside the exhaustiveness guarantee. Adding a ChatApi entry compiles cleanly, but the shim silently drops the new message. Seven reviewers flagged this independently.
Most P3s are documentation accuracy (dead file path, missing scaffolding steps, WebviewPanel vs WebviewView), one behavioral default change (speedtest error toasts), one code duplication (useIpc.command vs sendCommand), and test coverage gaps.
PS. Mafu-san's process audit found zero unverified claims in the PR description, all six structural claims hold against the diff. Pariston tried four framings and could not construct a simpler alternative. Takumi traced every concurrency path and found no issues. Ging-TS had no TypeScript modernization findings.
"This also violates the PR's own AGENTS.md rule: 'Never hand-roll ...
postMessage({ method, params }). UseonNotification/sendCommand." (Melody)
packages/speedtest/src/index.ts:32
P0 [DEREM-4] The postMessage import was removed (line 2), but this call site was not migrated. postMessage now resolves to the DOM global window.postMessage(message, options?), which TypeScript compiles without error (the DOM lib provides the overload). At runtime, window.postMessage({method:"speedtest/ready"}) dispatches a MessageEvent on the webview's own window, not to the extension host. The extension's ready handler (the only path that calls sendData() on initial open) never fires. The chart is blank until the user switches tabs and back.
"Confirmed in the built artifact:
sendCommandis inlined aso(...)/s(...), but line 32 emits barepostMessage(...), a global call." (Mafuuu)
Fix: sendCommand(SpeedtestApi.ready);
🤖
packages/webview-shared/src/react/useIpc.ts:119
P3 [DEREM-11] useIpc.command is a line-for-line copy of sendCommand from ipc.ts. Both take a CommandDef<P>, spread variadic args, and call postMessage({ method: def.method, params: args[0] }). The onNotification path was already migrated to delegate to the shared helper (line 146: return subscribe(definition, callback)). The symmetric command path was not. One-line fix: import { sendCommand } from "../ipc" and delegate. (Robin)
🤖
test/unit/webviews/speedtest/speedtestPanelFactory.test.ts:128
P4 [DEREM-16] "ignores unknown message methods" no longer matches actual behavior. The old code silently dropped unknown methods. The new code routes through dispatchCommand, which throws, logs a warning, and pops showErrorMessage (default showErrorToUser: () => true). The test asserts .not.toThrow() synchronously on an async dispatch, so the error toast is invisible. The test name is misleading and the assertion is vacuous for this path. (Bisky)
🤖
🤖 This review was automatically generated with Coder Agents.
…ness - Add isIpcCommand/Request, notifyWebview, dispatchCommand/Request, and onWhileVisible in src/webviews/util.ts; migrate tasksPanelProvider and speedtestPanel off their local copies. - Add sendCommand/onNotification in @repo/webview-shared for vanilla webviews; useIpc stays for React. - speedtestPanel calls buildCommandHandlers AND buildRequestHandlers, so adding a new action to SpeedtestApi is a compile error until a handler is wired. - Document the contract and the visibility/theme re-send guarantee in packages/webview-shared/README.md; AGENTS.md and CONTRIBUTING.md point at it. - Expand speedtestPanel tests (payload, visibility/theme re-send, viewJson dispatch, cleanup) and add ipc.test.ts for the new helpers.
Define ChatApi in shared and replace the ad-hoc {type, ...} message
handling with buildCommandHandlers / buildRequestHandlers plus
dispatchCommand / dispatchRequest. Outgoing notifications route
through a private notify() wrapper, mirroring tasksPanelProvider.
The iframe shim in the inline HTML keeps its own {type, payload}
contract with the Coder server, but its shim-to-extension side now
speaks the IPC wire format. Split the handler into handleFromIframe /
handleFromExtension + a toIframe helper + a showRetry builder so the
dispatch reads straight through.
Tests updated to the new wire format.
47ed5e2 to
23c96b9
Compare
Split src/webviews/util.ts into dispatch.ts (IPC runtime) and html.ts (scaffolding). Default dispatchCommand to not show errors, matching dispatchRequest, and let panels opt in per method. Tasks panel adds viewInCoder and viewLogs to its user-action set so dialogs still pop for clicked items. Chat shim now uses ?? for data fallback, validates URL before forwarding navigate, and references ChatApi sync requirements in a short comment block. Drop the duplicate notify wrapper in chat and tasks since notifyWebview accepts undefined webview. Rename "Unknown method" to "Unknown request" for symmetry. sendCommand omits params for void commands. Tests cover the dispatch error branches, void notifications, and the auth-error retry path. README tightened and clarified that the visibility resend only matters when retainContextWhenHidden is unset.
55ad7ce to
f11c7f9
Compare
Replaces the inline JS shim in chatPanelProvider with a real webview package at packages/chat/. The shim now imports ChatApi and uses sendCommand and subscribeNotifications, so renaming an entry on the API breaks the build at every callsite. Iframe and status div are still pre-rendered in the panel HTML so the iframe loads as soon as the page parses, matching the original code path; the bundle just attaches listeners. Adds subscribeNotifications to webview-shared, an exhaustive notification subscriber driven by a single window message listener and a Map dispatcher. Removes the public onNotification export; useIpc still uses the lower-level subscribeOne for per-call React subscriptions. Speedtest migrates to subscribeNotifications too. Pulls a buildWebviewCsp helper out of html.ts so the chat panel and the standard getWebviewHtml share one CSP source of truth, and adds getWebviewAssetUris for panels that compose their own HTML. README rewritten as a navigation map with a "Where each handler lives" table and pointers to the canonical reference packages.
|
/coder-agents-review |
There was a problem hiding this comment.
17 of 20 R1 findings addressed. The author fixed the P0, the P2, all P3s with inline threads, and all Nits. Thorough response.
Two findings remain unaddressed. Both were auto-folded into the review body (their lines fell outside the diff context), so the author had no threaded comment to reply to. Listing them here so they have a reply target.
DEREM-11 (P3) packages/webview-shared/src/react/useIpc.ts:119: useIpc.command is a line-for-line copy of sendCommand from ipc.ts. The onNotification path was already migrated to delegate to the shared helper (line 146: return subscribe(definition, callback)). The symmetric command path was not. One-line fix: import { sendCommand } from "../ipc" and delegate.
DEREM-16 (P4) test/unit/webviews/speedtest/speedtestPanelFactory.test.ts:128: "ignores unknown message methods" test name is misleading. After the dispatch migration, unknown commands throw internally, get caught, and log a warning (no longer silently ignored). With the default flip from DEREM-8 (55ad7ce), the user-facing behavior is closer to the old silent behavior (no error toast), but the internal path changed. The test assertion (.not.toThrow()) is vacuous because it checks synchronously against an async dispatch.
Further review is blocked until these are responded to (fix, acknowledge, or contest). The panel has not yet reviewed the new commits.
🤖 This review was automatically generated with Coder Agents.
DEREM-11: useIpc.command no longer hand-rolls postMessage. Delegates to sendCommand, which also brings in the void-command params-omission behavior so React webviews are consistent with vanilla. DEREM-16: speedtest panel test "ignores unknown message methods" was asserting against a sync throw on an async dispatch, which always passed regardless of behavior. Renamed to reflect the user-facing contract (no error dialog) and asserts on showErrorMessage after the dispatch settles.
|
Pushed 699e733 addressing the two remaining R1 findings:
|
|
/coder-agents-review |
There was a problem hiding this comment.
All 20 R1 findings verified fixed. The author's response was thorough: the P0 speedtest bug, the P2 shim exhaustiveness gap, all P3s, and all Nits were addressed across three commits. Melody walked every API pairing end-to-end and confirmed all chains agree. Mafuuu verified the four highest-severity fixes against the code and ran adversarial scenarios. The dispatch.test.ts and auth-retry tests are genuine and well-structured.
The fix commits introduced significant refactoring beyond the R1 fixes: util.ts split into dispatch.ts + html.ts, chat shim extracted into packages/chat/, subscribeNotifications added for compile-checked notification handling, dispatchCommand default flipped. The panel reviewed these changes.
1 P2, 3 P3, 2 Nit (1 from Netero, 1 new).
The P2 is a fix-chain regression from DEREM-5: subscribeNotifications in the chat shim is source-blind. The old inline script had a single message listener with event.source gating that ensured notification handlers only processed extension messages. The extraction into two independent listeners removed that invariant. If the Coder embed ever posts a message with a notification type string, the handler crashes with TypeError.
"If the Coder server embed ever posts a message with
type: 'coder:set-theme', listener 1 fires, extracts(msg as { data }).data(which isundefined), and the handler({ theme }) => toIframe(...)throwsTypeError." (Hisoka)
🤖 This review was automatically generated with Coder Agents.
DEREM-23: Chat shim now uses a single source-gated message listener. Iframe messages route to the foreign-protocol switch; extension messages dispatch through `buildNotificationRouter` (extracted from `subscribeNotifications`) so the exhaustiveness check is preserved and the dispatch loop is shared. An iframe message whose `type` matches a notification can no longer reach the typed handler. DEREM-24: Direct tests for `isIpcRequest` and `isIpcCommand` covering numeric `requestId`/`method`, missing fields, command vs request disambiguation, and null/non-object inputs. DEREM-25: Tests for `buildWebviewCsp`. Base CSP omits `frame-src`; the option injects it. DEREM-26: `embedUrl` now flows through `escapeHtml` before the iframe `src` interpolation, so a `chatId` containing `"` can't break out of the attribute. DEREM-21: New `test/webview/chat/index.test.ts` covers iframe forwarding (vscodeReady, chatReady, navigate with and without url), extension push (setTheme), source isolation against notification-typed iframe messages, and the auth-error retry flow. DEREM-22: Reworded the `useIpc` comment to describe the role without naming the helper.
isIpcCommand,isIpcRequest,notifyWebview,dispatchCommand,dispatchRequest,onWhileVisible) live insrc/webviews/dispatch.ts; HTML scaffolding (getWebviewHtml,getWebviewAssetUris,buildWebviewCsp) insrc/webviews/html.ts. All three panels dispatch through them.sendCommandandsubscribeNotificationsin@repo/webview-shared.subscribeNotificationsis exhaustive over the API's notifications: a newdefineNotificationwithout a handler is a build error. Speedtest and chat use it; React webviews keepuseIpc.buildCommandHandlersandbuildRequestHandlers(empty{}is fine). NewdefineCommand/defineRequestentries fail the build at any panel missing a handler, so the extension can't silently drop a future action.{type, ...}protocol ontoChatApi. The iframe shim was extracted into a typed bundle atpackages/chat/. A single source-gatedwindow.messagelistener bridges the iframe's{type, payload}contract andChatApi; iframe messages can never reach typed notification handlers.packages/webview-shared/README.mdrewritten as a navigation map with a "Where each handler lives" table and pointers to the canonical reference packages.CONTRIBUTING.mdandAGENTS.mdpoint at it as the single source of truth.