Files
fofos-admin/web/SyncButton.tsx
T
pastilhasandClaude Opus 5 78db209ea1 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>
2026-08-31 11:58:28 +01:00

67 lines
2.8 KiB
TypeScript

import { Loader2, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useMediaSync, useMediaSyncFeed } from './useMediaSync';
import { useReady } from './useReady';
/**
* Sync, in the panel's bar beside Build and Publish.
*
* ── Why it left the card ──
*
* It sat under the two folders it acts on, which was the right place while it was a detail of setting
* fofos up. It is not one: `derived/` being stale is the reason 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.
*
* The card keeps everything there is no room for up here: the progress bar, the failures, and the run
* history. Both read one slot, so they cannot disagree about what is true.
*
* ── It owns the fetching ──
*
* `useMediaSyncFeed` has exactly one caller and this is it, because the bar is mounted whenever
* anything in this plugin is open and the Administration card is mounted only sometimes. The thing
* that is always there should be the thing that asks.
*
* ── The label is the state ──
*
* `Sync 3` rather than a badge or a dot: the number IS the reason to press it, and a button that says
* "Sync now" when there is nothing to sync invites a press that does nothing. Idle and current, it
* still says "Sync" — the action stays available, because a file changed outside the watcher's notice
* is exactly the case somebody needs it for.
*/
export const SyncButton = () => {
const { running, percent, outstanding, busy, start } = useMediaSync();
const { rootSet } = useReady();
useMediaSyncFeed();
/*
Gated on the ROOT, not on `ready`.
Sync reads a folder on this machine and never speaks to fofos, so a broken connection is no reason
to withhold it — that distinction is the one `useReady` records as the bug that made `rootSet` a
separate answer in the first place.
*/
if (!rootSet) return null;
return (
<Button
size="sm"
variant="outline"
className="h-6 shrink-0 gap-1.5 px-2 text-xs"
onClick={() => void start()}
disabled={busy || running}
title={
running
? 'Building web-ready copies of everything in originals'
: outstanding > 0
? `${outstanding} file${outstanding === 1 ? '' : 's'} in originals have no current copy in derived`
: 'Rebuild anything in derived that is missing or out of date'
}
>
{running ? <Loader2 className="size-3 animate-spin" /> : <RefreshCw className="size-3" />}
{running ? `Syncing${percent === null ? '' : ` ${percent}%`}` : outstanding > 0 ? `Sync ${outstanding}` : 'Sync'}
</Button>
);
};