Show the build id, tame long table cells, and add purposeful motion

The build identifier now sits in the sidebar footer. "Is my deploy actually
live?" was only answerable by opening Session & Security, which is the wrong
place for the first question asked after every deploy.

Error-log rows carry stack traces and full URLs. They stretched their row far
past the viewport and pushed the other columns out of view, which is exactly
where the console is least usable. Values over 70 characters truncate and
expand on click.

Motion is limited to two things that mean something: content arriving
(staggered, so a grid resolves instead of snapping), and a fetch being in
flight — the refresh control spins and the status pill pulses, so a section
that legitimately returns nothing still shows that it tried. All of it is
disabled under prefers-reduced-motion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-25 17:49:48 +03:00
co-authored by Claude Opus 5
parent f9110a7d92
commit 3fb7bc5190
3 changed files with 117 additions and 1 deletions
+30 -1
View File
@@ -560,6 +560,8 @@
applyLanguage();
deviceFingerprint = await resolveFingerprint();
if (el.fpPreview) el.fpPreview.textContent = deviceFingerprint.slice(0, 12) + '…';
const stamp = $('buildStamp');
if (stamp) stamp.textContent = `build ${BUILD}`;
buildModules();
setupNavigation();
@@ -3149,13 +3151,23 @@
return wrap;
}
const LONG_CELL = 70;
function cellHtml(column, value) {
if (value === null || value === undefined || value === '') return '<span class="stamp">—</span>';
if (typeof value === 'object') return `<span class="stamp">${esc(JSON.stringify(value).slice(0, 60))}</span>`;
if (/status|type|state|result/i.test(column)) {
return `<span class="badge ${badgeClass(value)}">${esc(labelStatus(value))}</span>`;
}
return esc(formatValue(value, column));
const text = formatValue(value, column);
// Stack traces and URLs would otherwise stretch the row far past the
// viewport and push every other column out of view.
if (String(text).length > LONG_CELL) {
return `<span class="cell-long" title="click to expand">${esc(String(text).slice(0, LONG_CELL))}…</span>
<span class="cell-full" hidden>${esc(String(text))}</span>`;
}
return esc(text);
}
function formatValue(value, column = '') {
@@ -3667,6 +3679,11 @@
};
function refreshView(viewId, moduleId) {
setConnection('loading', t('Loading live data…'));
setTimeout(() => {
if (el.connectionPill?.dataset.state === 'loading') setConnection('live', t('Live database'));
}, 900);
const mod = MODULES.find((m) => m.id === moduleId);
if (mod) {
loadModule(mod, true); // force: bypass the loaded-once cache
@@ -3711,6 +3728,15 @@
});
});
document.addEventListener('click', (e) => {
const cell = e.target.closest('.cell-long');
if (!cell) return;
const full = cell.nextElementSibling;
if (!full?.classList.contains('cell-full')) return;
cell.hidden = true;
full.hidden = false;
});
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
@@ -3748,6 +3774,9 @@
if (!el.connectionPill) return;
el.connectionPill.dataset.state = state;
el.connectionText.textContent = text;
// Visible feedback that a fetch is running: a section with no data still
// needs to show that it tried.
el.refreshBtn?.classList.toggle('is-busy', state === 'loading');
}
function busy(btn, isBusy, label) {