# Vojo — Project Guide Matrix chat client (React 18 + TypeScript), forked from Cinny and rebranded as Vojo. This is only client repo (there is no server side sources) ## Quick Start ```bash npm start # dev server on :8080 npm run build # production build → dist/ npm run lint # eslint + prettier npm run typecheck # tsc --noEmit ``` Build: **Vite 5.4** with vanilla-extract, WASM, PWA plugins. ## Source Layout ``` src/ ├── index.tsx # Entry point ├── client/ │ ├── initMatrix.ts # Matrix SDK init (createClient, startClient, logout) │ └── secretStorageKeys.js # Crypto callbacks ├── types/matrix/ # Matrix protocol types (room.ts, accountData.ts, common.ts) └── app/ ├── i18n.ts # i18next config ├── pages/ │ └── App.tsx # Root component (providers, config loader) ├── features/ # Feature modules ├── components/ # Shared components ├── hooks/ # ~117 custom hooks ├── state/ # Jotai atoms ├── plugins/ # Content plugins ├── utils/ # Utilities └── styles/ # Vanilla-extract global styles ``` ## Pages & Routing (`src/app/pages/`) Router in `Router.tsx` — createBrowserRouter / createHashRouter. - **Auth**: `auth/login/`, `auth/register/`, `auth/reset-password/` - **Client**: `client/` — main layout after login - `home/` — Home timeline (path: `/home/`, root `/` redirects here) - `direct/` — DMs (path: `/direct/`) - `space/` — Space view (path: `/:spaceIdOrAlias/`) - `explore/` — Public rooms (path: `/explore/`) - `inbox/` — Notifications, invites (path: `/inbox/`) - `create/` — New room/space (path: `/create/`) - `sidebar/` — Tab components for sidebar nav - `WelcomePage.tsx` — Empty state - `SyncStatus.tsx`, `SpecVersions.tsx` — Connection status ## Features (`src/app/features/`) | Dir | Purpose | |-----|---------| | `room/` | Core room view — **RoomTimeline.tsx** (63KB), **RoomInput.tsx** (24KB), **RoomViewHeader.tsx** (18KB), MembersDrawer, MessageEditor, RoomTombstone | | `room-nav/` | Room list navigation items & categories | | `room-settings/` | Room-specific settings page | | `common-settings/` | Shared settings: general, members, permissions, emojis-stickers, developer-tools | | `space-settings/` | Space-specific settings | | `settings/` | User settings (general, account, notifications, devices, emojis, about, dev-tools) | | `lobby/` | Space/room lobby view | | `search/` | Global search | | `message-search/` | In-room message search | | `create-chat/` | DM creation flow | | `create-room/` | Room creation | | `create-space/` | Space creation | | `add-existing/` | Join existing rooms | | `join-before-navigate/` | Pre-join navigation logic | | `call/` | Element Call integration | | `call-status/` | Call state display | ## Key Components (`src/app/components/`) - `message/` — Message rendering (layout variants: compact, bubble, modern) - `editor/` — Slate-based rich text editor - `emoji-board/` — Emoji picker - `image-pack-view/` — Custom emoji pack management - `image-viewer/`, `Pdf-viewer/` — Media viewers - `sidebar/` — Sidebar navigation - `user-profile/` — User info, power chips, moderation - `member-tile/` — Member list items - `power/` — Power level UI - `upload-card/` — Upload progress cards - `url-preview/` — Link previews - `page/` — Page layout wrapper (Page, PageHeader, PageContent) - `setting-tile/` — Settings list item pattern - `sequence-card/`, `cutout-card/` — Card layouts - `uia-stages/` — User-interactive auth stages (email, captcha, token) - `room-intro/` — Room introduction card - `invite-user-prompt/`, `join-address-prompt/`, `leave-room-prompt/` — Dialogs ## State Management **Jotai** atoms in `src/app/state/`: - `settings.ts` — User preferences (MessageLayout, DateFormat, etc.) - `sessions.ts` — Active session - `upload.ts` — Upload progress - `room/` — roomInputDrafts, roomToParents, roomToUnread - `room-list/` — roomList, inviteList, sorting/filtering Some atoms persist to localStorage (e.g. `settings.ts`, `navToActivePath.ts`), others are in-memory only (e.g. `upload.ts`, `roomInputDrafts.ts`). Access via hooks in `state/hooks/`. ## Localisation (i18n) **Config**: `src/app/i18n.ts` — i18next + HTTP backend + language detector, `fallbackLng: 'ru'` **Locale files**: `public/locales/en.json`, `public/locales/ru.json` **Namespaces** (top-level keys in JSON): `Organisms`, `Auth`, `Settings`, `Search`, `Home`, `Direct`, `Room`, `Inbox`, `Explore`, `Create`, `RoomSettings` **Pattern**: ```tsx import { useTranslation } from 'react-i18next'; const { t } = useTranslation(); return {t('RoomSettings.some_key')}; ``` **Conventions**: - Each component gets its own `useTranslation()` call - In custom hooks with `useMemo`, add `[t]` to dependency array - `react-i18next` import goes after framework imports, before local imports - For dynamic values: `t('key', { count: 5 })` - For rare cases with HTML in translations: `dangerouslySetInnerHTML` (used sparingly, e.g. in RoomAddress.tsx) ## Key Libraries - **React 18.2** + React Router DOM 6 - **matrix-js-sdk 38.2** — Matrix protocol - **folds 2.6** — UI component library - **jotai 2.6** — State management - **vanilla-extract** — Type-safe CSS - **slate 0.123** — Rich text editor - **@tanstack/react-query 5** — Data fetching - **@tanstack/react-virtual 3** — Virtual scrolling - **i18next 23 + react-i18next 15** — Localisation - **Capacitor 8.3** — Native Android wrapper - **@capacitor/browser 8.0** — External link handling in native ## Android (Capacitor) **Requirements**: Node >=22, JDK 17+ (21 used), Android SDK with platform 36 + build-tools 36.0.0 **Config**: `capacitor.config.ts` — `appId: chat.vojo.app`, `webDir: dist` **Android project**: `android/` — generated, `targetSdkVersion 36`, `compileSdkVersion 36`, `minSdkVersion 24` **Build scripts**: ```bash npm run build:android:debug # full chain: build → sync → debug APK npm run build:android:release # full chain: build → sync → release APK npm run build:android:aab # full chain: build → sync → release AAB npm run android:sync # sync dist/ → android assets npm run android:apk:debug # gradle debug build only ``` **APK output**: `android/app/build/outputs/apk/debug/app-debug.apk` **Version**: `versionCode` and `versionName` auto-derived from `package.json` version (major*1000000 + minor*1000 + patch) **Key architecture decisions**: - Bundled build (dist/ copied into APK), not remote WebView - Service Worker kept active — critical for authenticated Matrix media (MSC3916 / spec v1.11+). Do NOT disable. `resolveServiceWorkerRequests` default `true`. - Edge-to-edge via `EdgeToEdge.enable()` in `MainActivity.java` + `windowLayoutInDisplayCutoutMode: shortEdges` - External links opened via `@capacitor/browser` plugin (see `src/app/utils/capacitor.ts`) - `body` background-color bound to folds theme variable `var(--oq6d070)` for consistent safe-area coloring - Safe-area insets applied on `#root` (not `body`) so theme background extends behind system bars **VSCode tasks** (`.vscode/tasks.json`): - `Deploy to vojo.chat` (Ctrl+Shift+D) — web deploy - `Deploy to Android (ADB)` (Ctrl+Shift+A) — build + adb install **ADB wireless**: pair via `adb pair : `, connect via `adb connect :`. Ports for pair and connect are different. **SDK location**: `/usr/lib/android-sdk`, also set in `android/local.properties` ## Matrix SDK Patterns ```tsx const mx = useMatrixClient(); // Get SDK instance const room = useRoom(); // Current room const stateEvent = useStateEvent(room, StateEvent.Type); // Room state const powerLevels = usePowerLevels(room); // Permissions ``` ## Git - Main branch: `dev` - Current work branch: `vojo/dev` - Semantic-release on `dev` branch - CI: GitHub Actions (build, deploy, docker, netlify)