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>
8.8 KiB
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/ 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 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/ |
| Vendored htmx 2.x for polling fragments | layout.html, development.md § Vendored front-end |
Assets embedded (//go:embed), no CDN at runtime |
view.go |
CSP default-src 'self' — no inline script or style="" |
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, development.md |
JavaScript is not a product requirement. 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 and docs/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.
| 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) 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 around42rem, 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 both48remand64rem). 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>, 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)
- Draw new mockups using only the vocabulary above.
- 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.