Grid density: cut the board finer or coarser, per browser #14

Merged
michael merged 3 commits from claude/grid-size into main 2026-09-22 11:45:54 -04:00
2 changed files with 26 additions and 5 deletions
Showing only changes of commit e383070a9e - Show all commits
+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 —
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,
so tiles keep their shape and simply change size. It saves with the
layout, so a named layout comes back at the density it was arranged on.
so tiles keep their shape and simply change size. Going coarser, a tile
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,
red = upstream unreachable (hover it for details). Tiles reconnect
automatically.
+22 -3
View File
@@ -296,8 +296,9 @@ function cellHeightFor(cols) {
}
/** 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;
only a tile now past the right edge is pulled back in. */
board gets denser or coarser and everything on it changes size with it.
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 } = {}) {
const next = normalizeGridCols(value);
const changed = next !== COLS;
@@ -305,9 +306,16 @@ function setGridCols(value, { save = true } = {}) {
grid.style.gridTemplateColumns = `repeat(${COLS}, minmax(0, 1fr))`;
document.documentElement.style.setProperty('--cell-h', cellHeightFor(COLS) + 'px');
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.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);
notifyResize(tile);
}
@@ -386,6 +394,17 @@ function isFree(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. */
function findSpot(w, h) {
for (let y = 0; y < 200; y += 1) {