AUDIO
Source snapshot · 12 October 2026

Reusable local web controls

ravet-surface::embed_web_controls embeds the reusable local JavaScript at one <!-- RAVET_CONTROLS --> marker in a trusted consumer-authored page. Missing or duplicate markers and output larger than 1 MiB reject. Preparation allocates on the UI thread; the page CSP must permit its inline script. This is a document assembly helper, with no remote content sanitization or implicit network bridge.

The script exposes window.RavetControls with seventeen components and TextEditorError:

Parameter IDs are unsigned 32-bit integers. post explicitly receives {type: 'edit'|'begin'|'value'|'end', id, value} or {type:'action', action}. Returning false synchronously rejects submission. Begin rejection leaves a slider idle; end rejection retains ownership for retry and prevents disposal. One slider or knob owns a gesture per document; starting another ends the previous one first. Native asynchronous errors, schema validation, host notification, queue capacity/reserved end slots and sample timing belong to the consumer transport. Use parameter attachments for Rust schema mappings. DOM sliders use physical linear values; rotary knobs optionally use logarithmic travel while still sending physical values.

For example, after embedding the script:

const slider = new RavetControls.Slider(
  document.querySelector('#gain'), document.querySelector('#gain-number'),
  {id: 1, minimum: 0, maximum: 4, step: 0.001},
  message => window.webkit.messageHandlers.ravet.postMessage(message)
);
slider.update(1); // host refresh sends no edits

Knob and slider/fader pointer gestures leave paired numeric editing before adjustment. They do not select the number or activate a surrounding label's numeric input when released. Knobs do not force keyboard focus on pointer down; native range inputs retain browser dragging and release pointer focus on pointer up/cancel. The shared theme suppresses pointer focus outlines through data-ravet-pointer-focus; keyboard input removes that marker and restores consumer focus styling. Direct clicks/Tab into numeric inputs still allow normal selection and committed number entry. Successful disposal restores any original marker attribute. This policy applies to every shared knob and slider, including embedded editors and standalone consumers.

Parameter snapshot refresh leaves focused controls and active gestures untouched. Labels, tab order, keyboard semantics and focus rendering use native HTML elements and consumer markup/CSS. dispose removes listeners; sliders and knobs first end any active gesture, returning false if that end was rejected. Retry before replacing controls or closing the bridge. Native editor owners still close host gestures on teardown.

The gain CLAP page uses the shared slider/number, toggle and accessible meter components. Its native WebKit probe exercises three complete lifecycle cycles, live gain edits, host automation, bypass, meters and resize. Additional in-page fixtures check all five shared components, pointer cancellation, held-key gesture grouping, begin/end rejection, disposal and choices with noncanonical numeric spelling. Browser keyboard checks also confirm the page tab order and begin/value/end submission from an actual navigation key. Browser automation left the document unfocused, so computed focus rendering is not visual-focus evidence. These are programmatic WebKit and browser checks. Broader platform runtime, screen-reader audits and further widget families remain pending. See the capability catalogue for drawing, layout, text, asset and theme capabilities and their backend limits. See decision 0034 and decision 0071.

The interactive examples use the shared rotary knobs. Their native fixture checks logarithmic drag, numeric clamping, navigation keys, cancellation, refresh suppression and retryable/rejected gesture ownership. This programmatic WebKit evidence is not an assistive-technology audit.

WEB_THEME_STYLE supplies --ravet-background, --ravet-panel, --ravet-raised, --ravet-border, --ravet-foreground, --ravet-muted, --ravet-accent, --ravet-accent-text and piano-key colors. The selected Amber default uses charcoal/plum surfaces and matches drawing::Theme::DARK; Theme::LIGHT is a warm paper companion. Consumers override semantic variables for their own themes. The standard standalone popup uses the same tokens. Shared styles disable text selection on UI labels and other body text, including the settings popup. Inputs, textareas, editable content and preformatted/code output remain selectable for editing and copying. Consumer CSS can override this policy.

Native piano fixtures check stopped-key opacity, black/white geometry, multi-pointer chords, glissando ordering, cancellation, focused-key release and nonfeedback external note highlighting. Synth/MPE fixtures also reject page overflow at default and smaller window sizes. These checks do not replace assistive-technology testing.

The reusable focus fixture runs 38 contract checks in both the collaborative Chromium browser and hidden macOS WebKit: bounded navigation, RTL/vertical selection, skipped members, original attribute restoration, piano note release and overlapping play keys. Actual browser Tab/ArrowRight/Enter events also reach one composite member, leave the composite on the next Tab, and produce one piano note-on/off source. The browser reports document.hasFocus() === false, so these are keyboard/DOM contracts, with no visual focus-ring or screen-reader claim. Build the hidden fixture from the native editor project:

cmake -S crates/ravet-surface/native -B target/native-editor -DRAVET_WEB_EDITOR_TESTS=ON
cmake --build target/native-editor --target ravet-web-focus -j 2
ctest --test-dir target/native-editor -R '^ravet-web-focus$' --output-on-failure

The native build assembles the same trusted local scripts/styles inline, matching consumer embedding and keeping WebKit's local read access restricted. No device or host window is opened. See decision 0169.

Topics in this guide

Bounded text editing

RavetControls.TextEditor wraps one consumer-labelled textarea with UTF-8 source limits, grapheme-safe selections, local revision ownership, bounded undo/redo and composition guards. See browser text editing for its API, native-input fallback and explicit consumer acknowledgement contract. Browser text editing is independent of Rust document tokens and shaped drawing.

Document ownership and embedded documents

Parameter controls and action buttons require connected elements in one live owner document. A slider/knob's optional number input belongs to that same document; invalid foreign inputs or missing owner windows reject before setting min/max, ARIA or styling attributes. Same-origin iframe controls use their own document's active element and window/document event listeners. Focused child controls reject snapshot refresh rather than letting a parent-document activeElement check overwrite editing. Slider and knob child-window pointerup/cancel/blur and child-document visibility signals finish their owned gesture; parent-window signals do not finish an unrelated child's gesture.

Gesture ownership uses a WeakMap keyed by the owner document. Slider, knob, toggle, choice, numeric commits and action buttons share the same document's owner; ending or beginning an edit in a different document does not force its owner to finish. A rejected end keeps the original owner, listeners and held state for retry, and blocks another control in that document from beginning or committing. Accepted retry releases ownership once. Real focus movement still causes native blur and may end the focused control's gesture.

Consumers keep the original document/window alive, finish/dispose controls before removing or hiding an iframe, and retry rejected ends before retirement. This does not observe arbitrary frame/stylesheet lifecycle or imply cross-origin access, shadow-root focus traversal, native-window focus, screenreader or backend certification. Isolated Chromium and hidden WebKit iframe fixtures validate the specified owner-document DOM contracts.