fix(review): #90 clearer mandatory-photo indicator (padlock + REQUIRED tag)

The mandatory clean-cal and dimension-overlay photos now show a padlock glyph
in their keep box and an amber REQUIRED tag at the top of the card, so the
owner can see at a glance which photos cannot be removed (vs. optional batch
photos, which keep a plain checkmark).
This commit is contained in:
Ty
2026-09-21 02:18:01 -07:00
parent 76fcb36a2b
commit e1b928ccbd
2 changed files with 24 additions and 7 deletions
+17 -6
View File
@@ -557,6 +557,9 @@
{#if p.id === primaryId}
<span class="ph-pri">PRIMARY</span>
{/if}
{#if p.required}
<span class="ph-req">REQUIRED</span>
{/if}
<span class="ph-seq" aria-hidden="true">{i + 1}</span>
<!-- Keep checkbox: always checked for required photos; a disabled
glyph (no check) when a batch photo is removed. -->
@@ -824,18 +827,26 @@
.pcard:not(.kept) { opacity: .4; }
.ph-thumb { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; }
.ph-pri { position: absolute; top: 6px; right: 6px; background: #00e5ff; color: #002; font-size: .56rem; font-weight: 800; padding: 1px 5px; border-radius: 2px; letter-spacing: .03em; pointer-events: none; }
.ph-req { position: absolute; top: 6px; left: 50%; transform: translateX(-50%); background: #ffb020; color: #201500; font-size: .52rem; font-weight: 800; padding: 1px 5px; border-radius: 2px; letter-spacing: .04em; pointer-events: none; }
.ph-seq { position: absolute; bottom: 6px; left: 6px; width: 18px; height: 18px; border-radius: 50%; background: rgba(0,0,0,.7); color: #fff; font-size: .66rem; display: flex; align-items: center; justify-content: center; pointer-events: none; }
.ph-keep { position: absolute; top: 6px; left: 6px; width: 16px; height: 16px; border: 2px solid #fff; border-radius: 3px; background: rgba(0,0,0,.5); pointer-events: none; }
.pcard.kept .ph-keep { background: #00e5ff; border-color: #00e5ff; }
.pcard.kept .ph-keep::after { content: ''; position: absolute; left: 4px; top: 1px; width: 4px; height: 8px; border: solid #002; border-width: 0 2px 2px 0; transform: rotate(45deg); }
/* Mandatory photos (clean cal + dimension-overlay): the keep checkbox is
checked AND disabled — it can't be unchecked, and a small lock glyph
signals "required, cannot be removed". The card keeps full opacity even
though it's always kept, and the cursor shows it's not a remove target. */
.pcard.required { border-color: #2a2a2a; }
checked AND disabled. A bold padlock glyph in the box signals "required —
cannot be removed", and the card carries a thin required outline so the
owner can tell mandatory photos apart from optional batch photos. */
.pcard.required { border-color: #00e5ff55; }
.pcard.required .ph-keep { background: #00e5ff; border-color: #00e5ff; }
.pcard.required .ph-keep::after { content: ''; position: absolute; left: 4px; top: 1px; width: 4px; height: 8px; border: solid #002; border-width: 0 2px 2px 0; transform: rotate(45deg); }
.pcard.required .ph-keep::before { content: ''; position: absolute; left: 2.5px; top: 5px; width: 7px; height: 5px; border: 1.5px solid #002; border-radius: 1px; }
/* Padlock: shackle (arc) + body, drawn large enough to read on a 16px box. */
.pcard.required .ph-keep::after {
content: ''; position: absolute; left: 3px; top: 2px; width: 8px; height: 8px;
border: 2px solid #002; border-bottom: none; border-radius: 4px 4px 0 0;
}
.pcard.required .ph-keep::before {
content: ''; position: absolute; left: 2.5px; top: 6px; width: 8px; height: 6px;
background: #002; border-radius: 1px;
}
.pcard.required { cursor: default; }
.ph-nudge { color: #e8a; font-size: .74rem; margin: 8px 0 0; }
</style>
@@ -213,12 +213,18 @@ test.describe('M3 Review photo strip (#90)', () => {
expect(bgImage).toContain('45deg');
// Mandatory photos: the clean calibration (seq 1) and the dimension-overlay
// copy (seq 2) are required — their keep checkbox is checked AND disabled.
// copy (seq 2) are required — their keep checkbox is checked AND disabled,
// and a REQUIRED tag marks them.
await expect(cards.nth(0)).toHaveAttribute('data-required', 'true');
await expect(cards.nth(1)).toHaveAttribute('data-required', 'true');
await expect(cards.nth(2)).toHaveAttribute('data-required', '');
await expect(cards.nth(0)).toHaveAttribute('aria-pressed', 'true');
await expect(cards.nth(1)).toHaveAttribute('aria-pressed', 'true');
await expect(cards.nth(0)).toHaveAttribute('aria-disabled', 'true');
await expect(cards.nth(1)).toHaveAttribute('aria-disabled', 'true');
await expect(cards.nth(2)).toHaveAttribute('aria-disabled', 'false');
// Two REQUIRED tags (cal + overlay), none on the batch photo.
await expect(page.getByText('REQUIRED', { exact: true })).toHaveCount(2);
// Tapping a mandatory photo is a no-op: it stays checked + primary (can't remove).
// The card is aria-disabled, so force the click to exercise the handler directly.