Files
claudemesh/apps/web/src/config/paths.ts
Alejandro Gutiérrez 5bffdb1d30
Some checks failed
CI / Tests / 🧪 Test (push) Has been cancelled
feat(web): live mesh dashboard — real data through extracted MeshStream
Wires the Discord-style demo UI to real user data. Users with 1+ meshes
now get situational awareness: who's online, what's in the queue, what
the broker saw recently — polling every 4s, all E2E encrypted.

Extraction pass:
- New `<MeshStream peers messages channelLabel footer>` renderer at
  modules/marketing/home/mesh-stream.tsx — pure presentation, no
  playback engine, no data fetching. Handles peer filter, hover-for-
  ciphertext tooltip, animated message list.
- demo-dashboard.tsx refactored to use it: keeps the playback loop,
  traffic-light chrome, and script-driven messages; passes everything
  to MeshStream via props. ~120 LOC shorter.

Backend:
- new GET /api/my/meshes/:id/stream in packages/api (same authz gate
  as /my/meshes/:id — owner OR non-revoked member). Returns:
  - up to 20 live presences (disconnectedAt IS NULL), joined to
    meshMember for displayName
  - up to 50 most-recent message_queue envelopes with metadata only:
    sender + displayName, targetSpec, priority, createdAt, deliveredAt,
    byte size, and a 24-char ciphertext preview (this IS what the
    broker sees — no plaintext anywhere in the response)
  - up to 20 recent audit events

- getMyMeshStreamResponseSchema in schema/mesh-user.ts matches exactly.

Frontend:
- new LiveStreamPanel client component at modules/mesh/live-stream-panel.tsx
  — react-query with refetchInterval: 4000ms, refetchIntervalInBackground
  false. Maps presences + envelopes to MeshStream's Peer/Message shape,
  classifies targetSpec into message type ("tag:*" → ask_mesh, "*" →
  broadcast, else direct). Passes through the ciphertextPreview as the
  hover content — no fake ciphertext in live view.
- new route /dashboard/meshes/[id]/live with server-side authz preflight
  via /my/meshes/:id. Mounts LiveStreamPanel inside a dashboard page
  shell with breadcrumb back to mesh detail.
- Mesh detail page gets a new "Live" pill button (clay-pulsing dot)
  next to "Generate invite link" in the header.
- paths config gets dashboard.user.meshes.live(id).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:51:14 +01:00

126 lines
3.3 KiB
TypeScript

const ADMIN_PREFIX = "/admin";
const AUTH_PREFIX = "/auth";
const BLOG_PREFIX = "/blog";
const DASHBOARD_PREFIX = "/dashboard";
const LEGAL_PREFIX = "/legal";
const API_PREFIX = "/api";
// AI apps routes (no prefix - top-level routes)
const APPS_CHAT = "/chat";
const APPS_IMAGE = "/image";
const APPS_TTS = "/tts";
const APPS_PDF = "/pdf";
const APPS_AGENT = "/agent";
const DEMO_PREFIX = "/demo";
const pathsConfig = {
index: "/",
demo: {
index: DEMO_PREFIX,
scrollTest: `${DEMO_PREFIX}/scroll-test`,
},
apps: {
chat: {
index: APPS_CHAT,
chat: (id: string) => `${APPS_CHAT}/${id}`,
},
image: {
index: APPS_IMAGE,
history: `${APPS_IMAGE}/history`,
detail: (id: string) => `${APPS_IMAGE}/${id}`,
generation: (id: string) => `${APPS_IMAGE}/generation/${id}`,
},
tts: APPS_TTS,
pdf: {
index: APPS_PDF,
detail: (id: string) => `${APPS_PDF}/${id}`,
chat: (id: string) => `${APPS_PDF}/${id}`,
},
agent: APPS_AGENT,
},
admin: {
index: ADMIN_PREFIX,
users: {
index: `${ADMIN_PREFIX}/users`,
user: (id: string) => `${ADMIN_PREFIX}/users/${id}`,
},
organizations: {
index: `${ADMIN_PREFIX}/organizations`,
organization: (slug: string) => `${ADMIN_PREFIX}/organizations/${slug}`,
},
customers: {
index: `${ADMIN_PREFIX}/customers`,
customer: (id: string) => `${ADMIN_PREFIX}/customers/${id}`,
},
meshes: {
index: `${ADMIN_PREFIX}/meshes`,
mesh: (id: string) => `${ADMIN_PREFIX}/meshes/${id}`,
},
sessions: {
index: `${ADMIN_PREFIX}/sessions`,
},
invites: {
index: `${ADMIN_PREFIX}/invites`,
},
audit: {
index: `${ADMIN_PREFIX}/audit`,
},
},
marketing: {
pricing: "/pricing",
contact: "/contact",
blog: {
index: BLOG_PREFIX,
post: (slug: string) => `${BLOG_PREFIX}/${slug}`,
},
legal: (slug: string) => `${LEGAL_PREFIX}/${slug}`,
},
auth: {
login: `${AUTH_PREFIX}/login`,
register: `${AUTH_PREFIX}/register`,
join: `${AUTH_PREFIX}/join`,
forgotPassword: `${AUTH_PREFIX}/password/forgot`,
updatePassword: `${AUTH_PREFIX}/password/update`,
error: `${AUTH_PREFIX}/error`,
},
dashboard: {
user: {
index: DASHBOARD_PREFIX,
ai: `${DASHBOARD_PREFIX}/ai`,
vocabulary: `${DASHBOARD_PREFIX}/vocabulary`,
meshes: {
index: `${DASHBOARD_PREFIX}/meshes`,
new: `${DASHBOARD_PREFIX}/meshes/new`,
mesh: (id: string) => `${DASHBOARD_PREFIX}/meshes/${id}`,
invite: (id: string) => `${DASHBOARD_PREFIX}/meshes/${id}/invite`,
live: (id: string) => `${DASHBOARD_PREFIX}/meshes/${id}/live`,
},
invites: `${DASHBOARD_PREFIX}/invites`,
settings: {
index: `${DASHBOARD_PREFIX}/settings`,
security: `${DASHBOARD_PREFIX}/settings/security`,
billing: `${DASHBOARD_PREFIX}/settings/billing`,
},
},
organization: (slug: string) => ({
index: `${DASHBOARD_PREFIX}/${slug}`,
settings: {
index: `${DASHBOARD_PREFIX}/${slug}/settings`,
},
members: `${DASHBOARD_PREFIX}/${slug}/members`,
}),
},
} as const;
export {
pathsConfig,
DASHBOARD_PREFIX,
ADMIN_PREFIX,
BLOG_PREFIX,
AUTH_PREFIX,
API_PREFIX,
LEGAL_PREFIX,
};