website globe: report, live check (desktop JS on/off, 376 px menu), R-903 (no-JS phone menu); 130 -> 131

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 09:21:16 +02:00
parent 803af4798d
commit 42a4a4634b
10 changed files with 123 additions and 1 deletions
+63
View File
@@ -0,0 +1,63 @@
# REPORT — the website's language switch becomes a globe (2026-10-08)
> `REPORT-website-globe.md`, not `REPORT.md`: another session worked in this repo today (commit c7aade40).
| Part | What | State |
|---|---|---|
| A | The globe on every page, CSS, no JavaScript | **Done, live** (803af479) |
| B | Gates 3 and 13 read the globe from one `LANGS` list; leftover text switch fails; 5 decoys | **Done** |
| C | CHANGELOGs, `.claude/rules/website.md`, `10-localisation.md` ruling 11 + §10.7, STATUS | **Done** |
Register: before **130**, after **131**, opened **1** (R-903), closed **0**.
## What was built
- Every page's nav: `<details class="lang-globe">` with the sprite's `i-globe` (`aria-hidden`), `aria-label`/`title`
„Nyelv" / „Language", a list Magyar, English (same order everywhere). The page's own language points at the page
itself, is bold with a blue rule and carries `aria-current="true"`; the other item points at the twin.
- `site.css`: globe rules for desktop (list below the icon, right-aligned) and for the ≤900 px menu (last item, list
opens in place). Tokens only. `?v=2` → `?v=3` on all 16 pages. **There was no `.lang-switch` rule to retire** — the
text switch had no CSS.
- **No script added.** The controller's globe has no close-on-outside-click or Escape script either (searched
`lang-globe` in its templates and CSS: markup + CSS only).
- The current item is marked by weight and a rule, not by the controller's „✓" (`content: "\2713"`): U+2713 is in
the site's emoji gate range.
- `404.html` and the hidden prices page carry the globe too (their English item points at `/en/`): gate 3 requires one
nav across the Hungarian set, and gate 13 checks their globe's structure (not targets — they have no twin).
## Decoys (mutation on `website/en/apps.html` → failure line; `documentation/audits/website-globe-2026-10-08/decoys.txt`)
| Decoy | Failure line |
|---|---|
| Magyar item → `/` | `en/apps.html: the globe's hu item points at '/' — its twin is '/alkalmazasok'` |
| Magyar item also `current` + `aria-current` | `en/apps.html: the globe marks 'hu' as current (class current=True, aria-current=True) — only the page's own language 'en' is` |
| Magyar item removed | `en/apps.html: the globe lists [('en', 'English')] — every page lists exactly [('hu', 'Magyar'), ('en', 'English')], in that order` |
| items swapped | `en/apps.html: the globe lists [('en', 'English'), ('hu', 'Magyar')] — every page lists exactly …` |
| a text switch added back | `en/apps.html: a leftover text language switch (class="lang-switch") — the globe replaced it` (also gate 3: `<nav> differs from en/index.html`) |
The decoy suite (`scripts/test_gate_decoys.py`) runs all five with `must=` on these lines: all rejected. The old
`site/switch-wrong-twin` decoy was retired with the switch.
## Live check (headless Chrome on scratch guest 9202, after git-sync; `check.txt` + six screenshots)
- `/` and `/en/`, 1280 px, JavaScript on and off: the globe is closed at load, opens on a click; Magyar → `/`
(lang hu), English → `/en/` (lang en); the current one has `aria-current="true"`; no sideways scroll.
- 376 px, JavaScript on, inside the opened three-line menu: the list opens in place, both items visible, both go to the
right page, no sideways scroll.
- **376 px, JavaScript off: not possible** — the three-line menu itself is scripted, so it does not open. This is
older than the globe. Filed as **R-903** (P4).
## Files and pushes
- **803af479**: `website/*.html` (9), `website/en/*.html` (7), `website/assets/site.css`, `website/CHANGELOG.md`,
`scripts/site_gates.py`, `scripts/test_gate_decoys.py`, `scripts/CHANGELOG.md`, `.claude/rules/website.md`,
`documentation/architecture/10-localisation.md`, `STATUS.md`, `documentation/audits/website-globe-2026-10-08/`
(decoys.txt, globe.py). **CI: job 1538, success.**
- This report, the live evidence and R-903: the next commit (named in the chat).
- Earlier, `67b3fd23` (website refresh): CI job 1535 failed with no log and every step failing in the same second
(R-887's shape); `663421dd` on top passed (job 1536).
## Instruction-file edits
`.claude/rules/website.md`: one bullet added — the switch is a globe; a new language = one `LANGS` entry + its twins.
No rule was loosened.
@@ -0,0 +1,38 @@
const puppeteer = require('puppeteer');
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function items(p) {
return p.$$eval('.lang-globe-item', as => as.map(a => ({text: a.textContent, href: a.getAttribute('href'), cur: a.getAttribute('aria-current'),
vis: a.offsetParent !== null, r: (b => [Math.round(b.left), Math.round(b.right)])(a.getBoundingClientRect())})));
}
(async () => {
const b = await puppeteer.launch({args: ['--no-sandbox']});
for (const js of [true, false]) {
for (const [w, h] of [[1280, 800], [376, 812]]) {
for (const [u, tag] of [['https://felhom.eu/', 'hu'], ['https://felhom.eu/en/', 'en']]) {
const p = await b.newPage();
await p.setJavaScriptEnabled(js);
await p.setViewport({width: w, height: h});
await p.goto(u, {waitUntil: 'networkidle2', timeout: 60000});
const name = `${tag}-${w}-${js ? 'js' : 'nojs'}`;
const closed = await p.$eval('details.lang-globe', d => d.open);
if (w < 900) {
if (!js) { console.log(name, 'phone menu needs JavaScript (the three-line button is scripted); skipped'); await p.close(); continue; }
await p.click('#hamburger'); await sleep(500);
}
await p.click('summary.lang-globe-btn'); await sleep(300);
const open = await p.$eval('details.lang-globe', d => d.open);
const it = await items(p);
const scroll = await p.evaluate(() => [window.innerWidth, document.documentElement.scrollWidth]);
await p.screenshot({path: `/out/${name}.png`});
console.log(name, JSON.stringify({openBefore: closed, openAfterClick: open, items: it, viewport_scroll: scroll}));
for (const i of it) {
const q = await b.newPage(); await q.setJavaScriptEnabled(js);
const r = await q.goto(new URL(i.href, u).href, {waitUntil: 'domcontentloaded'});
console.log(' ', i.text, '->', q.url(), r.status(), await q.$eval('html', e => e.lang)); await q.close();
}
await p.close();
}
}
}
await b.close();
})().catch(e => { console.error(e); process.exit(1); });
@@ -0,0 +1,20 @@
hu-1280-js {"openBefore":false,"openAfterClick":true,"items":[{"text":"Magyar","href":"/","cur":"true","vis":true,"r":[1043,1161]},{"text":"English","href":"/en/","cur":null,"vis":true,"r":[1043,1161]}],"viewport_scroll":[1280,1280]}
Magyar -> https://felhom.eu/ 200 hu
English -> https://felhom.eu/en/ 200 en
en-1280-js {"openBefore":false,"openAfterClick":true,"items":[{"text":"Magyar","href":"/","cur":null,"vis":true,"r":[1043,1161]},{"text":"English","href":"/en/","cur":"true","vis":true,"r":[1043,1161]}],"viewport_scroll":[1280,1280]}
Magyar -> https://felhom.eu/ 304 hu
English -> https://felhom.eu/en/ 200 en
hu-376-js {"openBefore":false,"openAfterClick":true,"items":[{"text":"Magyar","href":"/","cur":"true","vis":true,"r":[129,344]},{"text":"English","href":"/en/","cur":null,"vis":true,"r":[129,344]}],"viewport_scroll":[376,376]}
Magyar -> https://felhom.eu/ 200 hu
English -> https://felhom.eu/en/ 304 en
en-376-js {"openBefore":false,"openAfterClick":true,"items":[{"text":"Magyar","href":"/","cur":null,"vis":true,"r":[129,344]},{"text":"English","href":"/en/","cur":"true","vis":true,"r":[129,344]}],"viewport_scroll":[376,376]}
Magyar -> https://felhom.eu/ 304 hu
English -> https://felhom.eu/en/ 304 en
hu-1280-nojs {"openBefore":false,"openAfterClick":true,"items":[{"text":"Magyar","href":"/","cur":"true","vis":true,"r":[1043,1161]},{"text":"English","href":"/en/","cur":null,"vis":true,"r":[1043,1161]}],"viewport_scroll":[1280,1280]}
Magyar -> https://felhom.eu/ 200 hu
English -> https://felhom.eu/en/ 200 en
en-1280-nojs {"openBefore":false,"openAfterClick":true,"items":[{"text":"Magyar","href":"/","cur":null,"vis":true,"r":[1043,1161]},{"text":"English","href":"/en/","cur":"true","vis":true,"r":[1043,1161]}],"viewport_scroll":[1280,1280]}
Magyar -> https://felhom.eu/ 304 hu
English -> https://felhom.eu/en/ 304 en
hu-376-nojs phone menu needs JavaScript (the three-line button is scripted); skipped
en-376-nojs phone menu needs JavaScript (the three-line button is scripted); skipped
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+2 -1
View File
@@ -272,7 +272,7 @@ stopping line that lies.
| **R-89** | Business & legal | P4 | Retention as a per-customer **commercial** policy on the hub | READY (increment 2) | — | Policy object + reconciler → ep0 prune job; keep box tokens write-only | CC |
| **R-794** | Business & legal | P4 | **[P3-LOW] redis 7.4 (RSALv2 / SSPL, not OSI) runs as a private cache in seven apps: dawarich, docmost, immich, nextcloud, outline, paperless-ngx, romm.** READ 2026-10-02 (`audits/licences-2026-10-02/TABLE.md`). Read as permitted (a private cache only its app uses is not Redis offered as a service — inferred). Valkey (BSD-3) or redis 8 (AGPL option) removes the question. **Needs:** a ladder step per app to valkey or redis 8, through the harness — no hurry. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: the row itself says no hurry; usage read as permitted.** | — | — | CC |
## Process & tooling — 20 rows (P3 3, P4 17)
## Process & tooling — 21 rows (P3 3, P4 18)
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|---|---|---|---|---|---|---|---|
@@ -296,6 +296,7 @@ stopping line that lies.
| **R-786** | Process & tooling | P4 | **[P3-LOW] SparkyFitness's onboarding record has six open rows** (`app-catalog-felhom.eu/onboarding/sparkyfitness.md`): 0.5 runtime internet (food search providers), 0.7 the phone app's sign-in route through traefik, 1.6 the env names the server reads, 1.7 the entrypoint read, 5.4 a second memory watch at another limit, 8.3 no logo/screenshots on felhom.eu (404). Everything else measured this session (bench + 9202). **Needs:** each row measured, or n/a with a reason. | **READY — rank P3-LOW; owner: CC (catalog)** **Re-ranked 2026-10-03: P3→P4: onboarding record completeness; no household meets it directly.** | — | — | CC |
| **R-807** | Process & tooling | P4 | **[P3-LOW] 13 apps stay UNDETERMINED because their seeds write only to the database, so their upload / media / cache / redis volumes stay empty.** MEASURED 2026-10-02 (`audits/persistence-sweep-2026-10-02/A/sweep/TABLE.md`): claper, crafty-controller, dawarich (redis), docmost, gramps-web, immich (ML cache), outline (+redis), sparkyfitness, tandoor, vikunja, wger, wishlist, zipline — plus plex (no seed route: a plex.tv claim token) and wanderer (no fixture; unhealthy under the gate). None is BROKEN: nothing was written outside a preserved folder. **Needs:** per app, a seed that uploads one file (or the volume named n/a with a reason: a redis cache, an ML model cache). | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: test coverage; nothing was found broken.** | — | — | CC |
| **R-896** | Process & tooling | P4 | **The catalog's `volume-persistence` gate runs its canary and the apps on the LOCAL Docker, so a full gate run on DooPlex acts on the production host — and there its self-test fails for every app.** SEEN 2026-10-06 night: a helper's full `catalog_gates.py` run on DooPlex built `felhom-volgate-canary:1` and ran the canary pair locally (`scripts/check-volume-persistence.py` canary self-test, ~line 830); it reported the self-test failing for every app, also on `main` (checked with gokapi). Not re-run (it would act on DooPlex again). Same class as R-650 (tests that reach real Docker act on DooPlex). | **OPEN — filed 2026-10-06 night** | — | Point the gate at the bench guest (as the upgrade harness does) or refuse to run on a host without a marker; then find why the self-test fails | CC |
| **R-903** | Process & tooling | P4 | **On a phone with JavaScript off, the website's menu does not open — so its links and the language globe cannot be reached there.** MEASURED 2026-10-08 (globe brief, headless Chrome at 376 px, JavaScript off): the three-line button is scripted (`#hamburger` toggles `.nav-links.is-open`), and below 900 px the nav list sits off-canvas until then. Older than the globe — the text switch had the same limit. The globe itself works with JavaScript off at desktop width (`audits/website-globe-2026-10-08/check.txt`). Fix direction: a CSS-only menu toggle (a `<details>` or a checkbox), a nav change on every page plus gate 3. | **OPEN — owner: CC** | — | Build the no-JS menu in a website task | CC |
<!-- DUE-CHECKS-BEGIN — machine-readable. Parsed by scripts/due_checks_gate.py.
One row per dated check. The R-number must have a row above. Dates are UTC.