AGENTS.md@packages/client/src/components/Gateway · git:20260829.9fe2de0 · 2026-08-29 · sha256 fda9bc4ddbb69456
AGENTS.md@packages/client/src/components/Gateway git:20260829.9fe2de0A
Immutable. This exact content is served forever at /api/v1/blob/fda9bc4ddbb69456.
# DOX — packages/client/src/components/Gateway
Reusable Gateway (tunnel-providers) UI sections + two hosts. User-facing label
"Gateway"; the wire keeps `tunnel`. Composed by the Gateway settings page and
the tabbed Gateway dialog. See change: add-tunnel-providers.
| File | Purpose |
|------|---------|
| `GatewayDialog.tsx` | Tabbed **Gateway** dialog (task 9.3): Setup / Access & QR (default) / Security. Reads `tunnel.provider`/`mode` via `GET /api/config`, persists via `PUT /api/config` (`tunnel` deep-merged). Composes the four sections; Security tab cross-refs the Security page. Exports `GatewayDialog`. `gateway-forget-reserved` button calls `disconnectTunnel({forget:true})` (releases v2 reserved name). See change: support-zrok-v2. |
| `GatewayEndpoints.tsx` | "Accessible at" tagged endpoint list (kind pill + TLS/no-TLS badge + copy) + **Add HTTPS URL** control (task 6.4). Add path: re-read config → `appendPublicBaseUrl` → PUT the TOP-LEVEL `publicBaseUrls` (seeded from legacy `pairing.publicBaseUrls` on first write) → refetch. See change: config-override-oauth-redirect-base. Client https/wss gate is UX-only. Exports `GatewayEndpoints`. |
| `GatewayPage.tsx` | **Gateway** settings page (task 9.2) under Network nav. Full-width host: provider/mode, connect-a-device (QR first; wraps `<GatewayPairQR>` in the `#connect-a-device` anchor the Security pairing link scrolls to, and passes the REQUIRED `onSetupRequested` focus handler — D3a), gateway URLs (`GatewayUrlManager`), accessible-at, setup guide, Security cross-ref. Self-manages provider/mode via GET/PUT config. Exports `GatewayPage`. |
| `GatewayPairQR.tsx` | Connect-a-device — **the one operator pairing surface** (collapse-pairing-into-gateway: Settings▸Security links here instead of duplicating). **One QR at a time via a network selector** (radio group over `pairingEps ++ linkEps`). Default = public TLS pairing endpoint (`pairingEps[0]`), else first link (`linkEps[0]`). Selected drives one `QrCanvas`: pairing → camera-scannable `https://<selected-tls>/pair#pi:pair:v1.<b64>` deep link (`encodePairingQrUrl` from `lib/pairing-qr.ts`, landing base = the SELECTED endpoint's url; payload in FRAGMENT, one-time code out of logs); link → bare `selected.url`. Copy-string stays bare `pi:pair:v1.…` (`encodePayloadString`) for Electron paste. Payload TLS-only `{v,id,code,urls[]}` re-guarded by `guardPairingUrls` (fail-closed; loopback http exempt — mirrors the server test-origin rule). `noSecureRoad` flag keys the explain/Set-up-the-Gateway/localhost-escape-hatch block on the `no_reachable_endpoint` RESPONSE only — never on endpoint count or an unloaded payload (D3). Setup action: optional `onSetupRequested` prop (dialog → Setup tab; page → focus provider section); fallback navigates to `/settings/gateway`, scrolling `#connect-a-device` into view when already there (D3a). Context panel: full selectable fingerprint + payload `urls[]` + copy-string + expiry + confirm input + Approve (D12; NOT gated on the countdown). Selector is `role=radiogroup` + per-row `role=radio`/`aria-checked`, roving tabIndex, arrow-key nav, Space/Enter commit; ●/○ non-colour cue. `QrCanvas` exposes `data-qr-text` for tests. Exports `GatewayPairQR`. See change: add-gateway-qr-network-selector. See change: make-pairing-qr-camera-scannable. See change: collapse-pairing-into-gateway. |
| `GatewayProviderSection.tsx` | Provider + mode segmented controls. Mode gated by the provider matrix (`supportsMode`); switching provider auto-selects a valid mode. Exports `GatewayProviderSection`. |
| `GatewayProviderActions.tsx` | Per-readiness-row action group: **Make primary** (D10) + the register-this-live-URL offer (D9). Both are two-click and state their consequence INLINE (no `window.confirm` — the consequence must be read). Rendered OUTSIDE the row `<button>`; a nested button is dropped by the browser. Ineligible offer modes render disabled WITH their reason. `disabled` does NOT clear `checked`, and the row survives every 5s tick — so the save filters through `retainAvailableModes` and both panels reset when their predicate drops. See change: add-zrok-custom-reserved-name. |
| `GatewayReadinessBoard.tsx` | Readiness board + dialog-bound poll (one tick on open, 5s while open, stopped on close, overlaps suppressed). Seeds `stateRef` synchronously before the first tick — the ref refreshes on RENDER, so a guard reading it would skip the immediate tick. Rows are one 52px line (D11). Polling is bound to the Setup TAB, not just the dialog: a tick shells out per provider. See change: add-zrok-custom-reserved-name. |
| `GatewayReservedName.tsx` | Setup step 3 — reserved-name input + `GatewayDegradedBanner`. Validates on BLUR, not per keystroke (each submit attempts a real reservation, so per-keystroke would reserve every prefix typed). Release/replace are confirm-gated and the copy names the exact URL destroyed — the name returns to zrok's global pool immediately. Renders `tunnelStopped` vs `liveUrlUnchanged` distinctly. See change: add-zrok-custom-reserved-name. |
| `GatewaySetupGuide.tsx` | Per-provider setup steps (D3): install=copy-paste; auth-token/activate=field+button → `runEnrollStep` (`POST /api/tunnel/enroll`, validated param); browser-auth/external=link. Also renders `GatewayUrlManager` so the first run gets the add-gateway action (same component as the Gateway page). Exports `GatewaySetupGuide`. |
| `GatewayUrlManager.tsx` | "Add a gateway URL" action (D12/D13) + per-gateway status row. One shared component rendered by BOTH `GatewayPage` and `GatewaySetupGuide`, so first-run and steady state cannot drift. Add = ONE `PUT /api/config` from `buildGatewayAddPatch`; states the scheme rules inline (`http://` → OAuth + QR checkboxes disabled with a reason, trusted network required, CIDR prefilled `/32` via `suggestTrustEntries`). Row shows the computed status (`data-status` ok/incomplete/conflicting/ineligible) with `Fix` (delta-only reconcile) and `Remove` (confirmation lists every reverted field). Exports `GatewayUrlManager`. See change: config-override-oauth-redirect-base. |