From c9d726dd64ede6ff4147486c9daf90968c72e6e8 Mon Sep 17 00:00:00 2001 From: Mikhail Yenuchenko Date: Tue, 11 Aug 2026 00:06:12 +0300 Subject: [PATCH] docs: agree the visual-style plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mark was approved on its own sheet and the panel was left on the default blue-on-cool-grey it shipped with, so the two read as different designs. Write down what the panel takes from the proof — brick as the accent, warm paper, IBM Plex self-hosted — and what it deliberately does not: the proof's mock puts navigation in a dark top bar, which has nowhere to keep the per-page section index the left column carries. Records the decisions that cost something to reverse: three font files (76 KB) served from the panel's own origin so the CSP stays default-src 'self', brick split into a fill and a text value because #7A3B2E is unreadable as a link on the dark scheme, and the test that outranks all of it — the accent must not be mistakable for a status badge. Co-Authored-By: Claude Opus 5 --- docs/plans/visual-style.md | 179 +++++++++++++++++++++++++++++++++++++ docs/roadmap.md | 16 ++-- 2 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 docs/plans/visual-style.md diff --git a/docs/plans/visual-style.md b/docs/plans/visual-style.md new file mode 100644 index 0000000..6801974 --- /dev/null +++ b/docs/plans/visual-style.md @@ -0,0 +1,179 @@ +# Plan: visual-style + +**Status:** agreed +**Version:** no bearing on semver — presentation only, no schema and no route +changes. +**Order:** independent of the feature roadmap; may be taken up between feature +items. + +--- + +## Goal + +Bring the control panel's surface in line with the mark that was approved in +[selfpost-proof.html](../assets/selfpost-proof.html): its palette, its +typography, and the plainness of its components. Today the panel is a default +blue-on-cool-grey admin theme standing next to a warm brick stamp, so the mark +reads as pasted onto someone else's page. + +## Scope + +**In:** +- `internal/web/view/static/panel.css` — colour tokens, typography, spacing, + every component rule. +- `internal/web/view/static/` — three self-hosted font files. +- Templates, only where a class has to be added or a wrapper introduced for a + rule to have something to attach to. +- `NOTICE` — the OFL attribution the font files oblige. + +**Out:** +- Any change to what a page does, which pages exist, or what an operator has to + click. No new features, no copy rewriting. +- The navigation's position and the two-column shell. The proof's panel mock + shows a horizontal bar on a dark header; the panel's left column also carries + the per-page section index (`.sections` plus the scroll-spy in `panel.js`), + which that layout has nowhere to put. Keeping the column is a deliberate + divergence from the mock, not an oversight. +- The mark files themselves (`logo.svg`, `logo-compact.svg`, `favicon.*`) — + already drawn, already converted to outlines. + +## Constraint that shapes everything + +The panel's Content-Security-Policy is a plain `default-src 'self'` with no +inline-style exemption ([security.md](../security.md)). Every rule lives in +`panel.css`; a `style="..."` attribute in a template is blocked and silently +does nothing. Self-hosted fonts are served from the panel's own origin and are +therefore already covered — no CSP change is needed, and none may be made. + +## Typography + +IBM Plex, self-hosted. The mark is Plex converted to outlines, so the panel +setting its own name in Segoe UI or Cantarell is the seam this whole item +exists to close. + +| File | Covers | Size | +|---|---|---| +| `static/ibm-plex-sans.woff2` | variable, weights 100–700, latin | 45.7 KB | +| `static/ibm-plex-mono-400.woff2` | mono regular, latin | 14.8 KB | +| `static/ibm-plex-mono-600.woff2` | mono semibold, latin | 15.7 KB | + +76 KB in total, in a 20 MB binary. The variable file replaces what would +otherwise be five static weights and lets the scale below use 300 and 500 +without paying per weight. + +Monospace is the one the operator actually reads: DKIM records, `mail.log` +lines, application logins, socket paths, generated passwords. `ui-monospace` +resolves to Consolas, SF Mono or DejaVu Sans Mono depending on the operator's +machine, and those differ in advance width — the six-column send log wraps +differently for each. A shipped mono makes those tables one layout. + +| Role | Family | Size | Weight | +|---|---|---|---| +| Body | sans | 15px / 1.5 | 400 | +| `h1` | sans | 1.55rem, tracking −0.01em | 300 | +| `h2` | sans | 1.05rem | 600 | +| `label` | sans | 0.9rem | 600 | +| Nav entry / active | sans | 0.95rem | 400 / 600 | +| `th` | **mono**, uppercase, tracking 0.08em | 0.75rem | 500 | +| `.st` status badge | **mono** | 0.78rem | 500 | +| `.code`, `.mono`, `.metric` | **mono** | 0.85rem | 400 | + +`font-display: swap`, so a cold load shows the system stack for a frame rather +than blank text. + +## Colour tokens + +Names stay as they are wherever they already exist: the dark scheme reassigns +the same custom properties, which is why no rule in the file needs +`!important`. Warm neutrals replace the cool greys; brick becomes the accent. + +| Token | Light | Dark | +|---|---|---| +| `--bg` | `#F4F2ED` | `#16181B` | +| `--fg` | `#12161C` | `#E9E6E0` | +| `--muted` | `#6B7684` | `#9AA1A9` | +| `--card-bg` | `#FFFFFF` | `#1D2024` | +| `--border` | `#DEDCD7` | `#2C2F34` | +| `--control-border` | `#CBC8C1` | `#3A3E44` | +| `--input-bg` | `#FFFFFF` | `#14161A` | +| `--code-bg` | `#EFEDE9` | `#14161A` | +| `--surface-bg` | `#EAE7E0` | `#23262B` | +| `--accent-fill` / `--on-accent` | `#7A3B2E` / `#FFFFFF` | `#8E4535` / `#FFFFFF` | +| `--accent-text` | `#7A3B2E` | `#CE7B66` | +| `--nav-active-bg` | `#EDE4DE` | `#2A1F1B` | + +Brick splits into a fill and a text value because `#7A3B2E` on `#16181B` is +about 2:1 — unreadable as a dark-scheme link. The fill lightens just enough to +keep white on it above 4.5:1; the text value lightens further. + +Status families (`--st-ok-*`, `--st-warn-*`, `--st-error-*`, `--st-unknown-*`), +the flash, the credential card and `--danger-*` keep their hues and are only +warmed to sit on paper. The one thing to watch is brick against `st-error` red: +the proof rejected several candidate colours precisely so that the mark would +not read as a status, and the same test now applies to every brick button +standing in a row of `error` badges. + +## Components + +Everything already in `panel.css`, in the order it appears there: card, form +controls, buttons (filled, outlined, danger), flash, table, status badge, +`.code`, nav (brand, links, sections, session), application list and its +disclosure panels, credential card, status page meters and facts, delivery +timeline, log tables, split layout, encrypt fields, footer. + +Two component-level changes rather than pure repaints, both forced by the +accent: + +- Row actions (`td.actions a.danger`, `Delete`) become outlined instead of + filled. A filled red button in a table row next to a filled brick button + reads as one block of colour. +- Nav entries carry the active state as brick text on a warm tint rather than + the current blue tint. + +## Order of work + +1. Fonts into `static/`, `@font-face` and the type scale in `panel.css`, + `NOTICE` attribution. Nothing else changes shape. +2. Token block: light and dark, both schemes in one pass. +3. Chrome: `layout.html`'s nav, footer, shell. +4. Signed-out pages: `login`, `setup` — the mark and one card, where the seam + is worst. +5. `dashboard`, `domain_detail`, `domain_delete`. +6. `deliveries`, `deliveries_rows`, `delivery`. +7. `status` + `status_body`, `mail_queue*`, `system_log*`. +8. `backup`, `account`, `users`, `user_form`, `encrypt_fields`. +9. `CHANGELOG.md` under `[Unreleased]`. + +## Verification + +- Every page rendered locally and screenshotted in both schemes before and + after (`panel.exe` on Windows, headless Edge), including a 375px-wide pass — + the nav column and the wide tables are where a repaint breaks layout. +- Contrast: body text and every status badge at 4.5:1 or better against its own + background, UI borders at 3:1. Brick on white is 7.3:1 by the proof's own + measurement; the dark-scheme values above are the ones to re-check. +- `go build ./... && go vet ./... && go test ./...`; the template guards in + `internal/web/view/templates_test.go` must stay green, and the static-asset + ETag test grows to cover the three font files. +- No `style=` attribute anywhere in `templates/` — the CSP would drop it. + +## Done when + +- The panel and the mark read as one design in both schemes, at the reading + measure and on the wide data pages. +- Nothing an operator does changed: same pages, same controls, same copy. +- Fonts are served from the panel's own origin under the unchanged CSP, and the + image works with no network access. +- `NOTICE` credits IBM Plex (OFL-1.1); build, vet and tests are green. + +## Risks + +- **Visual regression across 21 templates.** The panel has pages that are only + reachable mid-workflow (the credential card, the delete confirmation, the + encrypt fields). Mitigation: the page order above is a checklist, and each + step is screenshotted rather than assumed. +- **Brick against the error red.** If the two fight in a real row of the send + log, the accent gets pulled back to the mark and the buttons stay neutral — + the mark's colour is fixed, the panel's accent is the negotiable one. +- **Thin weights on dark.** `h1` at 300 is the one place a variable font makes + it easy to go too light; check it on the dark scheme before keeping it. diff --git a/docs/roadmap.md b/docs/roadmap.md index ed9ada0..9cdcb6c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -29,7 +29,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md). | domain-admin | Domain administrator role | **agreed** | [plans/domain-admin.md](plans/domain-admin.md) | | inbound-relay | Inbound relay (backup-MX / forwarding) | **agreed** | [plans/inbound-relay.md](plans/inbound-relay.md) | | contributing | `CONTRIBUTING.md` | candidate | — | -| visual-style | Обновление визуального стиля | candidate | — | +| visual-style | Panel visual style | **agreed** | [plans/visual-style.md](plans/visual-style.md) | | dmarc-reports | DMARC aggregate report ingestion and panel UI | candidate | [plans/dmarc-reports.md](plans/dmarc-reports.md) | **Recommended order** (not binding): **domain-admin → @@ -113,13 +113,15 @@ features. Styling must stay compatible with the panel CSP — rules live in `panel.css`, not inline (see [security.md](security.md) and the stylesheet header). -**Done when:** the panel reflects an agreed visual direction (starting -reference: [assets/selfpost-proof.html](assets/selfpost-proof.html)); light and -dark schemes remain supported; readability and contrast are preserved. +**Done when:** see [plans/visual-style.md](plans/visual-style.md). The agreed +direction is the mark's own — brick, warm paper, IBM Plex — taken from +[assets/selfpost-proof.html](assets/selfpost-proof.html); light and dark schemes +remain supported; readability and contrast are preserved. -**Dependencies / risks:** CSP constraints on how styles are applied; visual -regression across pages; low priority relative to functional work — take up -after explicit agreement, independently of the feature roadmap order. +**Dependencies / risks:** CSP constraints on how styles are applied (rules in +`panel.css`, never inline); visual regression across pages; the accent colour +must not read as a status badge. +**Order:** independent of the feature items; may be taken up between them. **Version:** no bearing on semver. ---