PR: #728: feat(channels): admin channel register page + live thread refresh UI [Story-2881] by @ankitmaheshwari-com
View PR →

Files changed

23 file(s), +1012 / -19 lines

Path Role + -
messages/en.jsonConfig+24-2
messages/hi.jsonConfig+24-2
src/app/[locale]/(workspace)/settings/firm/channels/page.tsxApp route+3-3
src/app/[locale]/(workspace)/settings/firm/channels/register/page.tsxApp route+78-0
src/app/api/internal/channels/register/route.tsApp route+11-0
src/components/settings/channels/channel-refusal-summary.stories.tsxComponent+38-0
src/components/settings/channels/channel-refusal-summary.tsxComponent+64-0
src/components/settings/channels/channel-register-page.stories.tsxComponent+61-0
src/components/settings/channels/channel-register-page.tsxComponent+64-0
src/components/settings/channels/channel-register-row.tsxComponent+89-0
src/components/settings/channels/channel-register-table.stories.tsxComponent+53-0
src/components/settings/channels/channel-register-table.tsxComponent+65-0
src/components/settings/channels/channels-fixtures.tsComponent+63-1
src/components/settings/channels/channels-page.tsxComponent+17-1
src/components/settings/channels/presentation.tsComponent+30-0
src/lib/agents/atlas-rail-store.tsLibrary+32-0
src/lib/agents/thread-refresh.test.tsLibrary+115-0
src/lib/agents/thread-refresh.tsLibrary+72-0
src/lib/agents/use-mira-conversation-rail.tsLibrary+48-8
src/lib/channels/gateway.tsLibrary+42-0
src/lib/kubb-client.tsLibrary+4-0
src/lib/surface-registry/page-card/catalog.tsLibrary+3-0
src/lib/surface-registry/route-registry.tsLibrary+12-2

Story-2881: Admin channel register page + live thread refresh UI

RFD-AGT-013 (T2m-b) ships the two app-side halves that surface the W2 backend work to people. Half one (CC-50): a firm admin opens Settings → Channels → Firm register (/settings/firm/channels/register) and sees every channel binding in the firm — who it belongs to, its masked number, lifecycle status, and a metadata-only activity rollup — plus the firm's refused-attempt summary. This visibility is part of the compensating stack that justified skipping step-up auth. Half two (CC-32): when a counsel talks to Mira on WhatsApp while the matter is open in the app, the open thread now catches up live — the app consumes the thread.updated refresh notice (functions Story-2880) and re-reads the canonical turn store, so the app always shows the whole conversation.

The register is a security surface, not a transcript: counts and timestamps only, subjects always masked, message content never crosses the wire (enforced backend-side in Story-2879 and asserted again here by the generated Zod contract).

Half one — the register, layer by layer

LayerWhat landed
Gateway (HR-21) src/lib/channels/gateway.ts — getRegister() + getRegisterWithResponseHeaders(): fetch as unknown, assertContract(channelRegisterResponseSchema, …) at the trust boundary.
BFF proxy src/app/api/internal/channels/register/route.ts (GET) + the toBrowserRoute mapping in src/lib/kubb-client.ts, so the generated useAppGetChannelRegister hook resolves in the browser.
Route (RSC) …/settings/firm/channels/register/page.tsx — forum-pattern server-prefetch → HydrationBoundary → client page (ADR-095). Mirrors the backend's require_firm_admin gate with isFirmAdmin(me) so the UI and the 403 can never disagree; non-admins get an honest, localized notice. Prefetch failure degrades to CatalogLoadError (Bug-1781). agentSurface exported; route registry + page-card catalog regenerated.
Components src/components/settings/channels/: channel-register-page.tsx (orchestrator, 64 lines), channel-register-table.tsx + channel-register-row.tsx (forums-table chrome: shared grid, header, hover rows; member avatar + real photo with initials fallback via the shared Avatar), channel-refusal-summary.tsx (total, last attempt, per-kind chips — unknown kinds render their raw wire identifier, never hidden). Each with co-located stories incl. the empty first-run state.
Member join The register carries userId only; the page joins it to useAppListMembers (same query params as the Team page, one shared cache entry) for name + photo. A binding whose member left the firm shows “Former member” — the row never disappears.
Entry point The counsel-facing Channels page header gets a “Firm register” action — rendered only for firm admins (showRegisterLink from the RSC's me).

Half two — live thread refresh (CC-32 / CC-54)

Tests & verification

Live-data seam (UAT)

Fixtures are not evidence the shipped path is wired: the register's live proof needs a staging firm with a real bound number (UAT M-01 ceremony), and the thread-refresh live proof needs the channel-ingress consumer running so a real WhatsApp turn fires the thread.updated frame end-to-end. Both are the human UAT cards attached to EPIC-2834 W3 — this story's issue stays in Review until that evidence lands.