Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 12 additions & 0 deletions frontend/jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,25 @@ configureReact({ asyncUtilTimeout: 10_000 });
configureDom({ asyncUtilTimeout: 10_000 });

// retrieved from https://stackoverflow.com/a/68539103/13021511
// Rail-delegacy round (SPEC-rail-delegacy.md) - `addEventListener`/`removeEventListener` added
// alongside the deprecated `addListener`/`removeListener` pair this polyfill already carried:
// `useViewportTier.ts` (display/useViewportTier.ts) calls the modern
// `MediaQueryList.addEventListener("change", ...)` form, which real browsers and jsdom's own
// native `matchMedia` both support - this polyfill hadn't caught up, so any component mounting
// that hook under Jest (SelectVersionResults.tsx, this round) threw
// "mql.addEventListener is not a function" the moment its effect ran. `matches: false` still
// resolves every tier query to false, which `useViewportTier.ts`'s own fallback chain reads as
// "desktop" - unchanged behavior for every existing caller, just no longer a hard crash for a new
// one.
global.matchMedia =
global.matchMedia ||
function () {
return {
matches: false,
addListener: function () {},
removeListener: function () {},
addEventListener: function () {},
removeEventListener: function () {},
};
};

Expand Down
51 changes: 50 additions & 1 deletion frontend/src/features/card/RequestedPrintingBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,21 @@ import { selectIsSearchQueryDegraded } from "@/store/slices/searchResultsSlice";

interface RequestedPrintingBadgeProps {
query: SearchQuery | undefined;
/** Rail-delegacy round (SPEC-rail-delegacy.md §C/RD7) - additive, optional pair. When
* `showOnlyOnMismatch` is true, the badge renders ONLY when `resolvedPrinting` (the card's
* `canonicalCard` ?? `suggestedCanonicalCard`) is missing or names a different printing than
* `query` requested - a genuine mismatch worth flagging, never a static second copy of an
* identity the D14 confidence band already shows once. `undefined`/`false` (every existing
* caller - CardSlot.tsx) preserves today's always-show-when-requested behavior untouched. */
showOnlyOnMismatch?: boolean;
resolvedPrinting?: { expansionCode: string; collectorNumber: string } | null;
}

export function RequestedPrintingBadge({ query }: RequestedPrintingBadgeProps) {
export function RequestedPrintingBadge({
query,
showOnlyOnMismatch = false,
resolvedPrinting = null,
}: RequestedPrintingBadgeProps) {
// Called unconditionally on every render of this component regardless of whether the badge
// ends up rendering anything - satisfies the rules-of-hooks the same way DisplayPage.tsx's own
// Rail component previously had to (see its own comment on why this selector runs ahead of any
Expand All @@ -39,10 +51,47 @@ export function RequestedPrintingBadge({ query }: RequestedPrintingBadgeProps) {
return null;
}

const isMismatch =
resolvedPrinting == null ||
resolvedPrinting.expansionCode.toLowerCase() !==
query.expansionCode.toLowerCase() ||
(query.collectorNumber != null &&
resolvedPrinting.collectorNumber !== query.collectorNumber);

if (showOnlyOnMismatch && !isMismatch) {
return null;
}

const printingBadge = `${query.expansionCode.toUpperCase()}${
query.collectorNumber ? " " + query.collectorNumber : ""
}`;

// The rail-delegacy round's `.mismatch` flag (SPEC-rail-delegacy.md §D.2) is a single warning-
// coloured style, not the two-state plain/degraded badge look every other caller keeps - see
// that table's `.rhead .mismatch` row (`10px` mono, `#ffc107`/`#111`, `padding:1px 7px`).
if (showOnlyOnMismatch) {
return (
<span
className="mismatch"
style={{
display: "inline-block",
marginTop: "5px",
background: "#ffc107",
color: "#111",
fontFamily: "monospace",
fontSize: "10px",
padding: "1px 7px",
borderRadius: 0,
}}
data-testid="requested-printing-badge"
data-degraded={isDegraded}
title="Requested printing differs from the resolved printing"
>
requested ≠ shown: {printingBadge}
</span>
);
}

return (
<span
className={`badge ${
Expand Down
45 changes: 30 additions & 15 deletions frontend/src/features/cardDetailedView/CardDetailedViewBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,20 @@ export function CardIdentifierCopy({ identifier }: CardIdentifierCopyProps) {

interface CardMetaTableProps {
cardDocument: CardDocument;
/** Rail-delegacy round (rev #2/RD7, SPEC-rail-delegacy.md) - additive, optional. `false` drops
* the "Canonical Card" row (the printing set/collector identifier) from this table entirely -
* the /display rail's "More details" disclosure passes this, since the same identifier already
* lives ONCE, always-visible, in the D14 confidence band (`ConfidenceElement.tsx`); repeating it
* here would be exactly the "static second copy" RD7 rules out. `undefined`/`true` (every other
* caller - the classic card-detail modal, CardDetailedViewBody's own default composition)
* preserves today's behavior unchanged - that surface has no D14 band of its own. */
showCanonicalCard?: boolean;
}

export function CardMetaTable({ cardDocument }: CardMetaTableProps) {
export function CardMetaTable({
cardDocument,
showCanonicalCard = true,
}: CardMetaTableProps) {
const getLanguagesQuery = useGetLanguagesQuery();
const getTagDisplayName = useTagDisplayName();
const languageNameByCode = Object.fromEntries(
Expand Down Expand Up @@ -117,20 +128,24 @@ export function CardMetaTable({ cardDocument }: CardMetaTableProps) {
["Date Created", cardDocument.dateCreated],
["Date Modified", cardDocument.dateModified],
["File Size", imageSizeToMBString(cardDocument.size, 2)],
[
"Canonical Card",
cardDocument.canonicalCard ? (
<>
<SetIcon
expansionCode={cardDocument.canonicalCard.expansionCode}
/>{" "}
{cardDocument.canonicalCard.expansionCode.toUpperCase()}{" "}
{cardDocument.canonicalCard.collectorNumber}
</>
) : (
"Unknown"
),
],
...(showCanonicalCard
? [
[
"Canonical Card",
cardDocument.canonicalCard ? (
<>
<SetIcon
expansionCode={cardDocument.canonicalCard.expansionCode}
/>{" "}
{cardDocument.canonicalCard.expansionCode.toUpperCase()}{" "}
{cardDocument.canonicalCard.collectorNumber}
</>
) : (
"Unknown"
),
],
]
: []),
[
"Canonical Aritst",
cardDocument.canonicalArtist != null ? (
Expand Down
Loading