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

129 lines
7.5 KiB
Markdown

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