Sync moves to the bar, beside Build and Publish

It sat under the two folders it acts on, which was right while it was a detail of setting fofos up. It
is not one: a stale `derived/` is why a picture is missing from a page somebody is looking at, and the
fix was three sections away in a card about where the site lives. Beside Build and Publish it reads as
what it is — the third thing you do to a site, and the one that has to happen first.

The card keeps what needs room: the progress bar, the failures, the run history.

Shared through `useGlobal`, keyed `['fofos-admin','media-sync']` — a slot in the app-wide query cache,
so no provider has to wrap the bar and the panel together and a third reader is one line. Not a
context: this codebase has one for Save because a SECTION owns that state and announces it upward.
Nobody owns this one; it is a fact about a folder, which is what a keyed slot is for.

`useGlobal` shares the value and not the work, so the fetching is a separate hook with exactly one
caller — the button. The bar is mounted whenever the plugin is open and the Administration card is
mounted only sometimes, so the thing that is always there is the thing that asks. Two callers would
poll the same endpoint twice a second.

Gated on `rootSet`, not `ready`: sync reads a folder on this machine and never speaks to fofos.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-31 11:58:28 +01:00
co-authored by Claude Opus 5
parent 307a7d665a
commit 78db209ea1
4 changed files with 304 additions and 168 deletions
+8 -2
View File
@@ -9,6 +9,7 @@ import { LayoutPanel } from './sections/LayoutPanel';
import { PagesPanel } from './sections/PagesPanel';
import { PageBuilderPanel } from './sections/PageBuilderPanel';
import { PublishButton } from './PublishButton';
import { SyncButton } from './SyncButton';
import { SaveTargetProvider, useSaveButton } from './SaveTarget';
import { DEVICES, ViewModeProvider, useViewMode, type DeviceName, type Mode } from './ViewMode';
import { MessagesPanel } from './MessagesPanel';
@@ -217,9 +218,14 @@ export const DetailPanel = () => {
</div>
<div className="flex flex-1 items-center justify-end gap-2">
{/* Left of Publish, and in that order for a reason: save what you wrote, then make it
public. A row that put them the other way round would read as the sequence backwards. */}
{/* The order is the sequence: save what you wrote, make the media web-ready, then make
it public. A row that put them any other way round would read as the steps backwards.
Sync sits left of Build for the same reason Build sits left of Publish — it is what
has to have happened first, and a stale `derived/` is a picture missing from the page
you just built. */}
<SaveButton />
<SyncButton />
<PublishButton />
</div>
</div>