UI: per-field DNS record copy + manual recheck button
closedProblem
The domain DNS page (internal/appview/ui/domain.templ) shows each record
as one pre-formatted zonefile string (name TYPE content) with a single
Copy button copying the whole line. Users configuring DNS through a web
form (type dropdown + separate name/content fields) must hand-split the
string. There is also no way to trigger a recheck — users wait blindly on
the 5-minute background verifier.
Proposal
- Replace
DNSView’s three preformatted strings with a[]DNSRecordView{Type, Name, Content}list (structured data already exists viagetDnsState/dnsprober.expectedRecords; only the UI flattens it). - Render a table: Type badge | Name (click-to-copy) | Content
(click-to-copy, DKIM truncated with expand). Reuse the generic
data-copyJS helper instatic/app.js. - Add
POST /domains/{id}/recheck: build adnsprober.Targetfrom store- mail defaults, call
prober.Verify, persist the status, redirect with a flash (activevsstill verifying — checked just now, auto-rechecks every 5m).
- mail defaults, call
Files
internal/appview/ui/view.go(DNSView),handler.go(buildDNS),domain.templ,static/app.js(only if helper needs work)- Tests: extend
ui_test.go(render + recheck handler with stub prober)
Acceptance
- Each Type/Name/Content field copies individually on click.
- Recheck button reports live state without waiting for the background sweep.
2 Comments
Addendum: collapse DNS when active + last-verified (bundled scope)
Once a domain is
active, keep listing the records is confusing. Extend this issue:verified_atto the domain table in the fresh schema (decided 2026-09-11: no ALTER migration — dev and smoke tables get blown away and redeployed; get the table design right instead of maintaining backwards compat). Update it in verifiersweepOneand in the manual recheck handler from this issue.domain.templ: whenStatus == active, collapse records into<details>showing “Active — last verifiedImplementation complete (all in working copy, uncommitted)
Subagent-driven execution of the plan, full suite
SOVRN_INTEGRATION=0 go test -count=1 ./...green.Store (
store.go,sqlite.go):Domain.VerifiedAt *time.Time;mail_domains.verified_at TEXTnullable in fresh DDL only (no ALTER);UpdateDomainStatus(ctx,id,status,verifiedAt)writes both atomically; all SELECT/INSERT/SCAN round-trip it.sqlite_test.gocovers nil→value→nil.Verifier + provision:
sweepOnestampss.now().UTC()on activation;ProvisionDomainseeds VerifiedAt when state==active. Unready path untouched. Tests assert non-nil on activate, nil when still verifying.DNSView (
view.go,handler.gobuildDNS):[]DNSRecordView{Type,Name,Content}— MX/SPF/DKIM with rdata-only Content.domain.templ: unverified = full Type|Name|Content table (Name/Content eachdata-copy, FULL values, JS-disabled selectable); active = collapsed<details>“Active — last verifiedRecheck (
handler.go):POST /domains/{id}/recheck— Verify from store+mail defaults,domain.Enableon verifying→active only (nil-Stalwart guard for tests),UpdateDomainStatuswith fresh now on EVERY active result (so re-rechecking active bumps last-verified), 303?msg=activevs?msg=still verifying — checked just now, auto-rechecks every 5m. No demotion on still-verifying (matches verifier semantics). Tests: activates, still-verifying, active-bumps-timestamp.Dev DB:
data/dev/sovrn.db*deleted (old schema confirmed lacking the column); freshstore.Openverified to createverified_at+ round-trip. Nextjust devboot recreates it. If orphaned Stalwart domains collide, runsovrn-reset.Known nits (accepted): flash
q()helper leaves em-dash unescaped in Location (pre-existing pattern); still-verifying persists nothing by design.