Lumen Docs

Share flow

Guest-shareable conversations with an approval step.

Vocabulary

  • Share request — a member asks to make a conversation externally shareable
  • Share token — an opaque URL fragment granting read + follow-up chat access
  • Public share page/share/<token> — no auth required

Flow

1. Member viewing a thread
        │
        ▼ clicks Share
2. Share request created (pending)
        │
        ▼ admin gets notification
3. Admin approves / rejects
        │
    approved
        │
        ▼
4. Share token generated + link emailed/copied
        │
        ▼
5. Guest opens /share/<token>
   - Sees conversation snapshot
   - Can ask follow-ups limited to same project context

UI patterns

Share button

Lives in:

  • Project detail topbar (icon-only <IconShare>)
  • Thread topbar in chat view
  • Conversation ellipsis menu

Clicking opens the Share modal — NOT the grant access modal. (These were confused in an early iteration, leading to issue #5 where the share button did nothing.)

Share modal (request side)

Request share access for "<thread title>"

 ┌────────────────────────────────────────┐
 │ This thread is private. Requesting    │
 │ share access will notify an admin.     │
 │                                         │
 │ [optional] Message to admin:           │
 │ ┌────────────────────────────────┐      │
 │ │                                │      │
 │ └────────────────────────────────┘      │
 │                                         │
 │                 [Cancel]  [Request]    │
 └────────────────────────────────────────┘

Admin review (at /admin/share-requests)

Pending requests list with:

  • Requester avatar + name
  • Conversation title + project
  • Message (if provided)
  • Approve / Reject buttons
  • Optional response message

Approve → creates ShareToken row, emails requester the URL. Reject → marks request rejected, notifies requester with optional message.

Public share page (at /share/<token>)

Full conversation rendered in read-only mode. Separate topbar (not the main app sidebar) — h-[52px], shows "Shared by <name>" + project badge + Back link.

Below the conversation, a simplified composer lets the guest ask follow-up questions using the same project's documents (but can't create new conversations in the project, can't edit, can't see other conversations).

Backend

| Endpoint | Purpose | |---|---| | POST /share-requests | Member creates request | | GET /share-requests?status=pending | Admin lists (with status filter) | | POST /share-requests/:id/approve | Admin approves → creates token | | POST /share-requests/:id/reject | Admin rejects | | GET /share/:token | Guest reads conversation (public) | | POST /share/:token/messages | Guest follow-up message (public) | | DELETE /share-tokens/:id | Admin revokes (invalidates link) |

Schema

model ShareRequest {
  id             String        @id @default(uuid())
  projectId      String
  conversationId String
  requesterId    String
  message        String?
  status         ShareStatus   @default(pending)
  respondedAt    DateTime?
  respondedById  String?
  responseMsg    String?
  createdAt      DateTime      @default(now())
  approvedTokenId String?      @unique   // when approved, links to the token
}

model ShareToken {
  id             String   @id @default(uuid())
  token          String   @unique  // opaque URL fragment, random 32 bytes
  projectId      String
  conversationId String
  createdById    String   // approver
  expiresAt      DateTime?
  revokedAt      DateTime?
  createdAt      DateTime @default(now())
}

Security notes

  • Token is random 32 bytes, base64url-encoded (~43 chars). Not guessable.
  • Token is stored plaintext in DB — if DB leaks, all tokens leak. Acceptable for dev phase.
  • No expiry by default. Add expiresAt if needed; UI doesn't expose this yet.
  • Revocation is soft (revokedAt timestamp). Checked on every guest request.
  • Guest follow-ups use the same RAG context as the original conversation. No leak of other conversations in the project.

UI state in the app

Notification badge on the "Share requests" nav item when pending count > 0:

{ href: "/admin/share-requests", label: "Share requests", icon: IconShare, badge: 2 }

The badge: 2 is currently hardcoded — when the backend wire-up is finished, replace with real pending count from useShareRequests({ status: "pending" }).

Related