7413e0dfab
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.
129 lines
7.5 KiB
Markdown
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.
|