Files
ds-chat/frontend
ksmithandClaude Sonnet 5.5 62b5eec2fb Show the date on message timestamps that aren't from today
A bare "4:04 PM" gave no way to tell today's message from one sent last
week. Today stays time-only; older messages gain just enough date to
place them: "Yesterday 4:04 PM", a weekday for the past week ("Mon
4:04 PM"), then "Oct 2, 4:04 PM" (with the year if it isn't this one).
Hovering a time shows the full date. All in the viewer's local time.

A new calendar day now always starts a new message group. Previously a
same-sender run that crossed midnight within the 5-minute window (11:58
PM then 12:01 AM) stayed under one header, which would have put the
later message under the wrong day's timestamp. The grouping rule moves
out of MessageList into messageGrouping.ts so it's unit-tested.

useLocalDay refreshes "today" at local midnight and when a hidden tab
becomes visible again, so a tab left open overnight doesn't keep showing
yesterday's messages as a bare time. .message-time no longer wraps, and
.message-author can shrink, so a long display name can't push the longer
label out of the row on a phone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-09 15:25:22 -06:00
..
2026-10-02 16:07:07 -06:00

DS Chat frontend

React 19 + TypeScript + Vite PWA. The full client for DS Chat: auth and invite-based signup, room CRUD with roles/invites, direct messages, real-time WebSocket chat (Markdown, @mentions, reactions, built-in and custom emoji, image/video/file attachments with previews, message editing and deletion), unread indicators and presence, per-user theming (presets plus a custom theme builder), Web Push/email notifications and a desktop- notification bridge, an active-sessions view for managing where you're logged in, offline caching and an auto-update banner via a custom service worker, and a site-admin portal — wired to the backend's REST API and /ws/chat WebSocket endpoint. See ../README.md and ../backend/README.md for full local setup.

Dev

npm install
npm run dev

The dev server proxies /api and /ws to http://localhost:8000 (see vite.config.ts), so the backend must be running for anything beyond the login page to work.

Build

npm run build

vite-plugin-pwa runs in injectManifest mode: instead of generating a service worker, it precaches the build output (src/sw.ts's precacheAndRoute) and injects that manifest into the hand-written worker at src/sw.ts. injectManifest mode was needed over the default generateSW because push/notificationclick listeners have to be hand-written into the worker.

Layout

src/
  main.tsx, App.tsx        routes: /login, /signup, /forgot-password, /reset-password,
                             /rooms, /rooms/:roomId, /admin (AdminRoute-gated), /help;
                             mounts UpdateBanner globally, ChatSocketProvider and
                             CustomEmojiProvider once authed
  types.ts                  shared request/response/WS-envelope types, mirroring the
                             backend's Pydantic schemas

  api/                      fetch wrappers, one file per backend resource: client
                              (base fetch/error handling), auth (incl. active sessions),
                              signup, rooms (incl. DMs), users, bots, webhooks, push,
                              admin, customThemes, customEmoji, uploads
  ws/useChatSocket.ts        the WebSocket hook: connect/reconnect with backoff,
                              join/leave rooms, send/edit/delete/react, visibility-gated
                              presence, triggers an SW update check on reconnect
  context/
    AuthContext.tsx            current-user state, hydrated via GET /api/auth/me
    ChatSocketContext.tsx      shares one useChatSocket instance across the app
    CustomEmojiContext.tsx     fetches the site's custom emoji once, exposes a
                                 shortcode lookup + a refresh() called after upload/delete

  lib/
    avatar.ts                  deterministic accent-color cycling for avatars
    emoji.ts, emojiNames.ts,
    emojiShortcodes.ts, recentEmoji.ts    emoji picker data + recency tracking
    fileSize.ts                 human-readable byte formatting
    lastUser.ts                 cached "who was I last logged in as" for offline shell render
    messageGrouping.ts          groups consecutive messages by sender/time, presence lookup
    push.ts                     PushManager subscribe/unsubscribe, VAPID key conversion,
                                  timeout-guarded so a browser that never settles the
                                  permission prompt can't leave the UI stuck forever
    swUpdate.ts                  bridges the SW registration to useChatSocket's reconnect hook
    theme.ts                     applies preset/custom themes as CSS custom properties

  hooks/
    useEscapeKey.ts              Escape-to-close for modals/popovers
    useOnlineStatus.ts           navigator.onLine, for the OfflineBanner
    useResizableWidth.ts         drag-to-resize (sidebar/panel widths)
    useWindowWidth.ts            viewport width, for responsive sidebar/pane layout

  components/
    ProtectedRoute.tsx, AdminRoute.tsx        auth/site-admin route guards
    TopBar.tsx, Sidebar.tsx, RoomRow.tsx        room list chrome (DMs and Rooms as
                                                  independently collapsible sections)
    ChatPane.tsx, MessageList.tsx, Composer.tsx    chat view: history+live merge,
                                                     message rendering (incl. deleted-message
                                                     tombstones), composer/attach/send
    MessageContent.tsx, MentionAutocomplete.tsx    Markdown rendering (mentions, room
                                                     links, custom emoji `:shortcode:`,
                                                     heading ids, sub/superscript) +
                                                     @mention highlighting/autocomplete
    ImageLightbox.tsx, VideoLightbox.tsx,
    FilePreviewModal.tsx                           attachment viewers (image/video/PDF/
                                                     text/Markdown)
    EmojiPicker.tsx, CustomEmojiUploadModal.tsx     reaction/composer emoji picker
                                                      (built-in + site's custom emoji) and
                                                      its upload dialog
    RoomInfoPanel.tsx                              room details/members/roles/email-
                                                     notification-toggle panel
    NewRoomModal.tsx, BrowseRoomsModal.tsx, UserPicker.tsx    room creation/discovery, member
                                                                picking (also how a DM starts)
    ProfileModal.tsx, ThemeBuilderModal.tsx, CustomThemePreview.tsx
                                                    profile settings (incl. active-sessions
                                                    list) + the custom theme editor (opened
                                                    in its own wide dialog) with a live,
                                                    hoverable mockup of the real UI
    RoomAvatar.tsx, UserAvatar.tsx                 avatar rendering (incl. presence dot)
    OfflineBanner.tsx, UpdateBanner.tsx            connectivity state / new-version-available prompt

  pages/
    LoginPage.tsx, SignupPage.tsx, ForgotPasswordPage.tsx, ResetPasswordPage.tsx
    ChatShellPage.tsx, AdminPage.tsx, HelpPage.tsx

  styles/tokens.css          design tokens (default theme: colors, spacing, etc.)
  sw.ts                      custom service worker (injectManifest): app-shell
                               precache + NetworkFirst runtime caching, push/notificationclick
                               handlers, SKIP_WAITING messaging for the update-prompt flow

License

AGPL-3.0-or-later — see ../LICENSE.