Skip to content

fix(qiraat): resolve overlapping reader colors in readers panel (#3286) - #3310

Open
shoaibhasann wants to merge 2 commits into
quran:productionfrom
shoaibhasann:fix/qiraat-readers-panel-overlapping-colors
Open

fix(qiraat): resolve overlapping reader colors in readers panel (#3286)#3310
shoaibhasann wants to merge 2 commits into
quran:productionfrom
shoaibhasann:fix/qiraat-readers-panel-overlapping-colors

Conversation

@shoaibhasann

Copy link
Copy Markdown

Summary

Closes: #3286

On /{chapter}:{verse}/qiraat, the readers/transmitters panel dropped a reading color whenever a reader appeared in more than one reading. On 2:245, the reading cards show four colors (white, green, blue, pink), but the left panel only showed three — the pink reading فَيُضَعِّفُهُ had no reader/transmitter association because Abū Jaʿfar and Ibn Kathīr are listed in both the default (white) and pink readings.

Root cause

ReaderItem colored each transmitter tag with a per-item first-match lookup:

readings.find(({ matrix }) => matrix?.readers?.includes(reader.id))

Because the white reading is ordered before the pink reading, an ambiguous reader always resolved to white, and the pink candidate was never reached. The same first-match assumption existed in the transmitter click-to-scroll handler. Per the issue, this pattern also affects 6:145, 7:161, 10:35, 12:23, 18:44, 19:25, 20:63, 22:39 (reader-level) and 7:165 (duplicate transmitter-level).

Fix

Replace the per-item lookup with a panel-wide assignment pass, buildTransmitterReadingAssignments, implementing the mitigation proposed in the issue:

  1. Direct matrix.transmitters matches stay authoritative.
  2. Unambiguous tags (one candidate reading) are assigned first; their colors are recorded as represented in the panel.
  3. Ambiguous tags (two or more candidates) then prefer the first candidate whose color is not yet represented, falling back to the previous first-match behavior when every candidate color already appears.

The transmitter click-to-scroll handler reuses the same assignment, so a tag's color and the card it scrolls to always agree.

For 2:245 this resolves correctly: ʿĀṣim keeps white, while Abū Jaʿfar and Ibn Kathīr map to the otherwise-missing pink.

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)

Scope Confirmation

  • This PR addresses one feature/fix only

Rollback Safety

  • Can be safely reverted without data issues or migrations

Test Plan

  • Unit tests added/updated
  • Manual testing performed

Testing steps:

  1. Open https://quran.com/2:245/qiraat.
  2. Select the junction for فَيُضَـٰعِفَهُۥ.
  3. Confirm the left reader/transmitter panel now shows all four colors (white, green, blue, pink), with Abū Jaʿfar and Ibn Kathīr color-coded pink.
  4. Click the pink transmitter tag and confirm it scrolls to the matching pink reading card.

New unit tests (transmitterReadingAssignments.test.ts) cover:

  • the 2:245 regression (overlapping reader-level mappings),
  • the 7:165 duplicate transmitter-level case,
  • direct matrix.transmitters precedence over reader-level fallback,
  • the all-candidates-already-represented fallback to first-match,
  • null reading color normalization,
  • the no-match and empty-readings edge cases.

Edge Cases Verified

  • 📭 Empty state handled (no readings → default/white assignments)

Pre-Review Checklist

Code Quality

  • I have performed a self-review of my code (file by file)
  • No any types used in production code (test helpers cast fixture objects, isolated to the test file)
  • No unused code, imports, or dead code included

Testing & Validation

  • New unit tests pass locally (vitest run)
  • Linting passes for the changed files (eslint)

Reviewer Notes

This is a frontend-only mitigation that preserves the current API payload shape. As the issue notes, the cleaner long-term fix is transmitter-level disambiguation in the payload; this change prevents missing colors in the panel regardless.

AI Assistance Disclosure

  • AI tools were used, and I have thoroughly reviewed and validated all generated code

…n#3286)

The readers panel colored each transmitter tag with a per-item first-match
lookup (`readings.find(r => r.matrix.readers.includes(reader.id))`). When a
reader appears in more than one reading without transmitter-level
disambiguation (e.g. 2:245, where Abū Jaʿfar and Ibn Kathīr are listed in both
the default and the pink readings), the earliest reading always won, so the
later reading's color never showed up in the panel.

Replace the per-item lookup with a panel-wide assignment pass
(`buildTransmitterReadingAssignments`):
- direct `matrix.transmitters` matches stay authoritative;
- unambiguous tags are assigned first and their colors recorded as represented;
- ambiguous tags then prefer the first candidate reading whose color is not yet
  represented, falling back to the previous first-match behavior when every
  candidate color already appears.

The transmitter click-to-scroll handler now reuses the same assignment, so a
tag's color and the card it scrolls to always agree.

Add unit tests covering the 2:245 regression, the 7:165 duplicate
transmitter-level case, direct-match precedence, the all-represented fallback,
null-color normalization, and the no-match / empty-readings edge cases.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ebb13e0e8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const unrepresented = candidates.find(
(reading) => !representedColors.has(normalizeColor(reading.color)),
);
assignments.set(transmitterId, toAssignment(unrepresented ?? candidates[0]));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mark each ambiguous assignment as represented

When multiple reading colors are only reachable through ambiguous tags, every ambiguous tag keeps choosing the same first unrepresented candidate because representedColors is not updated during pass 2. For example, if the panel has ambiguous tags that can represent white/green/pink and green/pink have no unambiguous tag, all of those tags are assigned green here, leaving pink hidden and making clicks unable to scroll to that reading—the same class of issue this helper is intended to fix.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch on the gap, thanks. Confirmed by reproducing it: when two or more reading colors are reachable only through ambiguous tags (no unambiguous representative), the original pass 2 assigned all of them the same first missing color and left the rest hidden.

I didn't apply the "mark each ambiguous assignment as represented" fix directly, though — it regresses the primary case this PR targets (2:245): after Abū Jaʿfar claims pink, Ibn Kathīr would find both of its candidate colors (white + pink) represented and fall back to candidates[0] = the white base reading, so Ibn Kathīr reverts to white — the exact bug being fixed.

Fixed in 43213a5 by splitting pass 2 into two sub-passes:

  • 2a assigns the first still-missing color and marks it represented, so a second color reachable only through ambiguous tags is covered by a different tag (your scenario).
  • 2b assigns tags whose candidate colors are all represented, reusing a color already chosen by another ambiguous tag so sibling transmitters stay grouped (both Abū Jaʿfar and Ibn Kathīr stay pink on 2:245, where pink is the only missing color), falling back to first-match otherwise.

Added two tests: one for the multi-hidden-color distribution case, one for the single-missing-color sibling-grouping case. Both pass alongside the existing 2:245 regression test.

Follow-up to the panel-wide assignment: pass 2 assigned each ambiguous tag the
first still-missing color but never marked that color represented, so when two
or more reading colors were reachable *only* through ambiguous tags, every tag
picked the same first color and the others stayed hidden — the same failure the
helper exists to prevent.

Split pass 2 into two sub-passes:
- 2a assigns the first still-missing color and marks it represented, so a second
  color reachable only through ambiguous tags is covered by a different tag.
- 2b assigns tags whose candidate colors are all represented, reusing a color
  already chosen by another ambiguous tag to keep sibling transmitters grouped
  (e.g. both Abū Jaʿfar and Ibn Kathīr stay pink on 2:245, where pink is the
  only missing color), falling back to first-match otherwise.

Naively marking every ambiguous assignment represented (one obvious fix) would
regress 2:245: after Abū Jaʿfar claims pink, Ibn Kathīr would find both its
colors represented and fall back to white — reintroducing the reported bug. The
sibling-reuse pass 2b avoids that.

Add tests for the multi-hidden-color case and the sibling-grouping (2:245) case.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Qiraat reader panel hides colors when reader mappings overlap

1 participant