Private
Public Access
Version bump in both pyproject.toml and package.json. Documentation update covers everything shipped since v1.0.0 (direct messages, message deletion, custom emoji, video attachments, DM/room email notifications, active sessions, and more), and corrects claims that had gone stale -- backend/README.md and DEPLOYMENT.md both still said "no server-side session revocation" and backend/README.md said "no custom/uploaded emoji," both now false. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
124 lines
6.7 KiB
Markdown
124 lines
6.7 KiB
Markdown
# 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`](../README.md) and
|
|
[`../backend/README.md`](../backend/README.md) for full local setup.
|
|
|
|
## Dev
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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 (DarkSingularity 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`](../LICENSE).
|