Merge branch 'fix/collection-remove-touch' into chore/reconcile-ui-review

This commit is contained in:
MechaCat02
2026-06-25 21:12:18 +02:00
2 changed files with 62 additions and 0 deletions

View File

@@ -406,6 +406,19 @@
opacity: 1;
}
/* Touch has no hover, so the reveal-on-hover treatment leaves the
remove buttons invisible and undiscoverable. Show them persistently
and enlarge the hit area on phones. (Capped well below 44px because
the button floats over a 4-up cover thumbnail — a full touch-floor
square would swallow the cover.) */
@media (max-width: 640px) {
.remove {
opacity: 1;
width: 32px;
height: 32px;
}
}
.icon-btn {
display: inline-flex;
align-items: center;

View File

@@ -0,0 +1,49 @@
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
// The per-card "remove" buttons on a collection are revealed on hover /
// focus-within (opacity 0 → 1), which leaves them invisible and
// undiscoverable on touch. They must be shown persistently at the mobile
// breakpoint. jsdom can't evaluate @media or layout, so this pins the CSS
// contract: the 640px block makes `.remove` opaque. (Same approach as the
// shared touch-target contract test.)
const SRC = readFileSync(
resolve(process.cwd(), 'src/routes/collections/[id]/+page.svelte'),
'utf8'
);
// Brace-balanced extraction of every `@media (max-width: 640px) { ... }` body.
function mobileBlocks(css: string): string {
const marker = '@media (max-width: 640px)';
let out = '';
let from = 0;
for (;;) {
const start = css.indexOf(marker, from);
if (start === -1) break;
const open = css.indexOf('{', start);
if (open === -1) break;
let depth = 0;
let i = open;
for (; i < css.length; i++) {
if (css[i] === '{') depth++;
else if (css[i] === '}') {
depth--;
if (depth === 0) break;
}
}
out += css.slice(open + 1, i) + '\n';
from = i + 1;
}
return out;
}
const squish = (s: string) => s.replace(/\s+/g, ' ');
describe('collection remove buttons on touch', () => {
it('reveals the remove buttons persistently at the mobile breakpoint', () => {
const mobile = squish(mobileBlocks(SRC));
expect(mobile).toMatch(/\.remove[^}]*opacity:\s*1/);
});
});