Everything under it is in order most of the time, and a card that is four
paragraphs of "fine" pushes the settings that are not fine off the screen. So it
shuts, with a check on the right, and opens on a click.
"Finished" is three bits reported upward by the parts that own them: the path is
saved, the checkout is a repository, and media/ both exists and is current. None
of those is knowable in the parent — each child fetches its own status — and
lifting all of it up to answer one question would be a far worse trade than
three booleans.
It is only collapsible while settled. A block you can shut on unfinished setup
is a way to hide the thing you came here to fix. And the children are NOT
RENDERED while it is shut rather than hidden, because a collapsed block whose
children still poll is a settled panel making requests all afternoon about
something nobody is looking at.
The `null` in `collapsed` is a sentinel for "nobody has decided", which is what
lets a deliberate expand survive. Setting it to false while unsettled spent it
on the first render — before any status had arrived — so the block could never
fold itself away at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things the panel could not answer. A run's state lives in a Map, so a
restart erased it — and with it any record that a file FAILED. That is the gap
the manifests structurally cannot cover: a derived folder records the size,
mtime and variants it was built from, so provenance is already precise, but a
file that did not build has no folder and leaves no trace. Once the watcher is
running, a failure at three in the morning would never be discovered.
So one JSON line per run, appended to media/.sync-log.jsonl. A file rather than
a table because db/schema.ts is emphatic that the connection is the only thing
this plugin stores, and a ledger is losable — re-run and it rebuilds. Beside
derived/ rather than inside it, so wiping derived does not erase the history of
what happened before, which is the comparison it exists for. Written in a
finally, so a run that died still records that it did.
And the line above the button now says what is TRUE rather than what a run once
did: reconcile() walks originals, reads each manifest, and reports current,
missing and changed. It uses upToDate — the same predicate the sync uses to skip
a file — because two definitions of "current" is how a panel comes to say
everything is in step while the next run rebuilds half of it. Missing and
changed are separated because only the second means somebody edited something.
The ledger is not polled. It reads the whole tree, so it loads on arrival and
again when a run stops — the only two moments its answer can change.
Sync also takes an optional folder now, so a subtree can be re-synced on its own
rather than the whole library. The scope is recorded on each run, because "3
built" means different things for one folder and for everything.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Derived media" was a section sitting beside the block that lists `originals/`
and `derived/` — the same subject described twice, leaving the reader to work
out that the two were about one thing.
So the control moves under those folders, inside Root Location, and appears only
once both exist: before that the button would be asking to write into a
directory that is not there, and Create is the thing to press. An order, not a
choice.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>