feat(library): a shelf that fills in as it reads, and one that carries the studio's look and frame back with it

- tiles land one at a time. The screenful a picked folder asks for was built all
  at once, and a HEIC or a RAW in it is a decode of its own, so the strip stood
  empty until every one of them was done: 2293ms on a roll of eighteen, with the
  batch arriving in one go (0/18 then 18/18 in a single step). Tiles are built in
  two lanes now and each one goes up the moment it is drawn — first picture at
  36ms, then 1, 2, 4, 6, 8, 9, 11, 12, 14, 15, 16, 17, 18 of 18, done at 2496ms.
  The threading is handed back between tiles (`scheduler.yield`), so a click
  still answers while the strip fills: no long tasks either side, worst frame gap
  169ms -> 164ms, a view toggle 49ms -> 41ms. The tiles are the same tiles; the
  wall's is drawn from the same counter.
- the studio's look reaches the shelf. A frame graded in the studio writes the
  tile the shelf draws, at neutral geometry — no turn, crop, watermark or frame,
  since the shelf turns a tile itself — and every screen holding the tile is told
  the moment it is written, so the row repaints without a reload.
- the shelf comes back on the frame the studio was holding. The frame a visit
  opened is kept with the visit, and coming back from the studio raises it: the
  strip centres it and the wall brings its card to the middle, even when the
  frame sits in another folder of the same tree. A shelf that opened on whatever
  it was left standing on sent the reader looking for the frame they had just
  been working on.
- the frame's name stands under the picture, in the studio's own meta bar.
This commit is contained in:
2026-10-09 21:52:38 +07:00
parent 859174d492
commit 7b3086b1db
4 changed files with 177 additions and 28 deletions
+10 -1
View File
@@ -288,6 +288,7 @@ export function ImageStage({
dims = null,
shotSpecs = null,
place = null,
photoName = null,
frameId = 'none',
framePanX = 0,
onFramePanXChange,
@@ -384,6 +385,9 @@ export function ImageStage({
dims?: { w: number; h: number } | null;
shotSpecs?: ShotSpecs | null;
place?: string | null;
// The file the stage is holding, as the disk named it: the row under the photo
// is where a reader checks that the frame they meant to open is the one open.
photoName?: string | null;
frameId?: FrameId;
framePanX?: number;
onFramePanXChange?: (panX: number) => void;
@@ -1998,9 +2002,14 @@ export function ImageStage({
{previewUrl ? (
<>
{shotSpecs || place || dims ? (
{shotSpecs || place || dims || photoName ? (
<div className="photo-meta-bar" data-key="photo-meta-bar">
{specsLine(shotSpecs ?? null, place, dims)}
{photoName ? (
<span className="photo-meta-name" data-key="photo-name">
{photoName}
</span>
) : null}
</div>
) : null}
<div className="toolbar">