nothing but Administration opens until the plugin is ready

every section was clickable and every one of them showed the same connection form,
because the panel forced it and the nav did not know. nine doors onto one message
taught nobody anything and made a setup step look like nine faults.

so there is now a gate, `useReady`, and both panels read it. that is the point of
putting it in a hook rather than in either panel: a nav that offers a row the
detail panel then refuses is worse than one that does not offer it.

the nav DECLINES TO OFFER — a locked section is a <div>, not a <Link>, so there is
no href to hover, no cmd-click into a new tab and nothing to focus. rendering a
link and swallowing the click leaves a row that looks navigable and behaves like
one everywhere except the click.

the detail panel HOLDS THE LOCK, and it has to, because `?section=gallery` typed
into the address bar or arriving from an old bookmark never passes through the
nav.

one line of copy in the foot rather than a badge on nine dimmed rows. without it a
locked nav reads as broken instead of as one step not done: the rows go grey and
nothing says why or what to do about it.

THE CONDITION IS HARDCODED FALSE, as asked. the plumbing around it is real — it
asks /status, re-asks when the connection changes, and reports while it is still
asking — so switching the gate on is one line. it returns a single boolean and not
its parts deliberately: "all the info needed in Administration" will grow past
`configured && lastProbeOk`, and callers must not start assembling their own
version of it out of the pieces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 00:25:57 +01:00
co-authored by Claude Opus 5
parent 5e96274ebd
commit 10b7bc34f7
4 changed files with 134 additions and 45 deletions
+12 -37
View File
@@ -1,11 +1,9 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useSearchParams } from 'react-router';
import { useClient } from 'hooks/useClient';
import { DEFAULT_SECTION, isSectionKey, sectionFor, type SectionKey } from './sections';
import { ConnectionPanel } from './ConnectionPanel';
import { MessagesPanel } from './MessagesPanel';
import { PlannedSection } from './PlannedSection';
import { useFofosChanged } from './channel';
import { useReady } from './useReady';
/**
* The right column: whichever section the nav has selected.
@@ -20,16 +18,14 @@ import { useFofosChanged } from './channel';
* to the same screen is a second thing to keep correct, and the two disagreed the moment a section was
* selected underneath a form that covered it.
*
* ── The unconfigured check lives HERE, once ──
* ── Not ready means Administration, whatever the URL says ──
*
* Every OTHER section needs a connection, so each would otherwise have to handle its absence and each
* would word it slightly differently. Asking `/status` once and sending an unconfigured plugin to
* Administration is one answer for all of them, and it makes "not connected yet" read as a setup step
* rather than as nine broken lists.
* The gate is `useReady`, shared with the nav so the two cannot disagree. Enforced HERE and not only
* there, because the nav can only decline to offer a link — `?section=gallery` typed into the address
* bar, or a bookmark from before the connection was forgotten, arrives without going through it. A
* nav that hides a door is a courtesy; this is the lock.
*/
type Status = { configured: boolean; lastProbeOk: boolean | null };
/**
* Which component renders which section.
*
@@ -50,39 +46,18 @@ export const DetailPanel = () => {
const raw = params.get('section');
const selected: SectionKey = isSectionKey(raw) ? raw : DEFAULT_SECTION;
const client = useClient();
const clientRef = useRef(client);
clientRef.current = client;
const { ready, loading } = useReady();
const [configured, setConfigured] = useState<boolean | null>(null);
const [signal] = useFofosChanged();
const check = useCallback(async () => {
try {
const status = await clientRef.current.get<Status>('/fofos-admin/status');
setConfigured(status.configured);
} catch {
// Cannot tell. Render the section rather than the setup form — a transient failure must not look
// like "you have not set this up", which is a much more alarming thing to be told, and would
// invite somebody to re-enter a connection that was never the problem.
setConfigured(true);
}
}, []);
useEffect(() => {
void check();
}, [check, signal]);
if (configured === null) {
if (loading) {
return <div className="flex size-full items-center justify-center p-6 text-sm text-muted-foreground">Loading…</div>;
}
/*
Unconfigured, so Administration regardless of what the nav says. The header then NAMES it, which is
the part the overlay could not do: the form used to appear under whichever row happened to be
highlighted, so it read as that section being broken rather than as one step not done yet.
Not ready, so Administration regardless of what the URL asked for. The header then NAMES it, which
is the part the old overlay could not do: the form used to appear under whichever row happened to
be highlighted, so it read as that section being broken rather than as one step not done yet.
*/
const current = sectionFor(configured ? selected : 'administration');
const current = sectionFor(ready ? selected : 'administration');
const Panel = current.built ? PANELS[current.key] : undefined;