Grid density: a coarser board settles its tiles instead of stacking them

Stepping the grid coarser pulled tiles past the right edge back in and
left them wherever that put them — on top of one another, which the
board never allows under a drag. Tiles now settle in reading order: one
that would land on a tile already settled takes the first free spot, the
same rule a newly added tile follows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Waters Church
2026-09-22 11:26:05 -04:00
co-authored by Claude Fable 5.1
parent 2af7ef27ba
commit e383070a9e
2 changed files with 26 additions and 5 deletions
+4 -2
View File
@@ -277,8 +277,10 @@ below. Nothing is installed without someone clicking.
**Grid** stepper sets how many columns this browser cuts the board into — **Grid** stepper sets how many columns this browser cuts the board into —
8 to 24, 12 by default. Finer lets tiles be smaller so more fit across (a 8 to 24, 12 by default. Finer lets tiles be smaller so more fit across (a
4K wall), coarser suits a small screen read close up; row height follows, 4K wall), coarser suits a small screen read close up; row height follows,
so tiles keep their shape and simply change size. It saves with the so tiles keep their shape and simply change size. Going coarser, a tile
layout, so a named layout comes back at the density it was arranged on. that no longer fits where it was moves to the first free spot rather than
onto another tile. It saves with the layout, so a named layout comes back
at the density it was arranged on.
- Every tile has a **status dot**: green = live, amber = connecting, - Every tile has a **status dot**: green = live, amber = connecting,
red = upstream unreachable (hover it for details). Tiles reconnect red = upstream unreachable (hover it for details). Tiles reconnect
automatically. automatically.
+22 -3
View File
@@ -296,8 +296,9 @@ function cellHeightFor(cols) {
} }
/** Cut the board into `cols` columns. Tiles keep their grid units — the /** Cut the board into `cols` columns. Tiles keep their grid units — the
board gets denser or coarser and everything on it changes size with it; board gets denser or coarser and everything on it changes size with it.
only a tile now past the right edge is pulled back in. */ A tile now past the right edge is pulled back in, and one that would then
sit on another moves to the first free spot instead. */
function setGridCols(value, { save = true } = {}) { function setGridCols(value, { save = true } = {}) {
const next = normalizeGridCols(value); const next = normalizeGridCols(value);
const changed = next !== COLS; const changed = next !== COLS;
@@ -305,9 +306,16 @@ function setGridCols(value, { save = true } = {}) {
grid.style.gridTemplateColumns = `repeat(${COLS}, minmax(0, 1fr))`; grid.style.gridTemplateColumns = `repeat(${COLS}, minmax(0, 1fr))`;
document.documentElement.style.setProperty('--cell-h', cellHeightFor(COLS) + 'px'); document.documentElement.style.setProperty('--cell-h', cellHeightFor(COLS) + 'px');
if (changed) { if (changed) {
for (const tile of tiles) { // A coarser board may not have room for every tile where it was. Settle
// them in reading order: pull a tile past the right edge back in, and
// give one that would then land on a tile already settled the first free
// spot instead — tiles never overlap here, any more than under a drag.
const settled = [];
for (const tile of tiles.slice().sort((a, b) => a.y - b.y || a.x - b.x)) {
tile.w = Math.max(1, Math.min(COLS, tile.w)); tile.w = Math.max(1, Math.min(COLS, tile.w));
tile.x = Math.max(0, Math.min(COLS - tile.w, tile.x)); tile.x = Math.max(0, Math.min(COLS - tile.w, tile.x));
if (settled.some((s) => overlaps(tile, s))) Object.assign(tile, spotAmong(tile.w, tile.h, settled));
settled.push(tile);
applyRect(tile); applyRect(tile);
notifyResize(tile); notifyResize(tile);
} }
@@ -386,6 +394,17 @@ function isFree(rect, excludeId) {
return isFreeExcept(rect, [excludeId]); return isFreeExcept(rect, [excludeId]);
} }
/** First spot, scanning top-to-bottom, left-to-right, clear of these tiles. */
function spotAmong(w, h, others) {
for (let y = 0; y < 400; y += 1) {
for (let x = 0; x <= COLS - w; x += 1) {
const rect = { x, y, w, h };
if (!others.some((o) => overlaps(rect, o))) return { x, y };
}
}
return { x: 0, y: 0 };
}
/** First free spot scanning top-to-bottom, left-to-right. */ /** First free spot scanning top-to-bottom, left-to-right. */
function findSpot(w, h) { function findSpot(w, h) {
for (let y = 0; y < 200; y += 1) { for (let y = 0; y < 200; y += 1) {