Skip to content

feat: implement SaaS auth gating with tiered access model - #197

Merged
ayoub3bidi merged 4 commits into
developfrom
feat/sass-auth-gating
Jul 3, 2026
Merged

feat: implement SaaS auth gating with tiered access model#197
ayoub3bidi merged 4 commits into
developfrom
feat/sass-auth-gating

Conversation

@ayoub3bidi

@ayoub3bidi ayoub3bidi commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Contribution workflow

  • Base branch is develop: This PR targets develop, not main.
  • Guidelines and docs: I have read CONTRIBUTING.md and the docs relevant to my change.
  • This template: I kept the PR template structure and filled in the sections below.

Description

Implements a tiered access model across the platform, introducing Anonymous and Free user tiers with differentiated capabilities. Establishes the entitlement service as the single authority for all access checks, gates visualizer controls, limits anonymous usage, enforces video export restrictions, and adds a theme switch sound effect.

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 📚 Documentation update
  • 🎨 Style/UI improvement
  • 🧪 Test addition or improvement

Related Issues

N/A

Changes Made

  • Entitlement service: Rewrote entitlementService.js from async Supabase-based plan fetch to synchronous tiered access model with getUserPlan(), canAccessAlgorithm(), canUseManualControls(), canChangeSpeed(), canUseCategoryControls(), canRunVisualization(), canViewComplexityPanel(), canRunVideoExport(), getExportWatermarkConfig(), canCustomizeExportWatermark(), and resetAllSessionCounters(). Anonymous session limits (12 visualizations, 2 complexity views) tracked via localStorage.
  • Algorithm entitlements: Created algorithmEntitlements.js defining the anonymous-tier allowlist — 18 of 45 algorithms across all 5 categories.
  • Gated UI controls: Lock icons in AlgorithmDropdown for restricted algorithms; gated manual mode, speed slider, and category controls in SettingsPanel; gated sort-order toggle in ControlPanel; locked graph scenarios in GraphScenarioDropdown — all for anonymous users.
  • Complexity panel gating: Added blur overlay after 2 anonymous complexity views across all 5 visualizers (ArrayVisualizer, GraphVisualizer, GridVisualizer, TreeVisualizer, GraphAlgorithmMatrixVisualizer).
  • Sign-in prompt modals: Refactored SignInPromptModal to support context-aware feature gates with dynamic titles, descriptions, and metadata interpolation (e.g., algorithm name, session limit).
  • Video export gating: Free users limited to 50 exports/day via per-user localStorage guard; pro users unlimited; anonymous users blocked. Mandatory Bayan Flow watermark for free tier.
  • Theme switch sound: Created themeSwitchSound.js with Web Audio-based light-switch click — forward playback for light mode, reversed + slowed (0.82x) for dark mode. Respects prefers-reduced-motion.
  • Other: Wrapped GitHubRepoBadge in Tooltip; updated index.css with Tailwind 4 directives (@utility text-theme-primary); added 10 new i18n keys across en/fr/ar; comprehensive updates to AGENTS.md / AGENTS_REFERENCE.md.
  • Auth integration: AuthProvider calls resetAllSessionCounters() on SIGNED_IN event; VisualizerApp integrates entitlement checks into play/sound/fullscreen/export handlers and forces autoplay mode + MEDIUM grid for anonymous users.

Testing

  • All existing tests pass
  • New tests added for new functionality
  • Manual testing completed

Test Results

3 new test files added (entitlementService, complexity gate, themeSwitchSound) + 10+ updated test suites across all gated components.

Code Quality

  • Code follows the project's coding standards
  • ESLint passes (pnpm lint)
  • Prettier formatting applied (pnpm format)
  • No console errors or warnings
  • Code is properly documented with JSDoc

Performance Impact

  • No performance impact — entitlement checks are synchronous localStorage lookups, no additional API calls.

Accessibility

  • Keyboard navigation works correctly
  • Screen reader compatibility maintained
  • Color contrast meets WCAG guidelines
  • Focus indicators are visible

Breaking Changes

None. Anonymous users experience reduced functionality (18 algorithms, 12 visualizations, autoplay only). Existing signed-in users remain unaffected.

Checklist

  • I have completed the Contribution workflow checklist at the top of this template
  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

Summary by CodeRabbit

  • New Features

    • Added entitlement-based gating across algorithms, controls, scenarios, and complexity panels, with locked-option interactions and blurred overlays.
    • Added richer sign-in prompts with feature-specific gate copy and metadata-driven messaging.
    • Added theme toggle sound playback for supported devices.
  • Bug Fixes

    • Improved export entitlement handling, including free-user daily limits, error reporting, and watermark configuration.
    • Refined gating consistency for anonymous users during visualization completion and related UI flows.
  • Style

    • Updated UI states (selected vs locked vs unavailable) across dropdowns and settings controls.
  • Documentation

    • Updated project documentation and workflow rules.

@netlify

netlify Bot commented Jul 3, 2026

Copy link
Copy Markdown

Deploy Preview for dev-bayanflow ready!

Name Link
🔨 Latest commit af73256
🔍 Latest deploy log https://app.netlify.com/projects/dev-bayanflow/deploys/6a480de0e80a660008593f33
😎 Deploy Preview https://deploy-preview-197--dev-bayanflow.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added documentation Improvements or additions to documentation style Improve styling, design, and animation tests labels Jul 3, 2026
@ayoub3bidi ayoub3bidi self-assigned this Jul 3, 2026
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds tiered entitlement gating across algorithms, controls, visualizers, and export flows, introduces theme-switch audio playback, and updates supporting translations, auth/session handling, docs, and tests. It also includes a few unrelated UI/config adjustments.

Changes

Tiered entitlement and feature gating

Layer / File(s) Summary
Entitlement contracts and core service
src/constants/algorithmEntitlements.js, src/services/entitlementService.js, src/constants/__tests__/*, src/services/__tests__/entitlementService.test.js
Adds plan-tier constants, an anonymous algorithm allowlist, and a localStorage-backed entitlement service with algorithm, control, visualization, complexity, export, watermark, and counter-reset checks, plus tests.
Auth session reset
src/contexts/AuthProvider.jsx, src/contexts/AuthProvider.test.jsx
Resets anonymous session counters via resetAllSessionCounters() on SIGNED_IN events, not on initial hydration.
Algorithm and control gating
src/components/AlgorithmDropdown.jsx, src/components/GraphScenarioDropdown.jsx, src/components/ControlPanel.jsx, *.test.jsx
Locks algorithm/scenario options and the sort-order toggle for ineligible users, routing clicks through gating callbacks with lock-icon styling.
SettingsPanel gating wiring
src/components/SettingsPanel.jsx, src/components/SettingsPanel.test.jsx
Computes entitlement flags and gates manual mode, speed control, and size controls behind sign-in prompts and onGatedFeatureClick.
Visualizer complexity-panel gating
src/components/ArrayVisualizer.jsx, GraphVisualizer.jsx, GraphAlgorithmMatrixVisualizer.jsx, GridVisualizer.jsx, TreeVisualizer.jsx, src/test/testUtils.jsx, related tests
Gates complexity panel display per completion for anonymous users, adds blurred overlays, and updates tests to use a new renderWithProviders helper.
Sign-in modal and VisualizerApp gating flow
src/components/SignInPromptModal.jsx, src/pages/VisualizerApp.jsx, src/video/useVideoExporter.js, related tests
Adds metadata-driven modal copy, centralizes gated-feature open/close state, enforces session-limit/export entitlement checks with watermark config, and reports export errors.
Gating-related translations
src/i18n/locales/{ar,en,fr}/translation.json
Adds sign-in prompt, export-unavailable, remaining-visualizations, and expanded featureGate strings.
AGENTS.md and reference documentation
AGENTS.md, docs/AGENTS_REFERENCE.md
Documents the tiered entitlement model, routes, visualizer UX contracts, auth/session rules, and the new public sound asset directory.

Theme switch sound effect

Layer / File(s) Summary
Theme switch sound utility
src/utils/themeSwitchSound.js, src/utils/themeSwitchSound.test.js, docs/AGENTS_REFERENCE.md
Adds a Web Audio API utility to play cached/reversed switch sounds, respecting reduced-motion preference.
ThemeToggle wiring
src/components/ThemeToggle.jsx, src/components/ThemeToggle.test.jsx
Plays the theme-switch sound on toggle before invoking onToggle.

Unrelated UI and config updates

Layer / File(s) Summary
GitHubRepoBadge tooltip
src/components/GitHubRepoBadge.jsx
Replaces title attribute with a Tooltip component for the repository link.
Tailwind config and utility directives
src/index.css
Adds @config/@custom-variant dark directives and converts a class into a Tailwind @utility.
Test setup AUXILIARY state
src/test/setup.js
Adds an AUXILIARY element state and color to mocked test constants.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Visualizer
  participant entitlementService
  participant ComplexityPanel

  Visualizer->>entitlementService: canViewComplexityPanel(user)
  alt allowed
    Visualizer->>entitlementService: incrementComplexityViewCount()
    Visualizer->>ComplexityPanel: render panel
  else blocked
    Visualizer->>Visualizer: setIsComplexityGated(true)
    Visualizer->>Visualizer: onGatedFeatureClick('complexity_limit')
    Visualizer->>ComplexityPanel: render blurred overlay
  end
Loading
sequenceDiagram
  participant User
  participant VisualizerApp
  participant entitlementService
  participant useVideoExporter

  User->>VisualizerApp: handleExportVideo()
  VisualizerApp->>VisualizerApp: openGatedFeature('export') if unauthenticated
  VisualizerApp->>entitlementService: canRunVideoExport(user)
  alt allowed
    VisualizerApp->>entitlementService: incrementVideoExportCount(user)
    VisualizerApp->>useVideoExporter: exportVideo(watermark)
  else blocked
    VisualizerApp->>useVideoExporter: reportExportError(message)
  end
Loading

Possibly related PRs

  • ayoub3bidi/bayan-flow#192: Both PRs touch the auth stack, with this PR extending AuthProvider to reset entitlement/session counters on sign-in.
  • ayoub3bidi/bayan-flow#193: Both PRs modify SignInPromptModal and the gated-feature wiring in VisualizerApp, with this PR extending it to entitlement-based gating and metadata support.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the PR’s main change: tiered auth gating and access control across the app.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sass-auth-gating

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: dependency version conflict. Check your lock file or package.json.


Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

Preview for Bayan Flow Staging ready!

Name Link
🔨 Latest commit 5c25418
🔍 Latest deploy log https://github.com/ayoub3bidi/bayan-flow/actions/runs/28678184849
😎 Deploy Preview https://pr-197-bayan-flow-staging.ayoub3bidi.workers.dev
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

Preview alias pr-197 on the staging worker. Updates automatically with new commits.

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ayoub3bidi

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
AGENTS.md (1)

13-43: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep AGENTS.md contract-only.

The snapshot counts/routes/test-command catalog are volatile reference data, not non-negotiable rules. Leaving them here will churn this file on every registry change.

As per coding guidelines, update docs/AGENTS_REFERENCE.md only for counts, file paths, and test commands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 13 - 43, Keep AGENTS.md contract-only by removing the
volatile snapshot data from this section: the tooling/version counts, route
table, source/test counts, and source-of-truth registry listings should not live
here. Move those reference details to docs/AGENTS_REFERENCE.md, and keep
AGENTS.md limited to stable rules and workflow guidance so it doesn’t churn when
registries or counts change.

Source: Coding guidelines

🧹 Nitpick comments (8)
src/components/ArrayVisualizer.jsx (1)

106-123: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider extracting the complexity-gate logic into a shared hook.

This exact block — the three effects (reset on isComplete, clear gate on user, count-once-per-completion) plus the hasCountedThisCompletion ref and isComplexityGated state — is duplicated verbatim in GraphAlgorithmMatrixVisualizer.jsx, GridVisualizer.jsx, and TreeVisualizer.jsx (and the overlay markup at Lines 175-188 is likewise copy-pasted). The copies are already drifting slightly (e.g., TreeVisualizer drops the explicit else). A single useComplexityGate(user, isComplete, onGatedFeatureClick) hook returning isComplexityGated, paired with a small ComplexityGateOverlay component, would centralize this and prevent divergence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ArrayVisualizer.jsx` around lines 106 - 123, Extract the
duplicated complexity-gate behavior from ArrayVisualizer’s effect block into a
shared useComplexityGate(user, isComplete, onGatedFeatureClick) hook, keeping
the reset-on-completion, clear-on-user, and count-once-per-completion logic in
one place. Have the hook own the hasCountedThisCompletion ref and
isComplexityGated state, and update GraphAlgorithmMatrixVisualizer,
GridVisualizer, and TreeVisualizer to consume it instead of inlining the same
effects. Also pull the repeated overlay markup into a small
ComplexityGateOverlay component so all visualizers render the gate UI
consistently and stop drifting.
src/services/entitlementService.js (2)

215-237: 🧹 Nitpick | 🔵 Trivial

Free-tier daily export cap is purely client-side and trivially bypassable.

canRunVideoExport/readDailyExportCount/writeDailyExportCount enforce the 50/day free-tier export limit entirely via localStorage keyed by a locally-hashed identifier. Any user can bypass this by clearing site storage, using a private window, or switching browsers — no server-side accounting exists. Same applies to the anonymous visualization/complexity-view counters (Lines 147-208). Given the PR frames this as an explicit product limit ("50 exports per day"), consider whether server-side enforcement (or at least server-side accounting used to reconcile) is planned, since the current guard only deters casual overuse rather than enforcing the stated limit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/entitlementService.js` around lines 215 - 237, The free-tier
export cap is only enforced in client-side helpers, so it can be bypassed by
clearing storage or changing browsers; update the entitlement flow around
canRunVideoExport, readDailyExportCount, writeDailyExportCount, and
incrementVideoExportCount so the 50/day limit is backed by server-side
accounting or validation rather than localStorage alone. Apply the same fix
approach to the anonymous counter logic in the related
visualization/complexity-view helpers so the product limit is enforced
consistently across sessions and devices.

49-76: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Inconsistent error handling around localStorage access.

readDailyExportCount wraps its localStorage/JSON access in try/catch, but writeDailyExportCount (and later, incrementVisualizationCount, incrementComplexityViewCount, canRunVisualization, getRemainingVisualizations, canViewComplexityPanel) call localStorage.getItem/setItem directly without guarding. If localStorage is unavailable or throws (quota exceeded, restricted storage policies), these unguarded calls will throw and break the calling flow (e.g. handlePlay, video export) for anonymous/free users.

Consider a small shared safe-storage helper reused across all these functions.

🔧 Proposed helper
+function safeLocalStorageSet(key, value) {
+  try {
+    localStorage.setItem(key, value);
+  } catch {
+    // Ignore storage failures; feature degrades gracefully.
+  }
+}
+
 function writeDailyExportCount(user, count) {
   const storageKey = getDailyExportStorageKey(user);
   if (!storageKey) return;

-  localStorage.setItem(
+  safeLocalStorageSet(
     storageKey,
     JSON.stringify({ date: getUtcDateKey(), count })
   );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/entitlementService.js` around lines 49 - 76,
`readDailyExportCount` already defensively handles storage failures, but
`writeDailyExportCount` and the other entitlement helpers still access
`localStorage` directly and can throw in restricted environments. Add a small
shared safe-storage wrapper around `localStorage.getItem`/`setItem` and use it
from `writeDailyExportCount`, `incrementVisualizationCount`,
`incrementComplexityViewCount`, `canRunVisualization`,
`getRemainingVisualizations`, and `canViewComplexityPanel` so the entitlement
flow keeps working when storage is unavailable or quota-limited.
src/components/AlgorithmDropdown.test.jsx (1)

112-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Good coverage of the locked-click path. Consider also adding a case with user={null} and no categoryType to pin down the intended (fail-closed) behavior once the production logic is tightened per the companion comment in AlgorithmDropdown.jsx.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/AlgorithmDropdown.test.jsx` around lines 112 - 133, Add a test
in AlgorithmDropdown.test.jsx that covers the anonymous-user locked click path
when categoryType is omitted, using AlgorithmDropdown and onLockedAlgorithmClick
to verify the intended fail-closed behavior. Render with user={null} and no
categoryType, click a locked option such as Quick Sort, and assert the callback
is either not called or only called according to the tightened production logic
so the default behavior is pinned down.
src/i18n/locales/en/translation.json (1)

289-290: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing plural forms for visualizationsRemaining.

i18next requires _one/_other suffixes to correctly pluralize when count is passed; without them, the same string is used for all values, producing "1 visualizations remaining" at the critical last-visualization moment for anonymous users. The same gap applies to the fr and ar files.

🌐 Proposed fix (apply analogous pattern to fr/ar)
-    "visualizationsRemaining": "{{count}} visualizations remaining"
+    "visualizationsRemaining_one": "{{count}} visualization remaining",
+    "visualizationsRemaining_other": "{{count}} visualizations remaining"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/i18n/locales/en/translation.json` around lines 289 - 290, The
`visualizationsRemaining` translation is missing i18next plural variants, so add
the `_one` and `_other` forms in the `translation.json` entry and apply the same
pattern in the `fr` and `ar` locale files. Update the locale keys near
`visualizationsRemaining` to use plural-aware entries so `count` resolves
correctly for singular and plural cases, and keep the existing `noPath` key
unchanged.
src/components/AlgorithmDropdown.jsx (1)

121-145: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Locked state isn't announced to assistive tech.

The Lock icon is aria-hidden, so screen-reader users get no indication an option is locked until after activating it. Consider adding a visually-hidden label (e.g., "(locked)") to the button when isLocked is true.

♿ Proposed fix
                       {algo.label}
                     </span>
+                    {isLocked && (
+                      <span className="sr-only">
+                        {t('common.locked', 'locked')}
+                      </span>
+                    )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/AlgorithmDropdown.jsx` around lines 121 - 145, The locked
state in AlgorithmDropdown is only shown via the Lock icon, which is hidden from
assistive tech, so add an accessible text cue when isLocked is true. Update the
item rendering path in the AlgorithmDropdown component so the same branch that
renders the Lock icon also includes a visually hidden label like “(locked)” on
the option/button, while keeping the icon itself decorative. Use the existing
isLocked, Lock, and option rendering logic to locate the change.
src/components/SettingsPanel.jsx (1)

367-403: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add test coverage for size (array/grid) gating.

The slider and button gating logic here mirrors the speed-control pattern (intercept click/change, call onGatedFeatureClick('category_controls'), block the underlying onSizeChange), but SettingsPanel.test.jsx only adds tests for manual mode, speed, and graph scenario gating — not for this size-control path. Since this is a new gated behavior on a critical settings surface, a regression here (e.g., accidentally calling onSizeChange before the gate check) would go undetected.

Want me to draft tests asserting onGatedFeatureClick('category_controls') fires (and onSizeChange/onArraySizeChange does not) when an anonymous user interacts with the size slider and size buttons?

Also applies to: 418-444

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/SettingsPanel.jsx` around lines 367 - 403, Add test coverage
in SettingsPanel.test.jsx for the size-control gating path in SettingsPanel’s
size slider and size buttons. Verify that when canUseCategorySettings is false,
interacting with the size control triggers
onGatedFeatureClick('category_controls') and does not call the underlying
onSizeChange or onArraySizeChange handlers. Mirror the existing speed-control
gating tests so the regression surface around the size control stays covered.
src/components/GraphScenarioDropdown.jsx (1)

106-130: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: locked options have no accessible indication besides a hidden icon.

Lock is aria-hidden, and there's no aria-disabled/description conveying the locked state to assistive tech; users only discover it after activating the option and triggering the sign-in prompt.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GraphScenarioDropdown.jsx` around lines 106 - 130, The locked
option state in GraphScenarioDropdown is only shown with an aria-hidden Lock
icon, so assistive tech gets no indication that the item is unavailable. Update
the option rendering logic around isLocked and the Lock/Check display to expose
the locked state via accessible semantics, such as aria-disabled on the option
and a descriptive label or status text for locked items. Keep the current visual
icon, but make sure the locked state is announced without relying on the hidden
icon alone.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Around line 102-107: The Free-tier entitlement text in the access list is
inconsistent with the PR objective: it currently says video export is unlimited
for Free users, but the shipped behavior should cap Free exports at 50 per day
with a mandatory watermark. Update the Free tier description in AGENTS.md to
match the actual export entitlement, keeping the Pro-tier note about watermark
customization/removal intact.
- Around line 51-53: The sound guidance is too broad and conflicts with the
ThemeToggle SFX added in this PR; update the rule in AGENTS.md to scope the
no-click-sound constraint to visualization playback only, or explicitly carve
out the ThemeToggle exception. Keep the guidance aligned with the existing sound
architecture references like soundEvents.js and soundManager so UI-specific
sounds are not universally blocked.

In `@src/index.css`:
- Around line 8-9: Stylelint is currently treating the new Tailwind v4 at-rules
in the index.css import block as unknown, so update the Stylelint configuration
to allow Tailwind-specific at-rules used by this file. Adjust the config for
scss/at-rule-no-unknown (or equivalent) to ignore `@config` and `@custom-variant`,
or switch to a Tailwind-aware Stylelint setup, so the existing Tailwind
directives in src/index.css lint cleanly.

In `@src/test/setup.js`:
- Around line 132-138: The mocked STATE_COLORS palette in the test setup has
AUXILIARY set to a different value than production, so update the test fixture
to match the production constant used by src/constants/index.js. Keep the change
in the state color mock aligned with the existing STATE_COLORS object so
snapshots and style assertions reflect runtime behavior.

In `@src/utils/themeSwitchSound.js`:
- Around line 63-92: The theme switch sound loader can get stuck on a rejected
promise, which prevents any later retry. Update ensureSwitchBuffers to clear
loadPromise whenever the async fetch/decode path fails, so playThemeSwitchSound
can attempt loading again on a future toggle; keep the existing caching for
success and make sure the failure reset happens around the loadPromise
assignment and the fetch/decodeAudioData flow.

---

Outside diff comments:
In `@AGENTS.md`:
- Around line 13-43: Keep AGENTS.md contract-only by removing the volatile
snapshot data from this section: the tooling/version counts, route table,
source/test counts, and source-of-truth registry listings should not live here.
Move those reference details to docs/AGENTS_REFERENCE.md, and keep AGENTS.md
limited to stable rules and workflow guidance so it doesn’t churn when
registries or counts change.

---

Nitpick comments:
In `@src/components/AlgorithmDropdown.jsx`:
- Around line 121-145: The locked state in AlgorithmDropdown is only shown via
the Lock icon, which is hidden from assistive tech, so add an accessible text
cue when isLocked is true. Update the item rendering path in the
AlgorithmDropdown component so the same branch that renders the Lock icon also
includes a visually hidden label like “(locked)” on the option/button, while
keeping the icon itself decorative. Use the existing isLocked, Lock, and option
rendering logic to locate the change.

In `@src/components/AlgorithmDropdown.test.jsx`:
- Around line 112-133: Add a test in AlgorithmDropdown.test.jsx that covers the
anonymous-user locked click path when categoryType is omitted, using
AlgorithmDropdown and onLockedAlgorithmClick to verify the intended fail-closed
behavior. Render with user={null} and no categoryType, click a locked option
such as Quick Sort, and assert the callback is either not called or only called
according to the tightened production logic so the default behavior is pinned
down.

In `@src/components/ArrayVisualizer.jsx`:
- Around line 106-123: Extract the duplicated complexity-gate behavior from
ArrayVisualizer’s effect block into a shared useComplexityGate(user, isComplete,
onGatedFeatureClick) hook, keeping the reset-on-completion, clear-on-user, and
count-once-per-completion logic in one place. Have the hook own the
hasCountedThisCompletion ref and isComplexityGated state, and update
GraphAlgorithmMatrixVisualizer, GridVisualizer, and TreeVisualizer to consume it
instead of inlining the same effects. Also pull the repeated overlay markup into
a small ComplexityGateOverlay component so all visualizers render the gate UI
consistently and stop drifting.

In `@src/components/GraphScenarioDropdown.jsx`:
- Around line 106-130: The locked option state in GraphScenarioDropdown is only
shown with an aria-hidden Lock icon, so assistive tech gets no indication that
the item is unavailable. Update the option rendering logic around isLocked and
the Lock/Check display to expose the locked state via accessible semantics, such
as aria-disabled on the option and a descriptive label or status text for locked
items. Keep the current visual icon, but make sure the locked state is announced
without relying on the hidden icon alone.

In `@src/components/SettingsPanel.jsx`:
- Around line 367-403: Add test coverage in SettingsPanel.test.jsx for the
size-control gating path in SettingsPanel’s size slider and size buttons. Verify
that when canUseCategorySettings is false, interacting with the size control
triggers onGatedFeatureClick('category_controls') and does not call the
underlying onSizeChange or onArraySizeChange handlers. Mirror the existing
speed-control gating tests so the regression surface around the size control
stays covered.

In `@src/i18n/locales/en/translation.json`:
- Around line 289-290: The `visualizationsRemaining` translation is missing
i18next plural variants, so add the `_one` and `_other` forms in the
`translation.json` entry and apply the same pattern in the `fr` and `ar` locale
files. Update the locale keys near `visualizationsRemaining` to use plural-aware
entries so `count` resolves correctly for singular and plural cases, and keep
the existing `noPath` key unchanged.

In `@src/services/entitlementService.js`:
- Around line 215-237: The free-tier export cap is only enforced in client-side
helpers, so it can be bypassed by clearing storage or changing browsers; update
the entitlement flow around canRunVideoExport, readDailyExportCount,
writeDailyExportCount, and incrementVideoExportCount so the 50/day limit is
backed by server-side accounting or validation rather than localStorage alone.
Apply the same fix approach to the anonymous counter logic in the related
visualization/complexity-view helpers so the product limit is enforced
consistently across sessions and devices.
- Around line 49-76: `readDailyExportCount` already defensively handles storage
failures, but `writeDailyExportCount` and the other entitlement helpers still
access `localStorage` directly and can throw in restricted environments. Add a
small shared safe-storage wrapper around `localStorage.getItem`/`setItem` and
use it from `writeDailyExportCount`, `incrementVisualizationCount`,
`incrementComplexityViewCount`, `canRunVisualization`,
`getRemainingVisualizations`, and `canViewComplexityPanel` so the entitlement
flow keeps working when storage is unavailable or quota-limited.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 64f2869c-c34a-47d7-be61-defefada44cf

📥 Commits

Reviewing files that changed from the base of the PR and between 866bead and 5c25418.

⛔ Files ignored due to path filters (1)
  • public/ui/sfx/switch-on.mp3 is excluded by !**/*.mp3
📒 Files selected for processing (41)
  • AGENTS.md
  • docs/AGENTS_REFERENCE.md
  • src/components/AlgorithmDropdown.jsx
  • src/components/AlgorithmDropdown.test.jsx
  • src/components/ArrayVisualizer.complexityGate.test.jsx
  • src/components/ArrayVisualizer.jsx
  • src/components/ControlPanel.jsx
  • src/components/ControlPanel.test.jsx
  • src/components/GitHubRepoBadge.jsx
  • src/components/GraphAlgorithmMatrixVisualizer.jsx
  • src/components/GraphAlgorithmMatrixVisualizer.test.jsx
  • src/components/GraphScenarioDropdown.jsx
  • src/components/GraphScenarioDropdown.test.jsx
  • src/components/GraphVisualizer.jsx
  • src/components/GraphVisualizer.test.jsx
  • src/components/GridVisualizer.jsx
  • src/components/SettingsPanel.jsx
  • src/components/SettingsPanel.test.jsx
  • src/components/SignInPromptModal.jsx
  • src/components/SignInPromptModal.test.jsx
  • src/components/ThemeToggle.jsx
  • src/components/ThemeToggle.test.jsx
  • src/components/TreeVisualizer.jsx
  • src/constants/__tests__/algorithmEntitlements.test.js
  • src/constants/algorithmEntitlements.js
  • src/contexts/AuthProvider.jsx
  • src/contexts/AuthProvider.test.jsx
  • src/i18n/locales/ar/translation.json
  • src/i18n/locales/en/translation.json
  • src/i18n/locales/fr/translation.json
  • src/index.css
  • src/pages/VisualizerApp.jsx
  • src/pages/VisualizerApp.test.jsx
  • src/services/__tests__/entitlementService.test.js
  • src/services/entitlementService.js
  • src/services/entitlementService.test.js
  • src/test/setup.js
  • src/test/testUtils.jsx
  • src/utils/themeSwitchSound.js
  • src/utils/themeSwitchSound.test.js
  • src/video/useVideoExporter.js
💤 Files with no reviewable changes (1)
  • src/services/entitlementService.test.js

Comment thread AGENTS.md
Comment on lines +51 to +53
- **Sound:** semantic events in `soundEvents.js` only (16 event kinds) — not from localized descriptions; visualization-only (no UI click sounds); uses Tone.js singleton `soundManager` with 5 synths through master chain (gain → filter → compressor → reverb)
- **Export:** interactive + Remotion parity; `buildExportSoundCues()` from same sound events; WAV assets in `public/video-export/sfx/` (18 pre-rendered files)
- **Feature gating:** Tiered access model — see Auth contracts for Anonymous vs Free tier access; `SignInPromptModal` blocks gated features; `entitlementService.js` is the single authority for all access checks; `src/constants/algorithmEntitlements.js` defines the anonymous-tier algorithm allowlist

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Scope the no-click-sound rule.

This blanket ban now conflicts with the ThemeToggle SFX added elsewhere in the PR. Either carve out that exception or narrow the rule to visualization playback only.

Based on learnings, sound must remain visualization-only and not UI click sounds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 51 - 53, The sound guidance is too broad and
conflicts with the ThemeToggle SFX added in this PR; update the rule in
AGENTS.md to scope the no-click-sound constraint to visualization playback only,
or explicitly carve out the ThemeToggle exception. Keep the guidance aligned
with the existing sound architecture references like soundEvents.js and
soundManager so UI-specific sounds are not universally blocked.

Source: Learnings

Comment thread AGENTS.md
Comment on lines +102 to +107
- **Free tier (Google sign-in):**
- All 45 algorithms, unlimited visualizations
- Manual controls, all 4 speed presets
- Full complexity panel access, all category-specific controls
- Code Panel, Insight Panel, Sound, Fullscreen
- Video Export: unlimited for Free tier with mandatory watermark; Pro tier adds watermark customization/removal; internal daily abuse guard exists (not user-facing)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fix the Free-tier export cap.

This says Free export is unlimited, but the PR objective limits Free users to 50 exports/day with a mandatory watermark. The entitlement contract here needs to match the shipped behavior.

Based on the PR objective, Free users are limited to 50 exports/day.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 102 - 107, The Free-tier entitlement text in the
access list is inconsistent with the PR objective: it currently says video
export is unlimited for Free users, but the shipped behavior should cap Free
exports at 50 per day with a mandatory watermark. Update the Free tier
description in AGENTS.md to match the actual export entitlement, keeping the
Pro-tier note about watermark customization/removal intact.

Comment thread src/index.css
Comment on lines +8 to +9
@config "../tailwind.config.js";
@custom-variant dark (&:where(.dark, .dark *));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Stylelint will fail on new Tailwind v4 at-rules.

@config and @custom-variant are Tailwind v4 at-rules not recognized by the current Stylelint config (scss/at-rule-no-unknown), per the static analysis hints. This will break the lint pipeline unless the config is updated to allow these at-rules (e.g. via ignoreAtRules or a Tailwind-aware Stylelint syntax/plugin).

🔧 Suggested Stylelint config update
   rules: {
+    'at-rule-no-unknown': [true, { ignoreAtRules: ['config', 'custom-variant', 'utility', 'theme', 'plugin', 'apply'] }],
+    'scss/at-rule-no-unknown': null,
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 8-8: Unexpected unknown at-rule "@config" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 9-9: Unexpected unknown at-rule "@custom-variant" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.css` around lines 8 - 9, Stylelint is currently treating the new
Tailwind v4 at-rules in the index.css import block as unknown, so update the
Stylelint configuration to allow Tailwind-specific at-rules used by this file.
Adjust the config for scss/at-rule-no-unknown (or equivalent) to ignore `@config`
and `@custom-variant`, or switch to a Tailwind-aware Stylelint setup, so the
existing Tailwind directives in src/index.css lint cleanly.

Source: Linters/SAST tools

Comment thread src/test/setup.js
Comment on lines +132 to +138
STATE_COLORS: {
default: '#e5e7eb',
comparing: '#fbbf24',
swapping: '#f97316',
sorted: '#10b981',
pivot: '#8b5cf6',
auxiliary: '#9ca3af',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep the AUXILIARY mock color aligned with production.

src/constants/index.js already defines AUXILIARY as #6b7280, but this test setup uses #9ca3af. If any snapshot or style assertions rely on the mocked palette, they’ll drift from runtime behavior.

♻️ Suggested fix
-    auxiliary: '`#9ca3af`',
+    auxiliary: '`#6b7280`',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
STATE_COLORS: {
default: '#e5e7eb',
comparing: '#fbbf24',
swapping: '#f97316',
sorted: '#10b981',
pivot: '#8b5cf6',
auxiliary: '#9ca3af',
STATE_COLORS: {
default: '`#e5e7eb`',
comparing: '`#fbbf24`',
swapping: '`#f97316`',
sorted: '`#10b981`',
pivot: '`#8b5cf6`',
auxiliary: '`#6b7280`',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/setup.js` around lines 132 - 138, The mocked STATE_COLORS palette in
the test setup has AUXILIARY set to a different value than production, so update
the test fixture to match the production constant used by
src/constants/index.js. Keep the change in the state color mock aligned with the
existing STATE_COLORS object so snapshots and style assertions reflect runtime
behavior.

Comment on lines +63 to +92
async function ensureSwitchBuffers() {
if (switchOnBuffer && switchOffBuffer) {
return { on: switchOnBuffer, off: switchOffBuffer };
}

if (!loadPromise) {
loadPromise = (async () => {
const context = getAudioContext();
if (!context) {
return null;
}

const response = await fetch(SWITCH_SOUND_URL);
if (!response.ok) {
throw new Error(
`Failed to load theme switch sound (${response.status})`
);
}

const arrayBuffer = await response.arrayBuffer();
const decoded = await context.decodeAudioData(arrayBuffer);
switchOnBuffer = decoded;
switchOffBuffer = reverseAudioBuffer(decoded, context);

return { on: switchOnBuffer, off: switchOffBuffer };
})();
}

return loadPromise;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Failed load permanently disables the sound.

If fetch/decodeAudioData rejects (e.g. transient network error, non-ok response), loadPromise stays set to the rejected promise. Every later playThemeSwitchSound call reuses that same rejected promise via the if (!loadPromise) guard, so the sound is silently broken for the rest of the session — it never retries.

🔧 Proposed fix: reset loadPromise on failure so a later toggle retries
   if (!loadPromise) {
     loadPromise = (async () => {
       const context = getAudioContext();
       if (!context) {
         return null;
       }

-      const response = await fetch(SWITCH_SOUND_URL);
-      if (!response.ok) {
-        throw new Error(
-          `Failed to load theme switch sound (${response.status})`
-        );
-      }
-
-      const arrayBuffer = await response.arrayBuffer();
-      const decoded = await context.decodeAudioData(arrayBuffer);
-      switchOnBuffer = decoded;
-      switchOffBuffer = reverseAudioBuffer(decoded, context);
-
-      return { on: switchOnBuffer, off: switchOffBuffer };
+      try {
+        const response = await fetch(SWITCH_SOUND_URL);
+        if (!response.ok) {
+          throw new Error(
+            `Failed to load theme switch sound (${response.status})`
+          );
+        }
+
+        const arrayBuffer = await response.arrayBuffer();
+        const decoded = await context.decodeAudioData(arrayBuffer);
+        switchOnBuffer = decoded;
+        switchOffBuffer = reverseAudioBuffer(decoded, context);
+
+        return { on: switchOnBuffer, off: switchOffBuffer };
+      } catch (error) {
+        loadPromise = null;
+        throw error;
+      }
     })();
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function ensureSwitchBuffers() {
if (switchOnBuffer && switchOffBuffer) {
return { on: switchOnBuffer, off: switchOffBuffer };
}
if (!loadPromise) {
loadPromise = (async () => {
const context = getAudioContext();
if (!context) {
return null;
}
const response = await fetch(SWITCH_SOUND_URL);
if (!response.ok) {
throw new Error(
`Failed to load theme switch sound (${response.status})`
);
}
const arrayBuffer = await response.arrayBuffer();
const decoded = await context.decodeAudioData(arrayBuffer);
switchOnBuffer = decoded;
switchOffBuffer = reverseAudioBuffer(decoded, context);
return { on: switchOnBuffer, off: switchOffBuffer };
})();
}
return loadPromise;
}
async function ensureSwitchBuffers() {
if (switchOnBuffer && switchOffBuffer) {
return { on: switchOnBuffer, off: switchOffBuffer };
}
if (!loadPromise) {
loadPromise = (async () => {
const context = getAudioContext();
if (!context) {
return null;
}
try {
const response = await fetch(SWITCH_SOUND_URL);
if (!response.ok) {
throw new Error(
`Failed to load theme switch sound (${response.status})`
);
}
const arrayBuffer = await response.arrayBuffer();
const decoded = await context.decodeAudioData(arrayBuffer);
switchOnBuffer = decoded;
switchOffBuffer = reverseAudioBuffer(decoded, context);
return { on: switchOnBuffer, off: switchOffBuffer };
} catch (error) {
loadPromise = null;
throw error;
}
})();
}
return loadPromise;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/themeSwitchSound.js` around lines 63 - 92, The theme switch sound
loader can get stuck on a rejected promise, which prevents any later retry.
Update ensureSwitchBuffers to clear loadPromise whenever the async fetch/decode
path fails, so playThemeSwitchSound can attempt loading again on a future
toggle; keep the existing caching for success and make sure the failure reset
happens around the loadPromise assignment and the fetch/decodeAudioData flow.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…eportExportError, and remove dead null branch in GitHubRepoBadge

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/components/GraphVisualizer.test.jsx (1)

183-205: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same missing localStorage cleanup as the sibling test in GraphAlgorithmMatrixVisualizer.test.jsx.

anon_complexity_views is set via localStorage.setItem but never removed. As established, Vitest only resets global state (localStorage included) between files, not between tests in the same file, so this leaks into later tests in this suite if any are added or reordered.

🧹 Proposed fix
     } finally {
       vi.useRealTimers();
+      localStorage.removeItem('anon_complexity_views');
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GraphVisualizer.test.jsx` around lines 183 - 205, The
anonymous complexity view test is leaking localStorage state by setting
anon_complexity_views without cleaning it up afterward. Update the
GraphVisualizer.test.jsx test case that uses renderGraph and vi.useFakeTimers to
remove the anon_complexity_views key in the test teardown, matching the cleanup
pattern used elsewhere in the suite so later tests in the same file are not
affected.
src/components/GraphAlgorithmMatrixVisualizer.test.jsx (1)

98-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing localStorage cleanup after the test.

localStorage.setItem('anon_complexity_views', '2') is never cleared. Since Vitest shares global state (including localStorage) across tests within the same file and only resets between files, this value persists into subsequent tests in this suite. The next test happens to not exercise isComplete, so it's currently harmless, but this is fragile against future test additions or reordering.

🧹 Proposed fix
     try {
       localStorage.setItem('anon_complexity_views', '2');

       renderWithProviders(
         ...
       );

       act(() => {
         vi.advanceTimersByTime(1000);
       });

       const blurOverlay = document.querySelector('.backdrop-blur-md');
       expect(blurOverlay).toBeInTheDocument();
       expect(screen.getByText('Complexity Analysis')).toBeInTheDocument();
     } finally {
       vi.useRealTimers();
+      localStorage.removeItem('anon_complexity_views');
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/GraphAlgorithmMatrixVisualizer.test.jsx` around lines 98 -
129, The test in GraphAlgorithmMatrixVisualizer.test.jsx leaves the
anon_complexity_views localStorage value behind, which can leak state into later
tests. Update the anonymous user overlay test to clean up that key after
rendering and assertions, ideally in the existing finally block alongside
vi.useRealTimers, so the GraphAlgorithmMatrixVisualizer suite stays isolated and
repeatable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/components/GraphAlgorithmMatrixVisualizer.test.jsx`:
- Around line 98-129: The test in GraphAlgorithmMatrixVisualizer.test.jsx leaves
the anon_complexity_views localStorage value behind, which can leak state into
later tests. Update the anonymous user overlay test to clean up that key after
rendering and assertions, ideally in the existing finally block alongside
vi.useRealTimers, so the GraphAlgorithmMatrixVisualizer suite stays isolated and
repeatable.

In `@src/components/GraphVisualizer.test.jsx`:
- Around line 183-205: The anonymous complexity view test is leaking
localStorage state by setting anon_complexity_views without cleaning it up
afterward. Update the GraphVisualizer.test.jsx test case that uses renderGraph
and vi.useFakeTimers to remove the anon_complexity_views key in the test
teardown, matching the cleanup pattern used elsewhere in the suite so later
tests in the same file are not affected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 302462e9-26e1-4cf4-b60f-f4198e5c3abc

📥 Commits

Reviewing files that changed from the base of the PR and between 5c25418 and af73256.

📒 Files selected for processing (5)
  • src/components/GitHubRepoBadge.jsx
  • src/components/GraphAlgorithmMatrixVisualizer.test.jsx
  • src/components/GraphVisualizer.test.jsx
  • src/pages/VisualizerApp.test.jsx
  • src/video/useVideoExporter.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/VisualizerApp.test.jsx
  • src/components/GitHubRepoBadge.jsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation style Improve styling, design, and animation tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant