fix(apps): drop apps-section cap when visible cards already fill the row at full width

Previous cap shrank the box to "exactly N cards at min width", which
made 3 cards sit a few pixels short of the layout edge on a 3-column
viewport while 4 cards (which wraps internally) ran edge-to-edge —
visually inconsistent and the user flagged the gap.

updateAppsCount now measures the parent's available inner width
(minus the section's own 90px overhead: 22 margin + 22 padding + 1
border, doubled) and computes the natural column count the auto-fill
grid would pick at full width. If visible cards >= that count, the
function passes a sentinel (99) as --app-count so the formula
overshoots the 100%-44px parent cap and yields the layout-edge box.
Otherwise the cap still kicks in to hide card-shaped holes for 1-2
cards.

Also wired a window resize listener in the constructor so dragging
the window, snapping it, or opening devtools re-evaluates the
decision — the natural column count is viewport-dependent.

Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-05-28 00:25:23 +01:00
parent 081e15dcf2
commit 39558d82b0

View File

@ -39,6 +39,11 @@ class AppsManager {
constructor() {
this.cache = new Map();
this.setupTaskCompletionListener();
// The dynamic-width box decides cap-vs-full based on the parent's
// current size, so re-run it when the window resizes (drags, snaps,
// devtools open). updateAppsCount is a fast no-op when no
// #apps-section is on screen.
window.addEventListener('resize', () => this.updateAppsCount());
}
setupTaskCompletionListener() {
@ -547,9 +552,11 @@ class AppsManager {
});
}
// Sync --app-count on .apps-section to the number of currently-visible
// cards so the CSS max-width cap shrinks the glass box to exactly the
// cards it holds. Driven from render and from the sidebar search filter.
// Sync --app-count on .apps-section so the CSS max-width formula either
// shrinks the glass box around the visible cards (avoiding a card-shaped
// hole on the right) or — when the visible cards would already fill the
// row at full width — disengages the cap so the box runs edge-to-edge.
// Driven from render, sidebar search filter, and window resize.
updateAppsCount() {
const container = document.getElementById('apps-section');
if (!container) return;
@ -557,7 +564,27 @@ class AppsManager {
container.querySelectorAll('.app-card').forEach(card => {
if (card.style.display !== 'none') visible++;
});
container.style.setProperty('--app-count', Math.max(visible, 1));
visible = Math.max(visible, 1);
// How many columns the grid would naturally lay out at full width.
// If the visible count already meets that, suppress the cap (huge
// sentinel) so the formula gives way to the 100%-44px parent cap and
// the box reaches the layout edge. Without this the "exactly fills
// the row" case (e.g. 3 cards on a 3-col viewport) sits a few pixels
// shy of the edge while N+1 cards jumps straight to full width —
// visually inconsistent.
const style = getComputedStyle(container);
const minCol = parseFloat(style.getPropertyValue('--app-min')) || 300;
const gap = parseFloat(style.getPropertyValue('--app-gap')) || 20;
// Section eats 90px of parent's inner width before any card lands:
// 22px margin + 22px padding + 1px border, doubled.
const parent = container.parentElement;
const inside = parent ? Math.max(0, parent.clientWidth - 90) : 0;
const naturalCols = inside > 0
? Math.max(1, Math.floor((inside + gap) / (minCol + gap)))
: visible;
const effective = visible >= naturalCols ? 99 : visible;
container.style.setProperty('--app-count', effective);
}
// Client-side substring filter wired to the sidebar search box.