an agent deleted two proxy hosts and both stayed on screen. the person had the answer in the transcript and a stale list six inches above it — which is the one thing a chat beside a list has to get right, and the reason it is beside the list at all. so the chat panel bumps the refresh channel when a turn ran tools, and every section re-reads. gated on hadToolCalls: a turn that only talked changed nothing, and re-reading after every sentence would put six requests behind every reply. the channel is renamed with it. it was proxy-manager:connection-changed, named after its only publisher; it has two now and the second is the bigger one, so it says what it MEANS — something at NPM changed, read it again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
76 lines
2.9 KiB
TypeScript
76 lines
2.9 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
import { useSearchParams } from 'react-router';
|
|
import { useClient } from 'hooks/useClient';
|
|
import { DEFAULT_SECTION, isSectionKey, type SectionKey } from './sections';
|
|
import { HostsPanel } from './HostsPanel';
|
|
import { CertificatesPanel } from './CertificatesPanel';
|
|
import { ConnectionPanel } from './ConnectionPanel';
|
|
import { ListSection, REDIRECTIONS, STREAMS, DEAD_HOSTS, ACCESS_LISTS } from './ListSection';
|
|
import { useNpmChanged } from './channel';
|
|
|
|
/**
|
|
* The right column: whichever section the nav has selected.
|
|
*
|
|
* It reads `?section=` from the URL rather than being handed it — the two panels are siblings in a
|
|
* workspace and neither owns the other, and `docs/navigation-audit.md` puts addressable state in the URL
|
|
* for exactly this reason. A bogus or missing value falls back rather than rendering nothing.
|
|
*
|
|
* ── The unconfigured check lives HERE, once ──
|
|
*
|
|
* Every section needs a connection, so every section would otherwise have to handle its absence, and each
|
|
* one would say something slightly different. Asking `/status` once and rendering the connection form
|
|
* instead of any section is the same answer for all seven, and it means "no connection yet" reads as a
|
|
* setup step rather than as six broken lists.
|
|
*/
|
|
|
|
type Status = { configured: boolean };
|
|
|
|
export const DetailPanel = () => {
|
|
const [params] = useSearchParams();
|
|
const raw = params.get('section');
|
|
const section: SectionKey = isSectionKey(raw) ? raw : DEFAULT_SECTION;
|
|
|
|
const client = useClient();
|
|
const clientRef = useRef(client);
|
|
clientRef.current = client;
|
|
|
|
const [configured, setConfigured] = useState<boolean | null>(null);
|
|
const [connectionSignal] = useNpmChanged();
|
|
|
|
const check = useCallback(async () => {
|
|
try {
|
|
const status = await clientRef.current.get<Status>('/proxy-manager/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.
|
|
setConfigured(true);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
void check();
|
|
}, [check, connectionSignal]);
|
|
|
|
if (configured === null) {
|
|
return <div className="flex size-full items-center justify-center p-6 text-sm text-muted-foreground">Loading…</div>;
|
|
}
|
|
|
|
if (!configured || section === 'connection') return <ConnectionPanel />;
|
|
|
|
switch (section) {
|
|
case 'proxy-hosts':
|
|
return <HostsPanel />;
|
|
case 'certificates':
|
|
return <CertificatesPanel />;
|
|
case 'redirections':
|
|
return <ListSection {...REDIRECTIONS} />;
|
|
case 'streams':
|
|
return <ListSection {...STREAMS} />;
|
|
case 'dead-hosts':
|
|
return <ListSection {...DEAD_HOSTS} />;
|
|
case 'access-lists':
|
|
return <ListSection {...ACCESS_LISTS} />;
|
|
}
|
|
};
|