panel: align paired domain settings and export actions
test / test (push) Has been cancelled

Subgrid Domain settings rows, match Address mode and trusted-IP controls,
and pin Export/Danger buttons to a shared baseline in split cards.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-13 00:39:55 +03:00
parent 958d07fcdd
commit 8289ed3916
3 changed files with 110 additions and 50 deletions
+3 -2
View File
@@ -11,8 +11,9 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
(`N messages / Ws — Settings`) and in message-limit labels; Settings uses a
code-row for the L1 value. Domain and application rate-limit status uses the
shared `st` badge. Trusted-IP help sits under the IP field; domain rate-limit
help sits above Save; Address mode drops the duplicate field label; field-pair
top spacing aligns with paired DMARC labels.
help sits above Save; Address mode / trusted-IP columns share muted leads and
matched control height; Domain settings uses subgrid so DMARC and rate-limit
rows line up; paired `.split` cards pin primary actions to a shared baseline.
## [1.2.4] - 2026-08-12
+55 -5
View File
@@ -360,6 +360,19 @@ button.danger:hover, a.danger:hover { background: var(--danger-fill-hover); }
.app .actions > .panel .panel-buttons form { margin-top: 0; }
.app .actions > .panel .check-cols { margin-top: 0.6rem; }
.app .actions > .panel .check-col > form { margin-top: 0; }
.app .actions > .panel .check-col > .check-col-title { margin-top: 0; }
.app .actions > .panel .check-col > .muted { margin: 0; }
/* Address mode select ‖ trusted-IP field: same top offset and control height. */
.app .actions > .panel .check-col > .muted + form > select:first-of-type,
.app .actions > .panel .check-col > .muted + form > textarea[name="allowed_ips"] {
margin-top: 0.45rem;
min-height: 2.55rem;
box-sizing: border-box;
}
.app .actions > .panel .check-col > .muted + form > textarea[name="allowed_ips"] {
field-sizing: fixed;
overflow-y: auto;
}
.credential { border-color: var(--credential-border); background: var(--credential-bg); }
/* Panel navigation: rendered once from the layout, so it is present on every
authenticated page without each content template having to include it. */
@@ -538,6 +551,22 @@ meter { width: 5rem; height: 0.7rem; vertical-align: middle; margin-right: 0.4re
the cell to shrink below the card's intrinsic minimum when the column is
tight. */
.split > .card { margin-left: 0; margin-right: 0; min-width: 0; }
/* Paired cards often differ in body length (export's encrypt checkbox vs
danger's short blurb). Stretch both in the grid row and pin the primary
action to the bottom so the buttons share a baseline. */
.split > .card {
display: flex; flex-direction: column;
}
.split > .card > form {
display: flex; flex-direction: column; flex: 1 1 auto;
}
.split > .card > form > button[type="submit"]:last-child {
margin-top: auto;
}
.split > .card > a.danger:last-child,
.split > .card > a.btn:last-child {
margin-top: auto; align-self: flex-start;
}
/* The gap already spaces the cards inside the grid; the general .card + .card
rule would add a second gap's worth on top of it, and only to the second
card, which in one column reads as an uneven stack. The blocks around the
@@ -561,16 +590,37 @@ meter { width: 5rem; height: 0.7rem; vertical-align: middle; margin-right: 0.4re
margin: 1rem 0 0.35rem; font-size: 1.05rem; font-weight: 600;
}
.card > h2 + .check-col-title { margin-top: 0.55rem; }
/* Domain settings pairs DMARC rate limit: share four row tracks so titles,
leads, fields and action buttons line up across columns. */
@supports (grid-template-rows: subgrid) {
.check-cols-rows {
grid-template-rows: auto auto 1fr auto;
}
.check-cols-rows > .check-col {
display: grid;
grid-template-rows: subgrid;
grid-row: span 4;
}
}
.check-cols-rows > .check-col > .check-col-title { margin-top: 0; }
.check-cols-rows > .check-col > .muted { margin: 0; }
.check-col-fields { min-width: 0; }
.check-col-fields > form { margin: 0; }
.check-col-fields .field-pair { margin-top: 0; }
.check-col-fields .field-pair > div > label { margin-top: 0.9rem; }
.check-col-actions {
display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem 0.75rem;
margin-top: 0;
}
.check-col-actions > button { margin-top: 1.2rem; }
.check-col-actions > form.inline { margin: 0; }
.check-col-actions > form.inline > button { margin-top: 1.2rem; }
/* Two short fields on one row (rate-limit message count window). Falls to
one column when the parent is too narrow for both. */
.field-pair {
display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
gap: 0 1rem;
gap: 0 1rem; margin-top: 0.45rem;
}
/* First control row in a check-col form: match a bare label's top margin so
paired columns (DMARC rate limit) line up their first field labels. */
form > .field-pair { margin-top: 0; }
form > .field-pair > div > label { margin-top: 0.9rem; }
.field-pair > div { min-width: 0; }
.field-pair label { margin-top: 0.45rem; }
/* Hint under a field-pair sits tight above the form's submit button (the
+19 -10
View File
@@ -329,6 +329,7 @@
<div class="check-cols">
<div class="check-col">
<p class="check-col-title">Address mode</p>
<p class="muted">Which From addresses this application may use.</p>
<form method="post" action="/applications/{{.ID}}/mode">
<select name="mode" data-list-mode="{{$.List}}" aria-label="Address mode">
<option value="{{$.Wildcard}}" {{if eq .AddressMode $.Wildcard}}selected{{end}}>Any address of the domain</option>
@@ -345,12 +346,14 @@
<div class="check-col">
<p class="check-col-title">Optional trusted-IP override
{{if .HasLimit}}<span class="st st-ok">active</span>{{else}}<span class="st st-unknown">inactive</span>{{end}}</p>
<p class="muted">One per line or comma-separated.</p>
<form id="rl-{{.ID}}" method="post" action="/applications/{{.ID}}/ratelimit">
<label>Trusted client IPs (required; one per line or comma-separated)</label>
<textarea name="allowed_ips" rows="2" placeholder="203.0.113.10">{{.IPsText}}</textarea>
<textarea name="allowed_ips" rows="1" placeholder="203.0.113.10"
aria-label="Trusted client IPs">{{.IPsText}}</textarea>
<p class="muted">These IPs get a higher ceiling than the domain
(≤ level&nbsp;1) and skip the domain check; everyone else uses
{{if $.DomainHasRL}}the domain ceiling ({{$.DomainRLMaxNum}}){{else}}level&nbsp;1 only{{end}}.</p>
the domain level-2 limit{{if $.DomainHasRL}} ({{$.DomainRLMaxNum}}){{end}}
if set, otherwise level&nbsp;1.</p>
<div class="field-pair">
<div>
<label>Message limit (max {{$.L1Messages}})</label>
@@ -390,12 +393,13 @@
<div class="card" id="domain-settings">
<h2>Domain settings</h2>
<div class="check-cols">
<div class="check-cols check-cols-rows">
<div class="check-col">
<p class="check-col-title">DMARC reports</p>
<p class="muted">Default comes from <a href="/settings">Settings</a>;
override per domain here.</p>
<form method="post" action="/domains/{{.Domain.ID}}/dmarc">
<div class="check-col-fields">
<form id="dmarc-domain" method="post" action="/domains/{{.Domain.ID}}/dmarc">
<label for="dmarc_rua_mode">Aggregate reports (rua=)</label>
<select id="dmarc_rua_mode" name="dmarc_rua_mode" data-custom-mode="custom">
<option value="inherit"{{if eq .DMARCRuaMode "inherit"}} selected{{end}}>Same as Settings{{if .ProfileDMARCEmail}} ({{.ProfileDMARCEmail}}){{end}}</option>
@@ -409,17 +413,19 @@
autocapitalize="none" spellcheck="false" value="{{.DMARCRuaCustom}}"
placeholder="reports@your-mail-domain.com">
</div>
<button type="submit">Save DMARC report settings</button>
</form>
</div>
<div class="check-col-actions">
<button type="submit" form="dmarc-domain">Save DMARC report settings</button>
</div>
</div>
<div class="check-col">
<p class="check-col-title">Level-2 rate limit
{{if .DomainHasRL}}<span class="st st-ok">active</span>{{else}}<span class="st st-unknown">inactive</span>{{end}}</p>
<p class="muted">Level&nbsp;1 backstop: {{.L1Messages}} messages / {{.L1Window}}s — <a href="/settings#rate-limits">Settings</a>.</p>
<form method="post" action="/domains/{{.Domain.ID}}/ratelimit">
<div class="check-col-fields">
<form id="rl-domain" method="post" action="/domains/{{.Domain.ID}}/ratelimit">
<div class="field-pair">
<div>
<label for="d_max">Message limit (max {{.L1Messages}})</label>
@@ -433,8 +439,10 @@
</div>
<p class="muted">Applies to every client IP on this domain. Leave the
message limit empty to use level&nbsp;1 only.</p>
<button type="submit">Save limit</button>
</form>
</div>
<div class="check-col-actions">
<button type="submit" form="rl-domain">Save limit</button>
{{if .DomainHasRL}}
<form class="inline" method="post" action="/domains/{{.Domain.ID}}/ratelimit"
data-confirm="Remove the domain rate limit? Only the global level-1 limit will apply.">
@@ -445,6 +453,7 @@
</div>
</div>
</div>
</div>
<div class="split">
<div class="card" id="export">