feat: add intersecting point rotor mode for multiline plots#604
feat: add intersecting point rotor mode for multiline plots#604
Conversation
Adds a new rotor unit, INTERSECTING POINT NAVIGATION, available via Alt+Shift+Up/Down in multiline line plots. Left/Right navigate to the previous/next point intersection (data points present in both lines); slope intersections are excluded, reusing the goto feature's classifyIntersectionKind logic for consistency. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code ReviewOverviewThis PR adds a new INTERSECTING POINT NAVIGATION rotor mode for multiline line plots, available via Alt+Shift+Up/Down to cycle into, then Left/Right to step between data-point intersections. The implementation correctly integrates with the existing rotor/extrema infrastructure and reuses findAllIntersectionsForCurrentLine and finalizeExtremaNavigation. What Works Well
Issues1. Dead try-catch / redundant guard in moveIntersection()The guard at the top of moveIntersection() checks supportsIntersectionMode() before calling the move methods. But INTERSECTION_MODE is only added to the rotor cycle when that same condition passes, so the catch block can never fire in normal operation — the default throw in AbstractPlot is unreachable. This creates two contradictory defenses that together make the code harder to reason about. Suggested approaches:
2. getIntersectionMessage() duplicates getMessage()The new getIntersectionMessage(direction) re-implements the same isOff() / isTerse() branching that the existing getMessage(navType, direction) already handles. Calling this.getMessage('intersection', direction) would produce near-identical user-facing messages without the duplicated logic. If the slight wording difference ('found above the current point' vs 'found above the current value') is intentional for accessibility reasons, please add a comment explaining why; otherwise prefer reuse. 3. Missing unit tests for new navigation methodsThe test/model/line.test.ts file was not updated. The following cases have zero coverage:
The PR checklist marks the unit test item as unchecked — please add tests before merge. 4. No E2E coverage for the new rotor modee2e_tests/specs/multiLineplot.spec.ts already tests the Go-To intersection dialog but has no test for the Alt+Shift keyboard rotor cycle introduced by this PR. A minimal test cycling into INTERSECTING POINT NAVIGATION and pressing Left/Right would catch regressions. Minor SuggestionsmoveToPrevIntersection reverse loop — the manual backwards for loop could be replaced with the cleaner Array.prototype.findLast, available in modern target environments: const target = indices.findLast(index => index < this.col);Method placement — the sibling capability methods (moveUpRotor, moveDownRotor, supportsCompareMode) live on AbstractTrace, not AbstractPlot. Moving the three new intersection methods down to AbstractTrace would keep the capability APIs co-located and avoid confusion about which hierarchy level owns intersection semantics. SummaryThe core logic is sound, the caching story is correct, and the pattern matches the existing rotor infrastructure. Main blockers before merge: missing unit tests and the dead try-catch / guard inconsistency. The message duplication and findLast suggestions are clean-ups worth addressing but not blocking. |
- Drop redundant supportsIntersectionMode() guard in
RotorNavigationService.moveIntersection(); the try/catch already
handles traces that lack the capability, matching moveUpRotor/
moveDownRotor's dispatch style.
- Replace duplicated getIntersectionMessage() with a reuse of
getMessage('intersection', direction) for up/down feedback.
- Replace the reverse-for-loop in moveToPrevIntersection() with
Array.prototype.findLast for clarity.
- Add unit tests covering supportsIntersectionMode(), next/prev
navigation (including slope-intersection skipping), and boundary
cases where no point intersections are available.
- Add an E2E test that cycles the rotor into INTERSECTING POINT
NAVIGATION and verifies arrow keys do not throw.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review: feat: add intersecting point rotor mode for multiline plotsOverall: This is a well-scoped feature that integrates cleanly with the existing rotor architecture. The logic is correct, the tests cover the main cases, and the MVVC boundaries are respected. A few issues worth addressing before merge. What the PR doesAdds Issues1. Throw-based defaults are an antipattern here — prefer
|
Previously, pressing Left/Right at the first/last point intersection
caused TextService to announce "No plot info to display" instead of
a meaningful message. Root cause: moveToNext/PrevIntersection called
notifyRotorBounds(), which emits an empty warning state that
TextService formats as "No plot info to display".
- Remove notifyRotorBounds() from moveToNextIntersection and
moveToPrevIntersection; they simply return false at bounds.
- Capture the boolean return in RotorNavigationService.moveIntersection
and return getMessage('intersection', direction) when unmoved, so
the rotor area announces the bound message just like moveUp/moveDown
already do for intersection mode.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review — feat: add intersecting point rotor mode for multiline plotsOverviewThis PR adds an INTERSECTING POINT NAVIGATION rotor mode that lets users navigate between data-point intersections (shared x/y values) across lines in a multiline plot, using Left/Right arrows while in the new rotor mode. The implementation correctly skips slope intersections and reuses the existing Issues1. Base-class methods throw instead of returning
|
| Category | Status |
|---|---|
| Code correctness | Mostly correct; base-class throw vs. return-false is the main concern |
| Architecture (MVVC) | Compliant |
| Test coverage | Unit tests solid; E2E could be stronger |
| Security | No concerns |
| Performance | Low risk; intersection index recomputation worth documenting |
| Breaking changes | None |
The feature is a good addition. Addressing issues 1–3 above would improve robustness and maintainability before merge.
- Base-class moveToNextIntersection / moveToPrevIntersection now return
false instead of throwing. The supportsIntersectionMode() contract is
the guard; unsupported traces should be safe no-ops rather than errors.
- Rename finalizeExtremaNavigation -> finalizeNavigation across bar,
candlestick, heatmap, line, segmented. The method is generic
post-navigation cleanup and is now used by intersection navigation too,
so the extrema-specific name was misleading.
- Up/Down in intersection mode now announce a dedicated
"Up/down unavailable in intersection mode" message instead of the
directional getMessage('intersection', 'above'|'below') which
implied a vertical bound existed.
- Simplify RotorNavigationService.moveIntersection: drop the try/catch
(no longer needed now that base methods don't throw) and remove the
redundant "return bound message on non-AbstractTrace" path.
- Document that getPointIntersectionIndices reuses the per-row cache in
findAllIntersectionsForCurrentLine, so repeated calls are cheap.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review — feat: add intersecting point rotor mode for multiline plotsOverviewAdds Issues1. Capability methods belong on
|
| Category | Status |
|---|---|
| Architecture (MVVC) | Mostly compliant — AbstractPlot placement is a concern |
| Accessibility / UX | "Intersection value" message wording should be improved |
| Unit test coverage | Solid |
| E2E coverage | Weak — smoke only |
| Performance | Fine (caching already in place) |
| Security | No concerns |
| Breaking changes | None |
Issues 1 and 2 are the most impactful before merge.
Addresses review feedback on PR #604: - Boundary wording (issue #2): The previous getMessage('intersection', direction) output "No intersection value found to the left of the current value." The word "value" is inherited from compare-mode phrasing and reads oddly — an intersection is a coordinate, not a value. Replaced with a dedicated getIntersectionBoundMessage that announces "No intersection to the {left,right}" (terse) / "No intersection found to the {left,right} of the current point." (verbose). - Message duplication (issue #3): The off/terse/verbose branching was repeated in getIntersectionVerticalUnavailableMessage. Factored into a shared buildMessage(terse, verbose) helper that both intersection messages (and any future mode-specific messages) can reuse. Not folded into getMessage because getMessage's format is locked to "No <navType> value found <position>..." — diverging from that template is exactly what the new wording required. - E2E coverage (issue #4): Previous test only confirmed no-crash. Strengthened to assert the rotor area actually transitions to the bound message after ArrowRight / ArrowLeft and to the vertical-unavailable message after ArrowUp — proving the keys are routed through intersection mode rather than silently ignored. Issue #1 (move intersection methods from AbstractPlot to AbstractTrace) not addressed: sibling capability methods (moveLeftRotor, moveRightRotor, supportsCompareMode, dataModeName) all already live on AbstractPlot, so moving just the intersection methods would split the capability API across two classes. Happy to relocate all four groups together as a follow-up refactor. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review — feat: add intersecting point rotor mode for multiline plotsOverviewThis PR adds a new rotor navigation mode ( Issues1. Method placement creates an inconsistent API contract
// rotor.ts
if (!(activeTrace instanceof AbstractTrace)) {
return null;
}If these methods are only meaningful for trace-level plots, they should live on 2.
|
- Move supportsIntersectionMode / moveToNextIntersection / moveToPrevIntersection from AbstractPlot to AbstractTrace. Intersection navigation is a trace-level capability with no meaning at the figure or subplot level, so it belongs with the other trace-specific extension points (getExtremaTargets, navigateToExtrema, finalizeNavigation) on AbstractTrace. - Fold getMessage() through the new buildMessage(terse, verbose) helper so the off/terse/verbose branching lives in one place. Directional rotor bound messages and the intersection-specific messages now share a single branching site. - Rewrite the getAvailableModes() JSDoc with an explicit numbered ordering (Data -> Lower -> Higher -> Grid -> Intersection) so future contributors insert new modes at the correct position in the cycle. Scope: intentionally skips the reviewer's non-blocking concern about stale rotorIndex on context change (reviewer themselves flagged it as pre-existing) and the suggestion-tier items (findLast compat, unit test split, performance JSDoc rewording, E2E fixture pinning). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review — PR #604: Add intersecting point rotor mode for multiline plotsOverviewThis PR adds a new rotor mode ( Positive Highlights
Issues & Suggestions1.
|
| Category | Status |
|---|---|
| Architecture (MVVC) | ✅ Correct |
| Reuse of existing logic | ✅ Good |
| Test coverage | |
| Browser compatibility | findLast (ES2023) — low risk given ESNext target |
| Code style / KISS | ✅ Clean and readable |
| Observer notifications | ✅ finalizeNavigation() correctly called |
| Accessibility text modes | ✅ Terse / verbose / off all handled |
Overall this is a well-implemented, focused feature. The main asks before merge are: (1) verify or document the findAllIntersectionsForCurrentLine sort guarantee, and (2) consider adding a rotor-service unit test or an E2E data fixture with actual intersections to strengthen confidence.
- Document the sort in getPointIntersectionIndices: the upstream findAllIntersectionsForCurrentLine sorts by continuous x-coordinate, which can diverge from pointIndex order when intersections fall between sampled points. Rotor navigation moves by column, so the local sort by pointIndex is load-bearing, not a no-op. - Clarify why supportsIntersectionMode requires points.length > 1 (the outer dimension of the 2-D points array is the line count). - Comment the moveUp / moveDown early-return in intersection mode to make the intentional no-notify behaviour explicit — audio/text are deliberately suppressed since vertical navigation is unavailable. - Add an E2E test that uses examples/multiline_plot_intersection.html (which contains a real 3-way point intersection at (x=3, y=4)) so ArrowRight in intersection mode is exercised end-to-end, not just at the boundary. Complements the existing boundary-only test. Skipped (reviewer-labeled low-priority / out of scope): - Replacing findLast with reversed find — tsconfig target is ESNext. - Rotor-service unit tests — reviewer offered this OR an E2E fixture; the new E2E test covers the same path through the real command flow. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review: feat: add intersecting point rotor mode for multiline plotsOverviewThis PR adds a new INTERSECTING POINT NAVIGATION rotor mode that allows users to jump between data-point intersections across lines in multiline plots via Left/Right arrows. The implementation is well-structured and largely follows existing project conventions. What Works Well
Issues and Suggestions1. Missing HTML fixture (potential CI failure) The E2E test 2. const target = indices.findLast(index => index < this.col);
const target = [...indices].reverse().find(index => index < this.col);3. In 4. Vertical navigation gives no audio feedback The Up/Down handlers in intersection mode return the unavailable message but intentionally skip 5. Brittle E2E assertion for successful navigation await expect(textContainer).toContainText('3', { timeout: 2000 });
await expect(textContainer).toContainText('4', { timeout: 2000 });Checking bare digits will match any text containing those characters. A more robust assertion would use a regex like 6. E2E rotor cycle is tied to absolute mode count Using a hardcoded loop of 3 presses to reach INTERSECTING POINT NAVIGATION means inserting any future mode before it will silently break the test (it will land on the wrong mode). Consider cycling until the rotor area displays the expected string, rather than counting presses. 7. Nit:
return [...seen].sort((a, b) => a - b);Test Coverage Summary
SummaryThe feature is well-conceived and the implementation is clean. The main blockers before merge:
The |
- Replace Array.prototype.findLast (ES2023) in moveToPrevIntersection with a reversed find() for broader runtime safety. Semantics are identical; avoids relying on a recent-ES feature for an accessibility library whose users may run older system browsers. - Simplify getPointIntersectionIndices: the Set already uniquifies, so the parallel indices array is redundant. Now returns [...seen].sort((a, b) => a - b). - Add a console.warn on the unexpected non-AbstractTrace branch in RotorNavigationService.moveIntersection. Previously returned null silently; now surfaces the anomaly so it is not invisible in logs. - E2E: strengthen the "real intersection" assertion with regexes that bind digits to their axis labels (/x\D*3/ and /y\D*4/) instead of raw substring matches that would accept any "3"/"4" in the text. - E2E: replace the hardcoded 3-press rotor cycle with a cycle-until-match helper. Inserting a new mode before INTERSECTING POINT NAVIGATION in future no longer silently lands the test on the wrong mode. Reviewer blocker #1 (examples/multiline_plot_intersection.html) is a non-issue — the fixture is already tracked (git ls-files confirms). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review — feat: add intersecting point rotor mode for multiline plotsOverall, this is a well-structured addition. The implementation is clean, correctly layered in the MVVC architecture, and the reuse of the existing Bugs / Correctness1. // src/service/rotor.ts ~line 387-393
if (!(activeTrace instanceof AbstractTrace)) {
console.warn('[RotorNavigation] Active plot is not an AbstractTrace; ...');
return null; // ← null means "navigation succeeded" to callers
}In this service, Architecture / Design2. The existing That said, the current approach is functionally correct and simpler, so this is a style point rather than a blocking concern. 3.
Minor Issues4. The underlying 5. If a user cycles to Test Coverage6. Missing unit test: The unit suite covers:
Missing: 7. E2E: Up/Down in intersection mode only tested on the non-intersection fixture The second E2E test (using Positive Observations
Summary
Nothing here is blocking for a user-facing bug, but item 1 is worth a quick fix before merge. |
- moveIntersection defensive branch: previously returned null (which callers interpret as "navigation succeeded"). A null sentinel in a path where the model didn't move is misleading. Now returns an "Intersection navigation is not available" message through buildMessage so the rotor area informs the user their key press had no effect. Kept the console.warn for debugging. - Document the pre-existing rotorIndex context-switch limitation on getMode(): when a user cycles to a capability-gated mode (GRID_MODE / INTERSECTION_MODE) and focus moves to a trace that doesn't advertise that capability, rotorIndex % modes.length silently wraps onto a different mode. Resetting the rotor on context change is a broader UX decision tracked outside this PR. - Add unit test for moveToPrevIntersection when positioned before all intersections (col=0): asserts false return and that col is unchanged, complementing the existing "after last intersection" case. - E2E: in the real-intersection fixture test, assert that ArrowUp and ArrowDown still announce the "unavailable" message even when the fixture contains real intersections — confirms vertical navigation doesn't accidentally traverse between lines. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code ReviewOverviewThis PR adds INTERSECTING POINT NAVIGATION as a new rotor mode for multiline line plots. Users can cycle into the mode via Alt+Shift+Up/Down and then use Left/Right arrows to jump between data points that are shared (same coordinates) across two or more lines. Slope-only crossings are explicitly excluded. The implementation reuses Strengths
Issues & Suggestions1.
|
Main asks from the reviewer: - Add a 3-line point-intersection unit test so the Set-based dedup path in getPointIntersectionIndices is exercised. findAllIntersectionsForCurrentLine emits one entry per crossing pair (line0<->1, line0<->2), both with the same pointIndex; the new test confirms navigation treats it as one intersection. - Add rotor-service unit tests (test/service/rotor.test.ts) that mock Context and TextService and verify: moveRight/moveLeft dispatch to moveToNextIntersection / moveToPrevIntersection; moveUp/moveDown return the vertical-unavailable message; boundary hits return the "No intersection ..." bound message; terse/off text modes are honored. Covers the service-level logic previously only reached via E2E. - Clarify via comment in the E2E test that a bare relative path (examples/multiline_plot_intersection.html) resolves correctly under the Playwright baseURL of file://<projectRoot>/ configured in test-config.ts. Cleanup from the same review: - Replace [...indices].reverse().find() in moveToPrevIntersection with a reverse for-loop. Same semantics without allocating a reversed copy. - Extend the moveIntersection JSDoc to spell out the null = "move succeeded" return convention, matching sibling methods. - Reword the supportsIntersectionMode JSDoc to "opt-in per trace type" so it doesn't need updating when another trace type adopts it. Not addressed (reviewer explicitly labeled): - Style-point isIntersectionNavigable type guard (functionally correct as-is, not blocking). - getAvailableModes caching (pre-existing, low priority). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review – feat: add intersecting point rotor mode for multiline plotsOverviewThis PR adds INTERSECTING POINT NAVIGATION as a new rotor mode accessible via Alt+Shift+Up/Down on multiline line plots. Left/Right arrows then jump to the previous/next data-point intersection (exact coordinate match between lines), skipping slope-only crossings. The implementation is clean, well-documented, and follows the MVVC architecture throughout. ✅ Strengths
🐛 Issues / Concerns1.
|
moveUp / moveDown / moveLeft / moveRight each called isGridMode() and isIntersectionMode() — both of which resolve to getMode() -> getAvailableModes() — meaning getAvailableModes() ran twice per keystroke just to pick the mode branch. Cache getMode() once at the top of each method and compare against Constant.GRID_MODE / INTERSECTION_MODE directly. Semantics unchanged; both the existing LineTrace rotor unit tests and the new rotor-service tests continue to pass (16/16). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code ReviewPR: feat: add intersecting point rotor mode for multiline plots OverviewThis PR adds an INTERSECTING POINT NAVIGATION rotor mode to multiline line plots, accessible via Alt+Shift+Up/Down cycling. Left/Right arrow keys navigate to previous/next data-point intersections between lines, while Up/Down are explicitly blocked with a user-facing message. Slope intersections are excluded by reusing the existing The implementation follows MVVC architecture well: the Model layer gains opt-in capability methods, the Service layer dispatches to them, and existing tests are extended alongside new unit and E2E tests. The design choices are generally sound. Bugs / Blockers1. Missing example HTML fixture — E2E test will failThe E2E test at await page.goto('examples/multiline_plot_intersection.html', { waitUntil: 'load' });But 2.
|
| Category | Status |
|---|---|
| Architecture (MVVC) | ✅ Follows existing patterns |
| Feature correctness | ✅ Logic is sound |
| Missing fixture file | ❌ Blocker — E2E will fail |
Dead code (isIntersectionMode) |
|
console.warn in production path |
|
| Test coverage | ✅ Good; add row > 0 test |
| Rename scope | ℹ️ Fine, but note in PR description |
The core logic and design are solid. Please add the missing examples/multiline_plot_intersection.html fixture and remove the dead isIntersectionMode() method before merging. The other items are lower priority but worth addressing.
Addresses PR #604 review: - Remove isGridMode() and isIntersectionMode(). Both were made dead by the previous commit that cached getMode() locally in each move*() method and compared against Constant.* directly. Reviewer flagged isIntersectionMode() specifically; isGridMode() was dead for the same reason and is dropped in the same pass for consistency. - Update the getMode()-caching rationale comment in moveUp() to no longer reference the removed helpers. - Add a unit test that navigates intersections from row = 1. Confirms findAllIntersectionsForCurrentLine() scopes to the active row, not always row 0, and that changing the row changes which intersections are visible. Closes a coverage gap all previous cases had kept the default row. Not addressed (reviewer comment): - Alleged "missing fixture" examples/multiline_plot_intersection.html is already tracked (git ls-files confirms). - "Use project logger" — no util/logger.ts exists in this codebase; console.warn/error is the established convention (21 existing uses across 10 files). Adding a new logger utility is out of scope. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review: feat: add intersecting point rotor mode for multiline plotsOverviewThis PR adds a new Architecture and Design
Issues and Suggestions1. The underlying 2. In 3. Grid mode enriches its display string with dimension info (e.g., 4.
Test Coverage
SecurityNo external input is rendered as HTML, no dynamic code execution, no new dependencies introduced. SummaryThis is a solid, well-tested feature that fits cleanly into the existing rotor architecture. Actionable items:
The |
Per PR #604 review and DEBUGGING.md ("remove logs before commit"), the production console.warn for the defensive non-AbstractTrace branch is unnecessary — the returned unavailable message already communicates the failure to the user via the rotor area. The branch itself and the explanatory comment remain intact; only the log call and the "warn for debugging" fragment of the comment are removed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review — PR #604: Intersecting Point Rotor Mode for Multiline PlotsOverall: This is a well-structured feature addition that correctly follows the project's MVVC architecture. The intersection logic builds cleanly on existing infrastructure, test coverage is solid across all three layers (unit, service, E2E), and the UX messages are thoughtfully designed for screen reader users. A few items below warrant attention before merging. What This PR Does
Issues1. Documented Known Limitation Is a Real Accessibility Risk
For a screen reader–first tool this is a meaningful UX regression, not just a known limitation. A user who cycles into INTERSECTION_MODE on a multiline chart, then tabs to a single-line chart, will end up in an undisclosed mode. The fix (reset Suggestion: Open a tracking issue for this and link it from the comment so it doesn't get lost. 2.
|
| Layer | Tests Added | Quality |
|---|---|---|
Unit (test/model/line.test.ts) |
7 new tests | Good coverage of next/prev, boundary, dedup, row scoping, no-intersections case |
Service (test/service/rotor.test.ts) |
6 new tests | Covers all four directions, terse/off text modes, bound messages |
E2E (e2e_tests/specs/multiLineplot.spec.ts) |
2 new tests | Covers the "no intersections" boundary path and a real intersection fixture |
The cycleRotorTo helper in E2E is a good pattern — avoids hardcoded press counts that break silently when the rotor order changes.
One gap: there's no test verifying that INTERSECTION_MODE does not appear in the rotor cycle for a single-line trace. Adding that to rotor.test.ts would close the loop on the supportsIntersectionMode → getAvailableModes wiring.
Architecture Check ✅
- Navigation logic lives in
LineTrace(model layer) — correct. RotorNavigationServicehandles mode routing and user messages (service layer) — correct.finalizeNavigation()callsnotifyStateUpdate(), keeping the observer chain intact — correct.- No direct Redux dispatch or UI manipulation from service layer — correct.
Summary
This is a solid, well-tested feature. The two things I'd address before merging:
- Open a tracking issue for the rotorIndex-not-reset limitation so it doesn't stay as an in-file comment indefinitely.
- Add one more rotor unit test asserting that
INTERSECTION_MODEis absent from the available modes for a single-line trace.
Everything else is either pre-existing behavior or low-priority cleanup.
Closes the loop on the supportsIntersectionMode -> getAvailableModes wiring. Previously every unit test used a multiline trace, so there was no direct verification that the mode is actually excluded when supportsIntersectionMode() returns false. Cycles the rotor more than the plausible mode count on a single-line LineTrace and asserts INTERSECTION_MODE never appears in the visited modes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code ReviewOverviewThis PR adds an INTERSECTING POINT NAVIGATION rotor mode for multiline line plots, accessible via Alt+Shift+Up/Down. Users can then press Left/Right to jump between data points where two or more lines share the same sampled value ("point intersections"), explicitly excluding slope-only crossings. The implementation is well-structured and follows the MVVC architecture closely. Architecture & Code Quality ✅Strengths:
Potential Issues1. When cycling into 2. Each navigation call constructs a new 3. Known limitation (acknowledged in code, but worth surfacing here) The comment in
This pre-existed the PR but is now more impactful since a third capability-gated mode exists. A follow-up issue to reset Test Coverage ✅Coverage is comprehensive and well-designed: Unit tests (
Unit tests (
E2E tests (
Nits / Minor Suggestions
SummaryThis is a well-implemented, architecturally compliant feature. The test coverage is thorough — particularly the 3-way deduplication and row-scoping unit tests, and the
Neither is a blocker. Nice work! |
|
hello @soundaryakp1999 , i found a minor bug while navigating, there is only one intersection point at X=2, Y=4. but still in intersecting point rotor mode it moves to another point at X=8, Y=8 (or sometimes X=8, Y=7 (highlighting is flaky)). Could you please fix this issue? |
|
looking good to me now! |
|
Code Review PR 604 Intersecting Point Rotor Mode Overview This PR adds INTERSECTING POINT NAVIGATION to the rotor cycle for multiline plots. Right/Left in this mode jump between data points shared across lines (point intersections only; slope crossings excluded). The mode is scoped to LineTrace with more than 1 line via opt-in supportsIntersectionMode() on AbstractTrace. Implementation follows MVVC and has solid unit and E2E tests. Issues Unrelated file committed: .claude/scheduled_tasks.lock The diff includes .claude/scheduled_tasks.lock, a runtime lock file unrelated to this feature. Remove it before merging. Suggestions getMessage() computes strings even when text is off. The refactored version always evaluates preposition and position even when isOff() would suppress output. Not a performance concern, but the old early-return was more explicit about intent. Caching comment in moveUp/Down/Left/Right overstates the optimization. const mode = this.getMode() is a local variable assignment, not memoization. getMode() still recomputes getAvailableModes() on each call. The real benefit is clarity (one call, two comparisons). The cache framing in the comment should be simplified. Known limitation: rotor index not reset on context change. The getMode() comment correctly notes this. If a user cycles into INTERSECTION_MODE on a multiline trace and focus moves to a single-line trace, the modulo wrapping silently changes modes without announcing it. Track as a follow-up issue. dodged_barplot_large.html and multiline_plot_large.html are not referenced by any new test and appear unrelated to the feature. If they serve a purpose, note it in the PR description. Positives Opt-in design on AbstractTrace: supportsIntersectionMode(), moveToNextIntersection(), moveToPrevIntersection() all default to no-op/false so existing traces are completely unaffected. finalizeExtremaNavigation() to finalizeNavigation() rename is correct and consistent now that the method covers intersection navigation too. buildMessage() helper nicely reduces duplication of off/terse/verbose branching across all intersection message helpers. Defensive instanceof AbstractTrace check in moveIntersection() correctly handles the edge case where context changes between the mode list build and the key press. Set-based dedup in getPointIntersectionIndices() correctly handles n-way intersections (e.g., 3 lines sharing one point counted as one stop). E2E cycleRotorTo helper avoids fragile fixed press counts and stays correct if rotor order changes. Test coverage is thorough: boundary conditions, slope vs. point filtering, multi-way dedup, row scoping, terse/off text modes, and INTERSECTION_MODE exclusion for single-line traces. Architecture is correct: model methods live on LineTrace, dispatch in RotorNavigationService, no direct UI manipulation. Properly follows MVVC. Summary The feature implementation is solid. The only required change before merging is removing .claude/scheduled_tasks.lock from the commit. All other points are minor. The accessibility design (boundary announcements, vertical navigation blocked with a clear message) is appropriate for screen reader users. |
Code Review – PR #604: Intersecting Point Rotor Mode for Multiline PlotsOverall this is a clean, well-scoped feature. The extension model (opt-in via Summary of Changes
Issues1. Unrelated example file committed (
|
Per reviewer feedback, examples/dodged_barplot_large.html (1347 lines, a dodged barplot) is unrelated to intersection navigation and should not ship with this PR. examples/multiline_plot_large.html (824 lines) was introduced in the same earlier commit (b2ba563) with the same "large" stress-test naming and is similarly out of scope; removing it for consistency. Both files were added on this branch and are not referenced by any test or feature in this PR. The new test file test/service/rotor.test.ts is the only remaining branch-added file and is intentional. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review — PR #604: Intersecting Point Rotor Mode for Multiline PlotsOverviewThis PR adds a new Positives
Issues and Suggestions1.
|
Repeated arrow presses at a boundary (or up/down in intersection mode) announced once and then stayed silent for screen reader users. Other rotor modes don't have this issue because their boundary message flows through the trace state-update -> TextService -> notification path, and TextViewModel.notify() bumps a revision counter that Text.tsx uses as the React key on the role="alert" div, forcing a re-mount and a fresh SR announcement on every dispatch. Intersection mode was wired only to the rotor area (plain aria-live with no key), so identical messages didn't re-trigger the SR. Inject NotificationService into RotorNavigationService and route the intersection-bound and vertical-unavailable messages through notification.notify() in addition to the existing rotor-area dispatch. notify() no-ops on empty input, so text-off mode is unchanged. Tests: - Update rotor.test.ts mocks for the new constructor parameter. - Add a regression test that asserts notification.notify is called for the bound and vertical-unavailable paths, and that repeated key presses each call notify (so the alert region remounts each time). - Fix a pre-existing AxisConfig typing nit in the test fixture. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code Review — feat: add intersecting point rotor mode for multiline plotsOverviewThis PR adds Architecture and DesignStrengths
Potential issue — constructor is a breaking change
CorrectnessIntersection logic
Notification path for screen readers The private announceIntersectionMessage(message: string): string {
if (message) {
this.notification.notify(message);
}
return message;
}Known limitation documented The Test CoverageThe test suite is thorough:
Minor Issues
SummaryThe implementation is solid, well-tested, and follows project conventions. Main items before merge:
Overall this is a well-crafted, accessibility-thoughtful feature. The notification/re-announce pattern for repeated identical messages is a subtle but important accessibility detail — nice work. Review generated by Claude Sonnet 4.6 |
|
🎉 This PR is included in version 3.66.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Pull Request
Description
Adds a new rotor unit, INTERSECTING POINT NAVIGATION, available via Alt+Shift+Up/Down in multiline plots. Left/Right navigate to the previous/next point intersection (data points present in both lines); slope intersections are excluded, reusing the goto feature's classifyIntersectionKind logic for consistency.
Related Issues
Changes Made
Screenshots (if applicable)
Checklist
ManualTestingProcess.md, and all tests related to this pull request pass.Additional Notes