diff --git a/CHANGELOG.md b/CHANGELOG.md index 47dbd1f..2a4aaaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version ## [Unreleased] +### Changed + +- **A DNS record DNS already holds as shown is folded** (dns-record-collapse): + on an outbound domain's page, an inbound domain's and in Server › + Settings › Notification mail, a *published* record is one line - its + name, its tag and *Show values*, which opens the host, type and value to + copy them again. A record that is missing, weak, wrong or not checked + stays open as before, with *In DNS now* where it differs. A `
` + block, no script; *Re-check DNS* draws the page again, so a record that + has just turned right comes back folded. + ## [2.8.0] - 2026-10-16 The panel signs outgoing mail with DKIM itself, and OpenDKIM - a 2018 beta diff --git a/docs/roadmap.md b/docs/roadmap.md index e1f0265..96bced1 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -40,7 +40,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md). | local-time | The panel shows time, and counts its days, in a time zone other than UTC | agreed (2026-10-13; design questions open) | — | — | | notification-mail-recheck | The Notification mail records on Server › Settings can be checked again from where they are shown | agreed (2026-10-15) | — | — | | settings-save-row | Server › Settings: the Save button no longer stands alone under a long right column | agreed (2026-10-15) | — | — | -| dns-record-collapse | A DNS record that is right is shown folded: its name and its tag, the values on request | agreed (2026-10-15) | — | — | +| dns-record-collapse | A DNS record that is right is shown folded: its name and its tag, the values on request | done 2026-10-16, unreleased | — | — | | go-milter-upstream | Prepare an issue and a PR for go-milter: its header map outlives a message and is unbounded | prepared 2026-10-16, the owner posts | — | [third_party/go-milter/PATCHES.md](../third_party/go-milter/PATCHES.md) | | own-mta-builds | Analysis: build Postfix and OpenDKIM ourselves, patched, instead of Debian's packages | analysed 2026-10-15: no own builds; three rows proposed instead | — | [own-mta-builds-2026-10-15.md](own-mta-builds-2026-10-15.md) | | port25-bare-newline | The inbound relay's port 25 refuses bare newlines in DATA (`smtpd_forbid_bare_newline=normalize`) | shipped `2.6.1` | — | — | @@ -730,6 +730,16 @@ and closes without it under the panel's CSP. Whether a fold remembers being opened across the polls of a page, and whether *Re-check DNS* folds a record that has just turned right, are for the mockup. +**Done (2026-10-16):** a record whose tag is *published* and that has no +*In DNS now* is a `
` whose summary is the head: name, tag and +*Show values* (the eye of the icon subset - it has no chevron), the +values behind it. The head's note is hidden while folded, or a long one +(the server's SPF in a half column) wraps the fold to two lines; it shows +when opened. The two questions answered themselves: no page that draws +records polls them, and *Re-check DNS* posts and draws the page again, so a +fold is never kept across a refresh and a record that has just turned +right comes back folded. + ## notification-mail-recheck **Problem (owner, 2026-10-15):** Server › Settings shows the DNS records of diff --git a/internal/web/view/components.go b/internal/web/view/components.go index 69803ba..7f3aa65 100644 --- a/internal/web/view/components.go +++ b/internal/web/view/components.go @@ -413,6 +413,11 @@ type Record struct { Problem Text } +// Folded says the record is drawn folded to its head: DNS holds it as shown, +// so its values are of use only to copy again. A record that is missing, +// weak, wrong or not checked stays open. +func (r Record) Folded() bool { return r.Status.Status == "published" && r.InDNS == "" } + // HostField is the Copy field of the record's host. func (r Record) HostField() CopyField { return CopyField{Label: "Host / name", Value: r.Host} } diff --git a/internal/web/view/components_test.go b/internal/web/view/components_test.go index 4156206..0d53778 100644 --- a/internal/web/view/components_test.go +++ b/internal/web/view/components_test.go @@ -265,7 +265,7 @@ func TestDNSRecordShowsInDNSNowOnlyOnAMismatch(t *testing.T) { Host: "sp2026._domainkey.example.org", Type: "TXT", Value: "v=DKIM1; p=AAA", ValueRows: 3, })) mustContain(t, ok, - `

DKIM

publishedselector
`, + `

DKIM

publishedselectorShow valuesHide values
`, `
`, ``, `
`, ``) @@ -281,6 +281,28 @@ func TestDNSRecordShowsInDNSNowOnlyOnAMismatch(t *testing.T) { ``) } +// Only a record DNS holds as shown is folded; one that needs work stays open +// with its values in view, and the fold closes where it opened. +func TestDNSRecordFoldsOnlyWhenPublished(t *testing.T) { + for _, status := range []string{"mismatch", "fail", "warn", "unknown"} { + out := squash(partial(t, "dns_record", Record{Name: "SPF", Status: Tag{Status: status}, + Host: "example.org", Type: "TXT", Value: "v=spf1 a -all"})) + mustContain(t, out, `

SPF

`, `value="v=spf1 a -all"`) + mustNotContain(t, out, "`) || !strings.HasSuffix(ok, `
`) { + t.Errorf("a published record is not one
block:\n%s", ok) + } + if strings.Contains(ok, "
+ 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; } @@ -324,7 +335,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; } diff --git a/internal/web/view/templates/components.html b/internal/web/view/templates/components.html index f0fa361..428960a 100644 --- a/internal/web/view/templates/components.html +++ b/internal/web/view/templates/components.html @@ -131,10 +131,14 @@ {{define "health_card"}}{{.Name}}{{.Value}}{{.Sub}}{{end}} {{/* dns_record is one DNS record (input: Record): its status, what to publish - with a Copy button, and "In DNS now" only when it differs. */}} -{{define "dns_record"}}
-

{{.Name}}

{{if .Status.Status}}{{template "tag" .Status}}{{end}}{{if .Note}}{{template "inline" .Note}}{{end}}
-{{if .Host}}
+ with a Copy button, and "In DNS now" only when it differs. A record DNS + already holds as shown is Folded: a
whose summary is the head, + the values behind Show values. */}} +{{define "dns_record"}}{{if .Folded}}
+{{template "dns_record_head" .}}Show valuesHide values +{{else}}
+
{{template "dns_record_head" .}}
+{{end}}{{if .Host}}
{{template "copy_field" .HostField}}
@@ -143,7 +147,8 @@
{{if .Problem}}

{{template "inline" .Problem}}

{{end}}
-{{end}}
{{end}} +{{end}}{{if .Folded}}
{{else}}
{{end}}{{end}} +{{define "dns_record_head"}}

{{.Name}}

{{if .Status.Status}}{{template "tag" .Status}}{{end}}{{if .Note}}{{template "inline" .Note}}{{end}}{{end}} {{/* record_open starts the padded block a record sits in and record_close ends it, for what is not a DNS record but is laid out like one: the fields of a