Routed My connection / Firm register shell
The Channels surface gains its shared shell: one WhatsApp-branded header for both views, and a segmented switch between My connection and Firm register that is built as two routes, never client-side tabs.
Why two routes, not tabs
AGENTS.md "Route-per-mode" bans a single page.tsx holding useState for an
active tab and conditionally rendering two views on one URL. Precedent is Bug-2150: the document
viewer shipped Original / Text as client tabs and loaded pdfjs-dist for readers who never
opened the PDF. Here the register view carries the firm-wide table + members join; a reader on
My connection must not pay for it. So the segments are <Link>s to sibling
routes, styled to look like SegmentedFilter, with aria-current="page" on the
active one.
What changed
New settings/firm/channels/layout.tsx renders the shared
SettingsPageHeader (WhatsApp glyph + title + lede) once for both routes. Each
page.tsx keeps only its own toolbar row and content.
ChannelViewTabs (src/components/settings/channels/) is the routed
switch. Both pages compose it instead of duplicating it.
The entire toolbar row renders only when isFirmAdmin(me). A non-admin sees
header → content with no row — the design's lone one-tab pill is pointless and never renders.
SettingsPageHeader.icon now accepts a rendered node or any glyph component (Lucide or
the filled WhatsApp mark), not just LucideIcon. InlinePageHelp still
renders inside the h1. Every existing caller is unchanged.
loadAppMe is wrapped in React cache(), so the layout and the page dedupe
the /app/me resolution within one request.
New ChannelsShell keys added in en + hi. The persona is
Vani — the pre-rename design mock's "Mira" copy was not used.
Admin — My connection (active)
Talk to Vani on WhatsApp, right where you already work.
↳ personal binding / connect card renders here (Story-3066+).
Non-admin — My connection (row absent)
Talk to Vani on WhatsApp, right where you already work.
↳ content renders directly under the lede — no toolbar row, no pill.
Files
| File | Role |
|---|---|
settings/firm/channels/layout.tsx |
New fat layout — shared WhatsApp header for both routes. |
settings/channels/channel-view-tabs.tsx |
New shared routed tab strip (<Link>, aria-current). |
settings/firm/channels/page.tsx |
Thin — admin-gated toolbar row + ChannelsPage. |
settings/firm/channels/register/page.tsx |
Thin — keeps the server-side admin gate; adds the toolbar row. |
settings/channels/channels-page.tsx |
Header removed; showRegisterLink → isFirmAdmin. |
settings/channels/channel-register-page.tsx |
Header removed; renders register content only. |
settings/settings-page-header.tsx |
Icon prop widened to a node / any glyph component. |
messages/{en,hi}.json |
New ChannelsShell namespace; superseded Channels.openRegister removed. |
Verification
- Storybook —
Settings/Channels/ChannelViewTabs: admin (two segments, active per route) and non-admin (row absent). pnpm lint,pnpm test,pnpm build,pnpm check:routes— green.- Scope guard: register filter chip / search / kebab (Story-3069) and the connected / empty / code states (Stories 3066–3068) are intentionally NOT built here.
Files changed
14 file(s), +513 / -106 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +8 | -2 |
| messages/hi.json | Config | +8 | -2 |
| src/app/[locale]/(workspace)/settings/firm/channels/layout.tsx | App route | +34 | -0 |
| src/app/[locale]/(workspace)/settings/firm/channels/page.tsx | App route | +13 | -3 |
| src/app/[locale]/(workspace)/settings/firm/channels/register/page.tsx | App route | +17 | -8 |
| src/components/settings/channels/channel-register-page.tsx | Component | +5 | -9 |
| src/components/settings/channels/channel-view-tabs.stories.tsx | Component | +69 | -0 |
| src/components/settings/channels/channel-view-tabs.test.tsx | Component | +106 | -0 |
| src/components/settings/channels/channel-view-tabs.tsx | Component | +79 | -0 |
| src/components/settings/channels/channels-page.tsx | Component | +10 | -24 |
| src/components/settings/settings-page-header.tsx | Component | +40 | -13 |
| src/components/shared/segmented-filter.stories.tsx | Component | +30 | -1 |
| src/components/shared/segmented-filter.tsx | Component | +89 | -39 |
| tests/unit/channels-page.test.tsx | Test | +5 | -5 |