From pulp
Build or iterate on a Pulp WebView UI using the native WebView bridge, embedded assets, directory-backed dev resources, and focused WebView validation.
npx claudepluginhub danielraffel/pulp --plugin pulpThis skill uses the workspace's default tool permissions.
Use this skill when working on Pulp's optional WebView layer: HTML/JS panels,
Monitors deployed URLs for regressions after deploys, merges, or upgrades by checking HTTP status, console errors, network failures, performance (LCP/CLS/INP), content, and API health.
Share bugs, ideas, or general feedback.
Use this skill when working on Pulp's optional WebView layer: HTML/JS panels, Monaco-style tools, documentation views, or mixed native/WebView windows.
Pulp WebView is an opt-in compatibility layer, not the primary UI path. Default to native Pulp UI unless the task truly benefits from existing web content.
Main APIs:
pulp::view::WebViewPanelpulp::view::WebViewOptionspulp::view::WindowHostpulp::view::AssetManagerBridge contract:
window.pulp.postMessage(type, payload, id)window.pulp.on(type, callback)set_message_handler(...)post_message(...)evaluate_js(..., callback)PULP_BUILD_WEBVIEW=ON is now the honest opt-in switch for the common Pulp
WebView layer on:
Required platform notes:
gtk+-3.0 and webkit2gtk-4.1 available through pkg-configKeep claims narrow:
pulp-webview-palette build proofset_html(...)make_webview_bridge_bootstrap_script()1.5. Placeholder first paint
WebViewOptions::initial_html when the final page is dark-themed and the
platform WebView would otherwise flash white before the real content loadstransparent_background = true when the final page also wants a
transparent/native host backgroundpulp_add_binary_data(...)AssetManagermake_webview_embedded_resource_fetcher(...)make_webview_directory_resource_fetcher(...)Do not chase Monaco's deprecated AMD path.
Use a prebundled ESM workflow:
MonacoEnvironment.getWorkerUrl(...) or getWorker(...) at those generated worker fileswindow.monaco only if the page wants that global convenienceTreat raw Monaco source or raw ESM output as build inputs, not as the final browser payload. Worker and CSS handling belongs to the bundler.
Current proven local recipe:
cd /Users/danielraffel/Code/monaco-editor
npm install
npm run build-monaco-editor
cd /path/to/pulp
node examples/webview-monaco/build_monaco_bundle.mjs \
--monaco-root /Users/danielraffel/Code/monaco-editor \
--out-dir /path/to/pulp/build/examples/webview-monaco/dist
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DPULP_BUILD_WEBVIEW=ON \
-DPULP_MONACO_BUNDLE_DIR=/path/to/pulp/build/examples/webview-monaco/dist
cmake --build build --target pulp-webview-monaco -j8
./build/examples/webview-monaco/pulp-webview-monaco
Important notes from the working proof:
examples/webview-monaco/build_monaco_bundle.mjseditor.main.js so Monaco's real CSS/features come throughvs/languages/features/... layout@vscode/monaco-lsp-client; for the Phase 7 example we stub that at bundle time because the example is not doing LSPbridge unavailable is expected there because only the native host injects window.pulpeditor readyFor palette / inspector style UI:
WindowHostWindowHostattach_native_child_view(...)set_native_child_view_bounds(...)For standalone native-child embeds, do not size from startup constants once
the host is live. Read WindowHost::get_content_size() for the current
content bounds, attach the child with that size, and keep it in sync via
WindowHost::set_resize_callback(...). examples/webview-monaco/main.cpp
is the current reference pattern.
For plugin-editor embedding:
View::plugin_view_host() instead of creating a separate WindowHostWebViewPanel native handle through
PluginViewHost::attach_native_child_view(...)on_view_resized() via
PluginViewHost::set_native_child_view_bounds(...)on_view_closed() or the owning view destructorexamples/webview-plugin/ for the minimal Processor-backed example
that hosts a WebViewPanel directly inside a plugin editor subtreeFor a standalone app that should behave like a single-pane WebView shell:
pulp::format::StandaloneApp::run_with_editor(...)StandaloneConfig::show_settings_tab = false to skip the outer
standalone Editor/Settings chrome entirelyexamples/webview-plugin/main.cpp is the current kiosk-style proofWebViewOptions::initial_html
with a dark placeholder so the host opens into the final visual familyCMakeLists.txt via pulp_app_icon(...); it stays optional and
should not be wired through the runtime WebView code itselfFor focused local proof:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DPULP_BUILD_WEBVIEW=ON
cmake --build build --target pulp-test-webview -j8
./build/test/pulp-test-webview --reporter compact
If example code changed, also build the relevant example target:
cmake --build build --target pulp-webview-palette -j8
Current proof expectations:
For quick visual inspection, keep a static browser preview page beside the runtime assets when useful. That gives you a cheap screenshot path without pretending the browser preview replaces native validation.
For Monaco specifically:
When porting a WebView editor to the native bridge (translating browser <canvas> + Canvas2D code to pulp's canvas* bridge globals via a JS shim like Spectr's canvas2d-shim.ts), several browser idioms silently break. Authoritative reference is the import-design skill's "Canvas2D Bridge Gotchas" section — see that for the full rule set with example code. Top-of-mind summary:
ctx.arc() does not add to a path — bridge canvasArc strokes immediately. Synthesize as ~32 canvasLineTo segments so ctx.fill() honors the active gradient. Same applies to arcTo, ellipse, roundRect.(color, pos) pairs — passing JSON makes the bridge parse zero stops → silent fall-through to default white fill. canvasSetLinearGradient(id, x0, y0, x1, y1, c1, p1, c2, p2, ...).(cx, cy, R), not the spec's two-circle form. Map (x0,y0,r0,x1,y1,r1) → (x1, y1, r1) (visually identical when r0=0, same center).save_layer isolation — only since pulp v0.74.1 (#1372). Pre-#1372, ctx.clearRect() on one canvas erased pixels another sibling just painted. Pin SDK >= 0.74.1 for any multi-canvas editor.