Skip to content

feat: JCEF shared chat webview — pixel-identical chat UI in JetBrains and VS Code#70

Open
matze4u wants to merge 6 commits into
mainfrom
feat/road-to-jcef-chat-parity
Open

feat: JCEF shared chat webview — pixel-identical chat UI in JetBrains and VS Code#70
matze4u wants to merge 6 commits into
mainfrom
feat/road-to-jcef-chat-parity

Conversation

@matze4u

@matze4u matze4u commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

What

Ports the JetBrains chat tool window from Swing to JCEF and makes the VS Code webview bundle the single chat UI for both IDEs — pixel-identical chat in PhpStorm and VS Code, one UI codebase.

Roadmap: agents/roadmaps/road-to-jcef-chat-parity.md (fully processed: 22 done / 3 deferred / 1 cancelled) · ADR-055.

How

  • host-bridge.ts — the webview is now host-agnostic: acquireVsCodeApi().postMessage under VS Code, window.__e4uJcefPost / window.__e4uHostMessage under JCEF. Zero vscode API imports left in src/webview/.
  • build-jcef-html.mjspnpm run build additionally emits a self-contained chat.html (theme CSS + chat-app bundle inlined) into clients/jetbrains/src/main/resources/webview/ (gitignored build artifact). gradle check never needs the Node build — missing bundle degrades to a notice panel.
  • JcefChatPanel.ktJBCefBrowser.loadHTML() + JBCefJSQuery bridge; SnapshotJson.kt serializes the Kotlin model field-for-field to the TS ChatModelSnapshot shape (snapshot pushes queue until the webview posts ready).
  • ThemeCssExporter.kt — maps the active IDE LaF onto the --vscode-* variable set theme.ts consumes; live re-injection on theme switch (LafManagerListener), no reload.
  • Swing renderer retiredChatPanel, ChatMessageRenderer, SimpleMarkdownRenderer, ui/{Chip,Composer,Header,IconButton,ModePill,ModelPill,RoundedPanel,WelcomeCard,WrapLayout} deleted. Theme.kt (exporter source) and CostFooterFormatter (statusbar) stay. Statusbar widgets and the sidecar protocol are untouched.

Council

codex + gemini, 2 rounds (round 2 tiebreak converged unanimous): webview source stays in clients/vscode (1A) · loadHTML() with inlined bundle over a custom scheme handler (2A) · JBCefJSQuery + HostBridge (3A) · delete Swing now, notice-panel fallback (4A).

Verification

  • task ci green (lint, format, build, typecheck, test — incl. 8 new host-bridge tests).
  • ./gradlew check green (32 tests — 9 SnapshotJson contract tests, 4 ThemeCssExporter tests; detekt + ktlint clean).
  • Deferred to a human IDE session (tracked [~] in the roadmap): runIde smoke (send → stream → stop), keyboard focus / Cmd+Enter inside JCEF, theme screenshots Darcula / Light / high-contrast.

Notes for review

  • The host↔webview message contract is now cross-IDE API — rename fields only in lockstep with chat-model.ts (locked by SnapshotJsonTest).
  • pick-model / halt-answer / attach remain host-side no-ops on BOTH hosts (exact parity with chat-controller.ts); wiring them is tracked in road-to-mvp-ui-finish / v1.0.
  • kotlinx gotcha documented in the ADR: JsonObjectBuilder.put returns the previous value (Map semantics) — caught by a failing contract test during this run.

matze4u added 6 commits June 3, 2026 16:31
Replace the direct acquireVsCodeApi() coupling with a HostBridge
interface (post + onMessage). VS Code impl wraps postMessage/window
events; the JCEF impl speaks window.__e4uJcefPost (outbound) and
window.__e4uHostMessage (inbound) for the JetBrains host. Bootstrap
detects the host at runtime; 8 unit tests lock both bridges.

Part of road-to-jcef-chat-parity Phase 1 (council fork 3A, ADR-055).
scripts/build-jcef-html.mjs bundles chat-app.ts (same esbuild settings
as the VS Code webview) and inlines it into the chat-html-jcef.ts shell
— theme CSS inline, </script-escaped bundle, Kotlin-side placeholders
for the JBCefJSQuery bridge hook and the IDE-theme variable block.
Output lands in clients/jetbrains/src/main/resources/webview/chat.html
(gitignored build artifact, wired into pnpm run build); gradle check
never depends on it.

Council forks 1A + 2A (codex + gemini, 2 rounds): webview source stays
in clients/vscode; loadHTML() with the inlined bundle beats a custom
scheme handler for a self-contained ~27 KiB document. ADR-055.
JcefChatPanel hosts the shared webview document in a JBCefBrowser:
JBCefJSQuery bridge in (ready/send/stop/toggle-mode dispatched onto the
controller — pick-model/halt-answer/attach stay no-ops, exact parity
with the VS Code ChatController), SnapshotJson pushes field-for-field
ChatModelSnapshot JSON out (queued until the webview's ready), and
ThemeCssExporter maps the active LaF onto the --vscode-* variable set
with live LafManagerListener re-injection. JCEF-unsupported runtimes or
a missing bundle degrade to a notice panel.

Swing chat renderer retired in the same change (council fork 4A,
unanimous): ChatPanel, ChatMessageRenderer, SimpleMarkdownRenderer and
the ui/ component set are deleted; Theme.kt (exporter source) and
CostFooterFormatter (statusbar) stay. Contract locked by 9 SnapshotJson
+ 4 ThemeCssExporter tests; ./gradlew check green.

road-to-jcef-chat-parity Phases 0/2/3 + 4, ADR-055.

Gotcha worth keeping: kotlinx JsonObjectBuilder.put returns the
PREVIOUS value for the key (Map semantics) — never chain it with
?.let { put(...) } ?: put(key, JsonNull).
ADR-055 records the four council-decided forks (source location,
loadHTML loading, JBCefJSQuery bridge, Swing retirement) + index row.
road-to-jcef-chat-parity lands fully processed: 22 done, 3 deferred
(runIde focus/smoke + theme screenshots — need a human IDE session),
1 cancelled (host-side attach wiring — no-op on both hosts today, kept
in road-to-mvp-ui-finish). Dashboard updated by hand (regen script not
installed in this environment). Client READMEs document the shared
webview contract.
JCEF (Chromium) ships no default webview stylesheet, unlike VS Code —
buttons/selects fell back to UA fonts and chrome, scrollbars to the
chunky Chromium bars, and the model-select popup to the UA white list.
Add a form-control inherit reset, styled ::-webkit-scrollbar (VS Code
scrollbarSlider vars with translucent fallbacks), dark option colors,
and hover/focus states for the model pill. Switch .e4u-pill:hover to
background-color so the select's arrow gradient survives hover.

Shared CSS — both hosts render the same fixes.
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