From 5f04ab523bc518880e4fb6ff2768ed36c4861ec6 Mon Sep 17 00:00:00 2001 From: adam Date: Fri, 9 Oct 2026 22:09:55 +0000 Subject: [PATCH] Separate record groups with lines instead of greying repeated names Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 7 +++++++ internal/web/static/assets/pages/zone.js | 10 +++++++--- internal/web/static/assets/style.css | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55379b1..dfbbf4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ All notable changes to Jarl are documented here. The format follows ## [Unreleased] +### Changed + +- Records table: names are no longer greyed out when they repeat the row + above (it made those records look inactive). Records of the same name + are separated by a faint line instead, and a full line marks where the + next name starts. + ## [0.2.0] - 2026-10-09 ### Added diff --git a/internal/web/static/assets/pages/zone.js b/internal/web/static/assets/pages/zone.js index 221a5ec..efe5add 100644 --- a/internal/web/static/assets/pages/zone.js +++ b/internal/web/static/assets/pages/zone.js @@ -446,9 +446,8 @@ async function recordsTab(el, zone, query) { const draw = () => { const q = filter.value.trim().toLowerCase(); const rows = current.filter((r) => (!q || r.name.includes(q) || r.data.toLowerCase().includes(q) || (r.comment || "").toLowerCase().includes(q)) && (!typeFilter.value || r.type === typeFilter.value)); - let prevName = null; mount(list, table([ - { label: "Name", render: (r) => reverse && addressOf(r.name) ? [r.relative_name, " ", h("span", { class: "muted small" }, addressOf(r.name))] : r.relative_name, tdClass: (r) => { const rep = r.name === prevName; prevName = r.name; return "name" + (rep ? " repeat" : ""); } }, + { label: "Name", render: (r) => reverse && addressOf(r.name) ? [r.relative_name, " ", h("span", { class: "muted small" }, addressOf(r.name))] : r.relative_name, tdClass: "name" }, { label: "TTL", render: (r) => r.ttl !== undefined && r.ttl !== null ? String(r.ttl) : h("span", { class: "muted", title: "zone default" }, String(r.effective_ttl)), tdClass: "mono" }, { label: "Type", render: (r) => h("strong", null, r.type) }, { label: "Data", render: (r) => r.data, tdClass: "data" }, @@ -456,7 +455,12 @@ async function recordsTab(el, zone, query) { { label: "", render: (r) => r.pending === "delete" ? "" : h("span", null, h("button", { class: "small", title: "Edit", onclick: () => edit(r) }, "Edit"), " ", h("button", { class: "small danger", title: "Delete", onclick: () => del(r) }, "Delete")), tdClass: "actions-cell" }, - ], rows, { empty: current.length ? "No records match." : "No records yet.", rowClass: (r) => r.pending ? "pending-" + r.pending : "" })); + ], rows, { empty: current.length ? "No records match." : "No records yet.", rowClass: (r, i) => [ + r.pending ? "pending-" + r.pending : "", + // A record followed by more of the same name: a faint line, so the + // full line marks where the next name starts. + rows[i + 1] && rows[i + 1].name === r.name ? "same-name-next" : "", + ].filter(Boolean).join(" ") })); if (editing) mountEdit(); }; const del = async (r) => { diff --git a/internal/web/static/assets/style.css b/internal/web/static/assets/style.css index 5f630cf..00f405c 100644 --- a/internal/web/static/assets/style.css +++ b/internal/web/static/assets/style.css @@ -141,7 +141,7 @@ th { font-size: 12px; color: var(--muted); font-weight: 600; text-transform: upp tr:hover td { background: var(--info-soft); } td.data { font-family: var(--mono); font-size: 13px; word-break: break-all; } td.name { font-family: var(--mono); font-size: 13px; white-space: nowrap; } -td.name.repeat { color: var(--muted); } +tr.same-name-next td { border-bottom-color: color-mix(in srgb, var(--border) 40%, transparent); } td.actions-cell { white-space: nowrap; text-align: right; width: 1%; } tr.editing td { background: var(--accent-soft); } tr.pending-add td { background: var(--add); }