Skip to content

feat: notify users of current position with p key#526

Merged
ellvix merged 8 commits intomainfrom
feat-position
Mar 12, 2026
Merged

feat: notify users of current position with p key#526
ellvix merged 8 commits intomainfrom
feat-position

Conversation

@ellvix
Copy link
Collaborator

@ellvix ellvix commented Feb 17, 2026

Pull Request

Description

Adds a position announcement system (p key). Based on the text mode, user will hear their current position in percentage (terse mode), "Position 1 of 12" (verbose), or "Text mode is off. To enable, press the T key".

Related Issues

Addresses #421

Changes Made

  • textViewModel.ts — Added warnIfTextOff(): boolean method on TextViewModel. Checks if text mode is off; if so, fires a notification via notify() and plays playWarningTone() from AudioService. Returns true when the warning was issued. AudioService added as a constructor dependency to support this.
  • controller.ts — Passes audioService to the TextViewModel constructor.
  • describe.ts (AnnouncePositionCommand) — Calls warnIfTextOff() early in execute(), returning before announcing position if text is off. Also adds chart-type-specific position logic: single vs. multi-violin handling (announceSmoothPosition / announceMultiViolinPosition), explicit single-line plot routing, and isOff() fallback to terse formatting across all sub-methods.
  • toggle.ts (ToggleScopeCommand) — Accepts an optional TextViewModel and calls warnIfTextOff() before toggling scope (scope still toggles regardless).
  • factory.ts — Splits ACTIVATE_FIGURE_LABEL_SCOPE from DEACTIVATE_FIGURE_LABEL_SCOPE and passes textViewModel to both ACTIVATE cases only.

Additional Notes

  • warnIfTextOff() is designed for easy reuse — any future command just needs access to textViewModel and a one-line call at the top of execute().
  • The scope toggle still executes after the warning so keybindings remain functional; only text-producing commands (like position) early-return.
  • The isOff() fallback to terse formatting in the position sub-methods is a safety net — with the early guard in execute(), those branches won't be reached when text is off, but they preserve correct behavior if warnIfTextOff() is removed from the guard in the future.

@claude
Copy link

claude bot commented Mar 12, 2026

PR Review: feat: notify users of current position with p key

Overall, this is a well-structured PR that adds a useful accessibility feature. The renaming of Describe* to Announce* is a good consistency improvement. Below are my findings, from most to least critical.


Bug

1. playWarningTone() bypasses audio mode setting

In textViewModel.ts, warnIfTextOff() calls this.audioService.playWarningTone() unconditionally, which plays a warning tone even when the user has disabled audio. Every other call site uses playWarningToneIfEnabled() which respects the audio mode:

// textViewModel.ts (new) — bypasses audio mode
this.audioService.playWarningTone();

// describe.ts (existing) — respects user preference
this.audioService.playWarningToneIfEnabled();

This should use playWarningToneIfEnabled() to stay consistent with user preferences.


Architecture Concern

2. TextViewModel directly calling AudioService violates MVVC separation

Adding AudioService as a dependency of TextViewModel creates a cross-service dependency at the ViewModel layer. Per MVVC architecture, ViewModels bridge a single service's events to Redux — they shouldn't orchestrate multiple services.

The command layer (AnnouncePositionCommand.execute()) already has access to both audioService and textViewModel. Moving the coordination there would be architecturally cleaner:

// In AnnouncePositionCommand.execute()
if (this.textService.isOff()) {
  this.textViewModel.notify('Text mode is off. To enable, press the T key.');
  this.audioService.playWarningToneIfEnabled();
  return;
}

This avoids coupling TextViewModel to AudioService entirely.


KISS Violations

3. || this.textService.isOff() branches in sub-methods are dead code

The PR description acknowledges these as "safety nets," but the early warnIfTextOff() guard in execute() already returns before any sub-method is called when text is off. These branches are unreachable and should be removed from announce1DPosition, announce2DPosition, announceBoxplotPosition, announceCandlestickPosition, announceSegmentedBarPosition, announceMultiViolinPosition, announceMultiLinePosition, and announceScatter.

4. announceSmoothPosition is a no-op wrapper

This method delegates to announce1DPosition with identical arguments and adds zero logic. The call site could use announce1DPosition directly, or the wrapper should be removed.


Minor Issues

5. .claude/settings.local.json should not be committed

This is a user-local Claude Code settings file and should be in .gitignore, not committed to the repository.

6. Incorrect JSDoc on announceScatter

The comment reads "Announces position for 2D plots (e.g., heatmaps)" but this method is specifically for scatter plots — heatmaps use announce2DPosition. The distinction is intentional (scatter omits the "Position is" prefix), so the doc should reflect the actual purpose.

7. Redundant self-alias import in factory.ts

AnnounceXCommand as AnnounceXCommand,  // aliasing to itself, unnecessary

8. Missing blank line before announceScatter

announceMultiLinePosition and announceScatter are not separated by a blank line, unlike the rest of the file.


Positive Notes

  • Renaming Describe* to Announce* is a clear naming consistency improvement.
  • The multi-violin vs. single-smooth split (announceMultiViolinPosition / announceSmoothPosition) is a correct fix, well-documented.
  • warnIfTextOff() is easy to reuse in future commands.
  • ToggleScopeCommand optional textViewModel parameter is cleanly backwards-compatible.
  • Good catch on the announceMultiLinePosition parameter order fix (x/y swap for multi-line).

Summary

Issue Severity
playWarningTone() ignores audio mode Bug
TextViewModel depends on AudioService Architecture
Dead isOff() branches in sub-methods KISS violation
announceSmoothPosition no-op wrapper KISS violation
.claude/settings.local.json committed Config
Wrong JSDoc on announceScatter Minor
Self-aliased import Minor
Missing blank line Minor

The most important fix before merge is the playWarningTone()playWarningToneIfEnabled() bug. The architectural concern about TextViewModel + AudioService coupling is worth discussing — moving the coordination into the command layer would be cleaner, but the current approach is functional.

@ellvix ellvix merged commit 7e0172a into main Mar 12, 2026
5 of 7 checks passed
@ellvix ellvix deleted the feat-position branch March 12, 2026 03:57
@xabilitylab
Copy link
Collaborator

🎉 This PR is included in version 3.57.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@xabilitylab xabilitylab added the released For issues/features released label Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released For issues/features released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants