Files
ajet-chat/docs/prd/web-sm.md
2026-02-17 01:08:02 -05:00

22 KiB

PRD: Web Session Manager

Module: web-sm/ | Namespace: ajet.chat.web.* Status: v1 | Last updated: 2026-02-17


1. Overview

The Web Session Manager serves the browser-based chat UI. It renders HTML via Hiccup, uses Datastar for SSE-driven reactivity (server pushes HTML fragments to update the page), subscribes to NATS for real-time events, and calls the API for data reads/writes. No direct PostgreSQL access.

2. Architecture

Browser ←─ SSE ──→ Web SM ──→ NATS (subscribe events)
       ←─ HTML ──→        ──→ API (HTTP, data reads/writes)

Key principle: Full Datastar hypermedia. The server renders all HTML. Client-side JS is minimal — only what Datastar requires plus small helpers for image paste and textarea auto-resize.

3. Page Layout (Discord-Style)

┌─────┬───────────────┬─────────────────────────────────────────┬──────────────┐
│     │               │  # general                          ☰  │              │
│  C  │  CATEGORIES   │  ┌── channel topic text ──────────┐    │   THREAD     │
│  O  │               │  │                                │    │   PANEL      │
│  M  │  ▼ General    │  │  alice  10:30 AM               │    │  (when open) │
│  M  │    # general  │  │  hello everyone!                │    │              │
│  U  │    # random   │  │                                │    │  Thread in   │
│  N  │               │  │  bob  10:31 AM                  │    │  # general   │
│  I  │  ▼ Dev        │  │  hey! check this out            │    │              │
│  T  │    # backend  │  │  [image preview]                │    │  alice:      │
│  Y  │    # frontend │  │                                │    │  hello!      │
│     │               │  │  ── New Messages ──             │    │              │
│  I  │  ─────────    │  │                                │    │  bob:        │
│  C  │  DIRECT MSGS  │  │  carol  10:45 AM               │    │  reply here  │
│  O  │    alice      │  │  @bob nice work! 👍             │    │              │
│  N  │    bob ●      │  │                                │    │              │
│  S  │    group (3)  │  │  ── Load older messages ──     │    │              │
│     │               │  │                                │    │              │
│  +  │               │  ├────────────────────────────────┤    │              │
│     │  🔍 Search    │  │  @mention autocomplete ↕       │    │              │
│     │               │  │  [message input area      ] 📎│    │              │
│     │               │  │  bob is typing...              │    │              │
│     │               │  └────────────────────────────────┘    │              │
└─────┴───────────────┴─────────────────────────────────────────┴──────────────┘

3.1 Layout Regions

Region ID Description
Community Strip #community-strip Vertical icon strip (far left). Community avatars/initials. Click to switch. + button to create/join.
Sidebar #sidebar Channel list with collapsible categories. DM section below separator. Search at bottom.
Channel Header #channel-header Channel name, topic, settings icon.
Message List #message-list Paginated message display. "Load older" button at top. New message divider.
Message Input #message-input Auto-expanding textarea, @mention autocomplete, image paste, typing indicator display.
Thread Panel #thread-panel Slide-in panel on right when viewing a thread. Shows root message + replies.
Member List #member-list Optional right panel showing channel members + presence. Toggle via header icon.

3.2 Community Strip

┌─────┐
│ MT  │  ← "My Team" (initials, or community avatar)
├─────┤
│ OS  │  ← "Open Source"
├─────┤
│     │
│ ... │
├─────┤
│ DM  │  ← DMs section (always present, not community-scoped)
├─────┤
│  +  │  ← Create/join community
└─────┘
  • Active community: highlighted border/background
  • Unread indicator: dot badge on community icon
  • Mention indicator: red badge with count
  • DM icon always at top (or bottom — separate from communities)
  • Each community remembers the last-viewed channel

3.3 Sidebar — Channel List

▼ GENERAL                    ← category (collapsible)
  # general              3   ← unread count badge
  # random
  # announcements

▼ DEVELOPMENT
  # backend              ●   ← mention indicator
  # frontend
  # devops

────────────────────────────  ← separator

DIRECT MESSAGES
  alice                  ●   ← online indicator (green dot)
  bob
  group chat (3)         2   ← unread count
  • Categories collapsible (click header to toggle)
  • Channels: bold name = has unread messages
  • Unread count badge (number) for messages
  • Red dot for unread @mentions
  • DM section: shows online status dot, unread count
  • Right-click channel → context menu (mute, leave, mark read)

3.4 Message Display

Each message renders as:

┌─ avatar ─┬─────────────────────────────────────────────┐
│  [img]   │  alice                        10:30 AM      │
│          │  hello everyone! check out #backend          │
│          │                                              │
│          │  [inline image preview ─────────────]       │
│          │                                              │
│          │  👍 3  ❤️ 1  │  + Add Reaction  │  ⋮ More  │
│          │  💬 2 replies (last reply 5 min ago)         │
└──────────┴──────────────────────────────────────────────┘

Message elements:

  • Avatar (from user profile or OAuth)
  • Username (display name, colored if admin/owner)
  • Timestamp (relative for today, absolute for older)
  • Body (rendered Discord markdown → HTML)
  • Mentions: @alice highlighted, @here highlighted differently
  • Channel links: #backend clickable
  • Inline image preview (click for full size)
  • Reaction bar: emoji + count, click to toggle your reaction, + to add new
  • Thread indicator: reply count + last reply time, click to open thread panel
  • Hover actions: reply (thread), react, more (edit, delete, copy link)
  • Edited indicator: "(edited)" text after timestamp if edited_at is set

Message grouping: Consecutive messages from the same user within 5 minutes collapse (no repeated avatar/name).

3.5 Message Input Area

┌─────────────────────────────────────────────────────────┐
│  @bob ▼                              ← autocomplete    │
│  @bobby                              dropdown            │
│  @bob_builder                                           │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  [auto-expanding textarea                          ] 📎│
│                                                         │
│  alice is typing...                                     │
└─────────────────────────────────────────────────────────┘
  • Auto-expanding textarea (grows with content, max ~10 lines then scroll)
  • Enter to send, Shift+Enter for newline
  • @ triggers user mention autocomplete (search by username/display name)
  • # triggers channel autocomplete
  • / at start triggers slash command autocomplete
  • 📎 button for image upload (opens file picker, images only)
  • Ctrl+V / Cmd+V pastes clipboard images directly
  • Typing indicator: "alice is typing..." / "alice and bob are typing..." / "several people are typing..."

3.6 Thread Panel

┌──────────────────────────────────┐
│  Thread in #general          ✕  │
│─────────────────────────────────│
│  alice  10:30 AM                │
│  hello everyone!                │
│─────────── 3 replies ──────────│
│  bob  10:31 AM                  │
│  hey there!                     │
│                                 │
│  carol  10:32 AM                │
│  hi all!                        │
│                                 │
│  [reply input                ]  │
│  ☐ Also send to #general       │
└──────────────────────────────────┘
  • Opens as a right panel (does not replace message list)
  • Shows root message at top
  • Thread replies below
  • Reply input at bottom
  • Optional "Also send to #general" checkbox (broadcasts reply to channel)
  • Close button returns to normal view

3.7 Search Modal

┌──────────────────────────────────────────┐
│  🔍 Search...                            │
│  ┌────────────────────────────────────┐  │
│  │  [search input                  ]  │  │
│  │  Filter: ○ All ○ Messages          │  │
│  │          ○ Channels ○ Users        │  │
│  └────────────────────────────────────┘  │
│                                          │
│  Results:                                │
│  ┌────────────────────────────────────┐  │
│  │  #backend · alice · Feb 15         │  │
│  │  "check out the new **API** docs"  │  │
│  ├────────────────────────────────────┤  │
│  │  #general · bob · Feb 14           │  │
│  │  "the API is looking good"         │  │
│  └────────────────────────────────────┘  │
│  Load more results                       │
└──────────────────────────────────────────┘
  • Triggered by Ctrl+K or clicking search in sidebar
  • Search input with type filter tabs
  • Results show context (channel, author, date) with highlighted matches
  • Click result → navigate to message in channel (scroll to it)

4. Datastar SSE Integration

4.1 How It Works

Datastar uses SSE to push HTML fragment updates from server to client. The server decides what changes and sends targeted DOM updates.

Connection flow:

  1. Browser loads initial full page (server-rendered Hiccup)
  2. Datastar opens SSE connection to /sse/events
  3. Server subscribes to relevant NATS subjects for the user
  4. When events arrive (new message, reaction, typing, etc.), server: a. Renders the HTML fragment for the change b. Sends it over SSE with a Datastar merge directive c. Browser updates the DOM in-place

SSE endpoint: GET /sse/events

  • Params: ?community_id=<uuid> (which community is active)
  • Server holds connection open, pushes fragments

4.2 Datastar Fragment Types

Event Fragment Target Behavior
New message #message-list Append message HTML at bottom
Message edited #msg-{id} Replace message content
Message deleted #msg-{id} Remove element
Reaction added/removed #reactions-{msg-id} Replace reaction bar
Typing start #typing-indicator Update typing text
Typing stop #typing-indicator Update typing text
User online #member-{user-id} Update presence dot
User offline #member-{user-id} Update presence dot
Channel created #channel-list Append channel to sidebar
Unread count update #unread-{channel-id} Update badge number
Notification #notification-badge Update notification count
Thread reply #thread-{parent-id} Update reply count / append in thread panel

4.3 SSE Reconnection

If SSE connection drops:

  1. Datastar auto-reconnects (built-in)
  2. Server uses NATS JetStream to replay missed events since last event ID
  3. Last-Event-ID header used to resume from correct position

5. Client → Server Signals

All user actions are HTTP POSTs (Datastar form submissions):

Action Endpoint Payload
Send message POST /web/messages {channel_id, body_md, parent_id?}
Edit message POST /web/messages/:id/edit {body_md}
Delete message POST /web/messages/:id/delete
Add reaction POST /web/reactions {message_id, emoji}
Remove reaction POST /web/reactions/remove {message_id, emoji}
Switch channel POST /web/navigate {channel_id}
Switch community POST /web/navigate {community_id}
Mark channel read POST /web/read {channel_id, message_id}
Upload image POST /web/upload Multipart (image file)
Typing indicator POST /web/typing {channel_id}
Search POST /web/search {query, type?, community_id?}
Slash command POST /web/command {command, channel_id, community_id}
Create community POST /web/communities {name, slug}
Create channel POST /web/channels {name, type, visibility, category_id?}

All of these proxy to the API internally and return Datastar fragment responses.

6. Pages & Routes (Server-Rendered)

Route Description
GET / Redirect to last community or setup wizard
GET /app Main chat application (full page render)
GET /app/channel/:id Direct link to a channel
GET /app/dm/:id Direct link to a DM
GET /setup Community creation wizard (first-time only)

6.1 Setup Wizard Page

┌──────────────────────────────────────┐
│                                      │
│     Welcome to ajet chat!            │
│                                      │
│     Create your community            │
│                                      │
│     Name: [My Team              ]    │
│     Slug: [my-team              ]    │
│           chat.example.com/my-team   │
│                                      │
│     [  Create Community  ]           │
│                                      │
└──────────────────────────────────────┘
  • Shown after first-ever OAuth login
  • Slug auto-generated from name (lowercase, hyphenated)
  • On submit: creates community + #general, redirects to /app

7. Connection Tracking

Web SM maintains per-user connection state:

{user-id {:sse-connection   <http-kit-channel>
          :active-community uuid
          :active-channel   uuid
          :nats-subs        [sub-handles...]
          :last-seen        instant}}
  • On SSE connect: subscribe to user's communities on NATS, track connection
  • On SSE disconnect: unsubscribe from NATS, clean up
  • On community switch: unsubscribe old community subjects, subscribe new ones
  • Multiple tabs: each tab is a separate SSE connection with independent state

8. Unread Tracking

How unread counts are calculated:

  1. channel_members.last_read_message_id — stored in API DB
  2. When user views a channel: Web SM calls POST /api/channels/:id/read with the latest message ID
  3. Unread count = messages in channel with id > last_read_message_id
  4. Mention count = mentions targeting user in those unread messages
  5. On SSE events: Web SM pushes updated badge fragments for affected channels

Sidebar rendering:

  • Bold channel name if unread count > 0
  • Number badge for unread message count
  • Red dot if unread mentions exist
  • Community icon badge: sum of unread mentions across all channels in that community

9. Test Cases

9.1 Page Rendering

ID Test Description
WEB-T1 Initial page load GET /app returns full HTML with sidebar, message list, input area
WEB-T2 Channel list renders Sidebar shows categories + channels for active community
WEB-T3 DM list renders DM section shows user's DM channels
WEB-T4 Community strip renders Icon strip shows all user's communities
WEB-T5 Message list renders Channel messages displayed with correct formatting
WEB-T6 Markdown rendering Discord markdown renders correctly as HTML
WEB-T7 Mention rendering @<user:uuid> renders as @displayname with highlight
WEB-T8 Image inline preview Image attachments render as inline previews
WEB-T9 Thread indicator Messages with replies show reply count and link
WEB-T10 Setup wizard GET /setup shows community creation form for first-time users

9.2 SSE & Real-Time

ID Test Description
WEB-T11 SSE connection established Browser opens SSE, server subscribes to NATS
WEB-T12 New message appears Message from another user appears in message list without refresh
WEB-T13 Message edit updates Edited message content updates in-place
WEB-T14 Message delete removes Deleted message disappears from list
WEB-T15 Reaction update Adding/removing reaction updates reaction bar in real-time
WEB-T16 Typing indicator shows Other user typing shows indicator below input
WEB-T17 Typing indicator clears Indicator disappears after 15 seconds of no typing
WEB-T18 Presence update User going online/offline updates sidebar dot
WEB-T19 New channel appears Channel created by admin appears in sidebar
WEB-T20 Unread badge updates New message in other channel updates unread count badge
WEB-T21 SSE reconnect After connection drop, reconnects and catches up on missed events
WEB-T22 Community switch Switching community updates sidebar and message list
WEB-T23 Channel switch Switching channel loads new messages, marks old as read

9.3 User Actions

ID Test Description
WEB-T24 Send message Typing and pressing Enter sends message, appears in list
WEB-T25 Shift+Enter newline Shift+Enter adds newline, does not send
WEB-T26 @mention autocomplete Typing @ shows user dropdown, selecting inserts mention
WEB-T27 #channel autocomplete Typing # shows channel dropdown
WEB-T28 /slash command Typing / shows command autocomplete
WEB-T29 Edit message (hover) Hover → edit icon → inline edit mode → save
WEB-T30 Edit after 1 hour Edit button not shown for messages > 1 hour old
WEB-T31 Delete message Hover → delete icon → confirmation → message removed
WEB-T32 Add reaction Click + on reaction bar → emoji picker → reaction added
WEB-T33 Toggle reaction Click existing reaction emoji to toggle on/off
WEB-T34 Image paste Ctrl+V with clipboard image → upload → preview in input → send
WEB-T35 Image upload button Click 📎 → file picker → select image → upload → send
WEB-T36 Open thread Click thread indicator → thread panel opens on right
WEB-T37 Reply in thread Type in thread input → reply appears in thread
WEB-T38 Search Ctrl+K → search modal → type query → results shown → click to navigate
WEB-T39 Create channel Admin clicks + → form → submits → channel appears in sidebar
WEB-T40 Join public channel Click channel → join prompt → joined → messages load
WEB-T41 Leave channel Right-click → leave → channel removed from sidebar
WEB-T42 Collapse category Click category header → channels hidden → click again → shown
WEB-T43 Mark channel read Opening channel marks it as read, badge clears
WEB-T44 Paginated loading Scroll to "Load older" → click → older messages prepended
WEB-T45 Create community Click + on community strip → wizard → community created

9.4 Profile & Settings

ID Test Description
WEB-T46 User profile popover Click username → popover with avatar, name, status, role
WEB-T47 Set status Click own avatar → status input → save
WEB-T48 Set nickname In community settings → nickname field → save

9.5 Error Handling

ID Test Description
WEB-T49 API unreachable Shows error banner, retries
WEB-T50 Message send fails Error shown inline below input, message not lost
WEB-T51 Upload too large Error shown, file not uploaded
WEB-T52 Rate limited Error shown with retry countdown

9.6 Responsive / Layout

ID Test Description
WEB-T53 Desktop layout Full 3-column layout renders correctly
WEB-T54 Thread panel coexists Thread panel + message list visible simultaneously
WEB-T55 Long messages wrap Long messages wrap correctly, no horizontal scroll
WEB-T56 Code block rendering Fenced code blocks render with syntax highlighting
WEB-T57 Spoiler tags `