docs: record panel-redesign givens
test / test (push) Canceled after 0s

Capture the layout and component decisions for a future panel restyle so
mockups start from a frozen vocabulary instead of per-page widths.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
mix
2026-09-08 22:52:04 +03:00
co-authored by Cursor
parent 10f5b4e8e1
commit af9867bc2f
3 changed files with 227 additions and 0 deletions
+4
View File
@@ -23,6 +23,10 @@ document is not part of the release.
### Added
- Roadmap item and givens for a panel visual redesign
([docs/plans/panel-redesign.md](docs/plans/panel-redesign.md)) — layout
grammar (`measure` / `fill` plus Web Awesome layout CSS), no JS component
library, new mockups rather than `docs/assets/panel-ui/`. Status: candidate.
- Roadmap item and plan for `/preflight` installation check page
([docs/plans/preflight.md](docs/plans/preflight.md)) — instance-level
infrastructure verification (rDNS, TLS, ports, HELO banner, proxy headers,
+197
View File
@@ -0,0 +1,197 @@
# Plan: panel-redesign
**Status:** candidate
**Date:** 2026-09-08
**Version:** `1.x` MINOR once implementation is agreed; this file is givens, not a
build plan.
---
## What this file is
Source data for a visual redesign of the control panel. Mockups have not been
drawn yet. Implementation in `internal/web` waits on those mockups and on
roadmap status **agreed**.
HTML click-throughs in [docs/assets/panel-ui/](../assets/panel-ui/index.html)
are **not** the brief. They were an earlier layout experiment; new screens are
drawn from this file's vocabulary, not copied from that folder.
## Goal
Stop hand-tuning every card on every page to hide meaningless empty space.
Operators should get a panel whose width rules are the same on Status, a
delete-confirm, and a six-column log: a form stays at a reading measure, ops
content uses the remaining column, and a card fills its cell.
This is a visual and CSS-grammar change. Routes, POST behaviour, and htmx
fragments stay as they are unless a later mockup proves a control must move.
## Pain (why)
[panel.css](../../internal/web/view/static/panel.css) already juggles several
widths (`64rem` column, `48rem` measure, `main.wide`, `.card.narrow`,
`page-login`) and two names for one pairing (`.split` everywhere, `.pair` on
DMARC). Each new screen picks a width by eye. On a wide monitor that leaves
voids; stretching a single input to the window is unreadable. The fix is a
layout grammar, not another button library.
## Delivery constraints (fixed unless product says otherwise)
The panel is not an SPA. Changing any of these needs an explicit decision, not
a mockup flourish:
| Constraint | Where |
|---|---|
| Go `html/template`, server-rendered pages | [internal/web/view/](../../internal/web/view/) |
| Vendored htmx 2.x for polling fragments | [layout.html](../../internal/web/view/templates/layout.html), [development.md](../development.md) § Vendored front-end |
| Assets embedded (`//go:embed`), no CDN at runtime | [view.go](../../internal/web/view/view.go) |
| CSP `default-src 'self'` — no inline script or `style=""` | [security.go](../../internal/web/security.go), `TestNoTemplateUsesInlineScriptOrStyle` |
| State changes are native HTML POST | templates under `internal/web/view/templates/` |
| AGPL-3.0; new front-end must be MIT-or-better and listed in NOTICE | [NOTICE](../../NOTICE), [development.md](../development.md) |
JavaScript is not a product requirement. [security.md](../security.md) records
progressive enhancement so **authorization does not depend on JS** (origin
check, session, RBAC). `data-confirm` is deliberately not a security boundary.
The redesign may use JS for chrome (copy, `<dialog>`). It must not make a
script the only way to submit a form.
Brand carriers today: stamp, brick, IBM Plex — [product.md](../product.md) and
[docs/assets/selfpost-proof.html](../assets/selfpost-proof.html). New mockups
may restyle; they must not silently drop the mark or invent a second product
look.
## Screens in scope (information architecture)
Same pages as the live panel. Visibility flags (inbound, DMARC, global vs
domain-admin) stay as in [web.go](../../internal/web/web.go).
| Kind | Pages |
|---|---|
| Signed-out | login, setup |
| Global ops | Status (plus polled fragment), mail queue, system log, backup, users |
| Shared | domains, domain detail, deliveries, delivery, help, settings |
| Optional | inbound (+ domain, delete), DMARC hub / domain / report |
| Confirms / forms | domain delete, inbound delete, user create/edit/delete |
## Layout layer (decided)
**Hybrid.** Page-level regions are ours. In-page composition is vendored Web
Awesome **layout CSS only** (MIT Core), not the component library and not the
full `utilities.css` barrel.
Every Layout ([every-layout.dev](https://every-layout.dev/)) is the *school*
(Stack, Cluster, Sidebar, Grid). It is a paid book; redistribution is
forbidden. Do not vendor it, do not buy it for the image, do not put their
custom elements in the tree. Web Awesome `layout.css` is the MIT packaging of
those recipes (`wa-stack` ≈ Stack, `wa-cluster` ≈ Cluster, `wa-flank` ≈
Sidebar, `wa-grid` ≈ Grid/Switcher).
### Regions (our CSS)
Shell: navigation column plus content that takes the rest (today's `.shell`,
without a centred `64rem` well that leaves side voids).
Inside the content column, **two modes only**. A page must not invent a third
width:
- **`measure`** — one readable form (login, setup, delete confirm, user form).
Cap around `42rem`, **left-aligned**. Fields fill the measure, not the
window. Empty space to the right of a form on a wide monitor is acceptable.
- **`fill`** — ops: Status, tables, logs, queue, DNS, paired cards. One column
cap for all fill pages (pick a single value in mockups, e.g. ~`90rem`; do
not keep both `48rem` and `64rem`). A card fills its grid cell.
There is no `main.wide` or `.card.narrow` as a width exception. A narrow card
lives in `measure`. A wide table lives in `fill`.
### In-page (vendored WA CSS)
Embed under `/static`, same as htmx — never a runtime CDN.
| Take | Do not take |
|---|---|
| `utilities/layout.css` | barrel `utilities.css` |
| `utilities/gap.css` | `themes/default.css`, `native.css` |
| `align-items.css`, `justify-content.css`, `flex-wrap.css` if a mockup needs them | variants, prose, FOUCE, JS, icons, Pro |
| A short `:root` stub for `--wa-space-*` only | their `layers.css` (it styles `wa-page`) |
Declare `@layer wa-utilities` locally. One spacing scale for the whole layer
(map to today's `1.2rem` gaps if that looks right; do not keep a second scale).
`.card` with slots (head / body / actions) stays ours so a `wa-grid` cell can
stretch. Do not use `wa-card`.
### Class vocabulary
**Paired cards are `wa-grid`, not `wa-split`.** In Web Awesome, `wa-split` is
space-between (title ‖ action). `wa-grid` is `auto-fit` + `--min-column-size`,
which is today's `.split { minmax(22rem, 1fr) }`. Freeze `--min-column-size`
once (same order as `22rem`); do not retune it per page.
| Class | Use |
|---|---|
| `measure` / `fill` | page region only, never inside a card |
| `wa-stack` | form or card body; children stretch on the block axis |
| `wa-cluster` | button rows, tags, Copy beside a value |
| `wa-grid` | two or more equal cards or field columns |
| `wa-split` | card header: title ‖ help/action; label ‖ value |
| `wa-flank` | rare: fixed + fluid (icon beside a field) |
| `wa-gap-*` | change a gap; not a substitute for a region |
`wa-frame` is for media boxes; the panel has none — omit from mockups.
One child in `wa-grid` occupies the row (`auto-fit`, not `auto-fill`): a
domain-admin Settings card must not leave an empty second column.
**Forbidden in mockups and later in templates:** a one-off `max-width` “for
this page”; a second name for the same pairing (today `.pair` vs `.split`);
`margin-inline: auto` on a card inside a grid (that already collapsed `.split`
tracks).
**Ready when:** Status (`fill` + `wa-grid` pairs) and login (`measure` +
`wa-stack`) can be drawn with no extra width rule. If a third screen wants a
new `max-width`, the vocabulary is unfinished.
## Components (decided)
**No JavaScript component library.** Pico, Bootstrap JS, DaisyUI, FAST,
Shoelace, and Web Awesome custom elements (`wa-button`, `wa-dialog`,
`wa-drawer`, `wa-card`) are out. They do not fix empty cards; they add Lit or
another runtime, Shadow DOM, CSP risk, and friction with htmx swaps.
What the live panel actually uses — and what mockups should assume:
| Need | Source |
|---|---|
| Button, input, select, textarea, table | native HTML, our CSS (brick / Plex) |
| Card | our `.card` + slots |
| Flash, status tint | our CSS |
| Help / application panels | CSS disclosure (checkbox today); or `<details>` / Popover if a mockup prefers |
| Destructive confirm | `data-confirm` / `window.confirm`, plus existing confirm pages for high blast radius |
| Overlay, if a mockup truly needs one | HTML [`<dialog>`](https://developer.mozilla.org/docs/Web/HTML/Element/dialog), not `wa-dialog` |
| Copy-to-clipboard | existing `panel.js` |
There is no combobox, date picker, tab set, tree, or chart in the panel. If a
future mockup invents a widget native HTML cannot express, choose a library
then, for that widget only.
## Out of scope for this item
- New panel features, routes, or copy (those are other roadmap rows).
- Weakening CSP to accommodate a widget library.
- Web Awesome Pro, Font Awesome Kit, a JS bundler for Lit.
- Replacing htmx with a front-end framework.
- Implementing `docs/assets/panel-ui/` as-is.
## Next (not this file's job)
1. Draw new mockups using only the vocabulary above.
2. After mockups are accepted and this item is **agreed**: vendor the WA CSS
files, NOTICE / development.md rows, then restyle templates without changing
behaviour.
## Implementation checklist
None until mockups exist and the roadmap status is **agreed**. Do not start
vendoring or rewriting `internal/web` from this file alone.
+26
View File
@@ -36,6 +36,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md).
| template-data-typing | Typed structs for template data instead of `map[string]any` | candidate | — | — |
| structured-logging | `log/slog` with levels and fields | candidate | — | — |
| review-2026-08-followups | Remaining findings of the 2026-08-19 code review | candidate | — | — |
| panel-redesign | Panel visual redesign (layout grammar) | candidate | — | [plans/panel-redesign.md](plans/panel-redesign.md) |
| schema-squash | Squash SQLite migrations into a 2.x baseline | **2.x** | — | — |
**Recommended order** (not binding): the next feature is
@@ -294,6 +295,31 @@ and tagging it would only delete cross-platform test coverage.
---
## panel-redesign
**Goal:** a panel layout grammar so screens stop inventing per-page widths to
hide empty space. Forms stay on a reading measure; ops pages use the remaining
column; cards fill their cells. Givens (hybrid WA layout CSS + own `measure` /
`fill`, no JS component library, new mockups rather than
[docs/assets/panel-ui/](assets/panel-ui/index.html)):
[plans/panel-redesign.md](plans/panel-redesign.md).
**Boundary:** visual CSS and templates after mockups. No new routes or mail-path
behaviour. Not a front-end framework, not Web Awesome components, not Pico /
Bootstrap. The older HTML mockups in `docs/assets/panel-ui/` are not the brief.
**Done when:** mockups exist that use only the frozen vocabulary, then — once
this item is agreed — the live panel matches them without one-off `max-width`
exceptions. The givens file itself is not “done”.
**Dependencies / risks:** competes with feature work for attention; easy to
start restyling `internal/web` before the vocabulary is proven on paper.
Vendoring WA CSS needs NOTICE / licence rows. CSP stays `default-src 'self'`.
**Version:** `1.x` MINOR when implementation is agreed; `candidate` until then.
---
## schema-squash
**Goal:** when 2.x is cut, stop shipping the 1.x migration chain in the binary