Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc005dea85 | |||
| 4b2f3880ef |
+31
-11
@@ -5,6 +5,13 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
### Added
|
||||||
|
|
||||||
- panel: **domain-admin role** — global administrators manage panel users and
|
- 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
|
administrator into a global user; sessions and full backup restore carry users
|
||||||
and domain bindings.
|
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
|
### Changed
|
||||||
|
|
||||||
- panel: navigation session icons are distinct — **Settings** uses a gear,
|
- 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
|
- panel: `/` redirects domain administrators to `/domains`; global users still
|
||||||
land on `/status`. Navigation hides global-only sections for domain
|
land on `/status`. Navigation hides global-only sections for domain
|
||||||
administrators.
|
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
|
- 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
|
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
|
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
|
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
|
schemes both keep their contrast; no page, control or workflow changed. The
|
||||||
whose only card is the narrow one now takes its heading and footer down to the
|
send log stops breaking `Details` and `deferred` across two lines when a row
|
||||||
card's width instead of splitting them across the wider measure, and the send
|
is tight. Badge padding and line-height are tuned so lowercase labels sit
|
||||||
log stops breaking `Details` and `deferred` across two lines when a row is
|
centred in the box. The three WOFF2 files add ~76 KB to the image and are
|
||||||
tight.
|
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
|
- 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
|
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
|
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
|
page already applied — `sent` is ok, `deferred` a warning, `bounced` and
|
||||||
`rejected` errors, `queued` unknown because nothing has gone wrong yet.
|
`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
|
- 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.
|
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
|
Completed plan files (`domain-admin`, `visual-style`, `web-split`,
|
||||||
removed; history stays in git and the entries above. Inbound relay is the
|
`narrow-page-alignment`) are removed; history stays in git and the entries
|
||||||
main agreed 1.x+ item left on the roadmap.
|
above. Inbound relay is the main agreed 1.x+ item left on the roadmap.
|
||||||
|
|
||||||
## [1.1.0] - 2026-08-10
|
## [1.1.0] - 2026-08-10
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ docker run --rm -d --name selfpost-try \
|
|||||||
-e SELFPOST_HOSTNAME=mail.local.test \
|
-e SELFPOST_HOSTNAME=mail.local.test \
|
||||||
-e PANEL_COOKIE_SECURE=false \
|
-e PANEL_COOKIE_SECURE=false \
|
||||||
-v selfpost-try-data:/data \
|
-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):
|
**Get the setup URL** (pick one):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
selfpost:
|
selfpost:
|
||||||
image: ghcr.io/mixeme/selfpost:1.1.0
|
image: ghcr.io/mixeme/selfpost:1.2.0
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}"
|
SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}"
|
||||||
|
|||||||
+1
-1
@@ -141,7 +141,7 @@ Requires Go 1.26+ and `CGO_ENABLED=0`.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
make build # bin/panel, bin/selfpost-backup (VERSION=dev by default)
|
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:
|
Or directly:
|
||||||
|
|||||||
+1
-1
@@ -381,7 +381,7 @@ but it can look like an open port in external scans.
|
|||||||
## Fixed image tag
|
## Fixed image tag
|
||||||
|
|
||||||
`deploy/docker-compose.yml` pins an explicit version (`ghcr.io/mixeme/selfpost:X.Y.Z`),
|
`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
|
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:
|
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
|
the panel binary's embedded version and the image tag that produced it are the
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
the implementation lands (do not fix a major in advance).
|
the implementation lands (do not fix a major in advance).
|
||||||
**Order:** recommended next among agreed 1.x+ items (after `internal/web` split
|
**Order:** recommended next among agreed 1.x+ items (after `internal/web` split
|
||||||
and the domain-admin role, both shipped — see [CHANGELOG](../CHANGELOG.md)
|
and the domain-admin role, both shipped — see [CHANGELOG](../CHANGELOG.md)
|
||||||
`[1.1.0]` and `[Unreleased]`).
|
`[1.2.0]`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -30,7 +30,6 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md).
|
|||||||
| contributing | `CONTRIBUTING.md` | candidate | — |
|
| contributing | `CONTRIBUTING.md` | candidate | — |
|
||||||
| dmarc-reports | DMARC aggregate report ingestion and panel UI | candidate | [plans/dmarc-reports.md](plans/dmarc-reports.md) |
|
| 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) |
|
| 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
|
**Recommended order** (not binding): **inbound-relay** first among agreed
|
||||||
items — it is the largest remaining 1.x+ extension. Candidates need explicit
|
items — it is the largest remaining 1.x+ extension. Candidates need explicit
|
||||||
|
|||||||
@@ -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;
|
padding: 1.5rem; margin: 0 auto;
|
||||||
}
|
}
|
||||||
.card.narrow { max-width: 24rem; }
|
.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; }
|
label { display: block; font-weight: 600; margin: 0.9rem 0 0.3rem; }
|
||||||
input {
|
input {
|
||||||
width: 100%; padding: 0.55rem 0.7rem; font-family: inherit; font-size: 1rem;
|
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
|
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 card's own width makes the three line up and puts the block as a whole in
|
||||||
the middle of the page.
|
the middle of the page.
|
||||||
By page name and not by "the page holds a narrow card", which is what this
|
By page name and not by "the page holds a narrow card": signed-in pages that
|
||||||
briefly was. Settings and the user form hold one too, and narrowing their
|
hold a narrow card keep the 64rem column (see main:has(> .card.narrow)
|
||||||
column moves the navigation sideways with it — the shell centres the column
|
above) so the navigation stays put; only these two pages have no nav and
|
||||||
and the page as a pair, so 296px of it, measured. Their heading and card are
|
need the column itself narrowed. */
|
||||||
still not aligned with each other; that is worth fixing on its own terms and
|
|
||||||
not by making every page's chrome move. */
|
|
||||||
main.page-login, main.page-setup { max-width: 24rem; }
|
main.page-login, main.page-setup { max-width: 24rem; }
|
||||||
.card + .card { margin-top: 1.2rem; }
|
.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; }
|
.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; }
|
||||||
|
|||||||
Reference in New Issue
Block a user