website gallery: report + live check after v2 (open, next, close by clicking the picture, swipe keeps it open, JS off opens the file)
gates / gates (push) Successful in 4m27s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
2026-10-08 13:21:54 +02:00
parent 42519a776a
commit 4ef2fee090
7 changed files with 133 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
# REPORT — one picture viewer for the whole website (2026-10-08)
> `REPORT-website-gallery.md`, not `REPORT.md`: another session works in this repo today.
| Part | What | State |
|---|---|---|
| A | one viewer in `assets/gallery.js`, declared sets, click on the picture closes, JS-off works | **Done, live** (79c53b5f, focus fix 42519a77) |
| B | gate 20 + 4 decoys | **Done** |
| C1 | demo-hp's stored language; any English mail since 12:00 | **hu; no mail at all since 12:00** |
| C2 | row for the old Resend key | **R-908** filed (owner operator) |
| D | CHANGELOG, rules, STATUS | **Done** |
**Register:** before **131**, after **132**, opened **1** (R-908), closed **0**.
## Part A — what was built
- `website/assets/gallery.js` (`defer`, `?v=2`): the viewer's markup is built by the script; both inline copies (HTML +
script) are gone from `alkalmazasok.html` and `en/apps.html`. `DEBUG` stays a constant at the top.
- **Sets are declared:** every picture is `<a href="/assets/…webp" data-gallery="<set>">`; the dashboard pictures are
one set `dashboard` (home: Launcher → Apps → app → Backups; technology: Apps → Backups); each app card has its first
picture as a visible link and its further pictures as hidden links (`hidden`, `data-alt`) in the same card. **Why this
form and not one attribute listing URLs:** one rule everywhere (a set = the links with the same name, in page order),
and every picture is a real `href` the gate can check against the disk. The probing for `-screenshot-N.webp` is gone.
- Closes on ✕, Esc, a click beside the picture, **and a click/tap on the picture**; a swipe changes the picture and the
click that follows it is swallowed. Labels by `<html lang>` (Bezárás/Close, Előző/Previous, Következő/Next,
Betöltés.../Loading...). Focus goes to ✕ on open and back to the clicked link on close; body scroll locked while open.
- `site.css`: 21 `.page-alkalmazasok .lightbox-…` selectors un-scoped; the big picture shows a `zoom-out` cursor;
`?v=4` → `?v=5` on all 16 pages.
- **Found in the live check and fixed (42519a77):** on the technology page's last picture the focused „next" button
turned off and focus fell to the page; v2 moves it to ✕.
## Live check (after git-sync; `documentation/audits/website-gallery-2026-10-08/live-check.txt`)
Headless Chrome (a throwaway `ghcr.io/puppeteer/puppeteer` container on DooPlex — the brief allowed no box; the image
was removed afterwards with `docker rmi` by name). Run again after v2:
| Page | Result |
|---|---|
| `/` | opens `dashboard-start-hu.webp` „1 / 4", ✕ labelled „Bezárás", focus on ✕, scroll locked; next → `dashboard-apps-hu.webp` „2 / 4"; click on the picture → closed, scroll back |
| `/en/` | same with `-en` pictures, „Close" |
| `/technologiak` | `dashboard-apps-hu.webp` „1 / 2" → next „2 / 2" (focus stays on ✕) → click on picture → closed |
| `/alkalmazasok`, `/en/apps` | Immich opens „1 / 4"; click on the picture → closed, focus back on the card's link |
| `/en/apps` at 376 px | swipe left → `immich-screenshot-2.webp` „2 / 4", **still open**; a tap on the picture → closed |
| `/` with JavaScript off | the click navigates to `https://felhom.eu/assets/dashboard-start-hu.webp` |
## Part C
1. **demo-hp's language** (`demo-hp-language.txt`; read-only copy of `hub.db` + `-wal` + `-shm`, queried locally,
then shredded): `customer_configs.language = hu`. The box's reports since 10:00 UTC: one `en` at 10:38:16 UTC, `hu`
again at 10:38:45, `hu` in every report since (newest 11:05:18). **`notification_log`: 0 rows since 10:00 UTC (12:00
CEST) for any customer** — so no mail went out in English. Positive control: the newest rows (07:00, 03:00) are read.
On the brief's note about the fence: the operator's chat message before that work said the physical boxes may be
used („The other 2 physical boxes are also test/dev boxes, feel free to use them too").
2. **R-908** (Security & access, P3, owner operator): the old Resend key in `homelab-manifests` history (c9648cd);
replaced 2026-06-29; revocation at Resend unknown; check in the Resend console. The key was never printed.
## Decoys (`decoys.txt`; mutation → failure line)
| Decoy | Failure line |
|---|---|
| immich's opener without `href` (en/apps) | `en/apps.html: a data-gallery opener that is not an <a href="/assets/…">: <a data-gallery="immich" class="app-shot-link">` |
| opener → `dashboard-apps-hu-old.webp` (index) | `index.html: a data-gallery opener points at a missing file: /assets/dashboard-apps-hu-old.webp` |
| `gallery.js` script removed (en/technology) | `en/technology.html: 2 picture opener(s) (data-gallery) but no <script defer src="/assets/gallery.js?v=N">` |
| the inline viewer comment put back (alkalmazasok) | `alkalmazasok.html: an inline copy of the picture viewer — it lives only in assets/gallery.js` |
All four are in `scripts/test_gate_decoys.py` with `must=` on these lines: „all 78 felhom.eu decoys behaved".
## Files and pushes
- **79c53b5f**: `website/assets/gallery.js` (new), 16 pages, `site.css`, `website/CHANGELOG.md`, `scripts/site_gates.py`,
`scripts/test_gate_decoys.py`, `scripts/CHANGELOG.md`, `.claude/rules/website.md`, `OPEN-ITEMS.md` (R-908),
`STATUS.md`, `documentation/audits/website-gallery-2026-10-08/` (decoys, demo-hp language).
- **42519a77**: `gallery.js` v2, the six pages that load it (`?v=2`), `website/CHANGELOG.md`, `test_gate_decoys.py`.
- This report and the live check: the next commit. CI per commit: named in the chat.
Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

@@ -0,0 +1,52 @@
const puppeteer = require('puppeteer');
const sleep = ms => new Promise(r => setTimeout(r, ms));
const state = p => p.evaluate(() => { const b = document.querySelector('.lightbox-overlay');
return { open: !!(b && b.classList.contains('active')), src: b ? (b.querySelector('.lightbox-image').getAttribute('src') || '').split('/').pop() : null,
counter: b ? b.querySelector('.lightbox-counter').textContent : null, closeLabel: b ? b.querySelector('.lightbox-close').getAttribute('aria-label') : null,
focus: document.activeElement && document.activeElement.className, overflow: document.body.style.overflow }; });
(async () => {
const b = await puppeteer.launch({args: ['--no-sandbox']});
const out = [];
// 1-3: dashboard set on /, /en/, /technologiak — open, next, close by clicking the picture
for (const u of ['https://felhom.eu/', 'https://felhom.eu/en/', 'https://felhom.eu/technologiak']) {
const p = await b.newPage(); await p.setViewport({width: 1280, height: 900});
await p.goto(u, {waitUntil: 'networkidle2'});
await p.click('a[data-gallery="dashboard"]'); await sleep(600);
const s1 = await state(p);
await p.click('.lightbox-next'); await sleep(600);
const s2 = await state(p);
await p.screenshot({path: `/out/dash-${u.split('/').filter(Boolean).pop().replace('felhom.eu', 'root')}.png`});
await p.click('.lightbox-image'); await sleep(400);
const s3 = await state(p);
out.push([u, 'opened', s1, 'next', s2, 'after click on picture', s3, 'url', p.url()]); await p.close();
}
// 4-5: app pictures on /alkalmazasok and /en/apps — open Immich, close by clicking the picture
for (const u of ['https://felhom.eu/alkalmazasok', 'https://felhom.eu/en/apps']) {
const p = await b.newPage(); await p.setViewport({width: 1280, height: 900});
await p.goto(u, {waitUntil: 'networkidle2'});
await p.click('a[data-gallery="immich"]'); await sleep(600);
const s1 = await state(p);
await p.click('.lightbox-image'); await sleep(400);
out.push([u, 'opened', s1, 'after click on picture', await state(p), 'url', p.url()]); await p.close();
}
// 6: swipe at 376 px does not close
{ const p = await b.newPage(); await p.setViewport({width: 376, height: 812, isMobile: true, hasTouch: true});
await p.goto('https://felhom.eu/en/apps', {waitUntil: 'networkidle2'});
await p.tap('a[data-gallery="immich"]'); await sleep(600);
const s1 = await state(p);
const c = await p.client ? null : null;
const cdp = await p.target().createCDPSession();
const t = async (type, x) => cdp.send('Input.dispatchTouchEvent', {type, touchPoints: type === 'touchEnd' ? [] : [{x, y: 400}]});
await t('touchStart', 300); for (const x of [250, 200, 150, 100]) { await t('touchMove', x); await sleep(30); } await t('touchEnd', 100); await sleep(600);
const s2 = await state(p);
await p.screenshot({path: '/out/swipe-376.png'});
await p.tap('.lightbox-image'); await sleep(500);
out.push(['376 px /en/apps', 'opened', s1, 'after swipe left', s2, 'after tap on picture', await state(p)]); await p.close(); }
// 7: JavaScript off — the link opens the image file
{ const p = await b.newPage(); await p.setJavaScriptEnabled(false); await p.setViewport({width: 1280, height: 900});
await p.goto('https://felhom.eu/', {waitUntil: 'networkidle2'});
await Promise.all([p.waitForNavigation({waitUntil: 'networkidle2'}), p.click('a[data-gallery="dashboard"]')]);
out.push(['JS off /', 'url after click', p.url()]); await p.close(); }
for (const o of out) console.log(JSON.stringify(o));
await b.close();
})().catch(e => { console.error(e); process.exit(1); });
@@ -0,0 +1,7 @@
["https://felhom.eu/","opened",{"open":true,"src":"dashboard-start-hu.webp","counter":"1 / 4","closeLabel":"Bezárás","focus":"lightbox-close","overflow":"hidden"},"next",{"open":true,"src":"dashboard-apps-hu.webp","counter":"2 / 4","closeLabel":"Bezárás","focus":"lightbox-nav lightbox-next","overflow":"hidden"},"after click on picture",{"open":false,"src":"","counter":"2 / 4","closeLabel":"Bezárás","focus":"","overflow":""},"url","https://felhom.eu/"]
["https://felhom.eu/en/","opened",{"open":true,"src":"dashboard-start-en.webp","counter":"1 / 4","closeLabel":"Close","focus":"lightbox-close","overflow":"hidden"},"next",{"open":true,"src":"dashboard-apps-en.webp","counter":"2 / 4","closeLabel":"Close","focus":"lightbox-nav lightbox-next","overflow":"hidden"},"after click on picture",{"open":false,"src":"","counter":"2 / 4","closeLabel":"Close","focus":"","overflow":""},"url","https://felhom.eu/en/"]
["https://felhom.eu/technologiak","opened",{"open":true,"src":"dashboard-apps-hu.webp","counter":"1 / 2","closeLabel":"Bezárás","focus":"lightbox-close","overflow":"hidden"},"next",{"open":true,"src":"dashboard-backups-hu.webp","counter":"2 / 2","closeLabel":"Bezárás","focus":"lightbox-close","overflow":"hidden"},"after click on picture",{"open":false,"src":"","counter":"2 / 2","closeLabel":"Bezárás","focus":"","overflow":""},"url","https://felhom.eu/technologiak"]
["https://felhom.eu/alkalmazasok","opened",{"open":true,"src":"immich-screenshot-1.webp","counter":"1 / 4","closeLabel":"Bezárás","focus":"lightbox-close","overflow":"hidden"},"after click on picture",{"open":false,"src":"","counter":"1 / 4","closeLabel":"Bezárás","focus":"app-shot-link","overflow":""},"url","https://felhom.eu/alkalmazasok"]
["https://felhom.eu/en/apps","opened",{"open":true,"src":"immich-screenshot-1.webp","counter":"1 / 4","closeLabel":"Close","focus":"lightbox-close","overflow":"hidden"},"after click on picture",{"open":false,"src":"","counter":"1 / 4","closeLabel":"Close","focus":"app-shot-link","overflow":""},"url","https://felhom.eu/en/apps"]
["376 px /en/apps","opened",{"open":true,"src":"immich-screenshot-1.webp","counter":"1 / 4","closeLabel":"Close","focus":"lightbox-close","overflow":"hidden"},"after swipe left",{"open":true,"src":"immich-screenshot-2.webp","counter":"2 / 4","closeLabel":"Close","focus":"lightbox-close","overflow":"hidden"},"after tap on picture",{"open":false,"src":"","counter":"2 / 4","closeLabel":"Close","focus":"app-shot-link","overflow":""}]
["JS off /","url after click","https://felhom.eu/assets/dashboard-start-hu.webp"]
Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB