From 4e3e6bf256c44d7e03bce7eac649a09ca09d5cf6 Mon Sep 17 00:00:00 2001 From: poketonova Date: Sat, 18 Jul 2026 20:18:55 -0600 Subject: [PATCH] feat: add compact master set view --- scripts/evals/master-set-compact-mode.mjs | 384 ++++++++++ scripts/evals/run-all.mjs | 1 + scripts/smoke/browser-smoke.mjs | 831 ++++++++++++++++++++++ src/assets/main.css | 3 + src/components/MasterSetGallery.vue | 429 ++++++++++- 5 files changed, 1609 insertions(+), 39 deletions(-) create mode 100644 scripts/evals/master-set-compact-mode.mjs diff --git a/scripts/evals/master-set-compact-mode.mjs b/scripts/evals/master-set-compact-mode.mjs new file mode 100644 index 0000000..7608a66 --- /dev/null +++ b/scripts/evals/master-set-compact-mode.mjs @@ -0,0 +1,384 @@ +import { assert, ok, read, runEval } from './lib.mjs' + +runEval('master set compact mode v1 source guards', () => { + const gallery = read('src/components/MasterSetGallery.vue') + const portfolioView = read('src/views/PortfolioView.vue') + const portfolioStore = read('src/stores/portfolio.js') + + // ── Session-only density (default comfortable / off) ───────────────── + assert( + /Session-only density|session-only/i.test(gallery), + 'must label session-only density behavior in code', + ) + assert( + /const compact = ref\(false\)/.test(gallery), + 'compact must default false (comfortable / current mode)', + ) + assert( + /No Pinia\/Dexie\/localStorage|no Pinia, Dexie, or\s+localStorage/i.test(gallery), + 'must document no Pinia/Dexie/localStorage for compact preference', + ) + // No persistence APIs for the compact flag + assert( + !/localStorage\.(get|set)Item\([^)]*compact/i.test(gallery), + 'must not read/write localStorage for compact', + ) + assert( + !/sessionStorage\.(get|set)Item\([^)]*compact/i.test(gallery), + 'must not read/write sessionStorage for compact', + ) + assert( + !/persist(Now)?\(\)/.test(gallery), + 'gallery must not call store persist for density', + ) + // Parent still mounts gallery with v-if so unmount resets instance state + assert( + /v-if="msGalleryKey === g\.key"/.test(portfolioView), + 'parent must v-if gallery so compact resets on hide/unmount/reopen', + ) + + // ── Toggle control ─────────────────────────────────────────────────── + assert( + /msg-density-toggle/.test(gallery), + 'must expose density toggle control', + ) + assert( + /aria-pressed="compact \? 'true' : 'false'"/.test(gallery) + || /:aria-pressed="compact \? 'true' : 'false'"/.test(gallery), + 'density toggle must expose aria-pressed', + ) + assert( + /aria-label="compact \? 'Switch to comfortable layout' : 'Switch to compact layout'"/.test(gallery) + || /:aria-label="compact \?/.test(gallery), + 'density toggle must have aria-label', + ) + assert( + /compact = !compact/.test(gallery), + 'toggle must flip compact ref', + ) + + // ── Canonical visibleCards only — no synthetic range ───────────────── + assert( + /v-for="card in visibleCards"/.test(gallery), + 'both layouts must iterate visibleCards', + ) + const compactBlock = gallery.includes('msg-grid-compact') + ? gallery.slice(gallery.indexOf('msg-grid-compact')) + : '' + assert(compactBlock.length > 0, 'compact grid class must exist') + assert( + /v-for="card in visibleCards"/.test(compactBlock), + 'compact grid must v-for visibleCards (canonical fetched records)', + ) + // Strip HTML/JS comments before scanning for forbidden synthetic-range code + const codeOnly = gallery + .replace(//g, '') + .replace(/\/\/[^\n]*/g, '') + .replace(/\/\*[\s\S]*?\*\//g, '') + assert( + !/for\s*\(\s*let\s+\w+\s*=\s*1/.test(codeOnly), + 'must not synthesize 1..N placeholder loops', + ) + assert( + !/Array\.from\(\s*\{\s*length:\s*(setSize|total|group\.total)/.test(codeOnly), + 'must not build synthetic arrays from setSize/total', + ) + assert( + !/\b(setSize|group\.total)\b[\s\S]{0,40}(v-for|Array\.from|new Array)/.test(codeOnly), + 'must not drive v-for/Array.from from setSize/total placeholders', + ) + assert( + /never synthetic|canonical fetched|visibleCards/.test(gallery), + 'must label canonical visibleCards / no-synthetic-range intent', + ) + // Same fetch path — no second fetch for compact + const fetchCount = (gallery.match(/fetchSetCards\s*\(/g) || []).length + assert(fetchCount === 1, `exactly one fetchSetCards call (got ${fetchCount})`) + assert( + /const visibleCards = computed/.test(gallery), + 'visibleCards computed must remain the shared filter path', + ) + assert( + /filter\.value === 'need'/.test(gallery) && /!isOwned\(c\)/.test(gallery), + 'need filter must still use isOwned path', + ) + assert( + /function isOwned\(card\)/.test(gallery) + && /ownedIds\.value\.has\(card\.id\)/.test(gallery) + && /ownedKeys\.value\.has\(`\$\{name\}\|\$\{String\(card\.number/.test(gallery) + && /ownedKeys\.value\.has\(name\)/.test(gallery), + 'isOwned must keep cardId → name|number → name matching', + ) + + // ── Stale-mark exclusion ───────────────────────────────────────────── + assert( + /markedCards = computed\(\(\) => cards\.value\.filter\(c => props\.marks\[c\.id\] && !isOwned\(c\)\)\)/.test(gallery) + || /props\.marks\[c\.id\]\s*&&\s*!isOwned\(c\)/.test(gallery), + 'markedCards must exclude owned cards (stale marks)', + ) + assert( + /Safe marked set|stale-mark|still not owned/i.test(gallery), + 'must label stale-mark / safe marked set behavior', + ) + assert( + /foundToday[\s\S]{0,200}markedCards/.test(gallery) + || /markedCards\.value\.filter\(c => props\.marks\[c\.id\]/.test(gallery), + 'foundToday must derive from safe markedCards set', + ) + assert( + /emit\('add-found',\s*markedCards\.value\)/.test(gallery), + 'add-found must emit safe markedCards (no owned duplicates)', + ) + // Parent add-found + clear marks preserved + assert( + /@add-found="addFoundCards"/.test(portfolioView) + && /function addFoundCards/.test(portfolioView) + && /clearHuntMarks/.test(portfolioView), + 'parent add-found + clearHuntMarks path must remain', + ) + assert( + /function toggleHuntMark/.test(portfolioStore) + && /function clearHuntMarks/.test(portfolioStore), + 'store hunt mark APIs must remain', + ) + + // ── Compact interactions / a11y ────────────────────────────────────── + assert( + /msg-compact-primary/.test(gallery) && /msg-compact-preview/.test(gallery), + 'compact must split primary cell and separate preview control', + ) + assert( + /compactPrimaryLabel|Mark found|Unmark found|Preview owned/.test(gallery), + 'compact primary must expose owned/mark/unmark aria labels', + ) + assert( + /Preview \$\{card\.name|aria-label="`Preview/.test(gallery) + || /:aria-label="`Preview/.test(gallery), + 'compact preview control must have aria-label', + ) + assert( + /msg-compact-num/.test(gallery) && /msg-compact-name/.test(gallery), + 'compact cells must show number and name', + ) + assert( + /compactStatusLabel|Owned|Found|Need/.test(gallery), + 'compact must encode owned / needed / found status', + ) + // Compact avoids loading full card images in the grid (lightweight) + const compactTemplate = gallery.match( + / + + @@ -20,7 +38,15 @@
-
@@ -30,27 +56,84 @@
Fetching the full set…
{{ error }}
@@ -58,7 +141,7 @@