From fffee9cb5d9aff27a3a854e13be761c1f7aa03b3 Mon Sep 17 00:00:00 2001 From: Mix Date: Fri, 16 Oct 2026 12:21:49 +0300 Subject: [PATCH] design: dns-record-collapse - a published record is folded A record DNS already holds as shown is a
whose summary is the record's head: its name, its tag and "Show values" (ti-eye - the icon subset has no chevron); the host, type and value open behind it. The head's note is hidden while folded, so the fold stays one line in a half column (the server's SPF on Settings); it shows when opened. Missing, weak, mismatched and unchecked records stay open as before. Mockups: the kit's dns_record example, out-domain (DKIM and DMARC folded, SPF open on its mismatch), in-domain (MX), settings (DKIM folded, SPF missing and open); the help's DNS topic says so. Outlines unchanged (the block keeps sp-record). check.py: 0 violations. Co-Authored-By: Claude Opus 5.5 --- docs/assets/panel-redesign/panel/components.html | 8 ++++---- docs/assets/panel-redesign/panel/help.html | 2 +- docs/assets/panel-redesign/panel/in-domain.html | 6 +++--- docs/assets/panel-redesign/panel/out-domain.html | 12 ++++++------ docs/assets/panel-redesign/panel/settings.html | 6 +++--- .../panel-redesign/panel/src/components.html | 8 ++++---- docs/assets/panel-redesign/panel/src/help.html | 2 +- .../assets/panel-redesign/panel/src/in-domain.html | 6 +++--- .../panel-redesign/panel/src/out-domain.html | 12 ++++++------ docs/assets/panel-redesign/panel/src/settings.html | 6 +++--- docs/assets/panel-redesign/panel/theme.css | 14 +++++++++++++- 11 files changed, 47 insertions(+), 35 deletions(-) diff --git a/docs/assets/panel-redesign/panel/components.html b/docs/assets/panel-redesign/panel/components.html index 403bdfa..e424e4a 100644 --- a/docs/assets/panel-redesign/panel/components.html +++ b/docs/assets/panel-redesign/panel/components.html @@ -150,9 +150,9 @@
-
05

dns_record · copy_field

One block per record
-
-

Record

publishedA one-line note
+
05

dns_record · copy_field

One block per record · folded when DNS holds it as shown
+
+

Record

publishedA one-line noteShow valuesHide values
@@ -160,7 +160,7 @@
-
+

Record

mismatch"In DNS now" appears only when it differs
diff --git a/docs/assets/panel-redesign/panel/help.html b/docs/assets/panel-redesign/panel/help.html index 44d12c1..b2a9508 100644 --- a/docs/assets/panel-redesign/panel/help.html +++ b/docs/assets/panel-redesign/panel/help.html @@ -123,7 +123,7 @@
02

Outbound

-

DNS records

DKIM proves the message was signed by this relay, SPF lists the addresses allowed to send for the domain, DMARC tells receivers what to do when both fail and where to report. Publish all three exactly as shown; merge SPF into an existing record rather than adding a second one. A domain's status is the worst of its three records, and results are cached for a few minutes, so use Re-check DNS after publishing. The SPF check is shallow: it looks for this server's address in the record itself and does not follow include: or redirect=, so a record that relies on them is reported as a warning, not a failure. p=none does not affect delivery; tighten to quarantine, then reject, once the reports look clean. A domain's report address is set in its Domain settings.

+

DNS records

DKIM proves the message was signed by this relay, SPF lists the addresses allowed to send for the domain, DMARC tells receivers what to do when both fail and where to report. Publish all three exactly as shown; merge SPF into an existing record rather than adding a second one. A domain's status is the worst of its three records, and results are cached for a few minutes, so use Re-check DNS after publishing. A record DNS already holds as shown is folded to its name and status; Show values opens it to copy them again. The SPF check is shallow: it looks for this server's address in the record itself and does not follow include: or redirect=, so a record that relies on them is reported as a warning, not a failure. p=none does not affect delivery; tighten to quarantine, then reject, once the reports look clean. A domain's report address is set in its Domain settings.

Applications

An application is one SASL login, unique across all domains. It may send as any address of its domain or only as listed addresses, and, with a list of client IPs set, only from those addresses. The password is shown once at creation or regeneration and is not stored in readable form.

Sender check

An application sends only as itself, and that holds for every sender a message names. The envelope sender is held against the login by Postfix. The From: line, the one a mail program shows and the one DMARC holds the DKIM signature to, is held against it by the panel, under the same rule: an application set to any address of its domain may write any address of that domain, and one with a list of addresses only those. A message with any other From: - another of your domains, the server's own name, an address somewhere else, or none at all - is refused for good with 550 5.7.1. The line must hold one address, read as a mail program reads it: what a display name around it says is not judged, and a line holding more than one address is refused. A Sender: line, which a mail program shows beside From: as "on behalf of", is held to the same rule when the message has one. A system that writes for people outside your domains writes its own address in From: with the person's name, and the person's address in Reply-To:: their domain in From: would fail DMARC at the receiver anyway. It is a rejected row of the Outbound log, and its page says which address was refused. This is what keeps one application from sending, under this server's signature, as another domain or another application.

The check is strict. While the panel does not answer, Postfix does not accept mail unchecked: it replies with a temporary error and the application sends again later. Server › Health counts what that costs: for how long the panel did not answer while Postfix itself was running; how many connections Postfix turned away because of it - it refuses from the connection on, before a message is offered, so a client that tries three times is three and a monitor that only connects is one; and how many messages were refused with a temporary error in the middle, when the panel stopped answering after a message had begun. A time marked ≈ includes a start at which supervisord could not say since when Postfix was running: the time since the panel's last sign of life is counted, which may be more than Postfix was up. The card warns when a connection was turned away or a message refused in the last 24 hours. If the numbers stay at nothing, the check is where it should be; if they do not, it belongs in a process of its own.

Rate limits

Level 1 is a per-IP ceiling set in .env. Level 2 is optional, per domain and per application, entered by hand or computed from the last 30 days of sending, and can never exceed level 1. An application's own limit replaces its domain's.

diff --git a/docs/assets/panel-redesign/panel/in-domain.html b/docs/assets/panel-redesign/panel/in-domain.html index a4e42d7..e7315e4 100644 --- a/docs/assets/panel-redesign/panel/in-domain.html +++ b/docs/assets/panel-redesign/panel/in-domain.html @@ -101,8 +101,8 @@
01

MX record

Checked 1 min ago
-
-

MX

publishedKeep any existing primary MX if this is a backup-MX
+
+

MX

publishedKeep any existing primary MX if this is a backup-MXShow valuesHide values
@@ -110,7 +110,7 @@
-
+
diff --git a/docs/assets/panel-redesign/panel/out-domain.html b/docs/assets/panel-redesign/panel/out-domain.html index bb1241b..f9f215b 100644 --- a/docs/assets/panel-redesign/panel/out-domain.html +++ b/docs/assets/panel-redesign/panel/out-domain.html @@ -106,8 +106,8 @@
01

DNS records

Checked 3 min ago
-
-

DKIM

publishedNot a secret · selector sp2026
+
+

DKIM

publishedNot a secret · selector sp2026Show valuesHide values
@@ -115,7 +115,7 @@
-
+

SPF

mismatchMerge into the existing record — never publish a second one
@@ -131,8 +131,8 @@

The published record does not authorize 203.0.113.25. Add ip4:203.0.113.25 before ~all. Shallow check: include: is not followed.

-
-

DMARC

publishedReport address: example.org@dmarc.mail.example.org
+
+

DMARC

publishedReport address: example.org@dmarc.mail.example.orgShow valuesHide values
@@ -141,7 +141,7 @@

p=none does not affect delivery. Tighten to quarantine, then reject, once the reports look clean.

-
+
diff --git a/docs/assets/panel-redesign/panel/settings.html b/docs/assets/panel-redesign/panel/settings.html index b81138e..b92b957 100644 --- a/docs/assets/panel-redesign/panel/settings.html +++ b/docs/assets/panel-redesign/panel/settings.html @@ -111,8 +111,8 @@
04

Notification mail

DNS records of the server's own name
-
-

DKIM

publishedNot a secret · selector sp2026
+
+

DKIM

publishedNot a secret · selector sp2026Show valuesHide values
@@ -120,7 +120,7 @@
-
+

SPF

missingFor the server's own name, not for your domains
diff --git a/docs/assets/panel-redesign/panel/src/components.html b/docs/assets/panel-redesign/panel/src/components.html index 96152b9..bb41e1b 100644 --- a/docs/assets/panel-redesign/panel/src/components.html +++ b/docs/assets/panel-redesign/panel/src/components.html @@ -84,9 +84,9 @@
-
05

dns_record · copy_field

One block per record
-
-

Record

publishedA one-line note
+
05

dns_record · copy_field

One block per record · folded when DNS holds it as shown
+
+

Record

publishedA one-line noteShow valuesHide values
@@ -94,7 +94,7 @@
-
+

Record

mismatch"In DNS now" appears only when it differs
diff --git a/docs/assets/panel-redesign/panel/src/help.html b/docs/assets/panel-redesign/panel/src/help.html index b182baa..732a92a 100644 --- a/docs/assets/panel-redesign/panel/src/help.html +++ b/docs/assets/panel-redesign/panel/src/help.html @@ -57,7 +57,7 @@
02

Outbound

-

DNS records

DKIM proves the message was signed by this relay, SPF lists the addresses allowed to send for the domain, DMARC tells receivers what to do when both fail and where to report. Publish all three exactly as shown; merge SPF into an existing record rather than adding a second one. A domain's status is the worst of its three records, and results are cached for a few minutes, so use Re-check DNS after publishing. The SPF check is shallow: it looks for this server's address in the record itself and does not follow include: or redirect=, so a record that relies on them is reported as a warning, not a failure. p=none does not affect delivery; tighten to quarantine, then reject, once the reports look clean. A domain's report address is set in its Domain settings.

+

DNS records

DKIM proves the message was signed by this relay, SPF lists the addresses allowed to send for the domain, DMARC tells receivers what to do when both fail and where to report. Publish all three exactly as shown; merge SPF into an existing record rather than adding a second one. A domain's status is the worst of its three records, and results are cached for a few minutes, so use Re-check DNS after publishing. A record DNS already holds as shown is folded to its name and status; Show values opens it to copy them again. The SPF check is shallow: it looks for this server's address in the record itself and does not follow include: or redirect=, so a record that relies on them is reported as a warning, not a failure. p=none does not affect delivery; tighten to quarantine, then reject, once the reports look clean. A domain's report address is set in its Domain settings.

Applications

An application is one SASL login, unique across all domains. It may send as any address of its domain or only as listed addresses, and, with a list of client IPs set, only from those addresses. The password is shown once at creation or regeneration and is not stored in readable form.

Sender check

An application sends only as itself, and that holds for every sender a message names. The envelope sender is held against the login by Postfix. The From: line, the one a mail program shows and the one DMARC holds the DKIM signature to, is held against it by the panel, under the same rule: an application set to any address of its domain may write any address of that domain, and one with a list of addresses only those. A message with any other From: - another of your domains, the server's own name, an address somewhere else, or none at all - is refused for good with 550 5.7.1. The line must hold one address, read as a mail program reads it: what a display name around it says is not judged, and a line holding more than one address is refused. A Sender: line, which a mail program shows beside From: as "on behalf of", is held to the same rule when the message has one. A system that writes for people outside your domains writes its own address in From: with the person's name, and the person's address in Reply-To:: their domain in From: would fail DMARC at the receiver anyway. It is a rejected row of the Outbound log, and its page says which address was refused. This is what keeps one application from sending, under this server's signature, as another domain or another application.

The check is strict. While the panel does not answer, Postfix does not accept mail unchecked: it replies with a temporary error and the application sends again later. Server › Health counts what that costs: for how long the panel did not answer while Postfix itself was running; how many connections Postfix turned away because of it - it refuses from the connection on, before a message is offered, so a client that tries three times is three and a monitor that only connects is one; and how many messages were refused with a temporary error in the middle, when the panel stopped answering after a message had begun. A time marked ≈ includes a start at which supervisord could not say since when Postfix was running: the time since the panel's last sign of life is counted, which may be more than Postfix was up. The card warns when a connection was turned away or a message refused in the last 24 hours. If the numbers stay at nothing, the check is where it should be; if they do not, it belongs in a process of its own.

Rate limits

Level 1 is a per-IP ceiling set in .env. Level 2 is optional, per domain and per application, entered by hand or computed from the last 30 days of sending, and can never exceed level 1. An application's own limit replaces its domain's.

diff --git a/docs/assets/panel-redesign/panel/src/in-domain.html b/docs/assets/panel-redesign/panel/src/in-domain.html index 7a1afa1..8f10b19 100644 --- a/docs/assets/panel-redesign/panel/src/in-domain.html +++ b/docs/assets/panel-redesign/panel/src/in-domain.html @@ -35,8 +35,8 @@
01

MX record

Checked 1 min ago
-
-

MX

publishedKeep any existing primary MX if this is a backup-MX
+
+

MX

publishedKeep any existing primary MX if this is a backup-MXShow valuesHide values
@@ -44,7 +44,7 @@
-
+
diff --git a/docs/assets/panel-redesign/panel/src/out-domain.html b/docs/assets/panel-redesign/panel/src/out-domain.html index dccb4fe..a7a8867 100644 --- a/docs/assets/panel-redesign/panel/src/out-domain.html +++ b/docs/assets/panel-redesign/panel/src/out-domain.html @@ -40,8 +40,8 @@
01

DNS records

Checked 3 min ago
-
-

DKIM

publishedNot a secret · selector sp2026
+
+

DKIM

publishedNot a secret · selector sp2026Show valuesHide values
@@ -49,7 +49,7 @@
-
+

SPF

mismatchMerge into the existing record — never publish a second one
@@ -65,8 +65,8 @@

The published record does not authorize 203.0.113.25. Add ip4:203.0.113.25 before ~all. Shallow check: include: is not followed.

-
-

DMARC

publishedReport address: example.org@dmarc.mail.example.org
+
+

DMARC

publishedReport address: example.org@dmarc.mail.example.orgShow valuesHide values
@@ -75,7 +75,7 @@

p=none does not affect delivery. Tighten to quarantine, then reject, once the reports look clean.

-
+
diff --git a/docs/assets/panel-redesign/panel/src/settings.html b/docs/assets/panel-redesign/panel/src/settings.html index 1242104..deac9d2 100644 --- a/docs/assets/panel-redesign/panel/src/settings.html +++ b/docs/assets/panel-redesign/panel/src/settings.html @@ -45,8 +45,8 @@
04

Notification mail

DNS records of the server's own name
-
-

DKIM

publishedNot a secret · selector sp2026
+
+

DKIM

publishedNot a secret · selector sp2026Show valuesHide values
@@ -54,7 +54,7 @@
-
+

SPF

missingFor the server's own name, not for your domains
diff --git a/docs/assets/panel-redesign/panel/theme.css b/docs/assets/panel-redesign/panel/theme.css index b2bbbae..baabf2c 100644 --- a/docs/assets/panel-redesign/panel/theme.css +++ b/docs/assets/panel-redesign/panel/theme.css @@ -212,6 +212,17 @@ code { background: var(--sp-well); color: var(--sp-ink); border-radius: 3px; pad .sp-record-head { display: flex; align-items: center; gap: .6rem; margin-bottom: .75rem; } .sp-record-head h3 { font-size: 1rem; font-weight: 600; margin: 0; } .sp-record-head small { margin-left: auto; color: var(--sp-muted); text-align: right; } +/* A record DNS already holds as shown is folded to its head: a
+ whose summary is the head, opened without script. */ +details.sp-record > summary { list-style: none; cursor: pointer; } +details.sp-record > summary::-webkit-details-marker { display: none; } +details.sp-record:not([open]) > summary { margin-bottom: 0; } +details.sp-record:not([open]) > summary small { display: none; } +details.sp-record[open] .sp-record-head small + .sp-record-toggle { margin-left: 0; } +.sp-record-toggle { margin-left: auto; display: inline-flex; align-items: center; gap: .3rem; color: var(--sp-brick); font-size: var(--sp-fs-s); white-space: nowrap; } +.sp-record-toggle span + span { display: none; } +details.sp-record[open] .sp-record-toggle span { display: none; } +details.sp-record[open] .sp-record-toggle span + span { display: inline; } .sp-record .label, .sp-form .label { font-weight: 500; color: var(--sp-ink-2); margin-bottom: .25rem; } .sp-record .label { font-weight: 400; color: var(--sp-muted); } .input[readonly], .textarea[readonly] { font-family: var(--sp-mono); font-size: var(--sp-fs-s); background: var(--sp-well); box-shadow: none; } @@ -267,7 +278,8 @@ code { background: var(--sp-well); color: var(--sp-ink); border-radius: 3px; pad .sp-health { grid-template-columns: repeat(2, 1fr); } .sp-row, .sp-pair { grid-template-columns: 1fr; } .sp-record-head { flex-wrap: wrap; } - .sp-record-head small { margin-left: 0; text-align: left; flex-basis: 100%; } + .sp-record-head small { margin-left: 0; text-align: left; flex-basis: 100%; order: 1; } + details.sp-record[open] .sp-record-head small + .sp-record-toggle { margin-left: auto; } .sp-postmark { display: none; } .sp-cards { display: flex; flex-direction: column; padding-inline: .75rem; } .sp-cards > .column { display: contents; }