Mesh detail page at /dashboard/meshes/[id]:
- Header: flex-col → flex-row at sm breakpoint. Live/Invite buttons
stretch full-width stacked on mobile (flex-1), auto-width side-by-
side from sm up.
- "Generate invite link" truncates to "Invite" on mobile (viewport
constrained) so the button fits next to Live.
- Members + active-invites rows: stack metadata vertically on mobile
(flex-col → sm:flex-row), wrap badges inside with flex-wrap so the
member display-name + role + revoked badges don't horizontal-scroll.
Invites list at /dashboard/invites:
- Wrap the table in overflow-x-auto with min-w-[560px] on the table
itself. 5-column data-table that genuinely needs horizontal space
— don't fake it with card stacking, let the user scroll naturally.
Quick-send composer deferred to a follow-up — writes a message to the
mesh, which requires a client-side encryption decision (ed25519
keypair in the browser? key derivation from session? plaintext-to-
broker and break E2E?). Parked as its own spec.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Four new routes under /dashboard/(user)/*:
- /dashboard/meshes — card grid of user's meshes with myRole badge,
memberCount, tier, archived state. Empty state with "Create first mesh"
CTA.
- /dashboard/meshes/[id] — mesh detail (members list + active invites)
with "Generate invite link" CTA in header.
- /dashboard/meshes/new — placeholder route for create form (form lands
in next commit).
- /dashboard/meshes/[id]/invite — placeholder route for invite generator
(generator lands in next commit).
- /dashboard/invites — table of invites the user has issued across all
meshes, with derived status (active/revoked/expired/exhausted).
Sidebar nav (user group) extended with Meshes + Invites entries. paths
config extended with dashboard.user.meshes.{index,new,mesh,invite} and
dashboard.user.invites.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>