audit: the mockup comparison, the shared-design answer, and two register moves
gates / gates (push) Failing after 11m45s

documentation/audits/mockups-2026-10-11/
  COMPARE.md        the three directions side by side, measured the same way:
                      A  current preview   12 req /  270 KB
                      M1 dashboard         68 req / 1012 KB  (front page 1 660 px)
                      M2 hybrid            16 req /  150 KB  (phone 3 057 px, the shortest)
                    plus three sentences each (good / risk / cost) and the risk I
                    think is real: a non-technical visitor may read M1 as somebody
                    else's account and look for a login.
  SHARED-DESIGN.md  REPORT ONLY, nothing built. M1 needed 260 CSS rules: 188 (72%)
                    copied from the controller UNCHANGED, 8 adapted, 64 new. It used
                    188 of the controller's 826 rules (23%). The 18 tokens are
                    already identical - verified by parsing both :root blocks, zero
                    differing values.
                    Recommends (a) a canonical felhom-ui.css plus a copy-drift gate,
                    over (b) serving the controller's stylesheet to the website -
                    and recommends NOT building it until a direction is chosen.
  shots/            desktop and phone captures of all three, from the deployed pages

The finding that outranks the percentage, and the reason for the recommendation: a
component-level copy silently drops the element-level rules those components depend
on. The extractor copied every .sidebar/.content/.mobile-topbar rule and missed
"@media(max-width:768px){ body { flex-direction: column } }". Everything LOOKED
copied; the phone layout was broken in four measurable ways. Invisible to a diff,
invisible to review. A second instance the same evening: the drawer is gated on a
'js' class set by an inline script that was not copied either.

Register 129 -> 129 (one closed, one opened):
  R-929 CLOSED - NOT A DEFECT. Measured from the catalog: 60 templates, minus
        plant-it (abandoned) and wger (hidden) = 58 offered, exactly what the prose
        says; the page's 59th card is FileBrowser carrying the single "Beepitett"
        badge. Set difference both ways is empty. The premise was wrong on BOTH
        halves. Rule kept: before filing a count disagreement, check whether the
        counts are counting the same set.
  R-932 OPEN - 8 catalog strings across 5 apps carry an em dash, and they render in
        the controller's app page on every real box. Found because the mockups are
        the first thing to show catalog copy on a website page. Lives in a repo this
        session did not have in scope, so it is a row, not a fix.
This commit is contained in:
2026-10-11 12:26:46 +02:00
parent 3129d99b04
commit 7413e0dfab
11 changed files with 221 additions and 1 deletions
@@ -0,0 +1,91 @@
# Three directions for the website, side by side
Built 2026-10-11. **Nothing here is live.** All three are hidden under `/preview/`, carry
`noindex`, are blocked by `robots.txt`, are in no sitemap and are linked from no live page.
| | address |
|---|---|
| **A — the current redesign** | <https://felhom.eu/preview/> |
| **M1 — the site is the dashboard** | <https://felhom.eu/preview/m1/> |
| **M2 — hybrid** | <https://felhom.eu/preview/m2/> |
Screenshots: `shots/`. Desktop at 1440 px, phone at 390 px, all from the deployed pages.
## Measured, same method for all three
| | A, current preview | M1, dashboard | M2, hybrid |
|---|---:|---:|---:|
| Requests, first load | 12 | **68** | 16 |
| Weight, first load | 270 KB | **1 012 KB** | **150 KB** |
| HTML document alone | 26 KB | **134 KB** | 13 KB |
| Front page height, 1440 px | 6 385 px | **1 660 px** | 1 853 px |
| Front page height, 390 px | 10 145 px | 6 563 px | **3 057 px** |
| Third-party hosts | 0 | 0 | 0 |
| Horizontal overflow at 390 px | none | none | none |
M1's catalogue page (`alkalmazasok.html`) is **26 410 px tall on a phone** — 58 cards in one column.
That is its worst measured number and it is not easily fixable while the page's job is "show the
whole catalogue".
## M1 — the site is the dashboard
**What it does well.** A visitor sees the actual product in the first second, with 58 real apps, real
logos, real Hungarian taglines and a real screenshot behind every tile. Nothing on the page is a
promise about the product; the page *is* the product. It also makes the apps page genuinely useful
rather than a wall of names: every tile opens what that app does for a family and how to start it.
**What can go wrong.** A non-technical visitor can read a full-screen dashboard as *somebody else's
account* and look for a login — the brief's own worry, and I think it is the real risk. The welcome
card is the only thing saying "this is a demonstration, look around", and one card is thin defence.
The second risk is weight: **1 MB and 68 requests** on a site that compresses nothing (R-931), four
times the current preview, because the front page is 58 logos. The third is SEO: the front page's
text is a welcome card and 58 app names; the sentences Google currently ranks would have to live
somewhere else.
**What it would cost to finish.** The largest of the three. Six pages exist as studies and all six
need real design passes; the English twin set does not exist at all and this is the layout where
twinning is most work (every app panel doubles). The catalogue page needs a different phone answer.
The shared-design mechanism in `SHARED-DESIGN.md` §3(a) stops being optional. Call it several
sessions, and it changes what the website *is*, so the SEO baseline needs deliberate re-planning
rather than preservation.
## M2 — hybrid
**What it does well.** It is the lightest of the three by a clear margin — **150 KB over 16
requests**, and the shortest phone page at 3 057 px — while still showing the real dashboard in the
first screen, inside a window frame that reads unmistakably as "a picture of an application" rather
than "you are logged in". It keeps the website's own voice and structure, so the SEO work already
done survives, and it needs no new twin pattern.
**What can go wrong.** The window is a smaller promise than M1's: four tiles, not the real thing, so
it shows the product without quite letting a visitor *use* it. It is also the least distinctive of
the three — a hero beside a product shot is a normal landing page, which is exactly the charge the
current home page was redesigned to escape. And the window risks reading as a static image unless
the "click a tile" invitation is obvious.
**What it would cost to finish.** The smallest. It is one page plus its English twin, built out of
components that now exist, and it can borrow the current preview's lower sections nearly as they are.
One session, maybe two with the twin.
## A — the current redesign, for reference
Already written up in `audits/redesign-2026-10-10/`. It is the safest of the three, keeps every SEO
string, and is the only one with its "Ki áll mögötte" question already settled. Its weakness is the
one that started this: it still reads as *a website about a product* rather than the product.
## What I would say if asked
**M2 is the better bet, and M1 is the better idea.** M2 costs least, risks least, and keeps
everything already decided. M1 is the only one of the three that answers the original complaint
completely — but it is a different website, with a different SEO story, four times the weight, and a
real chance a visitor mistakes it for a login page.
If the appeal of M1 is "show the real thing immediately", **M2 already delivers most of that for a
fifth of the cost**, and the honest way to find out whether M1's risk is real is to put M1 in front
of two or three people who are not Viktor and watch whether they look for a password field.
## The register
Nothing here is filed yet. If a direction is chosen, the work to build it becomes rows; if M1 is
rejected, `website/preview/m1/`, `website/preview/m2/`, `website/preview/mockup.css` and the
`MOCKUPS` set in `scripts/site_gates.py` are deleted together.
@@ -0,0 +1,128 @@
# Could the website and the controller share one design? — measured, then answered
**Report only. Nothing in this document was built.** Written 2026-10-11 while building the M1/M2
mockups, which is the only reason the numbers below are measurements rather than estimates.
Baselines: `felhom.eu` `0c86e698b0`, `felhom-controller` `dcd89364a4` (**read only**),
`app-catalog-felhom.eu` `8ea72aa`.
---
## 1. What M1 actually reused
`website/preview/mockup.css` is deliberately three labelled blocks so this could be counted:
| Block | Rules | Size | What it is |
|---|---:|---:|---|
| 1. BASE, **adapted** | 8 | 2.5 KB | The controller's reset, tokens and `@font-face`, with the font URLs repointed `/static/fonts/` → `/assets/fonts/` |
| 2. CONTROLLER, **verbatim** | **188** | 19.4 KB | Copied unchanged out of `style.css` |
| 3. MOCKUP-ONLY, **new** | 64 | 7.2 KB | What neither product has an answer for |
| **Total** | **260** | **29.1 KB** | |
So of the 260 rules M1 needs, **188 (72%) are an unchanged copy** and only **64 (25%) are new**.
The controller's stylesheet has **826 rules**; M1 needed 188 of them, or **23%**. The rest is
storage, deploy wizards, monitoring, restore flows — surfaces a marketing site has no use for.
**The 18 design tokens are already identical.** Verified this session by parsing both `:root`
blocks: 18 names in each, **zero differing values, none missing on either side**. The brief said so
and it is true.
What the 64 new rules are actually for: the welcome card, the `minta` badge, the disabled-menu-item
treatment, the `:target` app panel, and M2's browser-window frame. Every one of them exists because
a *website* needs something a *dashboard* does not — none is a restyling of a controller component.
**Honest caveat on the 188.** Only **129 of the 186** class rules shipped (69%) match a class the
mockup markup actually uses; 57 came along because my extractor matched on selector prefix. A
hand-curated copy would be smaller. The 72% reuse figure is about *how much fits*, not about how
tight the file is.
## 2. The finding that matters more than the percentage
**A component-level copy silently drops the element-level rules those components depend on.**
The extractor keys on class names. It copied every `.sidebar`, `.content`, `.mobile-topbar` and
`.nav-burger` rule, including the whole `@media(max-width: 768px)` block — and silently missed this:
```css
@media (max-width: 768px) { body { flex-direction: column; } }
```
One bare element rule. Without it, measured at 390 px: the sticky mobile topbar stayed a **row**
flex item, pushed `.content` 135 px to the right, collapsed the launcher grid to **one column**, made
the front page **15 597 px** tall on a phone, and gave two pages a `scrollWidth` of 467 in a 375 px
viewport. Everything *looked* copied. Nothing said it was incomplete.
A second instance in the same build: the controller's off-canvas drawer is gated on a `js` class that
`layout.html` sets in an inline head script. The CSS was all there; the hook was not, so the drawer
never engaged.
**Both failures are invisible to a diff and invisible to review.** They are the argument for a
mechanism over a convention, and they are why I would not recommend "copy the components when you
need them" as the long-term answer.
## 3. The two mechanisms, with cost and risk
### (a) One `felhom-ui.css`, owned in `documentation/design/`, copied into both repos, with a gate
The pattern this project already uses for things that must stay identical in two places.
- **Cost.** Write the shared file once by pulling the common components out of both stylesheets
(realistically a day, because the two have drifted in ways nobody has catalogued). Then a gate in
each repo comparing its copy to the canonical one — the same shape as the existing copy-drift
gates, so roughly an afternoon including its decoy.
- **Risk.** Low and *visible*. A drift turns a gate red on the next push, naming the file. The
failure mode is an annoying red gate, not a silently broken phone layout.
- **What it does not solve.** Someone still has to decide what belongs in the shared file. Put too
little in and §2 happens again; put too much in and a controller-only change starts failing the
website's gate for no reason.
### (b) Serve the controller's `style.css` to the website as the source
- **Cost.** Near zero to start: one build step copying the file in.
- **Risk.** High, and the risks are the interesting ones:
- **It inverts ownership.** The marketing site's appearance becomes a side effect of a controller
release. A controller change made for a deploy wizard can alter the home page of the public site
without anyone intending it, between two releases nobody reviewed for that.
- **Weight.** The controller's stylesheet is **826 rules**; the website would carry all of them to
use 23%. On a site that currently serves **nothing compressed at all** (R-931), that is a real
cost on every page, not a theoretical one.
- **Different release cadences.** The controller ships several times a day; the website is
git-synced from `main` in a minute. They would be coupled at the speed of the faster one.
### Which I would pick
**(a), and not yet.**
(a) is right in shape: a canonical file plus a gate is the only one of the two where a divergence
*announces itself*, and §2 is a measured demonstration that divergence does not announce itself
otherwise.
But I would **not build it until the operator has chosen a direction**, because the question (a)
answers only exists if the website keeps wearing the dashboard's clothes. If M1 is rejected, the
website needs about a dozen shared atoms (button, tag, card, alert) and a shared stylesheet would be
a mechanism built for a problem that went away. If M1 is chosen, (a) stops being an improvement and
becomes **necessary** — at that point the website *is* a second renderer of the controller's design,
and keeping two hand-synced copies of 188 rules is exactly the arrangement that produced §2.
**A third option worth naming, which I would pick over (b):** keep the tokens shared (they already
are, and they are the half that never drifts) and let the components diverge on purpose. That is
today's arrangement, and it has cost nothing so far — because until this week nothing tried to show
the same component in both places.
## 4. What breaks if the controller's look changes
Under (a) or (b), the website follows the controller automatically. That cuts both ways:
- **Good.** A customer who sees the website and then their own box sees one product. Today they see
two designs that merely share colours. That is the whole case for M1.
- **Bad, and specific.** The controller's UI is driven by what a *household operating a box* needs,
and it changes for operational reasons several times a day. A denser `.launch-cell` for boxes with
40 apps would silently re-flow the public front page. A new state colour added for a restore
wizard would appear in the website's palette. A controller release that narrows the sidebar
changes the public site's navigation width.
- **The asymmetry to be explicit about:** the controller has a known audience of a handful of test
households and can be changed freely. The website is the public face and carries SEO and
first-impression weight. Coupling them means the *low-stakes* surface sets the pace for the
*high-stakes* one. Any shared-design mechanism needs a rule for that, and the simplest is that the
shared file holds only components both surfaces actually render — which is the discipline (a)
enforces and (b) does not.
Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

+1
View File
@@ -53,6 +53,7 @@ The full text of the row below: `git show 625d38645c:documentation/backlog/OPEN-
| Row | What | Closed | Evidence |
|---|---|---|---|
| **R-927** | **Monica was checked against the new-app checklist and STOPPED at 0.2 — upstream has not released anything in 17 months.** (P4) | CLOSED 2026-10-10 — **the operator ruled: keep it out.** The measurements that decided it: no release of any kind since 2025-04-21 and that one a prerelease; no stable release since v4.1.2 on 2024-05-04; the `4.x` branch its README calls "the stable and current version" untouched since 2024-05-04; a 13-month hole in the commit history ended by two commits that add a login-page notice saying the hosted instance and all its data will be deleted at the end of December 2026 "ahead of the new Monica version"; 790 issues open. Nothing in the catalog covers a family address book either way, and Radicale already carries contacts to a phone over CardDAV. **Reasoning kept:** an app is not added because a gap exists — it is added because the app can be kept. Look again when the rewrite ships. | `audits/new-apps-2026-10-10/FIT.md`; the operator's decision 2026-10-10 |
| **R-929** | **The app count disagrees with itself in three places: website prose 58, apps page 59 cards, catalog 60 templates.** (P4) | CLOSED 2026-10-11 — **NOT A DEFECT. The three numbers count three different things, and all three are correct.** MEASURED by reading every `.felhom.yml` in `app-catalog-felhom.eu` `8ea72aa`: **60** template directories, of which `plant-it` is `lifecycle: abandoned` and `wger` is `lifecycle: hidden`, so the controller offers **58** for a new install — exactly what the site's prose says. The page's **59th** card is **FileBrowser**, which carries the page's single „Beépített" badge because it is a built-in, not a catalog app. Set difference both ways: nothing offered is missing from the page, and nothing on the page is unaccounted for. **The premise was wrong on both halves** — no card is uncounted and no template is missing. **Rule kept:** before filing a count disagreement, check whether the counts are counting the same set. | `audits/mockups-2026-10-11/` (the catalog read that measured it); `scripts/catalog.py` output in that session's transcript |
| **R-915** | **The Meta app `felhom.eu` was in development mode, so anything it posted was visible only to people with a role on the app.** (P4) | CLOSED 2026-10-09 — the app is **Published**. What it had been waiting for was R-813's Terms of Service URL, which did not exist until the closed-test legal set went live that morning. Filled in Basic settings: **Privacy policy URL** `https://felhom.eu/adatkezeles`, **Terms of Service URL** `https://felhom.eu/feltetelek`, **User data deletion → Data deletion instructions URL** `https://felhom.eu/adatkezeles` (the notice says how: write to `info@felhom.eu`), **Category** „Vállalkozások és oldalak". Meta then reported **„All required app settings are complete"** — the app **icon turned out NOT to be required**, which is worth recording because the original row listed it among the blockers. **Read back from a full page reload, not from the success toast** (Meta's toasts have lied on this Page before): the sidebar badge reads `Published`, the string `Unpublished` is gone, and the page now offers `Unpublish`. No permission was added and no use case changed — `business_management` et al. stay „Ready for testing", which is all a system user needs for the business's own Page. | `audits/facebook-page-details-2026-10-09/E1-meta-app-published.jpg` |
**Reasoning kept.** Going Live is about *who can see what the app posts*, not about new powers: the system user already had the Page scopes, and nothing was granted here. The app icon, display name and contact e-mail were listed as Live blockers in the original row; only the two URLs and the category actually were.
+1 -1
View File
@@ -135,7 +135,7 @@ stopping line that lies.
| **R-770** | Apps & catalog | P4 | **[P3-LOW] Invidious — fit check only; the recommendation is not to build it.** READ 2026-10-01: playback needs `invidious-companion` (rolling `latest`, no version tags); PostgreSQL 14 (EOL 2026-11); `registration_enabled: true` by default; upstream: a bot check means „your IP is blocked from YouTube”, a 429 can last 24 h, triggered by „someone on your network” — on our boxes that IP is the household's. One bad period in 2026 (March, ~2 weeks). No report found of a family's other devices being bot-checked (inference). **Needs:** the operator's go / no-go. `audits/new-apps-2026-10-01/FIT.md` | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator** **Re-ranked 2026-10-03: P3→P4: a new-app idea waiting on a decision.** | — | — | operator |
| **R-771** | Apps & catalog | P4 | **[P3-LOW] moonlight-web — fit check only; not buildable through an HTTP-only tunnel at usable latency.** READ 2026-10-01: two unrelated projects (MrCreativ3001/moonlight-web-stream, the original; linckosz/moonlight-web); both need Sunshine/Apollo/Wolf on a gaming PC on the LAN and WebRTC over UDP (40000-40100/udp; linckosz recommends host networking and sends telemetry by default); both have a WebSocket fallback (high latency, all video through the tunnel); a logged-in user controls the PC's desktop. **Needs:** the operator's go / no-go (LAN-only use would need a different publishing model). `audits/new-apps-2026-10-01/FIT.md` | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator** **Re-ranked 2026-10-03: P3→P4: a new-app idea waiting on a decision.** | — | — | operator |
| **R-905** | Apps & catalog | P4 | **wger's collected style files (283 MB) go into every wger backup, though the app rebuilds them at every start.** Since R-762's fix (`DJANGO_DEBUG=False` + `collectstatic`, catalog `cf1ed43`) the static files sit in a named volume that the backup legs copy like data (measured 283 MB, `audits/design-build-2026-10-06/`); `collectstatic` regenerates them, so they are rebuildable bytes in every Tier-1 unit, Tier-2 copy and off-site snapshot. wger is `lifecycle: hidden` and no box runs it (hub read 2026-10-08). Options to weigh: a non-backed-up volume class for regenerable data, or an anonymous volume. Found by the R-762 helpers 2026-10-08. | **OPEN — needs a design before wger is shown** | — | Design the „regenerable volume" exclusion (catalog + controller backup legs) | CC |
| **R-929** | Apps & catalog | P4 | **The app count disagrees with itself in three places: the website's prose says 58, the apps page renders 59 cards, the catalog holds 60 template directories.** FOUND 2026-10-10 while writing the Facebook campaign, which had to quote a number. `site_gates.py` gate 15 prints „apps pages agree: 59 cards, 59 logos” (it compares the two language sets to EACH OTHER, so it cannot catch a count that is wrong on BOTH); the prose says 58 in 15 places per language set (`website/CHANGELOG.md` 2026-10-10 moved it 56→58 for Grocy + LubeLogger); `find templates -name .felhom.yml` in `app-catalog-felhom.eu` returns 60. So at least one card is not counted by the prose, and at least one template is not on the page — and the gate that looks at app counts is blind to both, because nothing compares the page to the CATALOG. **The campaign post says 58**, matching every prose claim on the site today; if 58 is wrong, a published post is wrong with it. **Fix shape:** reconcile the three (which templates are deliberately not shown — `lifecycle: hidden`, not installable — and which are missing from the page), then make the gate assert the page's card count against the catalog's shown-template count rather than against the other language. Needs the `felhom-app-catalog` skill's exclusion rules. | **OPEN — filed 2026-10-10 by the tester-recruitment session, which did NOT fix it (outside its brief)** | — | Reconcile page cards vs catalog templates vs the prose number; then point gate 15 at the catalog | CC |
| **R-932** | Apps & catalog | P4 | **Eight catalog strings carry an em dash, and they are shown to customers in the controller's own app page.** MEASURED 2026-10-11 while reading every `.felhom.yml` for the website mockups: 8 strings across 5 apps (`recipe-importer` 4, `romm` 1, `calibre-web` 1, `paperless-ngx` 1, `vaultwarden` 1) contain `—` in a `tagline`, `use_case` or `first_step`. These render in the controller's app-info page on every real box, and they will render on any website page that shows catalog copy — the mockups are the first thing to surface them. The house rule (`skills/felhom-web-design` §4) bans the em dash in customer-facing Hungarian and uses the spaced en dash as the gondolatjel; most of these want a colon or a full stop instead, so it is a per-sentence edit, not a find-and-replace. Small, but it lives in a repo this session did not have in scope. | **OPEN** — filed 2026-10-11 by the mockup session | — | edit the 8 strings in `app-catalog-felhom.eu`, one sentence at a time | CC |
## App updates — 4 rows (P3 4)