.claude/skills/webview-ui/SKILL.md
This skill should be used when the user asks to "create a web UI", "add a WebView", "build an HTML interface", "use Svelte", "use p5.js", "use three.js", "use React", "use web components", "send messages to JavaScript", "receive messages from JavaScript", "hot reload the UI", "use IWebViewControl", "embed a WebView", "WebView editor delegate", "use IPlugSendMsg", "web-based plugin UI", "load HTML in plugin", "use Vite", or discusses WebView setup, JS/C++ messaging, or web framework integration in an iPlug2 plugin.
npx skillsauth add iplug2/iplug2 webview-uiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Guidance for building WebView-based UIs in iPlug2 plugins. Covers integration mode selection, C++/JS messaging, web framework setup, resource loading, hot reloading, and audio-to-UI visualization.
Every full-WebView plugin sets up its editor in the constructor via mEditorInitFunc:
// In plugin constructor
#ifdef DEBUG
SetEnableDevTools(true);
#endif
SetCustomUrlScheme("iplug2"); // optional, needed for some frameworks
mEditorInitFunc = [&]() {
LoadIndexHtml(__FILE__, GetBundleID());
// OR for hot-reload development:
// LoadURL("http://localhost:5173/");
EnableScroll(false);
};
The plugin class inherits from Plugin. The build configuration must define WEBVIEW_EDITOR_DELEGATE and NO_IGRAPHICS so the delegate selection header (IPlugDelegate_select.h) routes to WebViewEditorDelegate.
On the web side, provide resources/web/index.html as the entry point. The C++ side automatically injects IPlugSendMsg() as the JS-to-native bridge function.
Reference examples: Examples/IPlugWebUI/ (minimal), Examples/IPlugSvelteUI/ (modern framework), Examples/IPlugP5js/ (creative coding).
| Scenario | Mode | Key Setup |
|----------|------|-----------|
| Entire UI is web content | Full WebView (WebViewEditorDelegate) | Define WEBVIEW_EDITOR_DELEGATE + NO_IGRAPHICS, use mEditorInitFunc |
| Mix of native IGraphics + embedded web panel | Embedded (IWebViewControl) | Normal IGraphics setup, attach IWebViewControl in layout lambda |
| Quick HTML display (logs, status) | Embedded (IWebViewControl) with LoadHTML() | Attach IWebViewControl, call LoadHTML(str) directly |
| Need | Framework | Build Step | Example |
|------|-----------|------------|---------|
| Simplest, no tooling | Raw HTML/CSS/JS + Web Components | None | Examples/IPlugWebUI/ |
| Creative coding / shaders | p5.js (bundled directly) | None | Examples/IPlugP5js/ |
| Modern reactive UI, TypeScript | Svelte + Vite | npm run build | Examples/IPlugSvelteUI/ |
| Component-heavy UI | React / Vue + Vite | npm run build | Follow Svelte pattern |
| 3D visualization | three.js (bundled or via npm) | Optional | Follow p5.js or Svelte pattern |
For detailed framework setup and hot reloading, consult references/framework-selection.md.
JS to C++ (send via IPlugSendMsg(jsonObj)):
| Function | Purpose | Key Fields |
|----------|---------|------------|
| SPVFUI(paramIdx, value) | Send parameter value (normalized 0-1) | paramIdx, value |
| BPCFUI(paramIdx) | Begin parameter change gesture | paramIdx |
| EPCFUI(paramIdx) | End parameter change gesture | paramIdx |
| SAMFUI(msgTag, ctrlTag, data) | Send arbitrary message (base64 data) | msgTag, ctrlTag, data |
| SMMFUI(statusByte, d1, d2) | Send MIDI message | statusByte, dataByte1, dataByte2 |
C++ to JS (called automatically via EvaluateJavaScript):
| Function | Purpose | Called By |
|----------|---------|-----------|
| SPVFD(paramIdx, normalizedValue) | Parameter value changed | SendParameterValueFromDelegate |
| SCVFD(ctrlTag, value) | Control value changed | SendControlValueFromDelegate |
| SCMFD(ctrlTag, msgTag, dataSize, base64) | Control message (ISender data) | SendControlMsgFromDelegate |
| SAMFD(msgTag, dataSize, base64) | Arbitrary message (incl. params init) | SendArbitraryMsgFromDelegate |
| SMMFD(statusByte, d1, d2) | MIDI message | SendMidiMsgFromDelegate |
For the complete messaging protocol, JSON schemas, and binary data exchange, consult references/messaging-protocol.md.
Parameters require a three-step gesture protocol for proper DAW undo/automation:
// 1. Begin gesture (e.g. on mousedown)
BPCFUI(paramIdx);
// 2. Send values during drag (normalized 0-1)
SPVFUI(paramIdx, normalizedValue);
// 3. End gesture (e.g. on mouseup)
EPCFUI(paramIdx);
On page load, WebViewEditorDelegate automatically sends all parameter info as a JSON blob via SAMFD with msgTag == -1. Decode it in your SAMFD handler:
function SAMFD(msgTag, dataSize, msg) {
if (msgTag == -1 && dataSize > 0) {
let json = JSON.parse(atob(msg));
if (json.id === "params") {
// json.params is an array of {name, type, min, max, default, ...}
// Use this to configure your UI controls
}
}
}
Parameter changes from the C++ side arrive via SPVFD(paramIdx, normalizedValue). Define this function globally to handle them.
The C++ side is identical to IGraphics -- declare a sender, feed it in ProcessBlock, transmit in OnIdle:
// .h
IPeakSender<2> mSender;
enum EControlTags { kCtrlTagMeter = 0 };
// ProcessBlock
mSender.ProcessBlock(outputs, nFrames, kCtrlTagMeter);
// OnIdle
mSender.TransmitData(*this);
Data arrives in JavaScript via SCMFD. Decode the base64 payload to typed arrays:
globalThis.SCMFD = (ctrlTag, msgTag, dataSize, msg) => {
const bytes = new Uint8Array(atob(msg).split('').map(c => c.charCodeAt(0)));
const header = new Int32Array(bytes.buffer, 0, 3); // [controlTag, nChans, chanOffset]
const data = new Float32Array(bytes.buffer, 12); // audio data
// data[0] = channel 0 peak, data[1] = channel 1 peak, etc.
};
For detailed visualization patterns, consult references/isender-and-visualization.md.
Use IWebViewControl to embed a WebView panel within an IGraphics UI:
#include "IWebViewControl.h"
// In mLayoutFunc:
pGraphics->AttachControl(new IWebViewControl(bounds, true,
[](IWebViewControl* pControl) { // onReady
pControl->LoadFile("index.html", "com.yourcompany.yourplugin");
},
[](IWebViewControl* pControl, const char* jsonMsg) { // onMessage
DBGMSG("Received: %s\n", jsonMsg);
},
enableDevTools), kCtrlTagWebView);
The WebView renders as a native overlay on top of IGraphics. Messages from JS arrive at the onMessage lambda as raw JSON -- no automatic parameter routing. Use pControl->EvaluateJavaScript("...") to call JS from C++.
Custom URL scheme: SetCustomUrlScheme("iplug2") serves web content as if from an iplug2:// origin. Required when frameworks generate absolute path imports. Set before the WebView opens.
DevTools: SetEnableDevTools(true) enables the browser inspector (right-click context menu on macOS, F12 on Windows). Typically wrap in #ifdef DEBUG.
| Concept | File |
|---------|------|
| IWebView base class | IPlug/Extras/WebView/IPlugWebView.h |
| WebView editor delegate | IPlug/Extras/WebView/IPlugWebViewEditorDelegate.h |
| Editor delegate selection | IPlug/IPlugDelegate_select.h |
| Embedded WebView control | IGraphics/Controls/IWebViewControl.h |
| ISender (audio-to-UI) | IPlug/ISender.h |
| JS bridge (vanilla) | Examples/IPlugWebUI/resources/web/script.js |
| JS bridge (TypeScript) | Examples/IPlugSvelteUI/web-ui/src/lib/iplug.ts |
| TypeScript declarations | Examples/IPlugSvelteUI/web-ui/src/types/iplug.d.ts |
| Raw HTML example | Examples/IPlugWebUI/ |
| Svelte example | Examples/IPlugSvelteUI/ |
| p5.js example | Examples/IPlugP5js/ |
| Embedded WebView example | Examples/IPlugOSCEditor/ |
| CMake WebView config | Scripts/cmake/WebView.cmake |
tools
Validate iPlug2 plugin builds using format-specific validators (auval, pluginval, vstvalidator, clap-validator) (project)
tools
Download iPlug2 dependencies including plugin format SDKs (VST3, CLAP, WAM) and optional Skia graphics backend libraries
tools
Take a screenshot of the plugin UI using the standalone app CLI for debugging and documentation
tools
This skill should be used when the user asks to "add a parameter", "define parameters", "create an enum parameter", "add a gain control", "add a frequency parameter", "use parameter groups", "randomize parameters", "reset to defaults", "smooth a parameter", "use LogParamSmooth", "create presets", "serialize state", "handle OnParamChange", "copy parameter values", "use InitDouble", "use InitEnum", "use InitBool", "use parameter shapes", "use ShapePowCurve", "parameter flags", or discusses parameter definition, grouping, batch operations, smoothing, presets, or state serialization in an iPlug2 plugin.