505260e227f7b223ea312068247287edbbb48f56
25
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
505260e227 |
design: the DMARC hub says reports are kept 500 a domain
The retention cap is counted per domain (the security commit after this one); the Ingest box says so: 500 reports a domain or 90 days. check.py: 0 violations; outlines.json unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ddde177776 |
design: the health cards of Overview open Health at its top
test / test (push) Canceled after 0s
Owner, 2026-10-11, after looking at the test server: the anchors work but mean little, Health is short and there is nothing to scroll past. The five cards (Machine, Processes, TLS certificate, Milter sockets, Reverse DNS) lose their #machine ... #hostname, as the links into Domain settings lost theirs. check.py: 0 violations; outlines.json unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f6838e4696 |
design: the hosted address form on the DMARC hub is in the smaller mono size
On the test server the form dmarc-reports+<domain>@selfpost.mixfed.ru broke over three lines in its column (2026-10-10). It takes the supporting size the report id and rua of a report already use. check.py: 0 violations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5b1a868ccf |
design: links into Domain settings open the page at its top
Seen by the owner on the test server (2026-10-10): following Report address, Rate limit or Export from the domain's menu opened Domain settings scrolled down to that box. The page is one screen and a bit, so the anchor only pushed the title and the first row out of view. The links (the domain menu, the DMARC record's note, the delete page, Server > Settings) lose their #reports / #limit / #export. check.py: 0 violations; outlines.json unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b951eae58c |
panel-redesign: no report mailbox for the server as a whole
test / test (push) Canceled after 0s
Follows the design commit before it.
- internal/dmarc: the ingest allow-list is the report address of each
domain that is on this host and nothing else;
DefaultHostedReportAddress and DefaultHostedSuggestion are removed.
Since 1.7.0 the general mailbox dmarc-reports@<hostname> was open
while no panel-wide default existed; the three-choice scheme had
left it open always.
- The DMARC hub's Ingest box shows the form of a hosted address,
dmarc-reports+<domain>@<hostname>.
- guide.md: what port 25 accepts.
Evidence: docs/assets/panel-redesign/evidence/dmarc.png re-shot (the
address form wraps after the plus sign in its column at 1440 px).
go test -count=1 ./internal/dmarc/... ./internal/web/... ./cmd/panel: ok
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
ea847fea44 |
design: the DMARC hub names the form of a hosted address, not a server mailbox
The owner's decision (2026-10-10): the mailbox for the server as a whole, dmarc-reports@<hostname>, goes. Under the three-choice scheme a domain can only choose its own hosted address, so nothing can be told to report to the general one. The Ingest box's fact becomes "Hosted addresses: dmarc-reports+<domain>@<hostname>". check.py: 0 violations; outlines.json unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
06ae9ca8d3 |
design: is-hidden is in the Bulma subset
The owner's instruction (2026-10-10). panel.js sets is-hidden, beside the hidden attribute, on a block that a control switches off - the fields of a mode that is not chosen, the password fields of an unticked encryption box, the domain lists of a global user - because hidden alone does not hide an element whose stylesheet gives it a display of its own (a box, a grid row). No screen writes the class and it draws nothing, so the components page has nothing to show for it; the list now names everything the panel uses. check.py: 0 violations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
be85cb71c9 |
panel-redesign: technical review - every page re-shot and audited
Stage 3, step 1 (Opus). Each step of stage 2 was reviewed against its
mockup when it landed; this closes the review over the whole, after the
changes of stage 3 (Help topics and the ? on boxes, the three-choice
report address, row actions, the filter's socket).
- Evidence: all 29 screenshots under docs/assets/panel-redesign/
evidence/ are re-shot from the current templates - the 28 pages of
pageFixtures and the sign-in page without an administrator - 1440 px
wide at each page's full height. Headless Edge against the real
templates served locally with their fixtures; NOT from the container
(no Docker on this machine) - the container is checked on the test
server at deployment.
- panel/audit.js over the same 29 pages at 1440 and 1280: 4160 texts
checked, problems: [] (type floor, contrast, sideways scroll,
overflow in boxes and heads, scrolling tables, clipped card values,
missing icons). Every ? link has the form /help#topic, and
TestEveryHelpLinkLandsOnATopic proves each lands.
- Guards: outlines, class vocabulary, page structure, components-only,
stylesheet equality, routes, static allow-list - green; the ratchet
is empty.
Where the product knowingly differs from a mockup (each recorded in the
commit of its step): the queue is the postqueue listing, not a table;
the outbound log has no status filter; the message history has no
"accepted from IP" step; Health draws the sockets this server has;
the Spam filter box states only what the configuration says; the user
form's e-mail help promises nothing that is not built; Overview's title
does not claim that mail is flowing. Roadmap screens (in-log, in-queue,
in-quarantine, in-filter-lists, preflight) are drawn and not built.
Open with the owner: is-hidden, which panel.js sets at run time, is not
in check.py's Bulma subset (the edit of check.py is his).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
96db0b7a86 |
panel-redesign: three choices for a domain's report address; row actions
Follows the design and guard commits before it. Written by Sonnet,
reviewed by Opus.
The report address
- Baseline: users.dmarc_default_mode, users.dmarc_default_address and
domains.dmarc_rua_user_id are gone. domains.dmarc_rua alone says
where reports go: '' is no reports (a new domain), the address
SelfPost derives for the domain is the hosted choice (while hosted
reports are on), anything else is a typed address.
- Removed: the user default and its store functions, a domain
following a user, keep / inherit, followCreator, POST /account/dmarc
with its handler, the DMARC box of Account, the user-delete item
about domains losing their address.
- Domain settings offers the three choices; beside the typed address a
"Use my e-mail" button fills in the profile e-mail (panel.js: one
generic data-fill hook; absent without a profile e-mail; it fills,
it does not save).
- The DMARC record's note names the address and no user. This closes
L4 of the security review.
- Domain export / import carries the address as a plain string.
- The ingest allow-list is the generic mailbox plus each domain's
address on this host; the generic mailbox is always open now.
- dev/migrate1x (not in the tree): a 1.x domain that inherited the
instance default gets that address as its own, so no published rua=
changes.
Row actions
- row_action partial (RowAction: Label, Post, Confirm, Danger);
panel.css has the mockup's three rules. A row of the Applications
table is Edit, New password, Delete; the confirmation texts are the
ones the application page's head uses. Both roles, with the checks
of the application page.
Tests cover: a new domain has no reports; each choice saved and shown
selected; hosted refused when hosted reports are off; a bad address
refused with what was typed kept; an untouched save changes nothing in
each state; the fill button with and without a profile e-mail; deleting
a user leaves every domain's address; the row's forms, targets and
confirmations for both roles and a 404 for a stranger.
Known: if hosted reports are switched off later, a stored hosted address
reads as a typed one. docs/guide.md still describes the old choices -
the documentation step.
Evidence
- docs/assets/panel-redesign/evidence/account.png,
out-domain-settings.png, out-domain.png, user-delete.png, help.png:
1440 px wide, headless Edge, real templates served locally with
their fixtures - NOT from the container.
- panel/audit.js on out-domain, out-domain-settings, account, help,
components and user-delete at 1440 and 1280: 1420 texts checked,
problems: [].
- In a browser: the fill button sets the field and does not submit;
the row buttons have the font, size and colour of the Edit link,
Delete the red of the other delete links; data-confirm fires on
both. Hover and focus states were not observed.
- go test -count=1 ./internal/web/... ./internal/store/...
./internal/dmarc/... ./internal/app/... ./internal/inbound/...
./cmd/panel: ok (ten packages; dev/migrate1x also ok).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
fc53ad61ee |
design: a domain's report address is one of three choices; row actions are POSTs
Two decisions of the owner, 2026-10-10.
The report address. "Three choices: no reports, the SelfPost address, a
typed address. No reports by default. A button that fills the typed
address with one's own e-mail from the profile." This replaces the
2026-09-21 design in which a domain followed a named user's default: a
co-administrator of a domain was shown another user's login name, and
three explicit choices are simpler.
- src/out-domain-settings.html: the box is a select of the three
choices and the address field with a "Use my e-mail" button.
- src/out-domain.html: the DMARC record's note names the address, with
no user in it.
- src/account.html: the DMARC reports box is gone; Account is Profile
and Password, one box per column.
- src/settings.html: the lead no longer points at Account for the
report address.
- docs/plans/panel-redesign.md: Account e-mail rewritten, the Routes
row of account without /account/dmarc. docs/schema-migrations.md:
the columns that go.
Row actions. New password and Delete in a row of the Applications table
are POSTs, so each is a form with a button that looks like the links
beside it - as Sign out became one.
- src/out-domain.html: Edit / New password / Delete per row, the box
foot about confirmations is back; the login cell does not wrap.
- theme.css: .table td.sp-actions form sits inline with the links'
spacing, and its button takes the link's colour and type.
- src/components.html shows the pattern once.
check.py: 0 violations. outlines.json: only account changed (two equal
columns, one box each).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
4ab63d08ae |
panel-redesign: Help with seventeen topics; every box links to its own
Follows the design commit before it. Written by Sonnet, reviewed by
Opus.
- view/help.go, templates/help.html: the mockup's topics, texts, ids
and menu. The page shows the groups the reader's menu shows:
Overview and Server for the global role, Outbound and Inbound by
reach (Inbound only with the feature on), Account for everyone;
boxes are numbered as shown.
- Box.Help set on the boxes the mockups mark; the inbound MX box has
its link again.
- TestEveryHelpLinkLandsOnATopic also fails on a topic that no page
links to, unless it is listed as menu-only (account) - and on a
stale exception.
Known until the change that follows: the dns, dmarc and account topics
describe the three-choice report address, which the code does not have
yet; Account's DMARC box still links to #dns.
Not verified by running: three sentences rest on Postfix behaviour
(delivery is deferred when a required TLS session cannot be made; a
milter with default_action=tempfail answers 4xx; with "any recipient"
an address the upstream refuses comes back as a bounce).
Evidence
- docs/assets/panel-redesign/evidence/help.png (1440x3600) and
in-domain.png: headless Edge, real templates served locally with
their fixtures - NOT from the container.
- go test -count=1 ./internal/web/... ./cmd/panel: ok (six packages).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
ef19c1448e |
design: Help covers every section; boxes link to their topic
The owner's decision (2026-10-10): help is needed by almost every
section, not by Overview and Outbound alone. The Help mockup had six
topics while other screens already linked to topics it did not have
(#dmarc, #inbound).
- src/help.html: seventeen topics in five boxes - Overview (checks,
certificate, rdns), Outbound (dns, apps, limits, log, queue, dmarc),
Inbound (inbound, upstream, recipients, filter), Server (backup,
users, settings), Account (account) - one paragraph each, and the
side menu lists them by group.
- A box head gets the sp-help link where a topic now exists: health,
out-domain-settings, out-app, out-log, out-message, out-queue, the
three DMARC screens, in-domains, in-domain, backup, users, user,
settings. No link on boxes that explain themselves (confirmations,
the shown-once password, sign-in) and at most one per box.
- outlines.json regenerated (check.py --write): only help changed -
Outbound holds six topics, and Inbound, Server and Account are new
boxes. 0 violations.
The dns, dmarc and account topics describe the report-address scheme the
owner decided the same day (no reports / the SelfPost address / a typed
address, no user default); the account and out-domain-settings mockups
are redrawn for it in the change that follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
32c6ae98a1 |
panel-redesign: the queue in the new shell; the old design is removed
Stage 2, steps 7 and 8 in one commit: the static guard fails as soon as
the ratchet holds no page while legacy.css still exists, and the tests
of the old layout need it while that layout exists - the two steps do
not split into states that pass on their own. Written by Sonnet,
reviewed by Opus.
The queue
- out-queue, named after its mockup: today's content - the listing of
postqueue -p in a log pane, polled as before (GET
/outbound/queue/fragment) - and the "How retries work" facts. The
mockup's table of messages is the roadmap's queue-as-a-table; the
outline guard exempts this page for exactly that reason. Routes and
the global-only check are unchanged; the page has no actions and
never had.
- Inbound has no Queue entry and no route serves one.
The old design is gone
- Deleted: layout_legacy.html, legacy.css, logo-compact.svg,
mail_queue.html, mail_queue_body.html.
- view.go has one path: every page is layout.html + components.html +
its own files; kitPages, kitFragments, the map-data branch of Render,
the dict and back template functions and metaOf's map branch are
removed. handlers: pageBase removed.
- legacy_pages.txt holds its header and no entry.
- panel.js: the old copy handler (button.copy in .code-row) is
removed. A show-when rule now applies to every element it matches,
and hiding adds Bulma's is-hidden beside the hidden attribute - a
.box or a grid row has a display of its own and stayed visible.
- The user form hides both domain lists again when the role is Global
(Box.DomainPick, an attribute; data-global-role on the select).
- Tests of the old layout are deleted; what they asserted about the
shell (version only signed in, the menu follows the user's reach,
Inbound only with the feature, Help present, the current entry
marked) is asserted on the kit pages, two tests added for it.
For the owner: is-hidden is a Bulma class outside the subset of
check.py. No template writes it - the script sets it at run time to
hide a block - so no guard sees it; say if it should be on the list.
Evidence
- docs/assets/panel-redesign/evidence/out-queue.png: 1440 px wide,
headless Edge, the real template served locally with its fixture -
NOT from the container (no Docker on this machine).
- panel/audit.js on out-queue, user and backup at 1440 and 1280:
366 texts checked, problems: [].
- In a browser against the local fixture server: the encryption
fields of Backup, the manual / auto limit fields and the custom
report address of Domain settings show and hide with their control;
all 28 fixture pages answer 200 with panel.css, /static/legacy.css
answers 404.
- Not exercised: polling against a real postqueue (none on Windows).
- go test -count=1 ./internal/web/... ./cmd/panel
ok internal/web
ok internal/web/auth
ok internal/web/handlers
ok internal/web/validate
ok internal/web/view
ok cmd/panel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
fa696efa8b |
panel-redesign: Server pages and Help on the kit; the help drawer is gone
Stage 2, step 6. Written by Sonnet, reviewed by Opus.
- Six pages named after their mockups: system-log, backup, users,
user, user-delete, help, with typed page data (view/server.go,
help.go), fixtures and tests. The old templates of the six are gone
(user_form.html, encrypt_fields.html deleted, the others rewritten)
and their names are off legacy_pages.txt, which now holds mail_queue
alone.
- Help is six help_topic blocks (checks, certificate, rdns, dns, apps,
limits); a test renders every fixture page and fails on a Help link
without a topic. The help drawer (help_drawer.html and its includes
in layout_legacy.html) is removed; its rules in legacy.css go with
the next step.
- The user form sets the user's e-mail (plan, Account e-mail): create
and update, validated, and a changed address resyncs DMARC for a
user whose default is the account e-mail - through the helper the
Account profile now shares.
- Handlers pass typed data; routes, POST targets, field names,
validation and authorization are unchanged. A test loads the user
form for four reach combinations and for the only global user, posts
it back untouched and finds the user unchanged.
Fixed on the way
- The user delete confirmation said the domains were not affected. A
domain that follows the user's default report address is left with
none; the page names those domains now.
- A refused backup lost the encryption choice and printed a literal
<code> element in its message.
Differences from the mockups, for the owner:
- Help has no DMARC and no Inbound topic: its outline fixes two boxes
of three topics, although other mockups link to #dmarc and #inbound.
Until the owner decides, Account's Help link goes to #dns and the
inbound MX box has none;
- system log: newest line first, as drawn; "refreshes on its own" (5 s
active, 30 s idle); only status=, warning: and error: lines are
coloured;
- backup: the file field is a plain file input; the encryption box is
ticked, as drawn and as on the domain export, so a plain download
means unticking it first;
- users: an E-mail column; the flash does not name the user;
- user form: no lead; the domain lists are no longer hidden by script
when Global is chosen (the server ignores them for that role).
Evidence
- docs/assets/panel-redesign/evidence/system-log.png, backup.png,
users.png, user.png, user-delete.png, help.png: 1440 px wide,
headless Edge, real templates served locally with their fixtures -
NOT from the container (no Docker on this machine).
- panel/audit.js on the six pages at 1440 and 1280: 720 texts checked,
problems: [].
- The system log's polling swap was not exercised in a browser; it
uses the mechanism of Overview and Health.
- go test -count=1 ./internal/web/... ./cmd/panel
ok internal/web
ok internal/web/auth
ok internal/web/handlers
ok internal/web/validate
ok internal/web/view
ok cmd/panel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
610023ef6a |
panel-redesign: Inbound domains, domain and delete on the kit; Spam filter box
Stage 2, step 5. Written by Sonnet, reviewed by Opus.
- Three pages named after their mockups: in-domains, in-domain,
in-domain-delete, with typed page data (view/inbound.go), fixtures
and tests. The engine pages inbound, inbound_domain and
inbound_delete are gone with their templates and are off
legacy_pages.txt.
- The Spam filter box (plan, Who sees what). The panel now reads
INBOUND_ANTISPAM_MILTER and INBOUND_ANTISPAM_MILTER_ACTION, with the
semantics of build/postfix-config.sh (on only with the inbound relay
on and a milter set; accept | tempfail). It reports them, nothing
more:
on Applies to / Filter (the address, global role only) / If the
filter is down (accepted unfiltered | deferred)
off one sentence; the variable is named for the global role only
The list shows "Spam filter on|off" in its head.
- Handlers pass typed data; routes, POST targets, field names,
validation and authorization (global: all inbound domains; domain
role: the assigned ones, another tenant's id is a 404; add and
delete are global-only) are unchanged. A test saves the upstream and
recipients forms untouched and finds the domain unchanged.
Left out of the mockups' Spam filter box and menu - no source before
inbound-antispam-panel (2.1): the engine name, the quarantine row, the
"refused at the door, see the log" row, the lists row, "Log for this
domain". Also "Checked N min ago" on the MX box: the check carries no
time.
Not done: the filter's socket on Health and Overview. internal/health
checks a unix socket by stat; the documented form of the filter's
address is inet:host:port and needs a dial that does not exist yet.
Differences from the mockups, for the owner:
- refusals and confirmations are the kit flash above the boxes;
- the MX tag is coloured by the check in every state; an MX that
points elsewhere shows "In DNS now";
- the lead of the domain page follows what is saved ("Not accepting
mail yet: no upstream is saved");
- the delete page lists only what the domain has;
- /help#inbound opens Help at the top until the Help step.
Evidence
- docs/assets/panel-redesign/evidence/in-domains.png, in-domain.png,
in-domain-delete.png: 1440 px wide, headless Edge, real templates
served locally with their fixtures (filter on) - NOT from the
container (no Docker on this machine).
- panel/audit.js on the three pages at 1440 and 1280: 352 texts
checked, problems: [].
- That the panel process sees the two variables inside the container
is read from supervisord.conf and entrypoint.sh, not run.
- go test -count=1 ./internal/web/... ./cmd/panel
ok internal/web
ok internal/web/auth
ok internal/web/handlers
ok internal/web/validate
ok internal/web/view
ok cmd/panel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
2f1ec65b30 |
panel-redesign: Outbound log, message and the DMARC report pages on the kit
Stage 2, step 4. Written by Sonnet, reviewed by Opus.
- Five pages named after their mockups: out-log, out-message, dmarc,
dmarc-domain, dmarc-report, with typed page data
(view/outbound_log.go, dmarc.go), fixtures and tests. The engine
pages deliveries and delivery are gone with their templates; the
three DMARC templates are written anew. All five old names are off
legacy_pages.txt.
- The log polls its table (GET /outbound/log/fragment, as before); the
page count in the box foot follows out of band, and the filter in
the head is no longer swapped away while it is being chosen.
- Handlers pass typed data; routes, query parameters, filters, paging
and authorization are unchanged.
- Kit: Foot.ID / Foot.OOB (attributes only, as on Head) and Fact.Small,
the mockup's "sp-mono sp-small" value, for a report id and an rua
that wrapped mid-word in their column.
- test/e2e status scrapers match the new tag markup. Not run: no
Docker here.
Fixed on the way
- The DMARC domain page read a field its source rows do not have and
answered 500 as soon as the domain had a source. It builds the rows
from the store's rollup now, with a test.
Differences from the mockups, for the owner:
- the log has no Status filter (the store filters by domain and
application only); a refused message is "rejected", a neutral tag as
the mockup's "rate-limited" is;
- the message history keeps the panel's own wording with full dates and
has no "accepted from <IP>" step - the client address is not stored;
- dmarc: before the first report the box says "None yet" with what to
check; a quiet mailbox is an amber "quiet" tag;
- dmarc-domain: the stamp is pass / (pass + fail) rounded down, amber
on any failure, red when nothing passed, absent without mail;
- dmarc-report: the title has no year; "this relay" marks record rows
as well as sources.
Known and left as it is (not introduced here): the DMARC hub answers 404
to a domain administrator while the menu shows the entry and the plan
gives that role the reports of its own domains. Opening it needs the
report list to refuse an empty domain filter first - for the stage-3
security review.
Evidence
- docs/assets/panel-redesign/evidence/out-log.png, out-message.png,
dmarc.png, dmarc-domain.png, dmarc-report.png: 1440 px wide, headless
Edge, real templates served locally with their fixtures - NOT from
the container (no Docker on this machine).
- panel/audit.js on the five pages at 1440 and 1280: 784 texts checked,
problems: [].
- Polling of the log was exercised in a browser against a local
fixture server with document.hidden overridden: four polls, one
table and one foot after each, the filter untouched.
- go test -count=1 ./internal/web/... ./cmd/panel
ok internal/web
ok internal/web/auth
ok internal/web/handlers
ok internal/web/validate
ok internal/web/view
ok cmd/panel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
acdfaaecd7 |
panel-redesign: Outbound domains, domain, settings, application on the kit
Stage 2, step 3. Written by Sonnet, reviewed by Opus.
- Six pages named after their mockups: out-domains, out-domain,
out-domain-settings, out-app, out-app-created, out-domain-delete,
with typed page data (view/outbound.go, outbound_app.go), fixtures
and tests. The engine pages dashboard, domain_detail and
domain_delete are gone with their templates and are off
legacy_pages.txt. GET .../settings has its own handler.
- Handlers pass typed data; routes, POST targets, field names,
validation, authorization (both roles, another tenant's id is a 404)
and Cache-Control: no-store on the password page are unchanged.
- Kit, attribute only: Action.Confirm writes data-confirm on a POST
action's form (panel.js already reads it).
- test/e2e/panel_client.go scrapes the new markup (copy fields, the
application row's Edit link). Not run: no Docker here.
Fixes found on the way
- Report address of a domain that follows another user's default: the
select had one "inherit" option, selected, so saving the form
untouched moved the domain to whoever saved it. It now offers that
user's default as "keep" (selected, saves nothing) and "My default
instead" as the change, as the mockup draws it; "keep" on a domain
that does not follow someone else is refused.
- panel.js: syncShowWhen stopped at the first matching rule, so the
auto-limit fields were never toggled with the limit mode. Every
matching rule applies now.
Differences from the mockups, for the owner:
- application rows carry Edit only. New password and Delete are POSTs,
and the kit has no link-styled POST control for a table row; both are
in the application page's head with a confirmation. Drawing them in
the row needs a design: change (as Sign out was);
- the application login is read-only on edit (the handler refuses a
rename); the submit says Create application on the empty form;
- multiplier bounds are the store's 1.5-5, default 2.5;
- "Recalculate" buttons exist where the stored limit is automatic;
- a domain administrator has no add box and no Delete; Delete domain is
global-only on the domain page and on settings;
- a fourth record, Report authorization, appears when the report
address is on another domain; record states missing / weak / not
checked are tags with the explanation under the value;
- refusals are the kit flash above the first box, and those of the
settings forms now show on the settings page; successful saves still
redirect to the domain page;
- the export checkbox is ticked by default, as drawn.
Evidence
- docs/assets/panel-redesign/evidence/out-domains.png, out-domain.png,
out-domain-settings.png, out-app.png, out-app-created.png,
out-domain-delete.png: 1440 px wide, headless Edge, real templates
served locally with their fixtures - NOT from the container (no
Docker on this machine). Taken before the report-address fix, which
does not change what the fixture renders.
- panel/audit.js on the six pages at 1440 and 1280 (text inside
[hidden] blocks skipped): 866 texts checked, problems: [].
- go test -count=1 ./internal/web/...
ok internal/web
ok internal/web/auth
ok internal/web/handlers
ok internal/web/validate
ok internal/web/view
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
5c654723c2 |
panel-redesign: Overview, Health, Account and Server > Settings on the kit
Stage 2, step 2. Written by Sonnet, reviewed by Opus.
- Four pages named after their mockups: overview, health, account,
settings, each with a typed page struct (view/overview.go, health.go,
account.go), a fixture and tests. The engine page "status" is gone
(status.html, status_body.html deleted); "settings" is Server >
Settings only. status and settings are off legacy_pages.txt.
- Overview: the stamp and its sentence are derived from the six cards
(the worst card sets the level and the lead names the cards that are
not fine); the outbound and inbound domain tables are built from the
existing service calls. Without the inbound relay the outbound box
takes the full width.
- Both pages refresh themselves as the old Status page did. Health
polls its first row of boxes and swaps the second out of band.
Overview polls its Server health box and the page head follows out of
band (GET /overview/fragment), so stamp, sentence and time move with
the cards.
- Kit, attributes only (no class, no CSS, markup unchanged when empty):
Box.Poll and Head.ID / Head.OOB.
- healthCards: the Queue card says "Empty" / "N queued" instead of
postqueue's line, which was clipped in a card; Processes and Milter
sockets carry a sub line. OverallStatus / OverallHeading are gone.
- Handlers pass typed data; routes, POST targets, field names,
validation and authorization are unchanged.
States the mockups do not draw, all from existing partials: errors and
successes as the kit flash above the boxes; Health with an unreadable
process list, a certificate without facts, a missing host name; Account
with the report authorization not published / not found / not checked
and with hosted reports off; empty domain tables as empty_state.
Differences from the mockups, for the owner:
- Overview title is one of "Everything is running normally." /
"Running, with warnings." / "A component needs attention.", not
"Mail is flowing..." (the checks do not prove that); the note says
"Updated HH:MM:SS UTC" without an interval, Health's lead says
"Refreshes automatically" (panel.js polls at 5 s active, 30 s idle);
- one Queue card ("outbound"), two milter sockets: what this build has;
- the days column is the stats window, min(30, retention);
- Health: no "check port 80" paragraph (the certificate's own detail
says what to check); a failing socket shows its detail under the path;
figures come from the sampler ("12%", "MiB");
- Account: the DMARC form keeps the address field for "Another
address" (hidden unless chosen); the count "N of M now" is the
domains following this user's default;
- Settings: Level 2 links the domain list only;
- /help#checks and /help#dmarc open Help at the top until the Help step.
Evidence
- docs/assets/panel-redesign/evidence/overview.png, health.png,
account.png, settings.png: 1440 px wide, full height, headless Edge,
real templates served locally with their fixtures - NOT from the
container (no Docker on this machine).
- panel/audit.js on the four pages at 1440 and 1280: 612 texts
checked, problems: [].
- Polling was exercised in a browser against a local fixture server
with document.hidden overridden (the pane was hidden): one element
per id after a swap, no leftover hx-swap-oob, about one request per
5 s. The first request of a visible tab was not observed.
- go test -count=1 ./internal/web/...
ok internal/web
ok internal/web/auth
ok internal/web/handlers
ok internal/web/validate
ok internal/web/view
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
5b979a0540 |
design: no Notifications box on Account and Server > Settings
Event notifications are not built (panel-notifications is a roadmap
candidate), and the outline of both screens forced a box that could only
say so. The owner's decision (2026-10-10): take the boxes out of the
mockups; they are drawn again as the first step of that feature's plan.
- src/account.html: box 03 Notifications removed, DMARC reports is 03;
the lead and the e-mail help no longer promise notifications or a
reset link.
- src/settings.html: box 02 Notifications removed, Sending rate limits
is 02; the lead no longer lists notifications.
- outlines.json regenerated (check.py --write): the two boxes are gone
from account and settings, nothing else moved. 0 violations.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
f59f4778c9 |
panel-redesign: sign-in without an administrator follows its mockup
- panel.css: .sp-signin-form .sp-form, as theme.css has it now.
- templates/login.html: the state is div.sp-form with the mockup's
heading and line; the empty form of
|
||
|
|
2b4a158423 |
design: sign-in before any administrator exists is a drawn screen
The owner asked for the state to be drawn (2026-10-09) instead of the
form.sp-form without action or controls that
|
||
|
|
ef8a82e4c3 |
panel-redesign: Sign in and Create administrator on the kit
Stage 2, step 1. Written by Sonnet, reviewed by Opus.
- templates/login.html, setup.html: written from the mockups as the
content of layout_signed_out - form.sp-form, kicker, h1, Bulma fields
with icons, the full-width primary button. Actions, field names,
autocomplete and autofocus are the old ones.
- view/signin.go: typed Login and Setup (Meta carries a title only, so
Engine.Render takes the signed-out layout); auth handlers pass them
instead of maps.
- login and setup are in kitPages, have fixtures, and are off
legacy_pages.txt.
States the mockups do not draw:
- a refused sign-in / setup is the kit's flash (notification is-danger
is-light) above the fields;
- login before any administrator exists shows the old sentence in a
form.sp-form that has no action and no control: the signed-out
stylesheet sizes and centres its column through "form" alone. A
workaround, listed for the owner.
Differences from the mockups, for the owner:
- the kicker is <p><span class="sp-kicker"> (the kit's partial is a
span; a page may not write the class), the mockup has <p
class="sp-kicker">: the block sits about 3px lower;
- login's kicker is the configured host name and is absent when there
is none; setup's is the mockup's fixed text;
- the footer is the layout's legal line (full copyright holder name);
- the user name field has autofocus, so it shows its focus ring.
Evidence
- docs/assets/panel-redesign/evidence/login.png, setup.png: 1440x900,
headless Edge, from the real templates served locally with their
fixtures - NOT from the container (no Docker on this machine).
- panel/audit.js on login, login with an error, login without an
administrator, setup, setup with an error, at 1440 and 1280:
118 texts checked, problems: [] ; no page is taller than 900px.
- go test -count=1 ./internal/web/...
ok internal/web
ok internal/web/auth
ok internal/web/handlers
ok internal/web/validate
ok internal/web/view
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
5be1504cfc |
design: Sign out is a POST form in the mockups, tinted like the links
Owner's instruction (2026-10-09): in the kit Sign out had no hover tint and
stood out among Account and Help. The cause was in the design, not in the
implementation: the mockups drew Sign out as a link, the panel signs out
with a POST, and Bulma tints only a.navbar-item.
- build.py: Sign out is a form with a submit button carrying navbar-item,
as the real shell has to write it; all pages rebuilt.
- theme.css: the form stretches the button across the menu and the button
is handed Bulma's own hover / focus / active variables, so it answers
like the entries above it. No width, no colour of its own.
check.py: 32 screens, 48 sp- classes, 0 violation(s); outlines.json unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
f861d78114 |
panel-redesign: the component kit - panel.css, partials, shell, kit page
Stage 0, steps 3-5, in one commit: the three were built together (the kit
page is what exercises the partials and the shell) and do not split into
states that build on their own. Written by Sonnet, reviewed by Opus.
- static/panel.css: shared/brand.css + panel/theme.css, rule for rule.
wordmark.svg and perf-edge.svg beside it; the stamp is logo.svg.
- templates/components.html: every partial of the plan's Three layers,
with its typed input in components.go. Wrappers are open/close pairs
(box_open/box_close, body_open/body_close, record_open/record_close).
Rich text is a Text value built from escaped runs - no field is
trusted HTML. status_tag and wbr_at are template functions; copy_field
is a partial whose button panel.js wires through data-copy.
- templates/layout.html: the shell as build.py draws it, the menu paths
written out literally, visibility from a typed Shell (role, feature
flags, the user's reach); the signed-out screen as a second define.
Not in the 2.0 menu: Inbound Log / Queue / Quarantine / Filter lists
and Preflight.
- GET /server/components (global role, 404 otherwise): the mockup
components page rebuilt from fixtures (kit.go) by the real templates.
- The old panel steps aside unchanged: panel.css -> legacy.css,
layout.html -> layout_legacy.html; a page uses the new layout iff it
is off legacy_pages.txt.
- legacy_pages.txt: @kit removed - the kit is under the guards for good.
Differences from the mockup components page, for the owner:
- the head button is Help, not "Cover sheet"; two "Example" links are
dropped (no fixed URL), two point at /outbound/domains and /login;
- the page sits in the Server group, so the sibling strip shows;
- Sign out is a POST form whose button carries navbar-item: same size
and type as the links, but without Bulma's hover tint;
- a head's actions are always wrapped in div.buttons.
Evidence
- docs/assets/panel-redesign/evidence/components.png: 1440 px wide, full
height, headless Edge. Taken from the real templates served locally,
NOT from the container (no Docker on this machine); the page reads
only its own fixtures and embedded assets.
- panel/audit.js on the kit page, 1440 and 1280:
{"pages":1,"widths":[1440,1280],"textsChecked":516,"problems":[]}
- go test ./internal/web/... (with @kit removed)
ok github.com/mixeme/selfpost/internal/web
ok github.com/mixeme/selfpost/internal/web/auth
ok github.com/mixeme/selfpost/internal/web/handlers
ok github.com/mixeme/selfpost/internal/web/validate
ok github.com/mixeme/selfpost/internal/web/view
TestRoutesFollowNavigation stays skipped behind @routes (stage 1).
Roadmap 10/27.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
7ef7f75246 |
release: 1.9.2: Review pass: port-25 milters, DMARC validation, rate-limit fix
Review pass over `1.4.0`…`1.9.1`: port 25 no longer shares the submission milter chain, DMARC reports are validated before they are stored, and the auto rate limit is correct for a non-default level-1 window. Schema migration `0010` (index only). Upgrading is a tag bump. Added: - SQLite migration `0010_send_log_app_login_index.sql` — - DMARC aggregate reports are accepted as **zip** as well as gzip or plain XML - e2e: `checkPort25ConfigCombinations` regenerates the Postfix configuration Changed: - README hero line and a **Positioning** block in `docs/product.md` record the Fixed: - The setup account's password is hashed at bcrypt cost `12` like every other - Login/setup rate limiter: protecting blocked buckets from eviction is now - `postqueue -p` output is bounded to 4 MiB (the timeout alone left the size of - [schema-migrations.md](docs/schema-migrations.md): the schema section was - postqueue reader is now time-bounded: `postqueue -p` runs under a 5-second … (11 more in CHANGELOG) Changed: - [roadmap.md](docs/roadmap.md) now carries everything the 2026-08-19 code - Extracted `internal/configsafe` — the empty-value and forbidden-character - Build, deploy and migration comments no longer cite the archived - Dropped the `parseDomainRateLimitForm` / `parseAppRateLimitForm` pass-through - Extracted shared helpers: `internal/atomicfile` (atomic temp-write+rename) and … (7 more in CHANGELOG) Removed: - Unused legacy asset `docs/assets/icon.png` (superseded by the four brand SVGs - Plan documents for already-shipped features: `domain-stats-auto-ratelimit`, Full detail: CHANGELOG.md (1.9.2). |