Compare commits

..

2 Commits

Author SHA1 Message Date
mix bc005dea85 release: 1.2.0
test / test (push) Has been cancelled
Close CHANGELOG [Unreleased] as 1.2.0; correct missing entries; bump the
compose pin and image references.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 17:39:11 +03:00
mix 4b2f3880ef panel: align heading with narrow cards on signed-in pages
Settings and the user form centred the title on the 48rem measure while
.card.narrow centred on 24rem, so their left edges drifted by 12rem. Cap every
direct child of main when a narrow card is present, without shrinking the
column, so navigation stays put and login/setup stay unchanged.

Co-Authored-By: Cursor <cursoragent@cursor.com>
2026-08-11 17:34:04 +03:00
9 changed files with 52 additions and 120 deletions
+31 -11
View File
@@ -5,6 +5,13 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
## [Unreleased]
## [1.2.0] - 2026-08-11
The second MINOR after 1.0.0: domain administrators with per-domain scope, a
panel visual refresh on the SelfPost palette, and refinements to navigation and
the send log. Upgrading runs one SQLite migration (`0005_panel_users`); the
single administrator becomes a global user.
### Added
- panel: **domain-admin role** — global administrators manage panel users and
@@ -15,6 +22,18 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
administrator into a global user; sessions and full backup restore carry users
and domain bindings.
### Fixed
- Docker build: `LICENSE` is no longer excluded by `.dockerignore`, so the
runtime image can copy it into `/usr/share/doc/selfpost/` as AGPL requires. A
clean build failed once the cached layer was invalidated.
- panel: on signed-in pages with a narrow card (**Settings**, the user form)
the heading, flash, card and footer now share one left edge. `.card.narrow`
had overridden only `max-width` while `main > *` still centred siblings on
the 48rem measure, so the card floated 12rem to the right of the title.
`main:has(> .card.narrow) > *` keeps the stack on 24rem without narrowing
the column, so the navigation stays put; login/setup are unchanged.
### Changed
- panel: navigation session icons are distinct — **Settings** uses a gear,
@@ -23,28 +42,29 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
- panel: `/` redirects domain administrators to `/domains`; global users still
land on `/status`. Navigation hides global-only sections for domain
administrators.
- panel: **Sign out** in the navigation column uses the same type size and
weight as the page entries above it; only the red tint and border mark it as
destructive.
- panel: **visual style** brought in line with the SelfPost mark — brick accent
and warm paper in place of the blue-on-cool-grey defaults, IBM Plex Sans and
IBM Plex Mono served by the panel itself, squarer corners, and column
headings, status badges and small labels set in the mono face. Light and dark
schemes both keep their contrast; no page, control or workflow changed. A page
whose only card is the narrow one now takes its heading and footer down to the
card's width instead of splitting them across the wider measure, and the send
log stops breaking `Details` and `deferred` across two lines when a row is
tight.
schemes both keep their contrast; no page, control or workflow changed. The
send log stops breaking `Details` and `deferred` across two lines when a row
is tight. Badge padding and line-height are tuned so lowercase labels sit
centred in the box. The three WOFF2 files add ~76 KB to the image and are
served from the panel's own origin, so the Content-Security-Policy is
unchanged (`default-src 'self'`).
- panel: the send log's **status is a badge**, in the same ok/warn/error/unknown
colours the status page and the DNS checks use, instead of the one place in
the panel where a status was bare text. The mapping is the one the delivery
page already applied — `sent` is ok, `deferred` a warning, `bounced` and
`rejected` errors, `queued` unknown because nothing has gone wrong yet.
The three
WOFF2 files add ~76 KB to the image and are served from the panel's own origin,
so the Content-Security-Policy is unchanged (`default-src 'self'`).
- docs: [roadmap.md](docs/roadmap.md) and [product.md](docs/product.md) no
longer list domain-admin or visual-style as open work — both ship in this line.
Completed plan files (`domain-admin`, `visual-style`, `web-split`) are
removed; history stays in git and the entries above. Inbound relay is the
main agreed 1.x+ item left on the roadmap.
Completed plan files (`domain-admin`, `visual-style`, `web-split`,
`narrow-page-alignment`) are removed; history stays in git and the entries
above. Inbound relay is the main agreed 1.x+ item left on the roadmap.
## [1.1.0] - 2026-08-10
+1 -1
View File
@@ -95,7 +95,7 @@ docker run --rm -d --name selfpost-try \
-e SELFPOST_HOSTNAME=mail.local.test \
-e PANEL_COOKIE_SECURE=false \
-v selfpost-try-data:/data \
ghcr.io/mixeme/selfpost:1.1.0
ghcr.io/mixeme/selfpost:1.2.0
```
**Get the setup URL** (pick one):
+1 -1
View File
@@ -22,7 +22,7 @@
services:
selfpost:
image: ghcr.io/mixeme/selfpost:1.1.0
image: ghcr.io/mixeme/selfpost:1.2.0
restart: unless-stopped
environment:
SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}"
+1 -1
View File
@@ -141,7 +141,7 @@ Requires Go 1.26+ and `CGO_ENABLED=0`.
```sh
make build # bin/panel, bin/selfpost-backup (VERSION=dev by default)
make build VERSION=1.1.0
make build VERSION=1.2.0
```
Or directly:
+1 -1
View File
@@ -381,7 +381,7 @@ but it can look like an open port in external scans.
## Fixed image tag
`deploy/docker-compose.yml` pins an explicit version (`ghcr.io/mixeme/selfpost:X.Y.Z`),
deliberately never `:latest`. The current pin is `1.1.0`. Intermediate
deliberately never `:latest`. The current pin is `1.2.0`. Intermediate
CHANGELOG sections (`0.2.0`…`0.6.0`) record development cuts from before that
image was published. Pinning matters because of the backup version check above:
the panel binary's embedded version and the image tag that produced it are the
+1 -1
View File
@@ -5,7 +5,7 @@
the implementation lands (do not fix a major in advance).
**Order:** recommended next among agreed 1.x+ items (after `internal/web` split
and the domain-admin role, both shipped — see [CHANGELOG](../CHANGELOG.md)
`[1.1.0]` and `[Unreleased]`).
`[1.2.0]`).
---
-97
View File
@@ -1,97 +0,0 @@
# Plan: narrow-page-alignment (heading and card do not share an edge)
**Status:** candidate
**Version:** no bearing on semver — presentation only.
**Order:** independent, small. Left over from
[visual-style](../roadmap.md) (closed in `5eaf665`).
---
## What it is
On **Settings** (`/account`) and the **user form** (`/users/new`, `/users/{uid}`)
the page's `h1` sits at the left of the reading measure while the card below it
floats to the right — they do not share a left edge, so the page reads as two
blocks that were laid out independently.
Measured on Settings at a 1240px viewport: the heading's box starts 12rem
(192px) left of the card's. The ink measures 187px, the remaining 5px being the
left side bearing of the "S".
## Why it happens
Two rules meet ([panel.css](../../internal/web/view/static/panel.css)):
```
main > * { max-width: 48rem; margin-left: auto; margin-right: auto; }
.card.narrow { max-width: 24rem; }
```
Every direct child of `main` is capped at the 48rem measure and centred in the
column. `.card.narrow` (specificity 0-2-0) overrides the `max-width` of
`main > *` (0-0-1) down to 24rem — but **not** the auto margins, which it does
not mention. So the card centres itself on the column at 24rem while the `h1`
centres itself on the same column at 48rem, and half the 24rem difference is
the 12rem offset between their left edges.
## What makes these two pages different
The panel has four templates with `class="card narrow"``login`, `setup`,
`account`, `user_form` — and eleven with a full-width `.card`. That gives three
groups, and only one of them has the problem:
| Group | Pages | Heading and card |
|---|---|---|
| Full-width card | `dashboard`, `status`, `backup`, `deliveries`, `delivery`, `domain_detail`, `domain_delete`, `mail_queue`, `system_log`, `users` | Both take the 48rem measure — same edge, nothing to notice |
| Narrow card, no navigation | `login`, `setup` | `main.page-login/.page-setup { max-width: 24rem }` narrows the whole column, so the `h1` is 24rem too — same edge |
| **Narrow card, with navigation** | **`account`, `user_form`** | **The column stays 64rem/48rem, so the two centre on different measures** |
So the distinguishing property is not Settings itself but a **combination**:
these are the only pages that are *both* signed-in (and therefore have a
navigation column beside them) *and* built from a single narrow card. Every
other page has one of those properties, never both.
## Why the obvious fix is not available
Copying what `login`/`setup` do — narrowing `main` — was tried in the restyle
and reverted in `aeba3f8`. `.shell` is a flex row that centres the navigation
column and the page **as a pair**, so narrowing the page slides the navigation
sideways: 296px between Settings and Domains, measured. The reason the same
rule is safe on `login`/`setup` is precisely that those pages have no
navigation column to be moved.
Two further attempts, also reverted: capping `main`'s children
(`main:has(> .card.narrow) > *`) moves the content block instead, and
left-aligning the card (`margin-left: 0`) moves it the other way. Anything that
changes the *column* is visible from the navigation; the fix has to change only
what happens **inside** the column.
## Directions to weigh
- Give the narrow card's `h1` (and footer) the same 24rem cap and the same auto
margins, so heading, card and footer centre on one measure while the column
keeps its width. Needs a hook — `main:has(> .card.narrow) > h1`, or a class
the two templates set on their own heading.
- Or wrap heading and card in one 24rem block inside `main`, which makes the
grouping explicit in the template rather than inferred by a selector.
- Or drop `.narrow` on these two pages and let their cards take the full
measure, as every other signed-in page does. Cheapest, and worth pricing:
the narrow card exists so a short form does not stretch its fields across
48rem, which is a real reason on `login` but weaker on Settings, whose card
is long.
## Done when
- On Settings and the user form the heading, the card and the footer line up on
one left edge.
- The navigation column and the page's own width are byte-identical across
every signed-in page — verified by rendering two pages at one viewport and
comparing the column's edges, which is how the 296px regression was caught.
- `login` and `setup` are unchanged.
## Risks
- Low. The trap is the one already sprung twice: a rule that looks like it only
affects one page but is read by the shell as a change of column width. Any
candidate must be checked against a second page, not only the page being
fixed.
-1
View File
@@ -30,7 +30,6 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md).
| contributing | `CONTRIBUTING.md` | candidate | — |
| dmarc-reports | DMARC aggregate report ingestion and panel UI | candidate | [plans/dmarc-reports.md](plans/dmarc-reports.md) |
| logrotate-mode | `mail.log` stops rotating in some builds | candidate | [plans/logrotate-mode.md](plans/logrotate-mode.md) |
| narrow-page-alignment | Settings and the user form: heading and card do not share an edge | candidate | [plans/narrow-page-alignment.md](plans/narrow-page-alignment.md) |
**Recommended order** (not binding): **inbound-relay** first among agreed
items — it is the largest remaining 1.x+ extension. Candidates need explicit
+16 -6
View File
@@ -161,6 +161,18 @@ h1 { font-size: 1.5rem; font-weight: 300; letter-spacing: -0.01em; margin-top: 0
padding: 1.5rem; margin: 0 auto;
}
.card.narrow { max-width: 24rem; }
/* A narrow card as a direct child of main means the page is a short form:
every sibling in the column (heading, flash, footer) must share that 24rem
measure, or they centre on 48rem while the card centres on 24rem and the
left edges drift by half the difference (12rem / 192px at a 16px root
measured). The column itself stays 64rem so the navigation does not move;
only the stack inside the column narrows. :has tracks the card rather than
a per-page class, so a new signed-in page that adds .card.narrow inherits
the alignment without a second stylesheet rule.
login/setup already shrink main to 24rem (below); this rule is then a no-op
relative to the parent. Do not replace that with :has alone: narrowing main
is what centres the signed-out block on the page when there is no nav. */
main:has(> .card.narrow) > * { max-width: 24rem; }
label { display: block; font-weight: 600; margin: 0.9rem 0 0.3rem; }
input {
width: 100%; padding: 0.55rem 0.7rem; font-family: inherit; font-size: 1rem;
@@ -200,12 +212,10 @@ form.inline { display: inline; margin: 0; }
left, three alignments on a page with four elements. Narrowing the column to
the card's own width makes the three line up and puts the block as a whole in
the middle of the page.
By page name and not by "the page holds a narrow card", which is what this
briefly was. Settings and the user form hold one too, and narrowing their
column moves the navigation sideways with it the shell centres the column
and the page as a pair, so 296px of it, measured. Their heading and card are
still not aligned with each other; that is worth fixing on its own terms and
not by making every page's chrome move. */
By page name and not by "the page holds a narrow card": signed-in pages that
hold a narrow card keep the 64rem column (see main:has(> .card.narrow)
above) so the navigation stays put; only these two pages have no nav and
need the column itself narrowed. */
main.page-login, main.page-setup { max-width: 24rem; }
.card + .card { margin-top: 1.2rem; }
.flash { background: var(--flash-bg); border: 1px solid var(--flash-border); color: var(--flash-fg); padding: 0.7rem 1rem; border-radius: 6px; margin-bottom: 1.2rem; }