PR-C1: adaptive video grid sizing (foundation) #2

Closed
hudson wants to merge 18 commits from flotilla-sph-adaptive-grid into dev
Collaborator

Foundation for the adaptive tiling rework. Behavior-preserving grid sizing.

  • Adds computeGridSize helper in src/app/call.ts: given tile count + container dimensions, returns rows/cols maximizing tile area with ~16:9 cells (smooth 1–12+ tiles).
  • Applies computed grid-template-columns/rows on the multi-grid (no-spotlight) path in VideoCallContent.svelte, replacing the fixed >2 tiles / grid-cols-2 heuristic.
  • Container size measured via bind:clientWidth/clientHeight.

Scope: multi-grid path only. Spotlight strip, screen-share selection, and device breakpoints are C2/C3/C4.

Bead: flotilla-sph. Tester verified via logic simulation (1/2/3/4/6/12 tiles, desktop + mobile aspects); lint/format pass, check shows only pre-existing welshman errors.

Foundation for the adaptive tiling rework. Behavior-preserving grid sizing. - Adds `computeGridSize` helper in `src/app/call.ts`: given tile count + container dimensions, returns rows/cols maximizing tile area with ~16:9 cells (smooth 1–12+ tiles). - Applies computed `grid-template-columns/rows` on the multi-grid (no-spotlight) path in `VideoCallContent.svelte`, replacing the fixed `>2 tiles / grid-cols-2` heuristic. - Container size measured via `bind:clientWidth/clientHeight`. Scope: multi-grid path only. Spotlight strip, screen-share selection, and device breakpoints are C2/C3/C4. Bead: flotilla-sph. Tester verified via logic simulation (1/2/3/4/6/12 tiles, desktop + mobile aspects); lint/format pass, check shows only pre-existing welshman errors.
hudson added 1 commit 2026-08-05 18:46:41 +00:00
Add computeGridSize helper in call.ts that computes optimal row/column
counts for a video tile grid given tile count and container dimensions,
maximizing tile size while maintaining ~16:9 cells.

Apply via computed grid-template-columns/rows on the multi-grid path
(no-spotlight) in VideoCallContent.svelte, replacing the fixed
grid-cols-1 / grid-cols-1 sm:grid-cols-2 heuristic. The multi-grid now
handles all tile counts from 1 to 12+ smoothly.

- computeGridSize(tileCount, containerWidth, containerHeight) -> GridSize
- Container dimensions bound via clientWidth/clientHeight
- useMultiGrid: dropped the > 2 check, now !useSpotlightLayout
- gridStyle: computed grid-template-columns/rows string from gridSize
- Scope: multi-grid only, no spotlight/strip/breakpoint changes
matt requested changes 2026-08-11 13:55:52 +00:00
matt left a comment
Owner

@hudson I want to take a different approach here, what's here doesn't seem to make the best use of space and it ends up with very horizontally stretched video at some points.

I want try try what jitsi meet does. Their app is open source, maybe you can read their code. But it seems like what they are doing is allowing video tiles to size between 16:9 and 9:16 depending on the layout. They do a good job filling whitespace by even using different sized tiles in different rows. For example if there are 3 participants the first row has two square tiles and the bottom row has a single centered 16:9 tile. Take a crack at that.

@hudson I want to take a different approach here, what's here doesn't seem to make the best use of space and it ends up with very horizontally stretched video at some points. I want try try what jitsi meet does. Their app is open source, maybe you can read their code. But it seems like what they are doing is allowing video tiles to size between 16:9 and 9:16 depending on the layout. They do a good job filling whitespace by even using different sized tiles in different rows. For example if there are 3 participants the first row has two square tiles and the bottom row has a single centered 16:9 tile. Take a crack at that.
hudson added 1 commit 2026-08-11 14:13:08 +00:00
Replaces the uniform grid-sizing helper (computeGridSize -> rows/cols)
with a Jitsi-inspired mixed-size layout algorithm (computeTileLayout).

Key changes:
- Tiles flex between 9:16 and 16:9 aspect ratio per row, matched to
  container dimensions
- Rows can have different tile counts (e.g. [2,1] for 3 participants,
  [3,2] for 5, [4,3] for 7)
- Last row with fewer tiles gets a wider (more landscape) aspect ratio
  while earlier rows stay more square-ish
- CSS grid replaced with flex rows; each tile's aspect-ratio set via
  inline style (no more hardcoded aspect-video)
- Container dimensions bound via bind:clientWidth/clientHeight
- Dead {:else} branch (always-true useMultiGrid) removed
- Overflow penalty, whitespace penalty, and balance penalty ensure
  reasonable layouts at all tile counts

Per @matt's PR review feedback: read jitsi-meet source
(react/features/video-layout/functions.web.ts,
react/features/filmstrip/functions.web.ts) for the tile sizing
approach.
hudson added 1 commit 2026-08-11 14:21:36 +00:00
Bug 1 — chicken-and-egg gating: '{:else if useMultiGrid && tileLayout}'
prevented the measuring container from ever mounting, because tileLayout
is undefined while gridWidth/gridHeight are 0, and the bind:clientWidth/
clientHeight are on the same div. Fix: always mount the measuring
container when useMultiGrid, gate only the rows with {#if tileLayout}.

Bug 2 — collapsed tile height: 'default' tile class lost aspect-video but
gained no replacement for intrinsic height. All children are absolutely
positioned (VideoCallTile 'absolute inset-0', profile fallback, badges,
label, pin button), so the div collapses to 0 height. Fix: add 'h-full'
to the default tile class so it fills the aspect-ratio wrapper.
hudson added 1 commit 2026-08-11 20:25:58 +00:00
Replaces the Jitsi-style mixed-row layout (~190 lines: partitions,
mixed-size rows, per-row aspect, penalty scoring) with a simple uniform
grid (~25 lines): all tiles identical px size, aspect flexes within
[3:4 .. 16:9], column count chosen to maximize area via key ordering
(overflow < waste < aspect closeness < -area).

Key changes:
- computeUniformGrid / UniformTileGrid replaces computeTileLayout/TileLayout
- TILE_ASPECT_MIN widened from 9/16 to 3/4 per @matt
- CSS grid with explicit px column/row tracks replaces flex rows
- mx-auto centers the grid block; orphan rows have same-size tiles
  (never full-width giants)
- Outer measuring container always mounted (bind:clientWidth/Height);
  inner grid gated on tileGrid
- Each tile wrapper: overflow-hidden rounded-2xl (grid cell sizes it)
- 'default' tile variant keeps min-h-0 h-full w-full (fills grid cell)

Per @matt's approved plan v2: flex-fill single rule everywhere, no
portrait special-casing, no mixed-size rows, uniform tiles, centered
block.
hudson added 1 commit 2026-08-11 20:30:53 +00:00
CSS grid + mx-auto centers the whole block but not orphan tiles within
their row (e.g. 5 tiles @1685x1342, the 2-tile row was left-aligned).
Switched to display:flex; flex-wrap:wrap; justify-content:center with
explicit width/height on each tile wrapper. This naturally centers every
row, including orphan rows with fewer tiles.

The computeUniformGrid algorithm is unchanged — still uniform px tile
sizes with aspect flexing in [3:4..16:9].
hudson added 1 commit 2026-08-11 20:35:20 +00:00
hudson added 1 commit 2026-08-11 20:51:32 +00:00
1. Array-comparison bug (CRITICAL): 'key < bestKey' coerces arrays to
   comma-joined strings, so [1668,...] < [23,...] is TRUE ('1'<'2').
   Replaced with explicit numeric lexicographic compare (compareKeys).
   Root cause of @matt's report: n=2 @1920x500 picked 1x2 stacked with
   1668px overflow instead of 2x1 side-by-side fitting nearly exactly.

2. <MIN branch bug (CRITICAL): fillAspect < 3/4 computed tileW = rowH *
   TILE_ASPECT_MIN which ALWAYS exceeds widthLimit (the column width),
   producing tiles wider than the container. Fix: tileW = widthLimit;
   tileH = widthLimit / TILE_ASPECT_MIN (letterbox vertically, fits).

3. Missing horizontal overflow scoring: added hOverflow to the key
   (after vOverflow) as belt-and-braces guard.
matt requested changes 2026-08-12 13:28:32 +00:00
matt left a comment
Owner

There is still a major bug with this. When enabling the second video the tile for the second video is rendered half off screen. We should only require scrolling when the tiles reach some unacceptable minimum size. Somewhere around 6 or 8 tiles on a standard portrait sized phone? Try again.

There is still a major bug with this. When enabling the second video the tile for the second video is rendered half off screen. We should only require scrolling when the tiles reach some unacceptable minimum size. Somewhere around 6 or 8 tiles on a standard portrait sized phone? Try again.
src/app/call.ts Outdated
@ -34,0 +37,4 @@
const TILE_GAP = 8
/**
* Uniform-size tile grid: all tiles are identical px dimensions, arranged
Owner

It's not really a uniform grid is it? It's a grid where the last row is centered. Uniform grid makes me think of a screen door.

It's not really a uniform grid is it? It's a grid where the last row is centered. Uniform grid makes me think of a screen door.
src/app/call.ts Outdated
@ -34,0 +69,4 @@
*/
export const computeUniformGrid = (
tileCount: number,
cw: number,
Owner

use real words here, not abbreviations

use real words here, not abbreviations
src/app/call.ts Outdated
@ -34,0 +102,4 @@
tileH = rowH
}
const bboxW = cols * tileW + (cols - 1) * TILE_GAP
Owner

prefer full words in these vars too: boundingBoxWidth, verticalOverflow, etc.

prefer full words in these vars too: boundingBoxWidth, verticalOverflow, etc.
src/app/call.ts Outdated
@ -34,0 +115,4 @@
-Math.round(tileW * tileH),
]
if (!bestKey || compareKeys(key, bestKey) < 0) {
Owner

I don't like that we are calling these "keys". Can we come up with a more semantically meaningful name?

I don't like that we are calling these "keys". Can we come up with a more semantically meaningful name?
hudson added 1 commit 2026-08-12 13:43:25 +00:00
Complete rewrite of the tile grid sizing algorithm addressing all
review feedback from @matt across both reviews.

Review #1 (Jitsi-style approach):
- Tiles flex between 3:4 (portrait) and arbitrarily wide (no upper
  clamp on aspect ratio) to avoid overflow.
- Mixed row sizes: when the last row is partial, those tiles are
  wider (16:9 'match heights') while full rows use square-ish
  tiles, creating a cohesive Jitsi-style look.
- Example: 3 participants = 2 square-ish tiles on top + 1 centered
  16:9 tile on bottom.

Review #2 (n=2 half-off-screen bug + minimum size policy):
- FIX: No upper-bound clamping on aspect ratio - tiles can be wider
  than 16:9 when the container is very wide. At n=2 on a wide desktop,
  tiles are 16:9 (889x500) with side whitespace instead of overflowing.
- MIN_TILE_HEIGHT=120: only allows overflow (scroll) when all tiles
  fall below this threshold. At n=2 tiles are always fully visible.
- For n=6-12 on a portrait phone, tiles shrink to fit within aspect
  bounds with no scroll.

Inline code review feedback:
- Full words everywhere: containerWidth, columnCount, tileWidth,
  tileHeight, aspectRatio, etc.
- No 'key' naming: replaced with LayoutScore struct with named fields
- Type renamed from UniformTileGrid to AdaptiveTileGrid + TileRow
- No 'compareKeys' - replaced with compareScores on named fields

Scoring: overflow penalty -> min-height penalty -> tile area ->
whitespace -> aspect deviation.
hudson added 1 commit 2026-08-12 13:49:28 +00:00
matt added 16 commits 2026-08-12 17:24:11 +00:00
Co-authored-by: Aditya Chaudhary <30+useradityaa@noreply.coracle.social>
Co-authored-by: Aditya Chaudhary <30+useradityaa@noreply.coracle.social>
Co-authored-by: Aditya Chaudhary <30+useradityaa@noreply.coracle.social>
Co-authored-by: Aditya Chaudhary <30+useradityaa@noreply.coracle.social>
Co-authored-by: Aditya Chaudhary <30+useradityaa@noreply.coracle.social>
- flex-nowrap on grid rows prevents subpixel rounding from wrapping the
  second tile onto a new row (caused 'half off screen' on desktop)
- flex flex-col on mobile wrapper gives the grid container a constrained
  height so it renders at narrow viewport widths
- minAspect=1:1 (square) on landscape containers, 3:4 on portrait
- scrollbar-gutter:stable on grid prevents scrollbar feedback loop
- Lower landscape breakpoint from width>height to width/height>=4:3
- fitTile helper caps tiles at 16:9 and shrinks to fit both dimensions,
  preventing overflow on ultrawide containers
- Mixed candidate uses uniform tile sizes for all rows; partial rows
  are centered with the same tile dimensions as full rows
matt added 1 commit 2026-08-12 17:27:30 +00:00
- Unify buildUniformRow + buildMixedCandidate into single buildCandidate
- Remove dead overflows field from AdaptiveTileGrid
- Add rowWidth to TileRow to eliminate magic 8 in template
- Simplify scoring: all rows uniform, no need for Math.min/reduce
- Fix stale comments referencing old mixed-size behavior
Owner
Edited and opened manually at https://gitea.coracle.social/coracle/flotilla/pulls/355
matt closed this pull request 2026-08-13 19:04:30 +00:00
matt deleted branch flotilla-sph-adaptive-grid 2026-08-13 19:04:35 +00:00

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: matt/flotilla#2
No description provided.