Read-only. fetch.py + tables.py (stdlib), TABLE.md (45 charts, % of
respondents), FELHOM.md (apps, features, reasons, hardware, trends, ten
ranked recommendations), METHOD.md (hashes, counts, matching, MIT licence,
limits). Survey files are not committed. One register row (R-989) and one
STATUS line ask the operator to rule on the recommendations.
The catalog published qBittorrent and Transmission today (app-catalog 9081a47). Here:
- two cards in the media category on both language sets, each with the project's own logo,
each saying what a closed peer port costs (slower swarms, no incoming peers)
- the published count 58 -> 60 on every page that states it, FAQ JSON-LD with its visible twin
- documentation/audits/qbittorrent-2026-10-12/: both bench runs, both box walks and verdicts,
verify-fixes.json, teardown-newapps.json, and the README brought to its final state
- the 2026-09-30 walk harness: a `type: path` deploy field is filled with the drive ROOT —
the controller refuses a path inside a drive (409), so the walker could not install any app
with a drive field at all
- R-988: the Usenet pair (SABnzbd, NZBGet), operator request, not started
- STATUS.md and REPORT-newapps-2026-10-12.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
The arr stack had no download client: radarr/sonarr/seerr decide what to fetch
and hand it to one, and there was none. Both templates are written and measured
on the bench (LXC 9401) and on 9202 through the product. Neither is published -
the onboarding records are not complete and the gate refuses a template without
one.
Found by measuring, each fixed:
- qBittorrent was unreachable on its own public name: WebUI\ServerDomains takes
SEMICOLONS; a comma list answers 401 Invalid Host header to everything.
Undocumented upstream; measured four ways through the app's own API.
- Both clients asked the household's router to open a port (qBittorrent
UPnP/NAT-PMP ON, Transmission port-forwarding-enabled true). The box opens no
port at home. qBittorrent ignores the seeded Session\UseUPnP=false at first
start but honours the same setting through its API, so it goes through
after_install: ON at 17:21:51, OFF at 17:21:56.
- Transmission could never go healthy (401 on every path, curl -f exit 22).
- Transmission's host whitelist refused its own healthcheck.
- The apparent lock-out was a measurement artefact: with stranger and household
on two different addresses, the stranger is banned and the household signs in
at the same instant (204). qBittorrent reads the real visitor address.
Downloads work with no inbound port, measured: 34 seeds / 4 MB/s (qBittorrent),
48 peers (Transmission), both complete, port confirmed closed.
9202 is back on the live catalog with both apps removed; bench 9401 stopped as
found; the live catalog's main untouched.
The first real tester answered on Prohardver today. His domain is bought in his
name (terms 1.1 §4), so his name, address, e-mail and phone leave this house -
and the privacy notice did not say so. Now it does, before the purchase.
Operator rulings, asked before the work and recorded in CONTEXT.md:
- Registrar for a tester's .hu domain: Nethely Kft.
- The tester's zone lives in FELHOM's Cloudflare account during the test. This
is what the build already assumed (day0-install A.1 + the one-zone token guard
of 01 §7 / R-138 option C) and nothing had recorded it; it answers open point 9
of the privacy draft.
Privacy notice 1.1:
- New §10 "A domained regisztrációja": exactly the four fields the .hu
Domainregisztrációs Szabályzat §1.2.2.1 requires of a natural person (the
optional mother's name, birth date and ID number are NOT asked for), by e-mail,
to Nethely and the .hu registry (both named with seats), why, how long, and
what is public - a natural person shows as "Természetes személy" only.
- It also states whose Cloudflare account holds the zone and that the domain
moves to the tester's own account when he leaves.
- §11 (recipients) gained the two new names: a recipients list that omits two
recipients is wrong. That is the one change beyond the new section.
- 1.0 -> 1.1 diff: the only removed/changed lines are the record's title and
derived-on date, the version line, and four renumbered headings.
marketing/testers/kit/: five files + README, Hungarian, first person. Questions
about the machine and the net (each traced to day0-install Part B or the FAQ),
the domain data request, the tester's acceptance e-mail (no domain purchase
before it arrives), the welcome e-mail with the recovery-code warning, and the
operator's own step list. No personal data in any of it - placeholders only.
Also: EMAIL-privacy-1.1.md for the existing testers, the legal README pointing at
the 1.1 records, and one plural verb the voice sweep missed in the FAQ.
The old card's lettering was plain white and in some other face. The card now
takes assets/logo-wordmark.png - the same file the sidebar shows - scaled to the
old lettering's ink height and alpha-composited in its place, so felhom is white,
.eu is blue, and the drawn outline is there. The cloud mark beside it is still
the old card's pixels, which were always right. og:image ?v=3.
- S4 / R-242: GoldenVouchChecker - a published golden unvouched for 2 h raises golden_not_vouched (operator only)
- S6 / R-244: the delete cascade de-references app_log_issues; one-time sweep flag -sweep-app-log-residue=count|apply
- S10 / R-266: read_failed storage entry shows "read failed", not 0 %
- S29 / R-250: host-key scan IPv4 first, ~2 min ladder, "safe to press Save again" error
- S30 / R-246: stale_at retired (reader, setter, badge)
- S32 / R-521: storage_reconnected ends the storage_disconnected cooldown on both channels
- S33 / R-402: host page "Last full check (off-site)" line; two wire-gate allowlist entries removed
Each with a test seen failing first. Not built or deployed yet (operator-attended deploy).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
The voice sweep reached the visible text and stopped there. description,
og:description and twitter:description on both contact pages still said "Irj
nekunk es szemelyre szabott ajanlatot keszitunk" / "Write to us and we will
make a personal offer" - six lines nobody sees on the page and everybody sees
under a shared link, which is where the operator was about to post one.
The operator went to post the link and found the card out of date: it was the
2026-07-18 design - a headline and two lines of text on a dark field, nothing of
the product, and older than everything the site has become since.
- og-image.png and og-image-en.png rebuilt around the laptop, with the hero's
own poster frame on its screen.
- scripts/make_og_card.py: the headline, logo and button are lifted as PIXELS
from the previous card and composited with `lighter` onto flat --bg-0, because
the site's font is a woff2 and nothing here can draw a glyph with it. A new
headline therefore CANNOT be made with this script - new words go back to the
design tool and this takes that card as its source. The lede lines are gone;
the laptop says what they said.
- ?v=2 on every og:image and twitter:image (20 pages): a scraper caches by URL,
so a new picture at the old URL would reach nobody. A card already attached to
a post still does not change - that post must drop the preview and re-add the
link after a re-scrape.
A page-by-page review of the live site (1440px and 390px) found six defects and
two editorial questions. The operator answered three questions; the answers and
the evidence are in REPORT-review-2026-10-12.md.
Fixed:
- The contact form showed its consent error before any submit, on both pages:
#privacyError sits outside any .form-group and the hide rule was scoped to
.form-group. Scoped to the page. Checked: load (none) -> submit unticked
(shown) -> tick (gone), with no message sent.
- The consent checkbox is the site's own control now: 24px box, keyboard focus
ring, and a second <label for="privacy"> at 44x44 round it for touch.
- The contact page promised a reply within 24 hours while /feltetelek 1.1 §4
promises no reply time. Now 1-2 days usually, and no promise during the test.
- sitemap.xml was missing /szolgaltatasok and /en/services: 18 -> 20 URLs.
- The pre-shell mobile-menu script still ran on 20 pages and logged
"[mobile-menu] Missing elements" on every one. Removed.
- The sidebar's tester button was btn-outline while the hero's was btn-primary.
- The home page's "Mit tud a doboz ma?" was three bare links; each card now
carries 2-3 of the services page's own items, word for word.
- A plain Facebook link in the footer of all 22 pages. No script, widget or
pixel: the only external hosts any page refers to are still felhom.eu and
stats.felhom.eu.
Voice (operator ruling: "I" everywhere a person speaks): contact, tester and FAQ
pages in both languages, and the home and services pages that repeat the same
promises. An inclusive "we" (you and I, talking) stays plural, and the legal
texts are untouched. Each FAQ answer exists twice (JSON-LD + visible) and gate 18
holds them equal - it caught the five answers whose markup split the sentence.
Hero clip rebuilt: Immich first, so the poster is a wall of real photographs;
the Jellyfin scene starts after the demo's library wall (eleven pieces of poster
art whose licence cannot be quoted from a first-party source); 1.3s of blank
screen cut from the OnlyOffice scene. The film that plays, Caminandes 3:
Llamigos, is CC BY per Blender's own video platform ("licence": {"id": 1,
"label": "Attribution"}) - NOT public domain as website/CHANGELOG.md said
yesterday. That sentence is corrected in place and the film is credited under
the hero caption.
New gate 24: sitemap.xml lists every public page once, with its twin's hreflang
pair, and lists nothing else. Decoy: site/sitemap-missing-page, the exact shape
the reviewer found by hand.
site.css ?v=30; hero clip and poster ?v=2.
- 09 §3: the decision sheet S1-S38, the operator's words quoted where they differ (S1, S2, S8, S32, S37)
- closed: R-831 R-870 R-698 R-327 R-230 R-719 R-352 R-526 R-770 R-771 R-913 R-290 R-209a R-769 R-616 R-884, and R-243 (dated check: the mail fired 2026-10-11 16:13:51Z, hub log agrees)
- R-698: the known limit written in 07 §6.6; new row R-933 (mirror supported catalog images, P3)
- R-327: capability-map row BUILT; stands claim re-titled and moved to built; page re-rendered
- R-913: the customer Cloudflare token recipe in day0-install A.1; 01 §7 points at it
- R-884: read only - ArgoCD Synced, live Deployment equals git
- R-887 dated check NOT met (4 lost jobs 10-10/11); re-dated 2026-10-19 after the S21 change
- 22 open rows carry their ruling; STATUS: the sheet is ANSWERED
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
Two notes from the operator, 2026-10-12: the white/blue heading split was lost
in the M3 switch and should return everywhere, the apps page categories
included; and the home page's sections ran into one another.
- 41 headings get their <span> back (home 5+5, apps 6+6, FAQ 6+7, services 3+3).
The style never went away - site.css has styled `h2 span` blue all along; the
M3 rewrite dropped the markup. No text changed: the span wraps words that were
already there. The three home headings that predate M3 are split where they
were split then.
- Apps and FAQ categories are new to the treatment: a name with an & splits
there, a phrase on its last words. Five one-word headings stay plain.
- The home page's sections get a 1px --line rule with 2.75rem of air on each
side, so it sits centred in the gap; the first also parts the hero from the
page. Home page only.
- Blue on the page background measures 7.9:1 - AA at any size.
- site.css ?v=28.
The controller's rule centres the menu on the globe (left: 50% +
translateX(-50%)). On the website the globe sits at the far left of the sidebar
footer, so 60px of the 120px menu hung outside the window and both labels were
cut. Anchored to the button's left edge in site.css instead, opening rightwards
into the sidebar; shell.css stays a verbatim copy.
Measured after the fix: menu x 24->144 inside a 240px sidebar. site.css ?v=27.
The operator recorded the six scenes and ruled on their length: use them whole,
"they had been cut as I liked them". Jellyfin, Tandoor, OnlyOffice, Immich,
RomM, Paperless, 0.6s crossfades, 36.6s looping at 904x556.
- hero-clip.mp4 (1 134 KB) + hero-clip.webm (1 295 KB) + hero-poster.webp
(53 KB), one set for both languages: the recordings are the same on / and /en/.
- The hero <video> sits in the frame's cut-out hole at z-index 1, the frame above
it at 2. No autoplay attribute: the page's script starts it when the hero is on
screen, pauses it when it leaves, and never starts it under
prefers-reduced-motion. With JS off the poster stays.
- make_hero_clip.py reads each scene's own length (ffprobe) instead of cutting
every one to a fixed window, and the xfade offsets count from the running sum
minus one fade per transition already made.
- site.css ?v=26 on all 22 pages.
NOT verified: playback in this workstation's Chrome, whose media pipeline loads
nothing at all today (a 1 KB generated WAV blob stalls the same way, with no
error) - which rules the clip out as the cause. Geometry, layering and the
poster frame were verified in the browser.
Jellyfin, Paperless and RomM were filmed on those projects' own public demo
sites; the films in the Jellyfin scene are public-domain prints.
The operator asked for the laptop's screen to show a few seconds of real apps
running. His decision: he records the raw clips, the assembly is built here. So no
Playwright and no new dependency - ffmpeg 7.1.5 is already on DooPlex.
NOT ON THE PAGE YET. The live hero still shows the still picture; this commit is the
two finished pieces plus the measurements behind the format choice.
scripts/make_hero_clip.py
NN-appname.ext in -> scaled and centre-cropped to the screen's 1.626:1, crossfaded,
out as mp4 (h264) + webm (VP9) + a webp poster. Runs on DooPlex; Windows has no
ffmpeg. Not part of any build, gate or deploy: the site has no build step, so the
output is committed like any other picture. Proven end to end against six
placeholder scenes built from the repo's own app screenshots (21s loop -> mp4
480 KB, webm 609 KB, poster 83 KB).
WHY NOT A GIF - measured, six crossfaded scenes:
904x556 mp4 331 KB | webm 411 KB | GIF 2676 KB for HALF the length
1130x696 mp4 775 KB | webm 1196 KB | webp 2551 KB
A GIF is ~11x the mp4 for the same seconds and dithers to 256 colours on a dark UI.
website/assets/hero-laptop-frame.webp
The laptop render with its SCREEN CUT OUT to transparency, so a clip plays under the
bezel with the glass reflection over it. The reflection is PAINTED: lifting the
bright pixels out of the AI-drawn screen dragged a ghost of the drawn UI along.
Hole: left 11.041% top 12.345% width 79.817% height 64.305%, ratio 1.626:1. Two
pixels of slack - a tight rect showed the drawn sidebar's highlight, a loose one
showed page background at the top left; both were seen in the browser.
The page will load it as <video muted loop playsinline preload="none"> with the first
frame as poster, started only on scroll-into-view and never under
prefers-reduced-motion. First paint: frame 59 KB + poster ~80 KB against today's single
57 KB still; the ~330 KB clip comes after, or never if the visitor does not scroll
there. No "loading" graphic needed - the poster IS the loading state.
The recording brief is in E:\felhom-video\README.txt (outside the repo: the raw files
are large and the repo is public).
Its anchor was "Who is behind it" in en/index.html, which became "More about me" when
the home line went first person. The suite refused to build the decoy rather than skip
it, which is the behaviour that makes an anchor change visible.
1. THE HOME PAGE SPOKE ABOUT THE OPERATOR IN THE THIRD PERSON ("Ha irsz, o valaszol")
while the page it links to is in the first ("Ha irsz, en valaszolok"). Both home
pages now read "Nagyfenyvesi Viktor vagyok, a Felhom mogott en allok" / "I am Viktor
Nagyfenyvesi, and I am the one behind Felhom", and the link after it is "Rolam
bovebben" / "More about me" rather than repeating the question the sentence has just
answered. The legal pages keep the formal third-person name, which is their job.
2. 36px OF DEAD SPACE UNDER THE LANGUAGE GLOBE, and most of it was A LEAK GATE 22
COULD NOT SEE. The old chrome's
nav { position: fixed; top/left/right: 0; padding: 20px 0;
backdrop-filter: blur(12px); border-bottom: 1px solid var(--line);
z-index: 200 }
is a BARE ELEMENT selector, so it matched <nav class="sidebar"> too - and .sidebar
declares none of those, so all of them were in force on the sidebar. Scoped
body:not(.shell); the footer row's own padding went 16px -> ~8px.
Gate 22's first half compares selectors present in BOTH stylesheets, and `nav` is not
a selector shell.css ever writes, so this shape was outside its field of view. It now
has a SECOND HALF: an unscoped bare nav/header/main/aside rule in site.css is a
failure, because those elements ARE the shell (<nav class="sidebar">,
<header class="mobile-topbar">, <main class="content">). It convicted a second live
one on its first run: R-903's `@media (max-width:900px) and (scripting: none)
nav { position: static }` had been overriding the sidebar's own position on a phone
with scripting off. `footer` is deliberately NOT on the list, with the reason beside
it: the shell carries the website's own footer.
Decoy site/bare-element-rule-reaches-shell, seen to convict.
3. THE COMPARISON TABLE AND THE TWO CARDS under "Miert a sajatod?" were touching: .vs
ends on its own border and .band opens with a 2px blue rule, so they read as one
block. 2rem between them.
4. "MINDEN ALKALMAZAS" -> "AZ ALKALMAZASOKROL" / "About the apps" on all 22 pages. Every
app is on that one page, so the first submenu item is not a filter - it is the top of
the page, where the intro and the "Fontos tudnivalok" box are. The en-nav-drift decoy
was re-anchored on the new label.
Checked at 390px and 1440px on all 21 pages: no sideways scrolling, footer inside the
content column, the renamed item on every page. site.css?v=25.
Reported from his own screens. Five defects, two design.
1. THE CATEGORY ANCHORS WERE DEAD - all 100 of them. The sidebar's five app-category
links pointed at #fajlkezeles-irodai and friends; the ids are #fajlkezeles and
friends. The sidebar is copied into every page, so one wrong fragment was wrong
twenty times, and a dead fragment raises no error anywhere: the browser stays put.
New gate 23 resolves every in-site #fragment against the page it names (360 today).
It convicted two more on its first run: the home page's four hero tiles pointed at
/alkalmazasok#immich, which no page had ever carried, and the FAQ's contact button
pointed at a home-page section that had moved out. Every app card now carries its
slug as an id - taken from the card's own logo file, which gate 21 already ties to
the catalog, so the ids cannot drift from it silently.
Decoy site/dead-fragment puts the operator's own bug back and the gate convicts.
2. SZOLGALTATASOK IS A PAGE: /szolgaltatasok + /en/services, registered in TWINS, URL
and SHELL. The ten capability items moved VERBATIM out of the home page; the home
keeps the promise sentence, the three group names as links and a button, and is 345
Hungarian / 434 English words shorter. The .caps rules stopped being .page-index-
scoped, because the block now lives on a page with a different body class.
3. THE HOME HEADLINE is back in the hero column beside the picture, two lines with the
second in --blue-bright at clamp(2.1rem, 4.4vw, 3.2rem). M3 had moved it into
.page-header - the INNER pages' heading - so it shrank to 2.1rem and the domain
badge was stranded beside it.
4. THE HERO PICTURE is the real dashboard in a laptop. The frame is the operator's own
supplied render; the screen is dashboard-start-{hu,en}.webp composited into it, so a
visitor sees a real box's Launcher rather than a drawing of one - the supplied
screens are AI-drawn and misspell app names. 57 KB, named dashboard-laptop-<lang>
so gate 19 holds it to the page's language.
5. .page-lede HAD NO CSS AT ALL on 16 pages: a bare paragraph, full width of the
content column, hard against the box beneath it. 760px measure, centred over the
box, air beneath; left-aligned on /szolgaltatasok, which has no centred box.
6. THE SIDEBAR LOGO: the wordmark shipped at 0.67x the mark's width where the real
lockup (assets/logo.png, measured: mark 492px wide, wordmark 631px) has it at 1.28x.
Mark 112px, wordmark 25px tall, 28px less vertical padding.
7. SCROLLBARS are the page's own now. The first attempt missed the viewport's: it is
painted from <html>, and a `body.shell ::-webkit-...` rule only reaches scrollers
inside the page.
Also: the two Latin font subsets are preloaded. With font-display: swap the page
reflowed after first paint - measured at 1339px on /alkalmazasok#media.
Checked at 390px and 1440px on all 21 pages: no sideways scrolling, footer inside the
content column everywhere, drawer opens with all 13 items. site.css?v=24.
The two defects the operator found on his own phone are fixed and live since
5aeb36b5; this is what pins them so they cannot come back silently.
site_gates.py gate 22 parses site.css and shell.css into
(media context, selector, declarations) and fails on any property site.css
declares on a selector shell.css also styles that nothing overrides - neither
shell.css itself nor a `.shell ...`-scoped reset. site.css loads first, so
"later wins" is per PROPERTY, not per rule. Today: 689 site rules vs 119 shell
rules, 9 shared selectors, 0 leaks. .hamburger-box / .hamburger-line are an
explicit allow-list with the reason beside them.
It is media-aware precisely because the check it replaces was not. The STOP-1
leak diff excluded @media blocks and reported "exactly six" leaking
declarations; the real number is forty, and the worst of them
(@media (max-width: 900px) .nav-links { position: fixed; right: -100% })
parked the drawer's menu off-canvas and shipped.
Two decoys in test_gate_decoys.py, both the exact shape that went live, both
seen to convict:
site/shell-css-leak-in-media the body:not(.shell) scope taken back off that
one rule - the gate names the media query, the
selector and `right: -280px`
site/footer-outside-main <main> closed early on gyik.html - gate 3b's line
They plant and restore BYTES, with the anchor translated to the file's own line
ending: these files are CRLF in a Windows worktree and LF in a Linux one, and
replace_in's text-mode round trip would have rewritten a whole file on one side.
Also: CHANGELOG entries for both areas, the M3 report corrected where it quoted
its own wrong "exactly six", and one plain-language STATUS line.
Reported from a real phone. Both were mine, both shipped in the switch.
1. THE PHONE MENU WAS EMPTY. The drawer slid open correctly but had no items in
it. site.css's OLD mobile nav was still reaching the shell's sidebar: under
@media (max-width: 900px) it sets .nav-links to position:fixed; right:-100%,
which parked the menu off-canvas INSIDE the drawer. Measured at 390px and
860px: position fixed, right -280px.
The cause is a hole in my own check. At STOP 1 I diffed the two stylesheets'
declaration sets and found "exactly six" leaking declarations - but that diff
EXCLUDED @media blocks. Re-run media-aware, the real number is FORTY, and the
worst of them only apply below 900px, which is why desktop looked right and a
phone did not. A check that silently skips part of its subject is not a check;
this one reported a confident, wrong number.
Fixed at the source rather than per-property: every selector belonging to the
website's OLD chrome (nav, .nav-links*, .mobile-overlay, .hamburger, .logo*,
.lang-globe-li - 29 occurrences) is now scoped `body:not(.shell)`. Scoped, not
deleted, so a future non-shell page still works. .hamburger-box and
.hamburger-line are deliberately left unscoped: the shell's burger uses them.
Leaks after the fix: 40 -> 0.
2. THE FOOTER RENDERED AS A SECOND COLUMN at the top right on 16 pages. body is a
flex row on a shell page, and my insert only moved the footer inside <main>
when the page ENDED with </main>; pages with scripts after it fell through and
left the footer as a direct child of body, i.e. a flex sibling of the sidebar.
Moved inside <main> on all 16, each move asserted to have landed inside.
Both are now gated: gate 3b requires every shell page's footer to be INSIDE
<main class="content">, red-proofed by putting one outside and watching it convict.
site.css?v=23 / shell.css?v=23.
The decoys plant a known defect and assert the gate convicts, so when the markup
they anchor on changes they must be rebuilt rather than deleted - which is exactly
what test_gate_decoys.py refused to let pass ('the decoy's anchor is gone or
ambiguous').
en-nav-drift drifts a SIDEBAR entry now, and expects gate 3b's message
rather than gate 3's - the website's <nav> is gone
leftover-text-switch anchors on <details class=lang-globe>; the old nav's
<li class=lang-globe-li> wrapper no longer exists
lang-attr <html lang=en class=no-js>, the progressive-enhancement hook
ascii-hungarian-text anchors on the new English home's own link text; its old
'Contact form' link went when the page became a real twin
The other decoys in that section still anchor correctly and were left alone -
checked each one against the new files rather than rewriting the lot.
Records what shipped, the two operator decisions as answered, what the website and
the controller now share and how a change flows between them, and the five faults
that measurement caught before the switch rather than after.
head-before.json / head-after.json are kept as the evidence that the switch changed
no SEO-bearing tag: the only difference across 19 pages is the dash substitution
inside the two FAQ pages' JSON-LD.
Part D. Every public page now wears the controller's frame around the website's
own content. One commit, as asked: never half old, half new.
20 pages switched (12 Hungarian incl. the hidden prices page, 8 English)
website/preview/ deleted entirely - A, M1, M2 and M3 studies all gone
robots.txt: the Disallow: /preview/ line removed with them
site.css?v=22 / shell.css?v=22 on every page
PROVED before pushing, not after:
* HEAD TAGS: title, meta description, keywords, author, robots, canonical, every
hreflang, all seven Open Graph tags, all four Twitter tags and the JSON-LD
captured on all 19 pages before the switch and compared after. The ONLY
difference anywhere is the dash substitution inside the two FAQ pages'
FAQPage JSON-LD - verified by applying the same substitution to the "before"
copy and getting an exact match. Nothing else moved.
* VISIBLE TEXT: identical to the live pages word for word, legal texts identical
with no substitution permitted at all.
* No page is left noindex (the two download pages' noindex,follow is their own,
pre-existing and unchanged), no page mentions /preview/, every page has a
footer, no page overflows at 390px.
THE HEAD-TAG DIFF EARNED ITS PLACE. The home page was about to ship carrying
`noindex, nofollow` from the preview, and had lost meta keywords, meta author, all
seven Open Graph tags, all four Twitter tags and the LocalBusiness JSON-LD -
because its head was hand-built while every other page carried its live one over.
The diff caught it; nothing on the rendered page would have. The generator now
uses the live head for EVERY page including the home one.
szolgaltatasok-nonpublic.html was converted too. It is in no menu and was not in
the brief's list, but leaving one page on the old chrome would have left the site
half-switched - which is the one thing part D was not allowed to do.
scripts/site_gates.py: SHELL is now the real page set; MOCKUPS is empty; gate 3's
old nav/footer loop keeps working for any page added later WITHOUT the shell, and
skips the ones that have it.
Caught BEFORE the switch, not after. Stripping the website's chrome also stripped
<footer>, and the shell has none of its own. Every one of the 19 pages had lost:
the link to the Adatkezelesi tajekoztato and the Feltetelek, the operator's name,
info@felhom.eu, and the copyright line. The legal pages are in no menu, so they
would have become unreachable from navigation entirely.
The footer is now carried VERBATIM from each live page into the bottom of the
content column. Verified on all 19: footer present, legal link present, e-mail
present, and the footer sits INSIDE <main class=content> rather than becoming a
flex sibling of the sidebar.
Gate 3b now checks the footer as well as the sidebar across the shell set, so the
guarantee gate 3 used to give is not lost in the move. Red-proofed: removing one
page's footer convicts.
Operator's decision at STOP 2: mirror it first, then switch. It was the old design
reframed - 10 786px tall against the Hungarian home's 4 186px - because the
/preview/ round never produced an English twin.
Every string is either lifted VERBATIM from the live English home (the ten
capability cards, the four dashboard captions, the five steps, both sides of the
cloud comparison, the three closed-test cards, the apps count line) or is one of
twelve entries in a TRANSLATED table - faithful translations of Hungarian
sentences the /preview/ round introduced, which therefore have no English original.
Nothing is invented, and the English promises nothing the Hungarian does not.
The generator ASSERTS that each string it lifts is still present upstream, so if
the live English page changes shape the build fails rather than silently shipping
a page with holes in it.
Its <head> is still the live page's, so title, description, canonical, hreflang,
Open Graph and JSON-LD survive untouched. 35.8 KB -> 25.4 KB.
Gate 16 (no Hungarian on an English page) passes, which matters here because this
is the one page where I wrote English prose rather than moving it.
Records the three faults that measurement found and looking would not have:
168 words silently deleted by the first page-header transform, the contact pages'
header sliding under the sidebar (content column at x=642 instead of 240), and two
pages that already scrolled sideways on a phone BEFORE M3.
Also raises the one thing needing an operator decision: the English home page is
the old design in the new frame, because the /preview/ round never produced an
English twin. 4 186px tall in Hungarian, 10 786px in English.
On /alkalmazasok the Alkalmazasok group renders expanded, which gives the sidebar
a 16px scrollbar and narrows it from 239 to 224. At .95rem the Hungarian label
wrapped to two lines there (the English one still fitted, which is why it showed
on one page and not its twin). Sized to the narrow case, measured not guessed.
kapcsolat.html and en/contact.html keep their page header OUTSIDE <main>. Left
there, the converted header became a FLEX SIBLING of the sidebar: measured, it
took 401px and pushed .content from x=240 to x=642, so the header sat behind the
sidebar and the form rendered at two thirds width.
Anything before <main> now moves inside it. Checked structurally across all 19
pages rather than by looking at two: no page has stray top-level content, and
every .content starts at x=240 with the full 1185px column.
Found by measuring where elements actually sit, after a screenshot looked wrong -
the same check also confirmed the two elements at x=-9999 on those pages are the
form's intentional spam honeypot, not a layout fault.
Measured at 390px on the LIVE pages, so this is pre-existing and not something the
shell introduced:
/letoltes scrollWidth 609 - the 64-character SHA-256 inside <code>
/adatkezeles scrollWidth 478 - a long facebook.com legal URL used as link text
and the same on their English twins.
overflow-wrap: anywhere changes only where the line breaks; selecting and copying
still yields the whole unbroken string, which matters for a checksum. The legal
pages' words are untouched - this is CSS.
Small enough to fix in the session rather than file.
Staged under /preview/m3/. Nothing live. 19 shell pages in total with the home.
Each page keeps its own <head> byte-for-byte apart from two additions (shell.css
and the no-js/js hook), so title, meta description, canonical, hreflang, Open Graph
and JSON-LD all survive the move untouched. The body loses the website's
<nav>/<footer> and the old hamburger script, and gains the shell.
PROVED, not assumed - a word-level diff of every page's visible text, live vs
shell, with the nav and footer removed from both:
* 18 of 18 pages: visible text IDENTICAL after the one allowed substitution
* adatkezeles and feltetelek: identical with NO substitution permitted at all.
The legal texts are reviewed documents; the frame changed, the words did not.
That check earned itself immediately. The first version of the page-header
transform rebuilt the block from its <h1> and first <p> and SILENTLY DROPPED the
rest: the "Fontos tudnivalok" / "Good to know" disclaimer banner on both apps
pages and the 404's English line - 168 words across three pages. Nothing looked
wrong on the page. The diff found it.
Em dashes: 318 replaced with the Hungarian gondolatjel across 16 pages (gyik and
en/faq 104 each). The FAQ pages' JSON-LD is swept with the SAME substitution, so
gate 17's equality with the visible answers still holds - it does, on both.
scripts/site_gates.py:
* SHELL now holds all 19 pages.
* is_en(): a page is English if it is a TWINS value OR lives under an en/
directory. Without the second half the dash-language, <html lang> and
no-Hungarian gates all judged preview/m3/en/* as Hungarian - 30 false fails.
* norm_block() also drops `is-open` and `aria-expanded`, because the shell
renders the nav group containing the active page open, exactly as the
controller does, and that legitimately differs per page.
The last change LOOSENS a check, so it was red-proofed BOTH ways: a one-character
difference in one menu label still convicts, and a state-only difference (the
active marker moving to another item) correctly does not.
One generator now owns the shell. The home page's content comes from m3.py and its
shell from the same function every other page uses, so the two cannot drift - which
they already had, and gate 3b caught it.