Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface ReaderItemProps {
reader: QiraatReader;
transmitters: QiraatTransmitter[];
readings: QiraatReading[];
readerColor?: string;
onInfoClick?: () => void;
onTransmitterClick?: (transmitterId: number) => void;
isClickable?: boolean;
Expand All @@ -26,6 +27,7 @@ const ReaderItem: React.FC<ReaderItemProps> = ({
reader,
transmitters,
readings,
readerColor,
onInfoClick,
onTransmitterClick,
isClickable = false,
Expand Down Expand Up @@ -57,12 +59,14 @@ const ReaderItem: React.FC<ReaderItemProps> = ({

if (transmitterReading) return transmitterReading.color || DEFAULT_COLOR;

// 2. If not found directly, the transmitter inherits color from its parent reader
// Find a reading where this reader appears in the matrix
// 2. If the parent component has pre-computed a color for this reader, use it
if (readerColor) return readerColor;

// 3. If not found directly, the transmitter inherits color from its parent reader
const readerReading = readings.find(({ matrix }) => matrix?.readers?.includes(reader.id));
if (readerReading) return readerReading.color || DEFAULT_COLOR;

// 3. Default fallback - no association found
// 4. Default fallback - no association found
return DEFAULT_COLOR;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ interface ReadersPanelProps {
onReaderInfoClick?: (readerId: number) => void;
}

const DEFAULT_COLOR = '#FFFFFF';

/**
* Responsive panel displaying all canonical readers with their transmitters.
* Desktop: Always visible as a side panel.
Expand Down Expand Up @@ -59,6 +61,47 @@ const ReadersPanel: React.FC<ReadersPanelProps> = ({
[readings],
);

const readerColorMap = useMemo(() => {
const map = new Map<number, string>();
const usedColors = new Set<string>();

const readerCandidates = readers.map((reader) => ({
reader,
candidates: readings.filter(
({ matrix }) => matrix?.readers?.includes(reader.id),
),
}));

// First pass: assign unambiguous readers (single candidate)
for (const { reader, candidates } of readerCandidates) {
if (candidates.length === 1) {
const color = candidates[0].color || DEFAULT_COLOR;
map.set(reader.id, color);
usedColors.add(color);
}
}

// Second pass: for ambiguous readers, prefer a candidate color not yet used
for (const { reader, candidates } of readerCandidates) {
if (candidates.length <= 1) continue;

const unusedCandidate = candidates.find(
({ color }) => color && !usedColors.has(color),
);

if (unusedCandidate) {
const color = unusedCandidate.color || DEFAULT_COLOR;
map.set(reader.id, color);
usedColors.add(color);
} else {
const color = candidates[0].color || DEFAULT_COLOR;
map.set(reader.id, color);
}
}

return map;
}, [readers, readings]);

return (
<div className={classNames(styles.panel, { [styles.expanded]: isExpanded })}>
{/* Mobile header with toggle */}
Expand Down Expand Up @@ -101,6 +144,7 @@ const ReadersPanel: React.FC<ReadersPanelProps> = ({
reader={reader}
transmitters={transmitters}
readings={readings}
readerColor={readerColorMap.get(reader.id)}

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 Route reassigned tags to the same reading

When a reader appears in multiple readings and the new readerColorMap chooses a later unused candidate, inherited transmitter tags are now colored for that later reading, but handleTransmitterClick still falls back to selectedJuncture.readings.find(reading.matrix?.readers?.includes(...)) in StudyModeQiraatTab/index.tsx, which scrolls to the first candidate instead. In the overlap case this makes a tag visually point to one reading while clicking it scrolls to a different card, so the click target should use the same disambiguation result as the color assignment.

Useful? React with 👍 / 👎.

onInfoClick={() => onReaderInfoClick?.(reader.id)}
onTransmitterClick={onTransmitterClick}
isClickable={!!onTransmitterClick}
Expand Down