Files
felhom.eu/documentation/audits/mockups-2026-10-11/SHARED-DESIGN.md
T
admin 7413e0dfab
gates / gates (push) Failing after 11m45s
audit: the mockup comparison, the shared-design answer, and two register moves
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.
2026-10-11 12:26:46 +02:00

7.5 KiB

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:

@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.