fix: wrap per-host status line so 320px viewport does not scroll (closes #42)
Some checks failed
check / check (push) Has been cancelled

This commit was merged in pull request #50.
This commit is contained in:
2026-08-10 16:07:28 +02:00
parent 25a852d35c
commit 852a11eec2
2 changed files with 40 additions and 19 deletions

View File

@@ -22,6 +22,8 @@ files, so merging it also closes most compliance gaps.
# Completed Steps
- 2026-08-10: per-host status line wraps below the 768px breakpoint instead of
forcing horizontal page scroll at 320px
- 2026-08-09: `Dockerfile.backend` reworked to the mandated Go multistage
lint-stage pattern: separate `lint` stage on the hash-pinned
`golangci/golangci-lint` image, `COPY --from=lint` stage dependency,

View File

@@ -537,6 +537,12 @@ class SparklineRenderer {
// --- UI Renderer -------------------------------------------------------------
// The per-host status line must stay wrappable: its populated content is
// wider than the host column at a 320px viewport, and `whitespace-nowrap`
// here overflows the element and forces the whole document to scroll
// horizontally.
const STATUS_TEXT_CLASS = "status-text text-xs text-right col-span-2 mt-5";
function hostRowHTML(host, index, showPin = true) {
const pinColor = host.pinned
? "text-blue-500"
@@ -562,7 +568,7 @@ function hostRowHTML(host, index, showPin = true) {
<span class="text-gray-500">---</span>
</div>
<a href="${host.url}" target="_blank" rel="noopener" class="text-xs text-gray-500 truncate block col-span-2 -mt-2">${host.url}</a>
<div class="status-text text-xs text-gray-500 whitespace-nowrap text-right col-span-2 mt-5" data-host="${index}">waiting...</div>
<div class="${STATUS_TEXT_CLASS} text-gray-500" data-host="${index}">waiting...</div>
</div>
<div class="flex-grow sparkline-container rounded overflow-hidden border border-gray-700/30">
<canvas class="sparkline-canvas w-full" data-host="${index}" height="${CONFIG.canvasHeight}"></canvas>
@@ -688,6 +694,26 @@ function buildUI(state) {
// --- UI Updaters -------------------------------------------------------------
// Renders `min 1ms / med 2ms / avg 3ms / max 4ms`. Each label, value and
// trailing separator is one unbreakable unit, so wrapping only ever happens
// between stats and a wrapped line never starts with a separator.
function statusStatsHTML(stats) {
return stats
.map(([label, value], i) => {
const sep =
i < stats.length - 1
? ` <span class="text-gray-500">/</span>`
: "";
return (
`<span class="whitespace-nowrap">` +
`<span class="text-gray-400">${label} </span>` +
`<span class="${latencyClass(value, "online")}">${value}ms</span>` +
`${sep}</span>`
);
})
.join(" ");
}
function updateHostRow(host, index) {
const latencyEl = document.querySelector(
`.latency-value[data-host="${index}"]`,
@@ -712,28 +738,22 @@ function updateHostRow(host, index) {
const min = host.minLatency();
const max = host.maxLatency();
if (host.status === "online" && avg !== null) {
statusEl.innerHTML =
`<span class="text-gray-400">min </span><span class="${latencyClass(min, "online")}">${min}ms</span>` +
` <span class="text-gray-500">/</span> ` +
`<span class="text-gray-400">med </span><span class="${latencyClass(med, "online")}">${med}ms</span>` +
` <span class="text-gray-500">/</span> ` +
`<span class="text-gray-400">avg </span><span class="${latencyClass(avg, "online")}">${avg}ms</span>` +
` <span class="text-gray-500">/</span> ` +
`<span class="text-gray-400">max </span><span class="${latencyClass(max, "online")}">${max}ms</span>`;
statusEl.className =
"status-text text-xs whitespace-nowrap text-right col-span-2 mt-5";
statusEl.innerHTML = statusStatsHTML([
["min", min],
["med", med],
["avg", avg],
["max", max],
]);
statusEl.className = STATUS_TEXT_CLASS;
} else if (host.status === "offline") {
statusEl.textContent = "unreachable";
statusEl.className =
"status-text text-xs text-red-400 whitespace-nowrap text-right col-span-2 mt-5";
statusEl.className = `${STATUS_TEXT_CLASS} text-red-400`;
} else if (host.status === "error") {
statusEl.textContent = "timeout";
statusEl.className =
"status-text text-xs text-orange-400 whitespace-nowrap text-right col-span-2 mt-5";
statusEl.className = `${STATUS_TEXT_CLASS} text-orange-400`;
} else {
statusEl.textContent = "connecting...";
statusEl.className =
"status-text text-xs text-gray-500 whitespace-nowrap text-right col-span-2 mt-5";
statusEl.className = `${STATUS_TEXT_CLASS} text-gray-500`;
}
SparklineRenderer.draw(canvas, host.history);
@@ -1044,8 +1064,7 @@ function greyOutUI(state) {
}
if (statusEl) {
statusEl.textContent = "paused";
statusEl.className =
"status-text text-xs text-gray-500 whitespace-nowrap text-right col-span-2 mt-5";
statusEl.className = `${STATUS_TEXT_CLASS} text-gray-500`;
}
// Grey out the status dot
const row = document.querySelector(`.host-row[data-index="${i}"]`);