Files
fofos-admin/web/useReady.ts
T
pastilhasandClaude Opus 5 c38d269faf Media moves to the foot, and each section asks its own question
TWO REPORTS, AND THE SECOND ONE WAS A DESIGN MISTAKE RATHER THAN A SETTING.

Media is pinned now, beside Administration. it sat at the end of the scrolling list,
which is the bottom of the LIST and not the bottom of the panel. it belongs in the
foot for the same reason Administration does: both are about this machine, while
everything above them is about the site's pages.

and it was LOCKED BEHIND A CONNECTION IT NEVER USES. `ready` was one global flag —
"can anything be opened" — and Media is a folder on this disk that never speaks to
fofos. so sections now declare what they need:

  'connection'  the site's pages, which read fofos
  'root'        Media, which needs a folder and nothing else
  'nothing'     Administration, because locking the door you fix things through is
                locking somebody out entirely

`sectionOpen` is one function, used by the nav to decide whether to offer a link and
by the detail panel to decide what to render — so the two cannot disagree about a
section. that was already the rule with one flag; it matters more now that each
section asks a different question.

`/status` gained `rootSet`, a BOOLEAN, and stays not-owner-only. the nav needs the
answer for every account that can see the section; the path itself stays behind
`/_config`, which is owner-only. whether a root has been chosen reveals nothing
about the machine — where it is would.

the empty-state line follows too: "set a root location below to begin" before there
is one, "connect fofos to open the rest" after — rather than one sentence naming a
step that is not the next one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 08:44:11 +01:00

91 lines
4.5 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from 'react';
import { useClient } from 'hooks/useClient';
import { useFofosChanged } from './channel';
/**
* Is this plugin ready to be USED — that is, may anything but Administration be opened?
*
* ── One condition, read in two places ──
*
* The nav decides whether a section is clickable and the detail panel decides what to render. Those
* are two answers to the same question, and a nav that lets you click a row the panel then refuses is
* worse than one that does not offer it. So the condition lives here and both panels read it, rather
* than each deciding for itself — the panels are siblings and neither may own the other.
*
* ── It is a GATE, not a hint ──
*
* Not ready means the site sections cannot be reached at all, by clicking or by typing `?section=` in
* the address bar. Every one of them is a window onto fofos, and without a working connection there is
* nothing behind any of them; offering nine doors onto the same "not connected" message taught nobody
* anything and made a setup step look like nine faults.
*
* ── Hardcoded false, deliberately, for now ──
*
* The shape is real and the plumbing is real: it asks `/status`, it re-asks whenever the connection
* changes, and it reports while it is still asking. Only the final verdict is pinned, so turning the
* gate on is one line rather than a feature.
*
* ── Both panels call this, so `/status` is fetched twice ──
*
* Known and accepted for now: two cheap GETs that answer the same question, kept in step because both
* re-run on the same `useFofosChanged` signal. The alternative is a context provider around the two
* panels, which is a shared-state boundary this does not yet need — and the duplication is visible
* here rather than hidden in a wrapper. If the condition grows expensive, that is the moment to fix
* it, not before.
*
* What it will become is roughly `configured && lastProbeOk` — the connection is stored AND the last
* probe reached fofos — but "all the info needed in Administration" is not settled yet and will grow
* past those two. That is why this returns a single boolean and not the parts: callers must not start
* assembling their own version of the condition out of the pieces.
*/
type Status = { configured: boolean; lastProbeOk: boolean | null; rootSet: boolean };
export type Readiness = {
/** The fofos connection works. Gates every section that READS the site. */
ready: boolean;
/**
* A root location has been chosen.
*
* Separate from `ready` because the two are genuinely different questions and one section depends on
* each. Media needs a folder on this machine; it never speaks to fofos, and gating it behind a
* connection it does not use was the bug that made this distinction necessary.
*/
rootSet: boolean;
/** Still asking. Callers should render nothing rather than guess — see below. */
loading: boolean;
};
export const useReady = (): Readiness => {
const client = useClient();
const clientRef = useRef(client);
clientRef.current = client;
const [status, setStatus] = useState<Status | null>(null);
const [signal] = useFofosChanged();
const check = useCallback(async () => {
try {
setStatus(await clientRef.current.get<Status>('/fofos-admin/status'));
} catch {
/*
Cannot tell. Treated as CONFIGURED rather than not — a transient failure must never present as
"you have not set this up", which is a far more alarming thing to be told and invites somebody
to re-enter a connection that was never the problem.
*/
setStatus({ configured: true, lastProbeOk: null, rootSet: false });
}
}, []);
useEffect(() => {
void check();
}, [check, signal]);
// ────────────────────────────────────────────────────────────────────────────────────────────────
// The gate. Replace `false` with the real condition — nothing else here has to change.
// ────────────────────────────────────────────────────────────────────────────────────────────────
const ready = false;
return { ready, rootSet: status?.rootSet ?? false, loading: status === null };
};