diff --git a/ashvale/dashboard.py b/ashvale/dashboard.py
index 7f08852..11b8f55 100644
--- a/ashvale/dashboard.py
+++ b/ashvale/dashboard.py
@@ -259,12 +259,30 @@ DASHBOARD_HTML = r"""
and the tendency chart. At 1280x800 it needed 351 px into 267, so it
clipped its own chart. Same treatment as the stat cards: give the height
back by shrinking the furniture, not by hiding the reading. */
+ /* The nerd tab's two dense panels grow with accumulated history: 18 learner
+ rows and a detector column whose coefficient list lengthens as labels come
+ in. Both silently spilled past their cards on a 13 inch screen. Tightening
+ the line box is worth more here than any font change, because these are
+ one-line-per-fact tables. */
@media (min-width:1024px) and (max-height:920px) {
+ #n-heads table td, #n-heads table th { padding-top:0; padding-bottom:0; line-height:1.25; }
+ #n-heads table { font-size:9px; }
+ #n-rel { row-gap:0; }
+ #n-theta > div { line-height:1.3; }
.cond-cap { display:none; }
.cond-icon svg { width:36px; height:36px; }
.cond-label { padding:0.35rem 0.5rem; }
}
@media (min-width:1024px) and (max-height:830px) {
+ /* The tendency chart's floor is what overflows once everything above it has
+ already been trimmed: a hard 42 px min-height cannot flex, so it pushes
+ 13 px past the card at 1024x768. Lower the floor rather than removing it,
+ so the chart stays a chart. */
+ /* html prefix for specificity: the Tailwind CDN injects its sheet after this
+ block, so min-h-[42px] wins at equal weight and the override did nothing. */
+ html .cond-tend { min-height:26px; }
+ /* Attribution rows overshot by 4 px, which one notch of leading covers. */
+ #n-theta > div { line-height:1.15; }
.cond-icon svg { width:30px; height:30px; }
/* 1366x768 is the most common laptop resolution in the world, so it has to
fit rather than nearly fit. These four give back about 36 px. */
@@ -625,7 +643,10 @@ DASHBOARD_HTML = r"""
').join('')
- || 'Nothing logged yet.';
+ // The card is overflow-hidden, so anything past its height was simply
+ // invisible: measured, up to 299 px of entries were being silently discarded
+ // at 1280x800. Render what fits and say how many are not shown, rather than
+ // pretending the list ends there.
+ // Rendered through a ResizeObserver rather than once. Measuring at call time
+ // gets a stale clientHeight: the pane has only just become visible and
+ // Chart.js resizes its siblings 40 ms later, which measured 13 rows into a box
+ // that ends up fitting 10. Observing the box means it also re-fits on window
+ // resize and on a theme change, with no timers to guess at.
+ logEvents = st.events || [];
+ trimLog();
const an = await fetch('/api/anomaly').then(r=>r.json());
el('e-health').innerHTML = Object.keys(an.health||{}).map(k=>{
@@ -1619,7 +1645,7 @@ async function loadNerd() {
'
σ '+k.slice(0,4)+''+fmt(cl.residual_std[k],3)+'
').join('');
const pr = d.precipitation||{};
- const co = (pr.coefficients||[]).slice().sort((a,b)=>Math.abs(b.weight)-Math.abs(a.weight)).slice(0,4);
+ const co = (pr.coefficients||[]).slice().sort((a,b)=>Math.abs(b.weight)-Math.abs(a.weight)).slice(0,3);
el('n-precip').innerHTML =
'