# CLAUDE.md — tweb (Telegram Web K) ## Project Overview **tweb** is a full-featured Telegram web client (https://web.telegram.org/k/) built with Solid.js and TypeScript. It implements Telegram's MTProto protocol directly in the browser (no third-party API wrappers). The codebase is large (~100k+ lines excluding vendor), mature, and highly performance-oriented. Author: Eduard Kuzmenko. License: GPL v3. ## Tech Stack | Layer | Technology | |---|---| | UI Framework | Solid.js (custom fork in `src/vendor/solid/`) | | Language | TypeScript 5.7 | | Build | Vite 5 | | CSS | SCSS (sass) | | Testing | Vitest | | Package Manager | pnpm 9 | | Protocol | MTProto (custom implementation) | | Storage | IndexedDB + CacheStorage + localStorage | | Workers | SharedWorker + ServiceWorker | ## Development ```bash pnpm install pnpm start # Dev server on :8080 pnpm build # Production build → dist/ pnpm test # Run tests (Vitest) pnpm lint # ESLint on src/**/*.ts ``` Debug query params: `?test=1` (test DCs), `?debug=1` (verbose logging), `?noSharedWorker=1` (disable shared worker). ## Directory Structure ``` src/ ├── components/ # Solid.js UI components (.tsx) │ ├── chat/ # Chat bubbles, topbar, sidebars │ ├── popups/ # Modal/popup components │ ├── mediaEditor/ # Media editing UI │ └── ... # 200+ feature folders ├── lib/ │ ├── appManagers/ # 55+ domain managers (chats, users, messages, etc.) │ ├── mtproto/ # MTProto protocol implementation │ ├── storages/ # IndexedDB/localStorage wrappers │ ├── rootScope.ts # Global event emitter & app context │ └── mainWorker/ # Background worker logic ├── stores/ # Solid.js reactive stores (13 stores) ├── helpers/ # 145+ utility functions ├── hooks/ # Solid.js hooks ├── pages/ # Auth pages (login, signup, etc.) ├── config/ # App constants, state schema, emoji, currencies ├── environment/ # Browser feature detection (39 modules) ├── scss/ # Global stylesheets ├── vendor/ # Third-party forks (solid, solid-transition-group) ├── scripts/ # Build & codegen scripts └── tests/ # Test files ``` ## Path Aliases Always use these aliases instead of relative paths: ```typescript @components/* → src/components/ @helpers/* → src/helpers/ @hooks/* → src/hooks/ @stores/* → src/stores/ @lib/* → src/lib/ @appManagers/* → src/lib/appManagers/ @environment/* → src/environment/ @config/* → src/config/ @vendor/* → src/vendor/ @layer → src/layer.d.ts (MTProto API types) @types → src/types.d.ts (utility types) @/* → src/ // Solid.js resolves to the custom fork: solid-js → src/vendor/solid solid-js/web → src/vendor/solid/web solid-js/store → src/vendor/solid/store ``` ## Code Style (enforced by ESLint) - **Indent**: 2 spaces (no tabs) - **Quotes**: single quotes; template literals allowed - **Line endings**: Unix (LF); file must end with newline - **No trailing spaces** - **Comma dangle**: never (`{a: 1, b: 2}` not `{a: 1, b: 2,}`) - **Object/array spacing**: no spaces inside braces/brackets - `{a: 1}` not `{ a: 1 }` - `[1, 2]` not `[ 1, 2 ]` - **Keyword spacing**: no space after `if`, `for`, `while`, `switch`, `catch` - `if(condition)` not `if (condition)` - `for(...)` not `for (...)` - **Function paren**: no space before paren — `function foo()` not `function foo ()` - **No `return await`**: use `return promise` directly - **Max 2 consecutive blank lines** - **`prefer-const`** with destructuring: `all` ## TypeScript Notes - `strict: true` but `strictNullChecks: false` and `strictPropertyInitialization: false` - `useDefineForClassFields: false` — important for class field behavior - `jsxImportSource: solid-js` — JSX is Solid.js, not React - MTProto types live in `src/layer.d.ts` (664KB, auto-generated); import from `@layer` - Utility types (AuthState, WorkerTask, etc.) live in `src/types.d.ts`; import from `@types` - Global types available everywhere: `PeerId`, `UserId`, `ChatId`, `BotId`, `DocId`, `Long`, `Icon`, `ApiError`, `ErrorType`, `MaybePromise`. Defined in `src/global.d.ts`. ## Key Patterns ### Solid.js Components Components are in `.tsx` files. Props typed inline. Use `classNames()` helper for class composition: ```typescript import {JSX} from 'solid-js'; import classNames from '@helpers/string/classNames'; export default function MyComponent(props: { class?: string, children: JSX.Element }) { return (
{props.children}
); } ``` ### CSS Modules Scoped styles use `.module.scss` files. Import as `styles`: ```typescript import styles from '@components/chat/bubbles/service.module.scss'; // Usage:
``` ### Solid.js Stores Stores in `src/stores/` use `createRoot` + `createSignal` and export a hook: ```typescript import {createRoot, createSignal} from 'solid-js'; import rootScope from '@lib/rootScope'; const [value, setValue] = createRoot(() => createSignal(initialValue)); rootScope.addEventListener('some_event', setValue); export default function useValue() { return value; } ``` ### App Managers Business logic lives in `AppManager` subclasses in `src/lib/appManagers/`. They communicate via `rootScope` events and are accessed via `rootScope.managers`: ```typescript import {AppManager} from '@appManagers/manager'; export class AppSomethingManager extends AppManager { protected after() { // Initialization after state loaded this.apiUpdatesManager.addMultipleEventsListeners({...}); } } ``` Normally most of the interaction with MTProto should be done through the app managers, wrapping the raw APIs with a nicer interface, caching layer, etc. Managers are the source of truth. Invoking MTProto methods is done via: ```typescript // invoke normally await this.apiManager.invokeApi('payments.checkCanSendGift', {gift_id: gift.id}) // invoke with deduplication await this.apiManager.invokeApiSingle('payments.checkCanSendGift', {gift_id: gift.id}) // invoke and do something with the result (only available inside managers) return this.apiManager.invokeApiSingleProcess({ method: 'some.method', params: {...}, processResult: (result) => { // when the result type has {chats, users} fields, use this method to save them this.appUsersManager.saveApiPeers(result); // when the result is `Updates`, use this method to handle them this.apiUpdatesManager.processUpdateMessage(result); } }); ``` ### rootScope Global event bus and context. Available everywhere: ```typescript import rootScope from '@lib/rootScope'; rootScope.addEventListener('premium_toggle', handler); rootScope.managers.appChatsManager.getChat(chatId); ``` IMPORTANT: `rootScope.managers.*` are asynchronous proxies to a whared worker. Every manager method returns a `Promise`, even if the manager's own methods seem synchronous. ### Imports from `@layer` All MTProto types come from `@layer`: ```typescript import {Message, Chat, User, InputPeer} from '@layer'; ``` ## CSS / SCSS - Global styles in `src/scss/` - Component-scoped styles in `.module.scss` next to component files - BEM-like class naming convention - CSS variables used for theming ## Important Files | File | Purpose | |---|---| | `src/index.ts` | App entry point, account/auth init | | `src/lang.ts` | All i18n strings (232KB) | | `src/layer.d.ts` | MTProto API types (auto-generated, 664KB) | | `src/types.d.ts` | Utility/app types | | `src/global.d.ts` | Global interface augmentations | | `src/config/state.ts` | Application state schema | | `src/config/app.ts` | App constants | | `src/lib/rootScope.ts` | Global event emitter | | `vite.config.ts` | Build configuration | | `eslint.config.mjs` | ESLint flat config | ## What NOT to Do - Do not add `eslint-disable` without a reason - Do not use `return await` (rule enforced) - Do not use spaces inside `{}` for objects or `[]` for arrays - Do not use `if (` with a space — use `if(` - Do not import from `react` or use React patterns — this is Solid.js - Do not use relative `../../` imports when an alias exists - Do not use `var` — use `const`/`let` - Do not add trailing commas in arrays/objects ## Running Tests ```bash pnpm test # all tests pnpm test src/tests/foo # specific test file ``` Vitest config: `threads: false`, `globals: true`, jsdom environment, setup in `src/tests/setup.ts`. # RTK (Rust Token Killer) - Token-Optimized Commands ## Golden Rule **Always prefix commands with `rtk`**. If RTK has a dedicated filter, it uses it. If not, it passes through unchanged. This means RTK is always safe to use. **Important**: Even in command chains with `&&`, use `rtk`: ```bash # ❌ Wrong git add . && git commit -m "msg" && git push # ✅ Correct rtk git add . && rtk git commit -m "msg" && rtk git push ``` ## RTK Commands by Workflow ### Build & Compile (80-90% savings) ```bash rtk cargo build # Cargo build output rtk cargo check # Cargo check output rtk cargo clippy # Clippy warnings grouped by file (80%) rtk tsc # TypeScript errors grouped by file/code (83%) rtk lint # ESLint/Biome violations grouped (84%) rtk prettier --check # Files needing format only (70%) rtk next build # Next.js build with route metrics (87%) ``` ### Test (90-99% savings) ```bash rtk cargo test # Cargo test failures only (90%) rtk vitest run # Vitest failures only (99.5%) rtk playwright test # Playwright failures only (94%) rtk test # Generic test wrapper - failures only ``` ### Git (59-80% savings) ```bash rtk git status # Compact status rtk git log # Compact log (works with all git flags) rtk git diff # Compact diff (80%) rtk git show # Compact show (80%) rtk git add # Ultra-compact confirmations (59%) rtk git commit # Ultra-compact confirmations (59%) rtk git push # Ultra-compact confirmations rtk git pull # Ultra-compact confirmations rtk git branch # Compact branch list rtk git fetch # Compact fetch rtk git stash # Compact stash rtk git worktree # Compact worktree ``` Note: Git passthrough works for ALL subcommands, even those not explicitly listed. ### GitHub (26-87% savings) ```bash rtk gh pr view # Compact PR view (87%) rtk gh pr checks # Compact PR checks (79%) rtk gh run list # Compact workflow runs (82%) rtk gh issue list # Compact issue list (80%) rtk gh api # Compact API responses (26%) ``` ### JavaScript/TypeScript Tooling (70-90% savings) ```bash rtk pnpm list # Compact dependency tree (70%) rtk pnpm outdated # Compact outdated packages (80%) rtk pnpm install # Compact install output (90%) rtk npm run