fix(boot): bring the session and the catalogue back without waiting on /me

A reload drew nothing until the account round trip landed, which on a
faraway API is the whole of the wait: 8.8s to the photo on the studio's
stage and 8.2s to the library's first row, against 0.9s and 0.3s now
(measured with every /api answer held back 8s).

The studio's restore effect returned early on `!authReady`. The gate is
only needed by the two files that ask a tier question — a RAW, which is
a PRO develop, and a catalogue frame, which walks `loadFile` — so it
keeps those and lets the session's own JPEG up on the first pass.
`restoredRef` stops the second pass, when /me lands, from decoding the
frame the first one already showed.

The library mounted nothing until /me answered. What the first paint
reads is now the answer the last visit got, the way the studio reads its
own session back: the frames are already on this disk and have no reason
to sit behind a round trip. An answer the API contradicts puts the note
back on screen the moment it arrives, and an API that never answers is
not written down as this account's standing.
This commit is contained in:
2026-10-08 17:09:53 +07:00
parent 196677dde2
commit 981a482e1b
2 changed files with 44 additions and 13 deletions
+19 -3
View File
@@ -242,19 +242,35 @@ const why = (err: unknown) => {
// from a folder that would be read for nothing is not the offer. The gate is
// here rather than in the router so the scan never starts for a guest — the
// catalogue component below is not mounted at all until the account is known.
//
// What the first paint reads is the answer the last visit got, the way the studio
// reads its own session back (see `loadState`): `/me` is a round trip, and a
// screen that draws nothing until it lands draws nothing for as long as the API
// is away — the frames the reader was working through are already on this disk
// and there is no reason to hold them behind it. The record only ever holds this
// visitor's own last answer, and an answer the API then contradicts puts the note
// back on screen the moment it arrives.
const ACCOUNT_KEY = 'recipescam.library.account';
export function Library() {
const { t } = useI18n();
const [state, setState] = useState<'loading' | 'guest' | 'ready'>('loading');
const [state, setState] = useState<'loading' | 'guest' | 'ready'>(
() => (recall(ACCOUNT_KEY) as 'guest' | 'ready' | null) ?? 'loading'
);
useEffect(() => {
let alive = true;
api
.me()
.then((me) => {
if (alive) setState(me?.user ? 'ready' : 'guest');
if (!alive) return;
const next = me?.user ? 'ready' : 'guest';
keep(ACCOUNT_KEY, next);
setState(next);
})
// No answer is treated like no account: the API being away is not a
// reason to open the shelves to whoever is on the page.
// reason to open the shelves to whoever is on the page. It is also not an
// answer, so it does not go in the record — the next visit asks again
// rather than reading the outage as this account's standing.
.catch(() => {
if (alive) setState('guest');
});