web: a frame is scored under the picture, and the wall is read through the score
The catalogue could say what a frame was and when it was shot, and nothing about
whether it was any good. A reader who had been through a roll of two thousand
had no way to say "these four", and the thumbnail wall drew every frame the open
folder held in the one order it had: newest shutter first, always.
The frame that is up now carries its own score — five stars under the picture,
and the star the click lands on is the score it gets. The star it already has
takes the score back, so a score that was given by mistake is taken off without
a sixth control. It is the catalogue's own row and nothing on the disk is
touched: the file is not written and not read, the browser only stores one more
number against a frame it already holds, and the number is the reader's.
The wall is read through three of them. The score is the first — four stars and
up is the floor, any rating is the wall as it was — because that is what a
reader who has just been through a roll wants next. The year the shutter fired
in is the second, offered as the years the open folder actually holds and not a
century of empty ones. The hours it fired in are the third, as a from and a to:
16:00 to 18:00 is the afternoon the reader was out, and either end alone is
"from 16:00" or "up to 18:00". All three read the shutter time, on this
machine's own clock — the same reading the frame's date line prints, so the
afternoon is the afternoon and not an offset of it. Beside them, the order: the
newest shutter first, the oldest first, or the most stars first.
What the filters hold is the wall and only the wall. The strip under the picture
is the shelf the open folder is, and a shelf that quietly loses three quarters
of its frames is not a shelf any more; the frame that is up has to stay reachable
while the wall is narrowed around it. That is also why the filters are drawn in
the thumbnail view: they are a way of looking through the shelf, not a property
of the roll, and nothing about them outlives the visit.
Verified:
library-check.mjs — 52 steps, all passed, five of them new. The frame that is
up is scored from the row under it and the catalogue holds star 4 against it
(aria-pressed on the fourth star true); the wall narrows to one tile at 4★,
to none at 5★, and back to three with the rating cleared; the year 2016 holds
the two JPEGs of three and 16:00–18:00 the same two, out of a roll whose
frames are written years apart in different parts of the day (the JPEG in the
afternoon of 15 Feb 2016, the RAW at seven in the morning of 1 Jan 2026);
read by score, the scored frame comes first. Every earlier step still holds,
including the two that count what a reading costs and the stand-in folder's
two frames — the JPEG carrying the GPS tag and the RAW printing
"26.4mm · f/2.8 · 1/320s".
scan-nav-check.mjs, roll-walk-check.mjs — all passed. frontend tsc --noEmit
clean, vite build clean.
ponytail: the score is one number on the frame's own row — no rating table, no
votes, no accounts; the catalogue is a per-browser shelf and so is the score. The
filters live in the thumbnail view's own state, so a reload starts with the whole
shelf again, which is what a filter is for. No text search: a roll of date folders
is three levels deep at most, and the three that a reader actually digs with are
the ones here — add the box when a folder of mixed names makes one worth typing
into.
Co-authored-by: PenguinHarness <noreply@penguin.local>
This commit is contained in:
@@ -198,8 +198,13 @@ await context.addInitScript(
|
||||
type: name.endsWith('.JPG') ? 'image/jpeg' : 'application/octet-stream',
|
||||
// A real handle reports one write time for one file, and the same one
|
||||
// on every read; a fresh `Date.now()` per call would make every frame
|
||||
// look touched and hide what is being checked.
|
||||
lastModified: Date.UTC(2026, 0, 1),
|
||||
// look touched and hide what is being checked. The two frames are
|
||||
// written years apart, in different parts of the day — the JPEG in the
|
||||
// afternoon of 15 Feb 2016 and the RAW at seven in the morning of
|
||||
// 1 Jan 2026 — because the shelf's own filters are read off the
|
||||
// shutter time: a year, and an hour range. Both are UTC and read back
|
||||
// on this machine's clock, which is UTC+7.
|
||||
lastModified: name.endsWith('.JPG') ? Date.UTC(2016, 1, 15, 9, 30) : Date.UTC(2026, 0, 1),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -311,6 +316,8 @@ const catalogue = () =>
|
||||
id: photo.id,
|
||||
bytes: photo.thumb?.size ?? 0,
|
||||
jpeg: head ? head[0] === 0xff && head[1] === 0xd8 : false,
|
||||
// The score the reader gave it, 0 when they have given none.
|
||||
star: photo.star ?? 0,
|
||||
});
|
||||
}
|
||||
return { thumbs, edits: edits.map((e) => ({ id: e.photoId, filter: e.recipe?.baseFilter, knobs: Object.keys(e.recipe?.adjustments ?? {}).length })) };
|
||||
@@ -693,6 +700,60 @@ await page.waitForTimeout(300);
|
||||
const walked = await page.$eval('[data-key="lib-strip"]', (el) => el.scrollLeft);
|
||||
step('a wheel tick walks the strip along', shelf.sw > shelf.cw && walked > 0, `${shelf.sw}px of tiles in ${shelf.cw}px, scrolled to ${walked}`);
|
||||
|
||||
// The reader's own score on a frame, and the wall read through it. The score is
|
||||
// given from the row under the picture and nothing else has to be opened or
|
||||
// visited to see it; the wall is then narrowed by that score, by the year the
|
||||
// shutter fired and by the hours it fired in. The check's frames were written so
|
||||
// that all three answer something: the JPEG in the afternoon of 15 Feb 2016, the
|
||||
// RAW at seven in the morning of 1 Jan 2026.
|
||||
await page.setViewportSize({ width: 1280, height: 720 });
|
||||
await page.click(`[data-key="lib-thumb-${jpgId}"]`);
|
||||
await page.click('[data-key="lib-star-4"]');
|
||||
const starred = (await catalogue()).thumbs.find((t) => t.id === jpgId)?.star;
|
||||
const lit = await page.$eval('[data-key="lib-star-4"]', (e) => e.getAttribute('aria-pressed'));
|
||||
step('a frame is given a score from the row under it', starred === 4 && lit === 'true', `catalogue ${starred}, 4th star ${lit}`);
|
||||
|
||||
await page.click('[data-key="lib-view-grid"]');
|
||||
await page.waitForSelector('[data-key^="lib-photo-"]', { timeout: 30_000 });
|
||||
// Each filter is set on its own and put back, so what the wall holds is read
|
||||
// after one choice and never after two.
|
||||
const wall = () => page.$$eval('[data-key^="lib-photo-"]', (els) => els.length);
|
||||
const setFilter = async (key, value) => {
|
||||
await page.selectOption(`[data-key="${key}"]`, value);
|
||||
await page.waitForTimeout(150);
|
||||
};
|
||||
await setFilter('lib-filter-star', '4');
|
||||
const fourUp = await wall();
|
||||
await setFilter('lib-filter-star', '5');
|
||||
const fiveUp = await wall();
|
||||
await setFilter('lib-filter-star', '0');
|
||||
step('the wall narrows to the frames that were given the stars', fourUp === 1 && fiveUp === 0 && (await wall()) === 3, `4★ ${fourUp}, 5★ ${fiveUp}, any 3`);
|
||||
|
||||
await setFilter('lib-filter-year', '2016');
|
||||
const in2016 = await wall();
|
||||
await setFilter('lib-filter-year', 'all');
|
||||
step('and to the year the shutter fired in', in2016 === 2, `2016 → ${in2016} tiles of 3`);
|
||||
|
||||
await setFilter('lib-filter-from', '16');
|
||||
await setFilter('lib-filter-to', '18');
|
||||
const afternoon = await wall();
|
||||
await setFilter('lib-filter-from', 'any');
|
||||
await setFilter('lib-filter-to', 'any');
|
||||
step('and to the hours it fired in', afternoon === 2, `16:00–18:00 → ${afternoon} tiles of 3`);
|
||||
|
||||
// The order the wall is read in, and back to the shelf: what is left behind is
|
||||
// the view the checks below read, so the filters go back to holding everything.
|
||||
await setFilter('lib-sort', 'star');
|
||||
const ordered = await page.$$eval('[data-key^="lib-photo-"]', (els) => els.map((e) => e.dataset.key));
|
||||
step(
|
||||
'the wall reads in the order that was asked for',
|
||||
ordered[0] === `lib-photo-${jpgId}`,
|
||||
`${ordered.map((k) => k.replace('lib-photo-', '').split('/').pop()).join(', ')}`
|
||||
);
|
||||
await setFilter('lib-sort', 'newest');
|
||||
await page.click('[data-key="lib-view-preview"]');
|
||||
await page.waitForSelector(thumbs, { timeout: 30_000 });
|
||||
|
||||
// A catalogue another tab has open — the tab that is still running yesterday's
|
||||
// build, holding the connection the new build used to ask for a version on. The
|
||||
// screen has to come up on it: the catalogue is opened as it is, with no version
|
||||
|
||||
Reference in New Issue
Block a user