feat: add auditory directional guidance towards svg element with mouse#587
feat: add auditory directional guidance towards svg element with mouse#587soundaryakp1999 wants to merge 14 commits into
Conversation
Code ReviewThanks for the contribution! The feature concept is well thought-out — using pitch and stereo pan to guide users toward the nearest curve point is a meaningful accessibility improvement. Here are some issues to address before this is ready to merge. Critical: DOM Access in the Model Layer
const bbox = nearest.element.getBoundingClientRect();
const centerX = bbox.x + bbox.width / 2;
const centerY = bbox.y + bbox.height / 2;
Architecture: Service Calling Service Directly
const guidance = this.commandContext.context.getTouchGuidance(x, y);
this.commandContext.audioService.playTouchGuidance(guidance);
Suggested approach: compute guidance in the mouse listener, then route it through the existing command infrastructure rather than calling Bug: Incomplete Resource Cleanup in
|
| Severity | Issue |
|---|---|
| Critical | DOM access (getBoundingClientRect) in Model layer |
| High | Mousebindingservice calling AudioService directly (architecture bypass) |
| Medium | gainNode/stereoPanner not tracked → not disconnected in stopAll() |
| Low | Volume floor of 0.05 when muted — clarify intent |
| Low | Missing blank line in plot.ts interface |
| Low | No unit tests for new directional logic |
The directional guidance idea is solid and the audio implementation (frequency mapping, rate-limiting by distance, stereo pan) is well-structured. Addressing the architecture and DOM-access concerns will make this mergeable.
|
I have addressed the feedback of claude bot |
abf853f to
e09e7f6
Compare
Follow-up ReviewGood progress addressing the previous review — the resource cleanup, volume floor, and command encapsulation are all improved. Here is a review of the code as it stands now. What's Well Done
Bug: Web Audio Nodes Created Before Volume Check
const oscillator = this.audioContext.createOscillator(); // created
const gainNode = this.audioContext.createGain(); // created
const guidanceVolume = this.volume * TOUCH_GUIDANCE_VOLUME;
if (guidanceVolume <= 0) {
return; // ← oscillator and gainNode are abandoned here
}
Fix: move the guard before node creation. const guidanceVolume = this.volume * TOUCH_GUIDANCE_VOLUME;
if (guidanceVolume <= 0) {
return;
}
const oscillator = this.audioContext.createOscillator();
const gainNode = this.audioContext.createGain();
// ...Performance:
|
| What | Why it matters |
|---|---|
PointerGuidanceCommand.execute() — with and without event |
Verifies the command correctly resets guidance on leave |
AbstractTrace.getTouchGuidance() |
The onCurve, verticalRelation, horizontalRelation logic is non-trivial |
resolveTouchGuidanceBeep with distancePx > maxDistancePx |
Boundary case — should return null |
AudioService.playTouchGuidance rate-limiting (nextTouchGuidanceBeepAt) |
The throttle is core to the UX |
Summary
| Severity | Issue |
|---|---|
| Medium | oscillator/gainNode created before volume guard → node leak |
| Medium | findNearestPoint called twice per pointer event |
| Low | PointerGuidance / TouchGuidance naming inconsistency |
| Low | Bar chart guidance gap not documented |
| Low | Test gaps for command, model, and audio rate-limiting |
The feature architecture is sound and the major issues from the previous review have been addressed. Fixing the node-leak bug and the double findNearestPoint call would make this ready for a final pass.
- Avoid double findNearestPoint per pointer event by merging moveToPoint and getTouchGuidance into moveToPointAndGetPointerGuidance - Move volume guard before Web Audio node creation in playPointerGuidanceBeep to prevent orphan node allocation - Rename TouchGuidance -> PointerGuidance for consistency (the feature serves mouse and touch input) - Document BarTrace.findNearestPoint hit-test asymmetry vs other traces
Code Review — PR #587: Auditory Directional Guidance for Mouse ExplorationOverviewThis PR adds pointer/touch guidance sonification: as the user moves the mouse near a chart, directional beeps (pitch = vertical relation, pan = horizontal relation, interval = proximity) guide them toward the nearest data point. The feature is well-scoped and the core idea is solid, but there are a few architectural and correctness issues worth addressing. ✅ Strengths
🔴 Issues1. Incomplete
|
- Fix click hover mode regression: split into pointermove (guidance) and
click (navigate-only) listeners so clicks no longer emit guidance beeps;
added executeNavigateOnly on PointerGuidanceCommand
- Remove now-dead moveToPoint method from Trace interface, AbstractPlot,
AbstractTrace, Figure, and Subplot (no remaining callers since the
pointer flow uses moveToPointAndGetPointerGuidance)
- Dedupe clamp/interpolate: added to MathUtil, used in audio.ts and
pointerGuidance.ts
- Document why PointerGuidanceCommand is instantiated directly rather
than through CommandFactory
- Reword the "inverted pan" comment in pointerGuidance.ts to describe
intent ("pan toward the curve") rather than implementation
- Add unit tests for PointerGuidanceCommand.execute and
executeNavigateOnly covering with/without event and coordinates
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review: feat: add auditory directional guidance towards SVG element with mouseOverviewThis PR introduces proximity-based auditory guidance for pointer/touch exploration. When the pointer is off-curve, directional beeps encode:
The feature is well-conceptualized and meaningfully advances MAIDR's core accessibility mission. Architecture & DesignWhat works well:
Concerns: 1. The JSDoc explains the deviation. However, 2. // box.ts and violinBox.ts
protected override moveToNearest(): void {
// Disabled for boxplots
}TypeScript allows this (parameter contravariance), but overriding a 4-parameter method with 0 parameters is surprising. Retaining the parameters as 3. All concrete implementations in-repo are updated — confirmed. But since this is a public interface, any external code implementing Potential Bugs4. const now = this.audioContext.currentTime;
if (now < this.nextPointerGuidanceBeepAt) { return; }If AudioContext is suspended (common before first audio interaction), 5. verticalRelation: y < nearest.centerY ? 'above' : 'below',
Missing Tests6. The rate-limiting logic (
7. No test for The existing tests cover Minor / Style
Summary
Overall this is solid work — the architecture is clean, the optimization rationale is well-justified, and the accessibility value is high. Addressing the missing |
- Rename PointerGuidanceState fields to cursorVerticalPosition / cursorHorizontalPosition; the new names anchor the reference frame to the cursor so call sites read unambiguously - Add PointerGuidanceCommand.reset() and route pointerleave + hover-mode changes through it instead of execute() with no arg - Restore full parameter list on Box/ViolinBox moveToNearest overrides so the override matches the base contract at a glance - Document AudioContext.currentTime behaviour when the context is suspended (before first user gesture) - Add unit tests for AudioService.playPointerGuidance covering AudioMode.OFF skip, on-curve bypass, off-curve playback, throttle window, throttle release after time advance, and reset on null guidance Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review – PR #587: Auditory Directional Guidance for Mouse ExplorationOverviewThis PR adds hover-driven directional audio guidance: as a user moves the mouse near a data curve, stereo-panned beeps guide them toward the nearest point. High pitch = point is above cursor; low pitch = point is below. Beep frequency increases as the user gets closer. The implementation combines navigation and guidance into a single Files changed: ~1,000 LOC across model, service, command, type, and test layers. What Works WellArchitecture
Audio resource management
Test coverage
Refactoring wins
Issues & Suggestions1. Throttle reset is tied to // src/service/audio.ts
if (this.mode === AudioMode.OFF || !guidance || guidance.onCurve) {
this.nextPointerGuidanceBeepAt = 0;
return;
}Resetting the throttle when audio is OFF means that if the user enables audio while hovering mid-path, the very next if (!guidance || guidance.onCurve) {
this.nextPointerGuidanceBeepAt = 0;
return;
}
if (this.mode === AudioMode.OFF) {
return; // don't reset throttle — preserve timing state
}2. Histogram and SegmentedTrace not in the diff
3. The 2× multiplier in the cleanup timeout is undocumented // src/service/audio.ts
}, POINTER_GUIDANCE_BEEP_DURATION * 1000 * 2);Why 2×? Is this to give the 4.
5. Missing test: There are no tests verifying that 6. This is documented and intentional, but pointer guidance can theoretically fire in scopes where it shouldn't (e.g., braille mode, figure-level navigation). Consider adding a guard in Minor Nits
SummaryThis is a well-designed, well-tested feature that follows the project's MVVC architecture. The main items to address before merging are: (1) verifying that all trace types with |
- Decouple throttle reset from AudioMode in playPointerGuidance: null / on-curve guidance still resets nextPointerGuidanceBeepAt, but AudioMode.OFF no longer does, so re-enabling audio mid-hover does not fire an immediate beep - Add Scope.TRACE guard to PointerGuidanceCommand so guidance does not fire while modals or other navigation scopes are active; pointer-leave reset still runs regardless of scope to keep throttle state clean - Document the 2x multiplier on the beep cleanup timeout (lets the exponential ramp tail finish before disconnecting) - Strengthen PointerGuidanceState docs to make the cursor-relative frame explicit and harder to invert at call sites - Add contract test for the moveToNearest hook used by BoxTrace and ViolinBoxTrace, plus a new AudioService test verifying that AudioMode.OFF preserves the throttle, plus scope-guard tests on the command Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review: Auditory Directional Guidance for Mouse ExplorationOverall Assessment: This is a well-designed, well-tested feature addition. The implementation follows MAIDR's MVVC architecture cleanly, the code is thoroughly documented, and the test coverage is solid. A few medium-priority issues and several minor points are worth addressing before merge. OverviewThe PR adds real-time auditory directional guidance when a user moves a pointer near a chart. High pitch indicates the curve is above the cursor; stereo panning draws the user left/right toward the nearest data point. Guidance beeps rate-limit using the AudioContext clock and silence when on-curve (deferring to the existing sonification chain). The implementation spans:
Medium Issues1. The 2. The 3. Guidance config values are not user-configurable The parameters in Minor Issues4. Linter workaround in test file is a code smell In 5. Edge case: cursor exactly at center cursorVerticalPosition: y < nearest.centerY ? 'above' : 'below',
cursorHorizontalPosition: x < nearest.centerX ? 'left' : 'right',When the cursor lands exactly on 6. The guidance beep fires in all non-OFF modes (SEPARATE, COMBINED, AUTOPLAY, etc.) without checking whether the current mode should suppress it. This may be intentional (guidance is mode-agnostic), but a comment clarifying why only Positive Highlights
Not Blocking, Worth Filing
|
- Tighten executeNavigateOnly parameter to PointerEvent | MouseEvent so the call site signals the supported event shapes explicitly - Make moveToNearest onCurve parameter non-optional and tighten NearestPoint to non-null so subclass overrides must accept the contract instead of silently recomputing or ignoring it - Drop unused jest import + jest.fn() linter workaround in moveToNearestOverride test - Document tie-break behavior at the exact center for cursor position relations - Clarify that pointer guidance is intentionally mode-agnostic across non-OFF audio modes
Code Review — PR #587: Auditory Directional Guidance for Mouse/TouchOverviewThis PR adds proximity-based auditory guidance to help users navigate to data points by moving the mouse. When hovering near (but not on) a curve, short beeps signal direction to the nearest data point — high pitch means the point is above the cursor, stereo panning pulls the user horizontally toward the curve. The feature is well-scoped and the implementation is generally clean. ✅ Strengths
🐛 Issues1. The 2. In 3.
const stereoPanner = this.audioContext.createStereoPanner?.()
?? this.audioContext.createPanner();4. No
💬 Suggestions5. // current
public constructor(context: Context, audioService: AudioService) {
// suggestion — `public` is the TypeScript default; omit it to reduce noise
constructor(context: Context, audioService: AudioService) {6. Test style inconsistency
7. No user-configurable guidance settings
8. Bar chart silence between bars is undiscoverable The design decision to return 9. Minor: // current
(globalThis as any).AudioContext = function () { return ctx; };
// prefer — avoids `any`
(globalThis as unknown as { AudioContext: new () => MockAudioContext }).AudioContext = ...SummaryThe architecture is sound — the guidance flow fits naturally into the existing Observer/Command/Service layers, and the test suite covers the important invariants. Resolve items 1 and 3 (type-check verification + |
- Add StereoPannerNode feature detection in playPointerGuidanceBeep so guidance degrades to mono on Safari < 14.5 instead of throwing - Document the intentional asymmetric pointerleave listener registration (only added for pointermove mode, not click mode) - Add scatter-style "side effect before delegating" contract test to lock down ScatterTrace.moveToNearest mode-switch semantics - Drop redundant public modifier on PointerGuidanceCommand constructor - Replace `globalThis as any` cast in audio.pointerGuidance test with a typed accessor - Standardize on it() within describe() for new pointer-guidance tests to match the lint rule
|
Code Review - PR 587: Auditory Directional Guidance for Mouse/Touch Exploration Overview This PR adds proximity-based audio guidance that helps users trace data curves with mouse/touch. When the cursor is off-curve, a beep fires with pitch encoding vertical direction (high = point is above cursor) and stereo pan encoding horizontal direction. Beep rate increases as cursor nears the curve. Well-documented, thoughtful implementation with solid unit test coverage. Architecture and Design Mostly compliant with MVVC. A few things to discuss: Command bypasses CommandFactory - PointerGuidanceCommand deliberately bypasses CommandFactory because pointer events carry coordinates the factory contract does not accept. The justification is sound, but the scope-validation logic from CommandExecutor.isValidForScope is now manually re-implemented inside PointerGuidanceCommand.isInTraceScope(). If the scope logic ever changes centrally, this copy will not update automatically. Consider a comment warning future maintainers, or a lightweight adapter that lets CommandFactory produce parameterized commands. Bar chart guidance is intentionally asymmetric - Bar charts only produce onCurve: true (because findNearestPoint returns null outside bars), so guidance beeps never fire when approaching from empty space. This is documented and intentional, but the UX differs noticeably from line/scatter. Worth confirming with the team that this is the desired final behavior. Potential Bugs and Concerns
Code Quality Highlights Positives:
Minor nits:
Test Coverage Three new test files cover the critical paths well:
Missing:
Summary This is a well-engineered feature with clear design intent and solid core test coverage. Main items before merge:
Overall the architecture is sound and the accessibility improvement is meaningful. Happy to discuss the CommandFactory deviation further if there is a preferred pattern for parameterized commands going forward. |
- Stop zeroing the guidance throttle on out-of-range pointer events (resolvePointerGuidanceBeep returning null), which would otherwise unblock a beep on every crossing of maxDistancePx - Note in PointerGuidanceCommand that isInTraceScope duplicates CommandExecutor.isValidForScope and must stay in sync - Trim the over-long class/method JSDoc in PointerGuidanceCommand - Add unit tests for MathUtil.clamp and MathUtil.interpolate - Add coverage for the out-of-range throttle path - Clarify the volume=100 settings mock in audio.pointerGuidance
Code Review — PR #587: Auditory Directional Guidance for Mouse ExplorationOverviewThis PR adds real-time audio guidance when users hover near data points: pitch signals vertical direction (high = point is above cursor), stereo pan signals horizontal direction (pan right = point is to the right). The feature is off-curve only — on-curve navigation continues through the existing Observer chain. The implementation is generally clean with solid test coverage. Architecture & DesignWhat is working well:
Concerns1. The PR comment acknowledges this:
This is a real maintenance hazard. If a new scope is added that should also suppress guidance, there are two places to update. Consider extracting scope validation to a shared utility or passing a 2. Histogram / Segmented / Smooth traces — Every 3. The field documents where the cursor is, but the pitch mapping inverts this:
A future contributor reading Suggested alternatives:
4. The JSDoc says "callers should check type PointerGuidanceState =
| { onCurve: true }
| { onCurve: false; distancePx: number; cursorVerticalPosition: 'above' | 'below'; cursorHorizontalPosition: 'left' | 'right' };5. Volume-0 silently advances the throttle // in playPointerGuidanceBeep:
if (guidanceVolume <= 0) { return; } // returns early — no beep
// back in playPointerGuidance — always runs:
this.nextPointerGuidanceBeepAt = now + beep.interval;When volume is 0 the user hears nothing, but the throttle still advances. After turning volume back up, they won't hear a beep until the current interval expires. Whether this is correct depends on intent — worth an explicit comment either way. 6. Guidance parameters not user-configurable
7. The map was widened from Bar Chart UX GapThe comment in Test CoverageThe test suite is thorough:
One gap: no tests for Nits
Summary
The core implementation is solid and the test coverage is commendable. Issues #2 (missing trace updates) and #3 (naming) are the most important to resolve before merge. |
Resolves the two review items flagged as merge-blockers. #3 Naming inversion (cursor frame → curve frame) The field formerly named `cursorVerticalPosition` described where the *cursor* sat, but the audio mapping inverts the frame: cursor below the curve plays a *high* pitch because the point is above. A contributor reading `cursorVerticalPosition: 'below'` would naturally expect a low sound — the opposite of intended. Renamed to describe the curve point's position relative to the cursor, which lines up 1:1 with the audio output: - `curveVertical: 'above'` → high pitch (point is up there) - `curveVertical: 'below'` → low pitch (point is down there) - `curveHorizontal: 'left'` → pan left (point is to the left) - `curveHorizontal: 'right'` → pan right (point is to the right) While here, also converted `PointerGuidanceState` to a discriminated union — `distancePx` and the direction fields no longer exist on the on-curve branch, so the previous "callers should check onCurve before using this" JSDoc smell is now enforced by the type checker. #2 Histogram / Segmented / Smooth `findNearestPoint` return type Verified: none of `Histogram`, `SegmentedTrace`, `SmoothTrace`, or `SmoothTraceSvgXY` override `findNearestPoint`. They inherit from `AbstractBarPlot` / `LineTrace`, both of which return the updated `NearestPoint` shape with `centerX` / `centerY`. Build passes as independent confirmation.
Code Review — PR #587: Auditory Directional Guidance for Mouse/PointerOverviewThis PR adds pointer-guidance sonification: when the user hovers near a data point, directional audio beeps cue them toward the nearest point (pitch = vertical, stereo pan = horizontal, beep rate = proximity). It is a meaningful accessibility enhancement. The implementation is largely solid — good test coverage, clean type design with a discriminated union ( Bugs / Correctness1. Throttle is consumed even when the beep is skipped (volume = 0) In // In playPointerGuidance (~line 766):
this.playPointerGuidanceBeep(beep.frequency, beep.pan, now);
this.nextPointerGuidanceBeepAt = now + beep.interval; // set regardless of whether beep playedFix: only advance the throttle when a beep was actually emitted, or move the 2. Scope changes mid-hover don't reset guidance throttle
Architecture3. The command is constructed directly in 4. ARIA live region spam during hover When Please confirm that Performance5. No debouncing of
The pre-existing hover code had the same issue, so this isn't a regression — but since the PR touches Minor Issues6. Comment verbosity CLAUDE.md asks for minimal comments (only when WHY is non-obvious). Several new JSDoc blocks explain WHAT the code does rather than WHY (e.g. the 7. Screen-space y clarification in The comment What's Good
SummaryItems 1 and 4 (throttle/volume correctness and ARIA announcement verification) should be resolved before merge. Item 5 (performance) is a pre-existing concern that can be tracked separately. Everything else is polish. The feature is well-motivated and the overall implementation is architecturally sound. |
#1 Throttle consumed at volume=0 (blocker) playPointerGuidance advanced nextPointerGuidanceBeepAt unconditionally, but playPointerGuidanceBeep returned early when volume*POINTER_GUIDANCE_VOLUME was 0 — the throttle slot was burnt without a beep firing, so turning volume back up left the user with one silent throttle window. Changed playPointerGuidanceBeep to return a boolean and only bump the throttle when a beep actually emitted. New test simulates volume=0 → 100 mid-flow to lock the contract down. #2 Scope mismatch leaves stale throttle armed execute() now calls reset() on out-of-scope events too (not just on missing event / missing coordinates), so a hover that started in Scope.TRACE and continued through a scope change does not delay the first beep after the user returns to trace scope. Updated the relevant unit test. #4 ARIA announcement on hover (verified, no change) The hover-driven moveToIndex → observer notification flow predates this PR — old AbstractTrace.moveToPoint already called moveToIndex when the cursor was in bounds, with identical observer fanout. The refactor into moveToNearest preserves the behavior 1:1, so screen-reader semantics are unchanged. #6 / #7 polish Trimmed restate-the-type JSDoc on PointerGuidanceBeep / PointerGuidanceConfig and noted in resolvePointerGuidanceBeep that "above" follows screen-space y.
Code Review: feat/auditory directional guidance (#587)OverviewThis PR adds real-time auditory directional guidance when hovering near a curve — high/low pitch encodes vertical direction, stereo panning encodes horizontal direction, and beep rate scales with distance. The implementation is well-reasoned with clear thought given to edge cases. The main concerns are architectural. Architecture & DesignCommandFactory bypass (medium concern)
The "keep the two in sync" note is the problem — this is a maintenance risk. If scope validation logic ever changes, it needs to be updated in two places. Consider whether Direct Commands in this codebase operate on Code QualityPositive highlights
Unconventional type formatting // src/type/state.ts
export type PointerGuidanceState
= | { onCurve: true }
| { onCurve: false; ... };The leading export type PointerGuidanceState =
| { onCurve: true }
| { onCurve: false; distancePx: number; curveVertical: 'above' | 'below'; curveHorizontal: 'left' | 'right' };
Potential IssuesTrace types not visible in the diff
Cleanup timeout vs. throttle minimum
No supplemental text cue for audio-off users The guidance is audio-only. Users who have audio disabled ( Test CoverageExcellent coverage for the new surface area:
The only gap is an integration-level test exercising the full chain (pointermove → Summary
The feature is thoughtfully implemented. The two action items before merge are: (1) confirm |
PR review round 9. Action item: extract the duplicated scope-check or document the link to its peer. There is no peer. The prior JSDoc cited a non-existent `CommandExecutor.isValidForScope`; in this codebase keyboard commands rely on hotkeys-js scope routing (only the active scope receives the event, so commands don't self-validate), while DOM pointer listeners receive every event regardless of scope. PointerGuidanceCommand is the only path that needs a self-check, so there is nothing to keep in sync. Rewrote the comment to describe the actual architecture instead of inventing a sync hazard. Action item: confirm type-check passes for all trace types. `npm run build` (which runs vite's TS check + vite-plugin-dts rollup across every trace) is green. `Histogram` and `SegmentedTrace` inherit `findNearestPoint` from `AbstractBarPlot`; `SmoothTrace` / `SmoothTraceSvgXY` inherit from `LineTrace` — no overrides in those files, so the updated `NearestPoint` shape is picked up automatically. Type formatting (leading `|` style): kept as-is. The convention matches `FigureState`, `SubplotState`, and `TraceState` defined immediately below in the same file.
Code Review: feat: add auditory directional guidance towards svg element with mouseOverall Assessment: This is a well-designed and thoughtful feature. The accessibility motivation is clear, the architecture is sound, and the test coverage is strong. A few issues worth addressing before merge. What This PR DoesWhen a user moves the mouse near a data curve, audio cues now guide them toward the nearest data point:
Strengths
Issues to Address1. Missing Jest globals import in
|
Future-proofs the test against a Jest config switch to `injectGlobals: false`, and matches every other PR test file which already imports describe/expect/it explicitly.
Code Review — PR #587: Auditory Directional Guidance for Mouse/PointerOverviewThis PR adds real-time directional audio beeps that guide users toward the nearest data point as they hover their mouse — high pitch = point is above, right pan = point is to the right, beep frequency increases as distance decreases. This is a meaningful accessibility improvement for blind and low-vision users exploring charts non-visually. The implementation is generally well-structured and follows MAIDR's MVVC architecture. Below is my detailed analysis. What Works WellArchitecture compliance
Safari graceful degradation const stereoPanner = typeof this.audioContext.createStereoPanner === 'function'
? this.audioContext.createStereoPanner()
: null;Horizontal guidance is silently lost on Safari < 14.5; vertical pitch still works. This is the correct tradeoff. Test coverage Issues and ConcernsBug Risk:
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
End-to-End Testing GuideThis document provides comprehensive information about running and writing end-to-end (e2e) tests for the MAIDR project. Prerequisites
Available Test ScriptsThe following npm scripts are available for running e2e tests: # Run all e2e tests
npm run e2e
# Run e2e tests with UI mode (for debugging)
npm run e2e:ui
# Run e2e tests in debug mode
npm run e2e:debug
# Install Playwright browsers and dependencies
npm run e2e:installTest StructureDirectory StructureTest CategoriesEach plot type's test suite includes the following categories:
Writing TestsPage ObjectsEach plot type has its own page object class that extends the base page object. Page objects encapsulate:
Example: class BoxplotVerticalPage extends BasePage {
async moveToDataPointBelow() {
// Implementation
}
async startDownwardAutoplay(options?: { timeout?: number }) {
// Implementation
}
}Test StructureTests are organized using Playwright's test framework: import { expect, test } from '@playwright/test';
import { BoxplotVerticalPage } from '../page-objects/plots/boxplotVertical-page';
test.describe('Boxplot Vertical Tests', () => {
let boxplotVerticalPage: BoxplotVerticalPage;
test.beforeEach(async ({ page }) => {
boxplotVerticalPage = new BoxplotVerticalPage(page);
await boxplotVerticalPage.navigateToPlot('boxplotVertical');
});
test('should move to data point below', async () => {
await boxplotVerticalPage.moveToDataPointBelow();
// Assertions
});
});Best Practices
Debugging TestsUI ModeRun tests in UI mode for visual debugging: npm run e2e:uiThis opens Playwright's UI where you can:
Debug ModeFor console debugging: npm run e2e:debugCommon Issues
Continuous IntegrationE2E tests are run:
GitHub ActionsThe workflow includes:
ContributingWhen adding new tests:
Resources |
Code Review — PR #587: Auditory Directional Guidance for Pointer ExplorationOverviewThis PR adds real-time auditory guidance beeps that direct the user toward the nearest data point as they move the mouse over a chart. High pitch = curve is above cursor; low pitch = below; stereo pan pulls toward the horizontal direction. The beep rate increases as the cursor approaches the curve. A solid accessibility feature with good fundamentals. Architecture & DesignStrengths
Concerns
Potential Bugs / Issues1. histogram.ts not updated All other trace types that override findNearestPoint have been updated to include centerX/centerY. If HistogramTrace overrides findNearestPoint independently (rather than inheriting from AbstractBarPlot), it will return the old type. TypeScript should catch this at build time, but worth confirming explicitly before merge. 2. Vertical direction comment is misleading In abstract.ts (moveToPointAndGetPointerGuidance): The comment reads as if the point has y < centerY, but y is the cursor position. Suggested rewrite: Screen-space y grows downward. When cursor y < centerY, the cursor is higher on screen than the nearest point — the curve point sits below. 3. Silent null return in Context for non-trace scopes Old Figure.moveToPoint called this.notifyStateUpdate() — a side effect that is correctly removed. The new base moveToPointAndGetPointerGuidance returns null for non-trace contexts. This is correct, but a comment in Context.moveToPointAndGetPointerGuidance noting the invariant (only traces return non-null) would help future readers. Code QualityMinor issues
Test CoverageThe test suite is genuinely strong:
One gap: no test for Mousebindingservice.update() switching hover modes and clearing guidance state. The path is covered transitively (registerEvents -> removeEventListeners -> reset()), but an explicit test would pin the invariant. SummaryThe implementation is solid and the test coverage is substantially better than average for a feature this size. Main items to address before merging:
Everything else is minor or informational. Great feature addition for non-visual chart exploration. |
|
(stale comment — see review below) |
Pull Request
Description
When the user moves the mouse, auditory queues point them towards the nearest point in the curve.
Related Issues
Changes Made
Earlier behavior: the sonified (x,y) position is played if the mouse curser or touchscreen pointer touches any of the elements present in the svg. Nearby points provide no indication of proximity to the curve.
Current behavior: The relative direction to the nearest point from curser is represented with auditory notification. High pitch indicates that the point is above the curser. stereo panning to the right indicates that the point is to the right side. This is designed to guide the user towards the point, which will help the user physically draw the curve on the mouse, thus reinforcing a realistic shape of the curve.
Screenshots (if applicable)
Checklist
ManualTestingProcess.md, and all tests related to this pull request pass.Additional Notes