Files
proxy-manager/web/channel.ts
T
pastilhasandClaude Opus 5 3d02cb6229 saving a connection now tells the other panels
reported on the first successful save: everything still showed no data and the
form stayed open, and a manual page refresh was the only way through.

two faults, one report. the form did not close itself — nothing cleared
?config, so a save left the settings view sitting on top of a plugin that now
worked. and the two reader panels are SIBLINGS in a workspace layout: they are
mounted independently, neither owns the other, and ConnectionPanel cannot call
into them. so they went on showing the failure they had loaded back when no
connection existed.

a channel is the sanctioned answer for exactly this — docs/navigation-audit.md
puts addressable state in the URL and leaves channels for "something changed,
re-read it". nobody wants to link to the moment after a save.

the nonce is a plain module counter, copied deliberately from officerdev's
channels.ts along with the reason: setSignal(n => n + 1) applies against the
value captured at render, so two bumps in one render window both compute
snapshot + 1 and the second overwrites the first — and nobody re-reads.
Date.now() is the same bug at millisecond scale.

disconnect bumps it too. the panels must stop showing hosts they can no longer
reach.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
2026-08-25 16:58:52 +00:00

44 lines
2.1 KiB
TypeScript

import { useCallback, useRef } from 'react';
import { defineChannel } from 'hooks/usePanelChannel';
/**
* "The connection changed — anyone reading NPM should read it again."
*
* ── Why a channel and not the URL ──
*
* `docs/navigation-audit.md` draws the line: anything ADDRESSABLE goes in the URL, and a channel is only
* for a signal — "something changed, re-read it". This is the second kind. Nobody wants to link to "the
* moment after the connection was saved", and there is nothing to select.
*
* ── Why it is needed at all ──
*
* The panels are siblings in a workspace layout. `ConnectionPanel` cannot call into `CertificatesPanel`
* and should not try — they are mounted independently and neither owns the other. Before this, saving a
* connection left both panels showing whatever they had failed to load a minute earlier, and the only fix
* a person had was a manual refresh. Reported on 2026-08-25 the first time a connection was saved through
* the form.
*
* ── The nonce is a plain counter, and that is not an accident ──
*
* Copied deliberately from `officerdev/src/channels.ts`, which records why. `setSignal(n => n + 1)` is
* WRONG here: the functional setter applies against the value captured at render, so two bumps inside one
* render window both compute `snapshot + 1` and the second silently overwrites the first — nobody
* re-reads. `Date.now()` has the same flaw in miniature, two bumps in one millisecond being one number.
* A module counter never reads React state, so it cannot be wrong however often it is called.
*/
const useConnectionChangedChannel = defineChannel<number>('proxy-manager:connection-changed', 0);
let nonce = 0;
export const useConnectionChanged = () => {
const [signal, setSignal] = useConnectionChangedChannel();
// The setter is a fresh closure every render and this `bump` goes into dependency lists, so it is read
// through a ref — stable identity without freezing the first render's setter.
const setRef = useRef(setSignal);
setRef.current = setSignal;
const bump = useCallback(() => setRef.current(++nonce), []);
return [signal, bump] as const;
};