fix+feat(signal-viewer): remove WebGL white-edge fringe; dampen zoom + shift-drag zoom-to-range#33
Merged
Merged
Conversation
The WebGL2 context is created with premultipliedAlpha:true and the renderer blends with blendFunc(ONE, ONE_MINUS_SRC_ALPHA), so the drawing buffer must hold premultiplied colour. The line fragment shader instead output straight alpha (vec4(u_color.rgb, u_color.a * coverage)) — full-brightness RGB while only alpha feathered — so the compositor un-premultiplied the low edge alpha and anti-aliased edges bloomed toward white. The envelope shader output the raw straight u_color, with the same latent issue at MSAA silhouette edges and for any translucent lane. Both fragment shaders now output premultiplied colour (rgb * a, a) where a = u_color.a * edge-coverage. Edges fade toward transparent black, revealing the dark chart cleanly and matching the Canvas2D drawLine/drawEnvelope AA. The host colour resolution, blend func, 1.2px line width, round-join feel, and the geometry-side envelope min-thickness clamp are unchanged. Adds a pure string-level regression test asserting both shaders emit premultiplied output; real rendered fidelity remains covered by the CI pixel-diff gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzEJ1kUhwobqVTnVusLcb
Wheel/pinch zoom was far too sensitive (50% span change per notch), so users overshot and hunted. Replace the fixed per-notch factor with a device-aware, gentle curve: factor = exp(normalizedDelta * WHEEL_ZOOM_RATE). DOM_DELTA_LINE notches and DOM_DELTA_PIXEL trackpad streams are normalized to a common magnitude and clamped per event so neither feels jumpy and one fat delta can't teleport the zoom. Cursor-anchored zoom is preserved (the time under the pointer stays put). Sensitivity is a single tunable constant for the product owner to tune in production (feel can't be validated in CI). Add a Shift+drag rubber-band: Shift+pointerdown starts a zoom-selection (distinct from a pan), drawing a theme-tokened semi-transparent band (a cheap DOM element — no waveform repaint) across the dragged x-range; release converts pixels -> time, clamps to session bounds, floors at the max zoom-in limit, and applies it. Shift-clicks and <5px drags are no-ops. Pointer capture keeps the drag tracking outside the canvas; a col-resize cursor arms while Shift is held for discoverability. Keyboard zoom (presets) is unchanged. Extract the math into a pure, unit-tested helper (signalZoom.ts): wheelDeltaToZoomFactor / normalizeWheelDelta / applyCursorAnchoredZoom / pixelRangeToTimeRange, with 21 tests covering device modes, the per-event clamp, multiplicative symmetry, cursor anchoring, and selection clamping. Does not touch the WebGL shader files edited elsewhere on this branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CzEJ1kUhwobqVTnVusLcb
kabaka
added a commit
that referenced
this pull request
Jun 17, 2026
…deploys (#34) The post-#33 main CI failed on a flaky Firefox E2E console-error assertion (analysis.spec.ts) that caught a benign NS_BINDING_ABORTED — a navigation/ worker-load abort surfaced by Playwright's juggler harness when the test rapidly goto's between analysis routes. It is not an app fault and it blocked the Build/Deploy pipeline. Add it to the existing benign-noise allow-list (alongside React Router and DevTools). Test-only change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two production follow-ups after the WebGL2 renderer went live (#31/#32), both reported by the product owner.
1. Remove the white edge on the waveform lines (
fix(charts):)The anti-aliased edges of the WebGL lines (e.g. the pink Pressure line) showed a bright white fringe the Canvas2D reference doesn't have. Root cause: a premultiplied-alpha mismatch — the context is
premultipliedAlpha:trueand blends(ONE, ONE_MINUS_SRC_ALPHA)(correct), but the line/envelope fragment shaders wrote straight colour (vec4(rgb, a*coverage)with full-brightness RGB), so feathered edge pixels were un-premultiplied by the compositor and bloomed toward white. Fix: shaders now output premultiplied colour (rgb*a, a), so edges fade to transparent dark exactly like the Canvas2D AA. Opaque interiors are mathematically unchanged; line width / round joins / envelope min-thickness clamp untouched. Adds a shader-source regression test. This moves WebGL output toward the reference, so the fidelity gate should improve or stay within tolerance.2. Fix zoom feel + add shift-drag zoom-to-range (
feat(signal-viewer):)Now that zoom is smooth, its sensitivity was glaring.
exp(normalizedDelta · WHEEL_ZOOM_RATE)curve atWHEEL_ZOOM_RATE = 0.0625(~6%/notch). Device-aware (line vs trackpad-pixel deltas normalized + clamped per event so a fling can't teleport), cursor-anchored (time under the pointer stays put), and reuses the existing rAF/WebGL hot path. The rate is a single named, commented constant for the owner to tune by feel.col-resizecursor while Shift is held; pointer-capture tracks drags outside the canvas. Keyboard navigation/data-cursor untouched.signalZoom.tshelper (wheelDeltaToZoomFactor,normalizeWheelDelta,applyCursorAnchoredZoom,pixelRangeToTimeRange) with 21 unit tests (the in-sandbox correctness proof; feel is tuned in prod).Tuning knobs for the owner
WHEEL_ZOOM_RATE(sensitivity),PIXEL_TO_NORMALIZED(trackpad feel),MIN_SELECTION_PX(shift-zoom dead-zone). Wheel-zoom still gates on Ctrl/Cmd (existing pinch behavior, unchanged).Tests
Full suite green (2,575) + new shader-source and zoom-helper tests; typecheck/lint/prettier clean. (Interaction feel can't be validated in CI — that's the owner's prod check.)
🤖 Generated with Claude Code
https://claude.ai/code/session_012CzEJ1kUhwobqVTnVusLcb
Generated by Claude Code