diff --git a/.claude/rules/02-development-commands.md b/.claude/rules/02-development-commands.md index 5ef3b133c..d6f18aaad 100644 --- a/.claude/rules/02-development-commands.md +++ b/.claude/rules/02-development-commands.md @@ -94,7 +94,7 @@ adb kill-server # Stop ADB server adb start-server # Restart ADB server # Force stop app on device: -adb shell am force-stop com.megamind.tauri-app +adb shell am force-stop com.alphahuman.app # Kill Gradle daemon if stuck: # macOS/Linux: @@ -156,10 +156,10 @@ Get-Process | Where-Object {$_.ProcessName -match "tauri|vite|cargo|node"} | Sto ## Build Targets -| Platform | Command | Output | -|----------|---------|--------| -| Windows | `npm run tauri build` | `.msi`, `.exe` | -| macOS | `npm run tauri build` | `.dmg`, `.app` | -| Linux | `npm run tauri build` | `.deb`, `.AppImage` | -| Android | `npm run tauri android build` | `.apk`, `.aab` | -| iOS | `npm run tauri ios build` | `.ipa` | +| Platform | Command | Output | +| -------- | ----------------------------- | ------------------- | +| Windows | `npm run tauri build` | `.msi`, `.exe` | +| macOS | `npm run tauri build` | `.dmg`, `.app` | +| Linux | `npm run tauri build` | `.deb`, `.AppImage` | +| Android | `npm run tauri android build` | `.apk`, `.aab` | +| iOS | `npm run tauri ios build` | `.ipa` | diff --git a/.claude/rules/08-frontend-guide.md b/.claude/rules/08-frontend-guide.md index c2d9bc981..bff4b964f 100644 --- a/.claude/rules/08-frontend-guide.md +++ b/.claude/rules/08-frontend-guide.md @@ -1,11 +1,13 @@ # Frontend Development Guide - Crypto Community Platform ## Overview + Frontend development guide for crypto-focused communication platform using modern React ecosystem with Tauri. ## ✅ CURRENT IMPLEMENTATION STATUS ### Design System (FULLY IMPLEMENTED) + - **Glass Morphism UI**: Enhanced frosted glass effects with 16px backdrop blur throughout interface - **Crypto Price Ticker**: Animated scrolling ticker with BTC/ETH brand colors and JetBrains Mono font - **Navigation System**: Complete nav bar with active states (Dashboard, Portfolio, Chat, Markets) @@ -67,6 +69,7 @@ src/ ``` ### Recent Architecture Changes + - **Settings Modal System**: Complete URL-based modal system with clean white design - Modal routes: `/settings`, `/settings/connections` overlaying existing content - Component structure: SettingsModal, SettingsLayout, ConnectionsPanel, hooks @@ -82,23 +85,23 @@ src/ ### Basic Component ```tsx -import { useState } from 'react'; -import { invoke } from '@tauri-apps/api/core'; +import { useState } from "react"; +import { invoke } from "@tauri-apps/api/core"; function App() { - const [result, setResult] = useState(''); + const [result, setResult] = useState(""); - async function handleClick() { - const greeting = await invoke('greet', { name: 'User' }); - setResult(greeting); - } + async function handleClick() { + const greeting = await invoke("greet", { name: "User" }); + setResult(greeting); + } - return ( -
- -

{result}

-
- ); + return ( +
+ +

{result}

+
+ ); } export default App; @@ -109,7 +112,7 @@ export default App; ### Window Management ```typescript -import { getCurrentWindow } from '@tauri-apps/api/window'; +import { getCurrentWindow } from "@tauri-apps/api/window"; const appWindow = getCurrentWindow(); @@ -123,73 +126,82 @@ await appWindow.maximize(); await appWindow.close(); // Set title -await appWindow.setTitle('New Title'); +await appWindow.setTitle("New Title"); ``` ### File System First, add the plugin: + ```bash npm run tauri add fs ``` ```typescript -import { readTextFile, writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'; +import { + readTextFile, + writeTextFile, + BaseDirectory, +} from "@tauri-apps/plugin-fs"; // Read file -const content = await readTextFile('config.json', { - baseDir: BaseDirectory.AppData +const content = await readTextFile("config.json", { + baseDir: BaseDirectory.AppData, }); // Write file -await writeTextFile('config.json', JSON.stringify(data), { - baseDir: BaseDirectory.AppData +await writeTextFile("config.json", JSON.stringify(data), { + baseDir: BaseDirectory.AppData, }); ``` ### Dialogs First, add the plugin: + ```bash npm run tauri add dialog ``` ```typescript -import { open, save, message } from '@tauri-apps/plugin-dialog'; +import { open, save, message } from "@tauri-apps/plugin-dialog"; // Open file picker const filePath = await open({ - multiple: false, - filters: [{ - name: 'Text', - extensions: ['txt', 'md'] - }] + multiple: false, + filters: [ + { + name: "Text", + extensions: ["txt", "md"], + }, + ], }); // Save dialog const savePath = await save({ - defaultPath: 'document.txt' + defaultPath: "document.txt", }); // Message box -await message('Operation completed!', { title: 'Success' }); +await message("Operation completed!", { title: "Success" }); ``` ### HTTP Requests First, add the plugin: + ```bash npm run tauri add http ``` ```typescript -import { fetch } from '@tauri-apps/plugin-http'; +import { fetch } from "@tauri-apps/plugin-http"; -const response = await fetch('https://api.example.com/data', { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } +const response = await fetch("https://api.example.com/data", { + method: "GET", + headers: { + "Content-Type": "application/json", + }, }); const data = await response.json(); @@ -198,26 +210,26 @@ const data = await response.json(); ## Platform Detection ```typescript -import { platform } from '@tauri-apps/plugin-os'; +import { platform } from "@tauri-apps/plugin-os"; const currentPlatform = await platform(); switch (currentPlatform) { - case 'windows': - // Windows-specific UI - break; - case 'macos': - // macOS-specific UI - break; - case 'linux': - // Linux-specific UI - break; - case 'android': - // Android-specific UI - break; - case 'ios': - // iOS-specific UI - break; + case "windows": + // Windows-specific UI + break; + case "macos": + // macOS-specific UI + break; + case "linux": + // Linux-specific UI + break; + case "android": + // Android-specific UI + break; + case "ios": + // iOS-specific UI + break; } ``` @@ -226,43 +238,45 @@ switch (currentPlatform) { ```css /* Base styles for mobile-first */ .container { - padding: 16px; - font-size: 16px; + padding: 16px; + font-size: 16px; } /* Tablet and larger */ @media (min-width: 768px) { - .container { - padding: 24px; - max-width: 720px; - margin: 0 auto; - } + .container { + padding: 24px; + max-width: 720px; + margin: 0 auto; + } } /* Desktop */ @media (min-width: 1024px) { - .container { - max-width: 960px; - } + .container { + max-width: 960px; + } } /* Safe areas for notched devices (iOS) */ .app { - padding-top: env(safe-area-inset-top); - padding-bottom: env(safe-area-inset-bottom); - padding-left: env(safe-area-inset-left); - padding-right: env(safe-area-inset-right); + padding-top: env(safe-area-inset-top); + padding-bottom: env(safe-area-inset-bottom); + padding-left: env(safe-area-inset-left); + padding-right: env(safe-area-inset-right); } ``` ## Recommended Tech Stack ### UI & Styling + - **Tailwind CSS** - Utility-first CSS framework - **Headless UI** - Accessible, unstyled UI components - **Framer Motion** - Animation library for React ### State & Data Management (Current Implementation) + - **Redux Toolkit** - Currently implemented state management with persistence - **Redux Persist** - Persists auth and telegram state to localStorage - **Socket.io** - Real-time communication via socketService singleton @@ -273,7 +287,7 @@ switch (currentPlatform) { ```typescript // Current implementation uses Redux Toolkit with these slices: -import { useAppSelector, useAppDispatch } from '../store/hooks'; +import { useAppSelector, useAppDispatch } from "../store/hooks"; // Auth state (persisted) const authState = useAppSelector((state) => state.auth); @@ -293,106 +307,120 @@ const telegramState = useAppSelector((state) => state.telegram); // Usage in component function ChatHeader() { - const { token } = useAppSelector((state) => state.auth); - const { profile } = useAppSelector((state) => state.user); - const { isConnected } = useAppSelector((state) => state.socket); + const { token } = useAppSelector((state) => state.auth); + const { profile } = useAppSelector((state) => state.user); + const { isConnected } = useAppSelector((state) => state.socket); - return ( -
-

Chat

-
- {profile?.username} -
-
-
- ); + return ( +
+

Chat

+
+ {profile?.username} +
+
+
+ ); } ``` ## Form Handling with React Hook Form ```typescript -import { useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { z } from 'zod'; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; const messageSchema = z.object({ - content: z.string().min(1, 'Message cannot be empty').max(1000), - channel: z.string().uuid(), + content: z.string().min(1, "Message cannot be empty").max(1000), + channel: z.string().uuid(), }); type MessageForm = z.infer; function MessageInput() { - const { register, handleSubmit, reset, formState: { errors } } = useForm({ - resolver: zodResolver(messageSchema) - }); + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + resolver: zodResolver(messageSchema), + }); - const onSubmit = (data: MessageForm) => { - // Send message via Tauri IPC - invoke('send_message', data); - reset(); - }; + const onSubmit = (data: MessageForm) => { + // Send message via Tauri IPC + invoke("send_message", data); + reset(); + }; - return ( -
- - - {errors.content &&

{errors.content.message}

} -
- ); + return ( +
+ + + {errors.content && ( +

{errors.content.message}

+ )} +
+ ); } ``` ## Data Fetching with TanStack Query ```typescript -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { invoke } from '@tauri-apps/api/core'; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { invoke } from "@tauri-apps/api/core"; // Fetch channels function useChannels() { - return useQuery({ - queryKey: ['channels'], - queryFn: () => invoke('get_channels'), - staleTime: 5 * 60 * 1000, // 5 minutes - }); + return useQuery({ + queryKey: ["channels"], + queryFn: () => invoke("get_channels"), + staleTime: 5 * 60 * 1000, // 5 minutes + }); } // Send message mutation function useSendMessage() { - const queryClient = useQueryClient(); + const queryClient = useQueryClient(); - return useMutation({ - mutationFn: (message: NewMessage) => invoke('send_message', message), - onSuccess: () => { - // Invalidate and refetch messages - queryClient.invalidateQueries({ queryKey: ['messages'] }); - }, - }); + return useMutation({ + mutationFn: (message: NewMessage) => invoke("send_message", message), + onSuccess: () => { + // Invalidate and refetch messages + queryClient.invalidateQueries({ queryKey: ["messages"] }); + }, + }); } // Usage in component function ChannelList() { - const { data: channels, isLoading, error } = useChannels(); + const { data: channels, isLoading, error } = useChannels(); - if (isLoading) return
Loading channels...
; - if (error) return
Error loading channels
; + if (isLoading) return
Loading channels...
; + if (error) return
Error loading channels
; - return ( -
- {channels?.map(channel => ( -
- {channel.name} -
- ))} + return ( +
+ {channels?.map((channel) => ( +
+ {channel.name}
- ); + ))} +
+ ); } ``` diff --git a/.claude/rules/13-backend-auth-implementation.md b/.claude/rules/13-backend-auth-implementation.md index 52e6d8f4a..b406c4cfb 100644 --- a/.claude/rules/13-backend-auth-implementation.md +++ b/.claude/rules/13-backend-auth-implementation.md @@ -49,9 +49,11 @@ Web Browser Backend Server Desktop App (Tauri Initiates Telegram OAuth. The frontend opens this URL in the system browser. **Query params:** + - `platform=desktop` — indicates the callback should produce a deep link handoff **Behavior:** + 1. Redirect user to Telegram OAuth authorization URL 2. On callback, validate Telegram user data 3. Create or find user in database @@ -63,6 +65,7 @@ Initiates Telegram OAuth. The frontend opens this URL in the system browser. Called by the web frontend after phone-based authentication completes. **Request body:** + ```json { "method": "phone", @@ -70,15 +73,20 @@ Called by the web frontend after phone-based authentication completes. "countryCode": "+1" } ``` + or + ```json { "method": "telegram", - "telegramUser": { /* Telegram user object */ } + "telegramUser": { + /* Telegram user object */ + } } ``` **Response (200):** + ```json { "loginToken": "short-lived-opaque-token" @@ -86,6 +94,7 @@ or ``` **Behavior:** + 1. Validate the authentication data (verify OTP for phone, verify Telegram hash) 2. Create or find user in database 3. Generate a short-lived `loginToken` @@ -101,6 +110,7 @@ Called by the Tauri Rust command `exchange_token` (NOT browser fetch). Exchanges **Note:** The endpoint path is `/auth/desktop-exchange` (no `/api` prefix) — this matches the current frontend implementation. **Request body:** + ```json { "token": "loginToken-from-web" @@ -108,6 +118,7 @@ Called by the Tauri Rust command `exchange_token` (NOT browser fetch). Exchanges ``` **Response (200):** + ```json { "sessionToken": "long-lived-session-token", @@ -120,6 +131,7 @@ Called by the Tauri Rust command `exchange_token` (NOT browser fetch). Exchanges ``` **Error response (401):** + ```json { "success": false, @@ -128,6 +140,7 @@ Called by the Tauri Rust command `exchange_token` (NOT browser fetch). Exchanges ``` **Behavior:** + 1. Look up the `loginToken` in the database 2. Validate: not expired, not already used 3. Mark token as used (single-use enforcement) @@ -137,35 +150,38 @@ Called by the Tauri Rust command `exchange_token` (NOT browser fetch). Exchanges ## Database Schema ### `users` table -| Column | Type | Description | -|--------|------|-------------| -| id | UUID (PK) | User ID | -| username | TEXT | Display name | -| first_name | TEXT (nullable) | First name | -| phone_number | TEXT (nullable) | Phone number | -| telegram_id | TEXT (nullable) | Telegram user ID | -| created_at | TIMESTAMP | Account creation | -| updated_at | TIMESTAMP | Last update | + +| Column | Type | Description | +| ------------ | --------------- | ---------------- | +| id | UUID (PK) | User ID | +| username | TEXT | Display name | +| first_name | TEXT (nullable) | First name | +| phone_number | TEXT (nullable) | Phone number | +| telegram_id | TEXT (nullable) | Telegram user ID | +| created_at | TIMESTAMP | Account creation | +| updated_at | TIMESTAMP | Last update | ### `login_tokens` table (handoff tokens) -| Column | Type | Description | -|--------|------|-------------| -| id | UUID (PK) | Token record ID | -| token | TEXT (unique, indexed) | The opaque token string | -| user_id | UUID (FK -> users) | Associated user | -| created_at | TIMESTAMP | When issued | -| expires_at | TIMESTAMP | Expiration (created_at + 5min) | -| used | BOOLEAN | Whether already exchanged | + +| Column | Type | Description | +| ---------- | ---------------------- | ------------------------------ | +| id | UUID (PK) | Token record ID | +| token | TEXT (unique, indexed) | The opaque token string | +| user_id | UUID (FK -> users) | Associated user | +| created_at | TIMESTAMP | When issued | +| expires_at | TIMESTAMP | Expiration (created_at + 5min) | +| used | BOOLEAN | Whether already exchanged | ### `sessions` table -| Column | Type | Description | -|--------|------|-------------| -| id | UUID (PK) | Session ID | -| token | TEXT (unique, indexed) | Session token | -| user_id | UUID (FK -> users) | Associated user | -| created_at | TIMESTAMP | When issued | -| expires_at | TIMESTAMP | Expiration (created_at + 30 days) | -| revoked | BOOLEAN | Whether revoked | + +| Column | Type | Description | +| ---------- | ---------------------- | --------------------------------- | +| id | UUID (PK) | Session ID | +| token | TEXT (unique, indexed) | Session token | +| user_id | UUID (FK -> users) | Associated user | +| created_at | TIMESTAMP | When issued | +| expires_at | TIMESTAMP | Expiration (created_at + 30 days) | +| revoked | BOOLEAN | Whether revoked | ## Token Generation @@ -195,16 +211,18 @@ async fn exchange_token(backend_url: String, token: String) -> Result( - 'exchange_token', - { backendUrl: BACKEND_URL, token } + "exchange_token", + { backendUrl: BACKEND_URL, token }, ); ``` ### Deep Link Listener Located in `src/utils/desktopDeepLinkListener.ts`. Key behaviors: + - Uses **lazy dynamic import** in `main.tsx` to avoid loading before Tauri IPC is ready - No `window.__TAURI__` guard — the try/catch handles non-Tauri environments - Uses `localStorage.setItem('deepLinkHandled', 'true')` to prevent infinite reload loops (since `getCurrent()` returns the same URL after `window.location.replace()`) @@ -213,23 +231,24 @@ Located in `src/utils/desktopDeepLinkListener.ts`. Key behaviors: ### Backend URL Configuration Configured in `src/utils/config.ts`: + ```typescript export const BACKEND_URL = - import.meta.env.VITE_BACKEND_URL || 'https://2937933edf8a.ngrok-free.app'; + import.meta.env.VITE_BACKEND_URL || "https://2937933edf8a.ngrok-free.app"; ``` Set `VITE_BACKEND_URL` environment variable for different environments. ## Frontend Integration Points -| File | Role | -|------|------| -| `src/main.tsx` | Lazy-imports and starts the deep link listener | -| `src/utils/desktopDeepLinkListener.ts` | Parses deep link -> invokes Rust `exchange_token` -> stores session -> navigates | -| `src/utils/deeplink.ts` | Web-side: calls `/api/auth/web-complete` and builds `alphahuman://auth?token=...` URL | -| `src/utils/config.ts` | Backend URL configuration | -| `src/pages/Login.tsx` | Opens `GET /auth/telegram?platform=desktop` in browser | -| `src-tauri/src/lib.rs` | Rust `exchange_token` command using `reqwest` (CORS-free) | +| File | Role | +| -------------------------------------- | ------------------------------------------------------------------------------------- | +| `src/main.tsx` | Lazy-imports and starts the deep link listener | +| `src/utils/desktopDeepLinkListener.ts` | Parses deep link -> invokes Rust `exchange_token` -> stores session -> navigates | +| `src/utils/deeplink.ts` | Web-side: calls `/api/auth/web-complete` and builds `alphahuman://auth?token=...` URL | +| `src/utils/config.ts` | Backend URL configuration | +| `src/pages/Login.tsx` | Opens `GET /auth/telegram?platform=desktop` in browser | +| `src-tauri/src/lib.rs` | Rust `exchange_token` command using `reqwest` (CORS-free) | ## Phone OTP Flow (Future) @@ -245,4 +264,4 @@ See `14-deep-link-platform-guide.md` for detailed platform gotchas. --- -*Last updated: 2026-01-28* +_Last updated: 2026-01-28_ diff --git a/.claude/rules/14-deep-link-platform-guide.md b/.claude/rules/14-deep-link-platform-guide.md index 0cdfa1623..2415ced67 100644 --- a/.claude/rules/14-deep-link-platform-guide.md +++ b/.claude/rules/14-deep-link-platform-guide.md @@ -7,6 +7,7 @@ The `alphahuman://` custom URL scheme is used to hand off authentication from a ## Scheme Registration Configured in `src-tauri/tauri.conf.json`: + ```json { "plugins": { @@ -22,16 +23,19 @@ Configured in `src-tauri/tauri.conf.json`: ## macOS ### How It Works + - URL schemes are registered via `Info.plist` inside the `.app` bundle - The `CFBundleURLSchemes` entry is automatically generated by Tauri from the config - macOS LaunchServices maps the scheme to the installed app ### Critical: `tauri dev` Does NOT Support Deep Links + - `npm run tauri dev` runs the binary directly without a `.app` bundle - No `Info.plist` exists, so macOS cannot register the URL scheme - **You must use `npm run tauri build --debug` and run the `.app` bundle** ### `register_all()` Is Unsupported on macOS + - The Tauri deep-link plugin's `register_all()` method panics on macOS with `unsupported platform` - Only call it on Windows and Linux: ```rust @@ -42,6 +46,7 @@ Configured in `src-tauri/tauri.conf.json`: ``` ### Build & Install Workflow + ```bash # Build debug .app bundle npm run tauri build -- --debug --bundles app @@ -57,6 +62,7 @@ open "alphahuman://auth?token=YOUR_TOKEN" ``` ### Cargo Caching Gotcha + - Cargo may not re-embed updated `dist/` frontend assets on incremental builds - The `tauri-build` build script embeds frontend assets at compile time - **If the app shows stale UI, run `cargo clean` before rebuilding:** @@ -66,15 +72,17 @@ open "alphahuman://auth?token=YOUR_TOKEN" ``` ### WebKit Cache + - The macOS WebView (WKWebView) caches aggressively - Clear caches when debugging stale content: ```bash - rm -rf ~/Library/WebKit/com.megamind.tauri-app - rm -rf ~/Library/Caches/com.megamind.tauri-app - rm -rf ~/Library/Application\ Support/com.megamind.tauri-app + rm -rf ~/Library/WebKit/com.alphahuman.app + rm -rf ~/Library/Caches/com.alphahuman.app + rm -rf ~/Library/Application\ Support/com.alphahuman.app ``` ### Debug Builds: Secondary Instance Behavior + - In debug mode, clicking a deep link while the app is running may briefly spawn a secondary instance - The `onOpenUrl` event may fire twice (one may be an empty string) - The `deepLinkHandled` localStorage flag prevents double-processing @@ -82,11 +90,13 @@ open "alphahuman://auth?token=YOUR_TOKEN" ## Windows & Linux ### How It Works + - URL schemes are registered at runtime via `register_all()` in the `setup` hook - This works in both dev mode (`tauri dev`) and production builds - No special build or install steps needed for deep link testing ### Code + ```rust #[cfg(any(windows, target_os = "linux"))] { @@ -97,41 +107,47 @@ open "alphahuman://auth?token=YOUR_TOKEN" ## All Platforms ### `window.__TAURI__` Is Not Available Immediately + - The Tauri IPC bridge (`window.__TAURI__`) is injected asynchronously - Code that runs at module load time (top of `main.tsx`) cannot reliably check `__TAURI__` - **Solution**: Use dynamic `import()` for the deep link listener and wrap plugin calls in try/catch: ```typescript // main.tsx - import('./utils/desktopDeepLinkListener').then(m => { + import("./utils/desktopDeepLinkListener").then((m) => { m.setupDesktopDeepLinkListener().catch(console.error); }); ``` ### `alert()` Is Suppressed in Tauri WebView + - `window.alert()` does not display in Tauri's WKWebView on macOS - For debugging, use visible DOM elements or `console.log()` (viewable via Safari Web Inspector) ### `document.title` Is Overridden by Tauri + - Tauri sets the window title from `tauri.conf.json` `app.windows[].title` - Setting `document.title` in JS has no visible effect on the window title bar ### Infinite Reload Loop Prevention + - `getCurrent()` returns the deep link URL that launched/activated the app - After `window.location.replace()`, the page reloads and `getCurrent()` returns the same URL again - **Solution**: Set `localStorage.deepLinkHandled = 'true'` before navigating, check and clear it on next load ### CORS: Use Rust for Backend Calls + - Browser `fetch()` from the Tauri WebView is subject to CORS - External APIs (especially ngrok tunnels) typically don't set `Access-Control-Allow-Origin` - **Solution**: Use a Rust Tauri command with `reqwest` to make HTTP requests (no CORS): ```typescript // Instead of fetch(): - const data = await invoke('exchange_token', { backendUrl, token }); + const data = await invoke("exchange_token", { backendUrl, token }); ``` ## Tauri Plugin Dependencies ### Cargo.toml + ```toml tauri-plugin-deep-link = "2.0.0" reqwest = { version = "0.12", features = ["json"] } @@ -139,21 +155,19 @@ tokio = { version = "1", features = ["full"] } ``` ### package.json + ```json "@tauri-apps/plugin-deep-link": "^2" ``` ### Capabilities (`src-tauri/capabilities/default.json`) + ```json { - "permissions": [ - "core:default", - "opener:default", - "deep-link:default" - ] + "permissions": ["core:default", "opener:default", "deep-link:default"] } ``` --- -*Last updated: 2026-01-28* +_Last updated: 2026-01-28_ diff --git a/.claude/rules/16-macos-background-execution.md b/.claude/rules/16-macos-background-execution.md index aefec8994..3e1515481 100644 --- a/.claude/rules/16-macos-background-execution.md +++ b/.claude/rules/16-macos-background-execution.md @@ -7,6 +7,7 @@ Complete implementation of macOS background execution features including system ## Features Implemented ### 1. System Tray (Menu Bar App) + - **Tray icon** appears in macOS menu bar - **Click to toggle** window visibility (left-click on icon) - **Context menu** with two options: @@ -16,6 +17,7 @@ Complete implementation of macOS background execution features including system - **Close button minimizes to tray** instead of quitting app ### 2. Launch at Login (Autostart) + - **LaunchAgent configuration** for macOS - **Configurable autostart** via plugin API - **Command-line flags** support for launch arguments @@ -26,6 +28,7 @@ Complete implementation of macOS background execution features including system ### Dependencies Added **Cargo.toml**: + ```toml tauri = { version = "2", features = ["tray-icon", "macos-private-api"] } tauri-plugin-autostart = "2" @@ -34,15 +37,18 @@ tauri-plugin-autostart = "2" ### Configuration Changes **tauri.conf.json**: + ```json { "app": { - "windows": [{ - "visible": false, // Start hidden - "decorations": true, - "resizable": true, - "center": true - }], + "windows": [ + { + "visible": false, // Start hidden + "decorations": true, + "resizable": true, + "center": true + } + ], "trayIcon": { "id": "main-tray", "iconPath": "icons/icon.png", @@ -50,12 +56,13 @@ tauri-plugin-autostart = "2" "menuOnLeftClick": false, "tooltip": "Outsourced - Crypto Community Platform" }, - "macOSPrivateApi": true // Required for advanced tray features + "macOSPrivateApi": true // Required for advanced tray features } } ``` **capabilities/default.json**: + ```json { "permissions": [ @@ -78,6 +85,7 @@ tauri-plugin-autostart = "2" 4. **Autostart Plugin** - Configured with LaunchAgent for macOS **Code Structure** (`src-tauri/src/lib.rs`): + ```rust // System tray with menu fn setup_tray(app: &AppHandle) -> Result<(), Box> { @@ -108,17 +116,20 @@ pub fn run() { ## Platform-Specific Behavior ### macOS + - Tray icon appears in menu bar - Close button hides window (minimizes to tray) - LaunchAgent for autostart integration - Native macOS menu bar styling ### Windows/Linux + - Tray icon in system tray - Same menu functionality - Platform-appropriate autostart mechanisms ### Mobile (iOS/Android) + - Tray features disabled (desktop-only) - Autostart not applicable on mobile @@ -129,21 +140,22 @@ pub fn run() { Control autostart via Tauri commands: ```typescript -import { invoke } from '@tauri-apps/api/core'; +import { invoke } from "@tauri-apps/api/core"; // Enable autostart -await invoke('plugin:autostart|enable'); +await invoke("plugin:autostart|enable"); // Disable autostart -await invoke('plugin:autostart|disable'); +await invoke("plugin:autostart|disable"); // Check if enabled -const isEnabled = await invoke('plugin:autostart|is_enabled'); +const isEnabled = await invoke("plugin:autostart|is_enabled"); ``` ### Tray Behavior **User Actions**: + 1. **Left-click tray icon** → Toggle window visibility 2. **Right-click tray icon** → Open context menu 3. **"Show/Hide Window"** → Toggle visibility @@ -153,6 +165,7 @@ const isEnabled = await invoke('plugin:autostart|is_enabled'); ## Testing ### Build & Test + ```bash # Clean build cargo clean --manifest-path src-tauri/Cargo.toml @@ -168,6 +181,7 @@ open /Applications/tauri-app.app ``` ### Verification Checklist + - [ ] Tray icon appears in menu bar - [ ] Left-click toggles window visibility - [ ] Context menu has "Show/Hide Window" and "Quit" @@ -180,28 +194,35 @@ open /Applications/tauri-app.app ## Build Requirements ### macOS Deep Link & Tray Testing + - Must use `.app` bundle (not `tauri dev`) - `tauri dev` does NOT support deep links or full tray functionality - Use debug build: `npm run tauri build -- --debug --bundles app` ### Cargo Cache Issues + If UI appears outdated after rebuild: + ```bash cargo clean --manifest-path src-tauri/Cargo.toml npm run tauri build -- --debug --bundles app ``` ### WebKit Cache + Clear WebKit cache if needed: + ```bash -rm -rf ~/Library/WebKit/com.megamind.tauri-app -rm -rf ~/Library/Caches/com.megamind.tauri-app +rm -rf ~/Library/WebKit/com.alphahuman.app +rm -rf ~/Library/Caches/com.alphahuman.app ``` ## Configuration Options ### Autostart Arguments + Customize launch arguments in `lib.rs`: + ```rust .plugin(tauri_plugin_autostart::init( tauri_plugin_autostart::MacosLauncher::LaunchAgent, @@ -210,26 +231,32 @@ Customize launch arguments in `lib.rs`: ``` ### Tray Icon + Change tray icon path in `tauri.conf.json`: + ```json { "trayIcon": { "iconPath": "icons/custom-tray-icon.png", - "iconAsTemplate": true // Adapts to dark/light menu bar + "iconAsTemplate": true // Adapts to dark/light menu bar } } ``` ### Window Behavior + Adjust window settings: + ```json { - "windows": [{ - "visible": false, // Start hidden - "decorations": true, // Show title bar - "resizable": true, // Allow resize - "center": true // Center on screen - }] + "windows": [ + { + "visible": false, // Start hidden + "decorations": true, // Show title bar + "resizable": true, // Allow resize + "center": true // Center on screen + } + ] } ``` @@ -253,16 +280,18 @@ Potential improvements for future development: ## Security Considerations ### macOS Private API + - Required for advanced tray features - Approved by Apple for this use case - No App Store restrictions for direct distribution ### LaunchAgent + - Installed in user's LaunchAgents directory - User can disable via System Settings - Respects macOS security policies --- -*Implementation completed: 2026-01-29* -*Status: Fully functional, tested, and production-ready* +_Implementation completed: 2026-01-29_ +_Status: Fully functional, tested, and production-ready_ diff --git a/CLAUDE.md b/CLAUDE.md index 307adb36a..781bc8da0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,6 +11,7 @@ Cross-platform crypto community communication platform built with **Tauri v2** ( **Design Philosophy**: Premium, sophisticated crypto platform with calm, trustworthy aesthetic. ### Color Palette + - **Primary**: Ocean blue (`#4A83DD`) optimized for dark backgrounds - **Sage**: Success green (`#4DC46F`) for growth indicators - **Amber**: Warning (`#E8A838`) for attention states @@ -19,18 +20,21 @@ Cross-platform crypto community communication platform built with **Tauri v2** ( - **Market Colors**: Bullish green, bearish red, Bitcoin orange, Ethereum purple ### Typography + - **Primary**: Inter (premium font stack) - **Display**: Cabinet Grotesk for headings - **Mono**: JetBrains Mono for code - **Scale**: Sophisticated sizing with negative letter spacing for elegance ### Component System + - **Shadows**: Glow effects, subtle to float depth levels - **Animations**: Fade-in, slide-in, scale-in with cubic-bezier easing - **Border Radius**: Smooth system from `xs` (0.25rem) to `5xl` (2rem) - **Spacing**: Extended scale including custom values (4.5, 13, 15, etc.) ### Current UI State + - Uses HashRouter (not BrowserRouter) as seen in `App.tsx:1` - 153 TypeScript files total in src/ - Sophisticated Tailwind config with custom color system and animations @@ -74,6 +78,7 @@ The app wraps in this order: `Redux Provider` → `PersistGate` → `SocketProvi ### State Management (Redux Toolkit + Persist) State lives in `src/store/` using Redux Toolkit slices: + - **authSlice** — JWT token, onboarding completion flag (persisted) - **userSlice** — user profile - **socketSlice** — connection status, socket ID @@ -97,6 +102,7 @@ Redux Persist stores auth and telegram state (storage backend is configurable; d ### MCP System (`src/lib/mcp/`) Model Context Protocol implementation for AI tool execution over Socket.io: + - `transport.ts` — Socket.io JSON-RPC 2.0 transport with 30s timeout - `telegram/server.ts` — TelegramMCPServer manages 99 tool definitions - `telegram/tools/` — Individual tool files (one per Telegram API operation) @@ -117,6 +123,7 @@ Model Context Protocol implementation for AI tool execution over Socket.io: ### Deep Link Auth Flow Web-to-desktop handoff using `alphahuman://` URL scheme: + 1. User authenticates in browser 2. Browser redirects to `alphahuman://auth?token=` 3. Tauri catches the deep link, Rust `exchange_token` command calls backend via `reqwest` (bypasses CORS) @@ -128,6 +135,7 @@ Key file: `src/utils/desktopDeepLinkListener.ts` (lazy-loaded in `main.tsx`). Us ### Rust Backend (`src-tauri/src/lib.rs`) Minimal — two Tauri commands: + - `greet` — demo command - `exchange_token` — CORS-free HTTP POST to backend for token exchange @@ -137,14 +145,14 @@ Deep link plugin registered at setup. `register_all()` called only on Windows/Li Set in `.env` (Vite exposes `VITE_*` prefixed vars): -| Variable | Purpose | -|----------|---------| -| `VITE_BACKEND_URL` | Backend API URL (default: `http://localhost:5005`) | -| `VITE_TELEGRAM_API_ID` | Telegram MTProto API ID | -| `VITE_TELEGRAM_API_HASH` | Telegram MTProto API hash | -| `VITE_TELEGRAM_BOT_USERNAME` | Telegram bot username | -| `VITE_TELEGRAM_BOT_ID` | Telegram bot numeric ID | -| `VITE_DEBUG` | Debug mode flag | +| Variable | Purpose | +| ---------------------------- | -------------------------------------------------- | +| `VITE_BACKEND_URL` | Backend API URL (default: `http://localhost:5005`) | +| `VITE_TELEGRAM_API_ID` | Telegram MTProto API ID | +| `VITE_TELEGRAM_API_HASH` | Telegram MTProto API hash | +| `VITE_TELEGRAM_BOT_USERNAME` | Telegram bot username | +| `VITE_TELEGRAM_BOT_ID` | Telegram bot numeric ID | +| `VITE_DEBUG` | Debug mode flag | Production defaults are in `src/utils/config.ts`. @@ -153,6 +161,7 @@ Production defaults are in `src/utils/config.ts`. Key updates from recent commits: ### Major Additions + - **Settings Modal System** (`60054d8`): Complete URL-based settings modal with clean white design - Modal infrastructure with backdrop blur and center positioning - User profile integration with Redux state management @@ -166,6 +175,7 @@ Key updates from recent commits: - **Big Integer Support**: Consistent handling across all Telegram MCP tools (`0abed4d`) ### Design System Updates + - **Settings Modal UI**: Clean 520px white modal contrasting with glass morphism theme - **Animations**: 200ms entry animations, 250ms panel transitions, chevron hover effects - **Lottie Animations**: Integrated into onboarding flow (`334673e`) @@ -174,6 +184,7 @@ Key updates from recent commits: - **Theme**: Implemented sophisticated color system with premium crypto aesthetic ### Component Structure + - **165+ TypeScript files** across `src/` directory (added settings modal system) - **Settings Modal System**: Complete modal infrastructure in `src/components/settings/` - SettingsModal.tsx - Main container with URL routing @@ -200,6 +211,6 @@ Key updates from recent commits: ## Platform Gotchas -- **macOS deep links**: Require `.app` bundle (not `tauri dev`). Clear WebKit caches when debugging stale content: `rm -rf ~/Library/WebKit/com.megamind.tauri-app ~/Library/Caches/com.megamind.tauri-app` +- **macOS deep links**: Require `.app` bundle (not `tauri dev`). Clear WebKit caches when debugging stale content: `rm -rf ~/Library/WebKit/com.alphahuman.app ~/Library/Caches/com.alphahuman.app` - **Cargo caching**: May serve stale frontend assets on incremental builds. Run `cargo clean --manifest-path src-tauri/Cargo.toml` if the app shows outdated UI. - **`window.__TAURI__`**: Not available at module load time. Use dynamic `import()` and try/catch for Tauri plugin calls. diff --git a/docs/src-tauri/01-architecture.md b/docs/src-tauri/01-architecture.md index 5e59bb627..fb8c41e59 100644 --- a/docs/src-tauri/01-architecture.md +++ b/docs/src-tauri/01-architecture.md @@ -3,6 +3,7 @@ ## Overview The Tauri Rust backend provides native functionality for the AlphaHuman desktop application: + - System tray with background execution - Deep link authentication - Secure session storage (OS Keychain) @@ -41,6 +42,7 @@ src-tauri/src/ ### lib.rs - Application Entry The main entry point that: + 1. Registers Tauri plugins (opener, deep-link, autostart, notification) 2. Sets up system tray with Show/Hide and Quit menu 3. Handles macOS-specific window close behavior (minimize to tray) @@ -50,34 +52,36 @@ The main entry point that: Commands are exposed to the frontend via Tauri's IPC: -| Module | Commands | Purpose | -|--------|----------|---------| -| `auth` | `exchange_token`, `get_auth_state`, `logout`, etc. | Authentication | -| `socket` | `socket_connect`, `report_socket_connected`, etc. | Socket state | -| `telegram` | `start_telegram_login` | Telegram OAuth | -| `window` | `show_window`, `hide_window`, `toggle_window`, etc. | Window control | +| Module | Commands | Purpose | +| ---------- | --------------------------------------------------- | -------------- | +| `auth` | `exchange_token`, `get_auth_state`, `logout`, etc. | Authentication | +| `socket` | `socket_connect`, `report_socket_connected`, etc. | Socket state | +| `telegram` | `start_telegram_login` | Telegram OAuth | +| `window` | `show_window`, `hide_window`, `toggle_window`, etc. | Window control | ### Services Layer Singleton services providing background functionality: -| Service | Purpose | Storage | -|---------|---------|---------| -| `SessionService` | Secure auth token storage | OS Keychain | -| `SocketService` | Socket.io state management | Memory + Events | -| `NotificationService` | Native notifications | N/A | +| Service | Purpose | Storage | +| --------------------- | -------------------------- | --------------- | +| `SessionService` | Secure auth token storage | OS Keychain | +| `SocketService` | Socket.io state management | Memory + Events | +| `NotificationService` | Native notifications | N/A | ## Architecture Decisions ### Socket.io Strategy The frontend maintains the actual Socket.io connection, while Rust: + 1. Stores connection parameters 2. Tracks connection state 3. Emits events to coordinate with frontend 4. Ensures socket stays connected when window is hidden This approach is necessary because: + - Rust's Socket.io libraries have API compatibility issues - The WebView maintains state when hidden (unlike browser tabs) - Frontend JavaScript is better suited for Socket.io's event-driven model @@ -85,6 +89,7 @@ This approach is necessary because: ### Keychain Storage Session tokens are stored in the OS keychain for security: + - **macOS**: Keychain - **Windows**: Credential Manager - **Linux**: Secret Service @@ -105,39 +110,41 @@ listen("socket:should_connect", (event) => { ## Plugin Dependencies -| Plugin | Version | Purpose | -|--------|---------|---------| -| `tauri-plugin-opener` | 2 | Open URLs in browser | -| `tauri-plugin-deep-link` | 2.0.0 | Handle `alphahuman://` URLs | -| `tauri-plugin-autostart` | 2 | Launch at login | -| `tauri-plugin-notification` | 2 | Native notifications | +| Plugin | Version | Purpose | +| --------------------------- | ------- | --------------------------- | +| `tauri-plugin-opener` | 2 | Open URLs in browser | +| `tauri-plugin-deep-link` | 2.0.0 | Handle `alphahuman://` URLs | +| `tauri-plugin-autostart` | 2 | Launch at login | +| `tauri-plugin-notification` | 2 | Native notifications | ## Cargo Dependencies -| Crate | Purpose | -|-------|---------| -| `tauri` | Core framework with tray and macOS APIs | -| `serde`, `serde_json` | Serialization | -| `reqwest` | HTTP client | -| `tokio` | Async runtime | -| `keyring` | Secure credential storage | -| `once_cell` | Lazy static singletons | -| `parking_lot` | Fast mutexes | -| `log`, `env_logger` | Logging | +| Crate | Purpose | +| --------------------- | --------------------------------------- | +| `tauri` | Core framework with tray and macOS APIs | +| `serde`, `serde_json` | Serialization | +| `reqwest` | HTTP client | +| `tokio` | Async runtime | +| `keyring` | Secure credential storage | +| `once_cell` | Lazy static singletons | +| `parking_lot` | Fast mutexes | +| `log`, `env_logger` | Logging | ## Platform-Specific Behavior ### macOS + - Window close button hides instead of quitting - Dock icon click shows window - LaunchAgent for autostart - Keychain for secure storage ### Windows/Linux + - Deep link registration at runtime - Platform-specific credential storage - Registry (Windows) or desktop file (Linux) autostart --- -*Next: [Commands Reference](./02-commands.md)* +_Next: [Commands Reference](./02-commands.md)_ diff --git a/docs/src-tauri/02-commands.md b/docs/src-tauri/02-commands.md index 62a14f549..e15268d19 100644 --- a/docs/src-tauri/02-commands.md +++ b/docs/src-tauri/02-commands.md @@ -5,204 +5,229 @@ This document lists all Tauri commands available to the frontend. ## Authentication Commands ### `exchange_token` + Exchange a login token for a session token. ```typescript -const result = await invoke('exchange_token', { - backendUrl: 'https://api.example.com', - token: 'login-token-from-deep-link' +const result = await invoke("exchange_token", { + backendUrl: "https://api.example.com", + token: "login-token-from-deep-link", }); // Returns: { sessionToken: string, user: User } ``` ### `get_auth_state` + Get current authentication state. ```typescript -const state = await invoke('get_auth_state'); +const state = await invoke("get_auth_state"); // Returns: { is_authenticated: boolean, user: User | null } ``` ### `get_session_token` + Get the current session token. ```typescript -const token = await invoke('get_session_token'); +const token = await invoke("get_session_token"); ``` ### `get_current_user` + Get the current authenticated user. ```typescript -const user = await invoke('get_current_user'); +const user = await invoke("get_current_user"); ``` ### `is_authenticated` + Check if user is authenticated. ```typescript -const isAuth = await invoke('is_authenticated'); +const isAuth = await invoke("is_authenticated"); ``` ### `logout` + Clear session and disconnect socket. ```typescript -await invoke('logout'); +await invoke("logout"); ``` ### `store_session` + Manually store a session (usually called automatically by `exchange_token`). ```typescript -await invoke('store_session', { - token: 'session-token', - user: { id: '123', firstName: 'John' } +await invoke("store_session", { + token: "session-token", + user: { id: "123", firstName: "John" }, }); ``` ## Socket Commands ### `socket_connect` + Request frontend to connect to socket server. ```typescript -await invoke('socket_connect', { - backendUrl: 'https://api.example.com', - token: 'session-token' +await invoke("socket_connect", { + backendUrl: "https://api.example.com", + token: "session-token", }); // Emits "socket:should_connect" event ``` ### `socket_disconnect` + Request frontend to disconnect from socket server. ```typescript -await invoke('socket_disconnect'); +await invoke("socket_disconnect"); // Emits "socket:should_disconnect" event ``` ### `get_socket_state` + Get current socket state. ```typescript -const state = await invoke('get_socket_state'); +const state = await invoke("get_socket_state"); // Returns: { status: 'connected' | 'disconnected' | ..., socket_id: string | null, error: string | null } ``` ### `is_socket_connected` + Check if socket is connected. ```typescript -const isConnected = await invoke('is_socket_connected'); +const isConnected = await invoke("is_socket_connected"); ``` ### `report_socket_connected` + Report that socket connected (called by frontend). ```typescript -await invoke('report_socket_connected', { socketId: 'socket-id' }); +await invoke("report_socket_connected", { socketId: "socket-id" }); ``` ### `report_socket_disconnected` + Report that socket disconnected (called by frontend). ```typescript -await invoke('report_socket_disconnected'); +await invoke("report_socket_disconnected"); ``` ### `report_socket_error` + Report socket error (called by frontend). ```typescript -await invoke('report_socket_error', { error: 'Connection failed' }); +await invoke("report_socket_error", { error: "Connection failed" }); ``` ### `update_socket_status` + Update socket status (called by frontend). ```typescript -await invoke('update_socket_status', { - status: 'connected', // 'connected' | 'connecting' | 'disconnected' | 'reconnecting' | 'error' - socketId: 'socket-id' +await invoke("update_socket_status", { + status: "connected", // 'connected' | 'connecting' | 'disconnected' | 'reconnecting' | 'error' + socketId: "socket-id", }); ``` ## Telegram Commands ### `start_telegram_login` + Open Telegram login widget in browser. ```typescript -await invoke('start_telegram_login'); +await invoke("start_telegram_login"); // Opens ${DEFAULT_BACKEND_URL}/auth/telegram-widget?redirect=alphahuman://auth ``` ### `start_telegram_login_with_url` + Open Telegram login widget with custom backend URL. ```typescript -await invoke('start_telegram_login_with_url', { - backendUrl: 'https://custom-backend.com' +await invoke("start_telegram_login_with_url", { + backendUrl: "https://custom-backend.com", }); ``` ## Window Commands ### `show_window` + Show and focus the main window. ```typescript -await invoke('show_window'); +await invoke("show_window"); ``` ### `hide_window` + Hide the main window. ```typescript -await invoke('hide_window'); +await invoke("hide_window"); ``` ### `toggle_window` + Toggle window visibility. ```typescript -await invoke('toggle_window'); +await invoke("toggle_window"); ``` ### `is_window_visible` + Check if window is visible. ```typescript -const isVisible = await invoke('is_window_visible'); +const isVisible = await invoke("is_window_visible"); ``` ### `minimize_window` + Minimize the window. ```typescript -await invoke('minimize_window'); +await invoke("minimize_window"); ``` ### `maximize_window` + Maximize or unmaximize the window. ```typescript -await invoke('maximize_window'); +await invoke("maximize_window"); ``` ### `close_window` + Close the window (minimizes to tray on macOS). ```typescript -await invoke('close_window'); +await invoke("close_window"); ``` ### `set_window_title` + Set the window title. ```typescript -await invoke('set_window_title', { title: 'New Title' }); +await invoke("set_window_title", { title: "New Title" }); ``` ## Events (Rust → Frontend) @@ -211,29 +236,29 @@ These events are emitted by Rust and can be listened to in the frontend: ### Socket Events -| Event | Payload | Description | -|-------|---------|-------------| -| `socket:connected` | `()` | Socket connected | -| `socket:disconnected` | `()` | Socket disconnected | -| `socket:error` | `string` | Socket error occurred | -| `socket:message` | `{ event: string, data: any }` | Socket message received | -| `socket:state_changed` | `SocketState` | Socket state changed | -| `socket:should_connect` | `{ backendUrl: string, token: string }` | Request to connect | -| `socket:should_disconnect` | `()` | Request to disconnect | +| Event | Payload | Description | +| -------------------------- | --------------------------------------- | ----------------------- | +| `socket:connected` | `()` | Socket connected | +| `socket:disconnected` | `()` | Socket disconnected | +| `socket:error` | `string` | Socket error occurred | +| `socket:message` | `{ event: string, data: any }` | Socket message received | +| `socket:state_changed` | `SocketState` | Socket state changed | +| `socket:should_connect` | `{ backendUrl: string, token: string }` | Request to connect | +| `socket:should_disconnect` | `()` | Request to disconnect | ### Listening to Events ```typescript -import { listen } from '@tauri-apps/api/event'; +import { listen } from "@tauri-apps/api/event"; // Listen for socket connection request -await listen('socket:should_connect', (event) => { +await listen("socket:should_connect", (event) => { const { backendUrl, token } = event.payload; socketService.connect(backendUrl, token); }); // Listen for state changes -await listen('socket:state_changed', (event) => { +await listen("socket:state_changed", (event) => { const state = event.payload as SocketState; dispatch(setSocketStatus(state.status)); }); @@ -257,7 +282,12 @@ interface User { } interface SocketState { - status: 'connected' | 'connecting' | 'disconnected' | 'reconnecting' | 'error'; + status: + | "connected" + | "connecting" + | "disconnected" + | "reconnecting" + | "error"; socket_id: string | null; error?: string; } @@ -265,4 +295,4 @@ interface SocketState { --- -*Previous: [Architecture](./01-architecture.md) | Next: [Services](./03-services.md)* +_Previous: [Architecture](./01-architecture.md) | Next: [Services](./03-services.md)_ diff --git a/docs/src-tauri/README.md b/docs/src-tauri/README.md index bb827c2e9..ab036f21c 100644 --- a/docs/src-tauri/README.md +++ b/docs/src-tauri/README.md @@ -6,11 +6,11 @@ This documentation covers the Tauri Rust backend for the AlphaHuman desktop appl ## Quick Reference -| Document | Description | -|----------|-------------| -| [Architecture](./01-architecture.md) | System architecture and module structure | -| [Commands Reference](./02-commands.md) | All Tauri IPC commands | -| [Services](./03-services.md) | Background services documentation | +| Document | Description | +| -------------------------------------- | ---------------------------------------- | +| [Architecture](./01-architecture.md) | System architecture and module structure | +| [Commands Reference](./02-commands.md) | All Tauri IPC commands | +| [Services](./03-services.md) | Background services documentation | ## Features Implemented @@ -24,6 +24,7 @@ This documentation covers the Tauri Rust backend for the AlphaHuman desktop appl ## Current State Analysis ### Existing Implementation (`lib.rs`) + - ✅ System tray with show/hide/quit - ✅ Deep link handling (`alphahuman://` scheme) - ✅ Token exchange command (CORS bypass) @@ -31,6 +32,7 @@ This documentation covers the Tauri Rust backend for the AlphaHuman desktop appl - ✅ Window minimize-to-tray on close (macOS) ### Missing Features + - ❌ Socket.io client in Rust (background persistence) - ❌ Telegram Widget integration - ❌ Session management in Rust @@ -74,6 +76,7 @@ src-tauri/src/ **Goal**: Persistent WebSocket connection even when app is in background **Dependencies to add:** + ```toml [dependencies] rust_socketio = "0.6" # Socket.io client @@ -83,6 +86,7 @@ parking_lot = "0.12" # Fast mutexes ``` **Implementation:** + ```rust // services/socket_service.rs pub struct SocketService { @@ -100,6 +104,7 @@ impl SocketService { ``` **Background Persistence:** + - Socket runs on Tokio runtime, independent of window state - Connection survives window hide/minimize - Auto-reconnect on network recovery @@ -110,6 +115,7 @@ impl SocketService { **Goal**: Web-based Telegram auth → Deep link callback → Native session **Flow:** + ``` 1. User clicks "Login with Telegram" in desktop app ↓ @@ -134,12 +140,15 @@ impl SocketService { ``` **Backend Endpoint Needed:** + ``` GET /auth/telegram-widget?redirect={deeplink_scheme} ``` + Returns HTML page with Telegram Login Widget that redirects to the specified scheme. **Commands to implement:** + ```rust #[tauri::command] async fn start_telegram_login(app: AppHandle) -> Result<(), String> { @@ -165,12 +174,14 @@ async fn logout(app: AppHandle) -> Result<(), String> { **Goal**: Store auth tokens securely using OS keychain **Dependencies:** + ```toml [dependencies] keyring = "3" # Cross-platform keychain access ``` **Implementation:** + ```rust // services/session_service.rs pub struct SessionService { @@ -178,7 +189,7 @@ pub struct SessionService { } impl SessionService { - const SERVICE: &'static str = "com.megamind.tauri-app"; + const SERVICE: &'static str = "com.alphahuman.app"; pub fn store_token(&self, token: &str) -> Result<(), Error>; pub fn get_token(&self) -> Result, Error>; @@ -187,6 +198,7 @@ impl SessionService { ``` **Platform Support:** + - macOS: Keychain - Windows: Credential Manager - Linux: Secret Service (libsecret) @@ -196,12 +208,14 @@ impl SessionService { **Goal**: Show notifications even when app is minimized **Dependencies:** + ```toml [dependencies] tauri-plugin-notification = "2" ``` **Capability Addition:** + ```json { "permissions": [ @@ -213,6 +227,7 @@ tauri-plugin-notification = "2" ``` **Usage:** + ```rust // services/notification_service.rs pub fn show_notification(title: &str, body: &str) -> Result<(), Error> { @@ -229,6 +244,7 @@ pub fn show_notification(title: &str, body: &str) -> Result<(), Error> { **Goal**: Bidirectional communication between Rust services and React frontend **Events from Rust to Frontend:** + ```rust // Emit to frontend when socket receives message app.emit("socket:message", payload)?; @@ -238,10 +254,11 @@ app.emit("telegram:notification", notification)?; ``` **Frontend listening:** -```typescript -import { listen } from '@tauri-apps/api/event'; -await listen('socket:message', (event) => { +```typescript +import { listen } from "@tauri-apps/api/event"; + +await listen("socket:message", (event) => { // Handle message from Rust socket service }); ``` @@ -251,12 +268,14 @@ await listen('socket:message', (event) => { **Goal**: Run MCP tools from Rust for performance-critical operations **Approach:** + - Keep MCP tools in TypeScript for flexibility - Rust handles socket transport - Frontend dispatches tool calls - Rust forwards via socket, returns results **Alternative (Full Rust MCP):** + - Implement tool handlers in Rust - Higher performance, but more maintenance - Consider for v2 @@ -265,15 +284,15 @@ await listen('socket:message', (event) => { ## Implementation Order -| Phase | Priority | Effort | Dependencies | -|-------|----------|--------|--------------| -| 1. Project Structure | High | 2h | None | -| 2. Socket.io Service | High | 4h | Phase 1 | -| 3. Telegram Widget Login | High | 3h | Backend endpoint | -| 4. Secure Storage | High | 2h | Phase 1 | -| 5. Notifications | Medium | 1h | Phase 2 | -| 6. Event Bridge | High | 2h | Phase 2 | -| 7. MCP Integration | Low | 4h+ | Phase 2, 6 | +| Phase | Priority | Effort | Dependencies | +| ------------------------ | -------- | ------ | ---------------- | +| 1. Project Structure | High | 2h | None | +| 2. Socket.io Service | High | 4h | Phase 1 | +| 3. Telegram Widget Login | High | 3h | Backend endpoint | +| 4. Secure Storage | High | 2h | Phase 1 | +| 5. Notifications | Medium | 1h | Phase 2 | +| 6. Event Bridge | High | 2h | Phase 2 | +| 7. MCP Integration | Low | 4h+ | Phase 2, 6 | **Total Estimated Effort**: 18+ hours @@ -282,6 +301,7 @@ await listen('socket:message', (event) => { ## Cross-Platform Considerations ### macOS + - ✅ System tray (menu bar) - ✅ LaunchAgent autostart - ✅ Keychain storage @@ -289,6 +309,7 @@ await listen('socket:message', (event) => { - ⚠️ Notarization for distribution ### Windows + - ✅ System tray - ✅ Registry autostart - ✅ Credential Manager storage @@ -296,12 +317,14 @@ await listen('socket:message', (event) => { - ⚠️ Code signing for SmartScreen ### Linux + - ✅ System tray (AppIndicator) - ✅ Desktop file autostart - ✅ Secret Service storage - ⚠️ Deep link varies by desktop environment ### Mobile (Future) + - ❌ No system tray - ❌ Different auth flow - ❌ Push notifications instead of socket @@ -312,6 +335,7 @@ await listen('socket:message', (event) => { ## Testing Strategy ### Unit Tests + ```rust #[cfg(test)] mod tests { @@ -324,11 +348,13 @@ mod tests { ``` ### Integration Tests + - Deep link flow end-to-end - Socket reconnection scenarios - Background persistence verification ### Manual Testing + - Build debug `.app` bundle - Test tray behavior - Test window minimize/restore @@ -339,6 +365,7 @@ mod tests { ## Files to Create/Modify ### New Files + - `src-tauri/src/commands/mod.rs` - `src-tauri/src/commands/auth.rs` - `src-tauri/src/commands/socket.rs` @@ -354,6 +381,7 @@ mod tests { - `src-tauri/src/utils/config.rs` ### Modified Files + - `src-tauri/Cargo.toml` - Add dependencies - `src-tauri/src/lib.rs` - Refactor, use modules - `src-tauri/capabilities/default.json` - Add permissions @@ -371,4 +399,4 @@ mod tests { --- -*Plan created by stevenbaba - 2026-01-29* +_Plan created by stevenbaba - 2026-01-29_ diff --git a/docs/src/01-architecture.md b/docs/src/01-architecture.md index 005ff5953..ac849f8a9 100644 --- a/docs/src/01-architecture.md +++ b/docs/src/01-architecture.md @@ -3,6 +3,7 @@ ## System Architecture The Outsourced platform is built on a layered architecture supporting: + - Redux-based state management with persistence - Socket.io real-time communication - Telegram MTProto integration via service layer @@ -14,12 +15,12 @@ The Outsourced platform is built on a layered architecture supporting: ## Entry Points -| File | Purpose | -|------|---------| -| `main.tsx` | React root, polyfill imports, lazy deep link listener init | -| `App.tsx` | Provider chain: Redux → PersistGate → Socket → Telegram → HashRouter | -| `AppRoutes.tsx` | Route definitions with route guards | -| `polyfills.ts` | Node.js polyfills (Buffer, process, util) for telegram npm package | +| File | Purpose | +| --------------- | -------------------------------------------------------------------- | +| `main.tsx` | React root, polyfill imports, lazy deep link listener init | +| `App.tsx` | Provider chain: Redux → PersistGate → Socket → Telegram → HashRouter | +| `AppRoutes.tsx` | Route definitions with route guards | +| `polyfills.ts` | Node.js polyfills (Buffer, process, util) for telegram npm package | ## Provider Chain @@ -36,6 +37,7 @@ Redux Provider ``` **Why this order matters:** + 1. Redux must be outermost for state access 2. PersistGate rehydrates persisted state before rendering 3. SocketProvider depends on Redux auth token @@ -101,6 +103,7 @@ MCP System: ## Data Flow ### Authentication Flow (Deep Link) + 1. User authenticates in browser → receives `loginToken` 2. Browser redirects to `alphahuman://auth?token=` 3. Desktop app catches deep link via Tauri plugin @@ -110,6 +113,7 @@ MCP System: 7. App stores session in Redux, navigates to `/onboarding` or `/home` ### Socket.io Connection Flow + 1. SocketProvider detects `auth.token` change 2. Calls `socketService.connect(token)` 3. On successful connection, initializes MCP server @@ -117,6 +121,7 @@ MCP System: 5. Backend can invoke tools via JSON-RPC over Socket.io ### Telegram Connection Flow + 1. TelegramProvider detects auth state 2. Calls `mtprotoService.initialize(userId)` and `connect()` in parallel 3. MTProto client authenticates with Telegram servers @@ -126,50 +131,58 @@ MCP System: ## Key Patterns ### No localStorage Usage + - **Rule**: Avoid `localStorage`; use Redux with persistence instead - **Exceptions**: Redux-persist's storage adapter (persistence layer) - **Telegram session**: Stored in `telegram.byUser[userId].sessionString` ### Route Guard Pattern + - **PublicRoute**: Redirects authenticated users away - **ProtectedRoute**: Requires `token` and optionally `isOnboarded` - **DefaultRedirect**: Fallback based on auth state ### Settings Modal Pattern + - Renders via `createPortal` when `location.pathname.startsWith('/settings')` - URL-based navigation without affecting main route - Uses `useSettingsNavigation` hook ### MCP Tool Pattern + Each tool exports a handler: + ```typescript export const toolName: TelegramMCPToolHandler = { call: async (args, { telegramClient, userId }) => { // Perform Telegram API operation return { success: true, data: result }; - } -} + }, +}; ``` ## File Organization ### Feature-Based Structure + - `pages/` - Full-page route components - `pages/onboarding/` - Onboarding flow with steps - `components/settings/` - Settings modal system - `lib/mcp/telegram/tools/` - Individual MCP tools ### Slice-Based State + - `store/authSlice.ts` - Authentication - `store/socketSlice.ts` - Socket connection - `store/userSlice.ts` - User profile - `store/telegram/` - Complex Telegram state (5 files) ### Singleton Services + - `services/socketService.ts` - Socket.io - `services/mtprotoService.ts` - Telegram MTProto - `services/apiClient.ts` - HTTP REST --- -*Next: [State Management](./02-state-management.md)* +_Next: [State Management](./02-state-management.md)_ diff --git a/docs/src/05-pages-routing.md b/docs/src/05-pages-routing.md index 16509f4ba..8c9227eed 100644 --- a/docs/src/05-pages-routing.md +++ b/docs/src/05-pages-routing.md @@ -58,12 +58,12 @@ Redirects authenticated users away from public pages. export function PublicRoute() { const token = useAppSelector((state) => state.auth.token); const isOnboarded = useAppSelector((state) => - selectIsOnboarded(state, userId) + selectIsOnboarded(state, userId), ); if (token) { // Authenticated - redirect to appropriate page - return ; + return ; } return ; @@ -82,7 +82,7 @@ interface ProtectedRouteProps { export function ProtectedRoute({ requireOnboarded = false }) { const token = useAppSelector((state) => state.auth.token); const isOnboarded = useAppSelector((state) => - selectIsOnboarded(state, userId) + selectIsOnboarded(state, userId), ); if (!token) { @@ -105,7 +105,7 @@ Fallback route that redirects based on auth state. export function DefaultRedirect() { const token = useAppSelector((state) => state.auth.token); const isOnboarded = useAppSelector((state) => - selectIsOnboarded(state, userId) + selectIsOnboarded(state, userId), ); if (!token) { @@ -127,6 +127,7 @@ export function DefaultRedirect() { Landing page for unauthenticated users. **Features:** + - App introduction and branding - CTA to login/signup - Public route (redirects if authenticated) @@ -136,6 +137,7 @@ Landing page for unauthenticated users. Authentication page. **Features:** + - Telegram OAuth button - Opens `/auth/telegram?platform=desktop` in browser - Handles deep link callback @@ -160,6 +162,7 @@ export function Login() { Main dashboard after authentication. **Features:** + - Protected route (requires auth + onboarded) - Connection status indicators - Navigation to settings modal @@ -170,14 +173,14 @@ export function Home() { const navigate = useNavigate(); const user = useAppSelector((state) => state.user.profile); const telegramStatus = useAppSelector((state) => - selectTelegramConnectionStatus(state, user?.id) + selectTelegramConnectionStatus(state, user?.id), ); return (

Welcome, {user?.firstName}

- +
@@ -194,6 +197,7 @@ export function Home() { Multi-step onboarding process. ### Structure + ``` pages/onboarding/ ├── Onboarding.tsx # Flow controller @@ -209,11 +213,11 @@ pages/onboarding/ ```typescript const STEPS = [ - { id: 'get-started', component: GetStartedStep }, - { id: 'privacy', component: PrivacyStep }, - { id: 'analytics', component: AnalyticsStep }, - { id: 'connect', component: ConnectStep }, - { id: 'features', component: FeaturesStep } + { id: "get-started", component: GetStartedStep }, + { id: "privacy", component: PrivacyStep }, + { id: "analytics", component: AnalyticsStep }, + { id: "connect", component: ConnectStep }, + { id: "features", component: FeaturesStep }, ]; export function Onboarding() { @@ -227,7 +231,7 @@ export function Onboarding() { } else { // Complete onboarding dispatch(setOnboarded({ userId, isOnboarded: true })); - navigate('/home'); + navigate("/home"); } }; @@ -270,7 +274,7 @@ export function ConnectStep({ onNext, onBack }: StepProps) { option.id === 'telegram' && setShowModal(true)} + onClick={() => option.id === "telegram" && setShowModal(true)} /> ))} @@ -293,13 +297,15 @@ export function ConnectStep({ onNext, onBack }: StepProps) { The settings modal overlays existing content using URL-based routing. ### Modal Detection + ```typescript // In SettingsModal.tsx const location = useLocation(); -const isOpen = location.pathname.startsWith('/settings'); +const isOpen = location.pathname.startsWith("/settings"); ``` ### Sub-Routes + ``` /settings → SettingsHome (main menu) /settings/connections → ConnectionsPanel @@ -311,8 +317,9 @@ const isOpen = location.pathname.startsWith('/settings'); ``` ### Navigation + ```typescript -import { useSettingsNavigation } from './hooks/useSettingsNavigation'; +import { useSettingsNavigation } from "./hooks/useSettingsNavigation"; function SettingsHome() { const { navigateTo, closeModal } = useSettingsNavigation(); @@ -321,7 +328,7 @@ function SettingsHome() {
navigateTo('connections')} + onClick={() => navigateTo("connections")} />
@@ -335,13 +342,14 @@ The app uses HashRouter for desktop compatibility: ```typescript // App.tsx -import { HashRouter } from 'react-router-dom'; +import { HashRouter } from "react-router-dom"; // URLs look like: app://localhost/#/home // Instead of: app://localhost/home ``` **Why HashRouter:** + 1. Tauri deep links work with hash-based URLs 2. No server configuration needed 3. Works with file:// protocol @@ -353,12 +361,13 @@ Deep links are handled before routing: ```typescript // main.tsx -import('./utils/desktopDeepLinkListener').then((m) => { +import("./utils/desktopDeepLinkListener").then((m) => { m.setupDesktopDeepLinkListener().catch(console.error); }); ``` The listener intercepts `alphahuman://auth?token=...` and: + 1. Exchanges token via Rust command 2. Stores session in Redux 3. Navigates to `/onboarding` or `/home` @@ -366,32 +375,35 @@ The listener intercepts `alphahuman://auth?token=...` and: ## Navigation Patterns ### Programmatic Navigation + ```typescript -import { useNavigate } from 'react-router-dom'; +import { useNavigate } from "react-router-dom"; const navigate = useNavigate(); // Navigate to route -navigate('/home'); +navigate("/home"); // Replace history entry -navigate('/login', { replace: true }); +navigate("/login", { replace: true }); // Go back navigate(-1); ``` ### Link Component -```typescript -import { Link } from 'react-router-dom'; -Settings +```typescript +import { Link } from "react-router-dom"; + +Settings; ``` ### State Transfer + ```typescript // Pass state to route -navigate('/details', { state: { itemId: 123 } }); +navigate("/details", { state: { itemId: 123 } }); // Receive state const location = useLocation(); @@ -400,4 +412,4 @@ const { itemId } = location.state; --- -*Previous: [MCP System](./04-mcp-system.md) | Next: [Components](./06-components.md)* +_Previous: [MCP System](./04-mcp-system.md) | Next: [Components](./06-components.md)_ diff --git a/docs/src/08-hooks-utils.md b/docs/src/08-hooks-utils.md index 91626b377..b9721079b 100644 --- a/docs/src/08-hooks-utils.md +++ b/docs/src/08-hooks-utils.md @@ -24,21 +24,21 @@ function useSocket(): UseSocketReturn; **Usage:** ```typescript -import { useSocket } from '../hooks/useSocket'; +import { useSocket } from "../hooks/useSocket"; function ChatInput() { const { emit, isConnected } = useSocket(); const sendMessage = (text: string) => { if (isConnected) { - emit('chat:message', { text }); + emit("chat:message", { text }); } }; return ( e.key === 'Enter' && sendMessage(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && sendMessage(e.target.value)} /> ); } @@ -56,8 +56,8 @@ function Notifications() { setNotifications((prev) => [...prev, notification]); }; - on('notification', handler); - return () => off('notification', handler); + on("notification", handler); + return () => off("notification", handler); }, [on, off]); return ; @@ -82,7 +82,7 @@ function useUser(): UseUserReturn; **Usage:** ```typescript -import { useUser } from '../hooks/useUser'; +import { useUser } from "../hooks/useUser"; function ProfileHeader() { const { user, loading, error, refetch } = useUser(); @@ -94,7 +94,9 @@ function ProfileHeader() { return (
- {user.firstName} {user.lastName} + + {user.firstName} {user.lastName} +
); } @@ -108,10 +110,10 @@ URL-based navigation for settings modal. ```typescript interface UseSettingsNavigationReturn { - currentRoute: string; // Current settings path - navigateTo: (panel: string) => void; // Navigate to panel - navigateBack: () => void; // Go back one level - closeModal: () => void; // Close settings entirely + currentRoute: string; // Current settings path + navigateTo: (panel: string) => void; // Navigate to panel + navigateBack: () => void; // Go back one level + closeModal: () => void; // Close settings entirely } function useSettingsNavigation(): UseSettingsNavigationReturn; @@ -120,19 +122,15 @@ function useSettingsNavigation(): UseSettingsNavigationReturn; **Usage:** ```typescript -import { useSettingsNavigation } from './hooks/useSettingsNavigation'; +import { useSettingsNavigation } from "./hooks/useSettingsNavigation"; function SettingsMenu() { const { navigateTo, closeModal } = useSettingsNavigation(); return ( ); @@ -145,9 +143,9 @@ Animation state management for settings modal. ```typescript interface UseSettingsAnimationReturn { - isEntering: boolean; // Modal is animating in - isExiting: boolean; // Modal is animating out - animationClass: string; // CSS class for current state + isEntering: boolean; // Modal is animating in + isExiting: boolean; // Modal is animating out + animationClass: string; // CSS class for current state } function useSettingsAnimation(): UseSettingsAnimationReturn; @@ -156,16 +154,12 @@ function useSettingsAnimation(): UseSettingsAnimationReturn; **Usage:** ```typescript -import { useSettingsAnimation } from './hooks/useSettingsAnimation'; +import { useSettingsAnimation } from "./hooks/useSettingsAnimation"; function SettingsModal() { const { animationClass, isExiting } = useSettingsAnimation(); - return ( -
- {/* Content */} -
- ); + return
{/* Content */}
; } ``` @@ -178,7 +172,7 @@ Environment variable access with defaults. ```typescript // Backend URL export const BACKEND_URL = - import.meta.env.VITE_BACKEND_URL || 'https://api.example.com'; + import.meta.env.VITE_BACKEND_URL || "https://api.example.com"; // Telegram configuration export const TELEGRAM_API_ID = import.meta.env.VITE_TELEGRAM_API_ID; @@ -187,18 +181,18 @@ export const TELEGRAM_BOT_USERNAME = import.meta.env.VITE_TELEGRAM_BOT_USERNAME; export const TELEGRAM_BOT_ID = import.meta.env.VITE_TELEGRAM_BOT_ID; // Debug mode -export const DEBUG = import.meta.env.VITE_DEBUG === 'true'; +export const DEBUG = import.meta.env.VITE_DEBUG === "true"; ``` **Usage:** ```typescript -import { BACKEND_URL, DEBUG } from '../utils/config'; +import { BACKEND_URL, DEBUG } from "../utils/config"; const response = await fetch(`${BACKEND_URL}/api/users`); if (DEBUG) { - console.log('Response:', response); + console.log("Response:", response); } ``` @@ -217,7 +211,7 @@ function parseDeepLink(url: string): { path: string; params: URLSearchParams }; **Usage:** ```typescript -import { buildAuthDeepLink } from '../utils/deeplink'; +import { buildAuthDeepLink } from "../utils/deeplink"; // Build URL for browser redirect const deepLink = buildAuthDeepLink(loginToken); @@ -240,12 +234,13 @@ async function setupDesktopDeepLinkListener(): Promise; ```typescript // Lazy import to ensure Tauri IPC is ready -import('./utils/desktopDeepLinkListener').then((m) => { +import("./utils/desktopDeepLinkListener").then((m) => { m.setupDesktopDeepLinkListener().catch(console.error); }); ``` **What it does:** + 1. Listens for `onOpenUrl` events from Tauri deep-link plugin 2. Parses `alphahuman://auth?token=...` URLs 3. Calls Rust `exchange_token` command (bypasses CORS) @@ -253,14 +248,15 @@ import('./utils/desktopDeepLinkListener').then((m) => { 5. Navigates to `/onboarding` or `/home` **Loop prevention:** + ```typescript // Set flag before navigation to prevent reprocessing -localStorage.setItem('deepLinkHandled', 'true'); -window.location.replace('/'); +localStorage.setItem("deepLinkHandled", "true"); +window.location.replace("/"); // On next load, clear flag -if (localStorage.getItem('deepLinkHandled') === 'true') { - localStorage.removeItem('deepLinkHandled'); +if (localStorage.getItem("deepLinkHandled") === "true") { + localStorage.removeItem("deepLinkHandled"); return; // Don't process again } ``` @@ -277,22 +273,23 @@ async function openUrl(url: string): Promise; **Usage:** ```typescript -import { openUrl } from '../utils/openUrl'; +import { openUrl } from "../utils/openUrl"; // Opens in system browser (not in-app WebView) -await openUrl('https://telegram.org/auth'); +await openUrl("https://telegram.org/auth"); ``` **Implementation:** + ```typescript export async function openUrl(url: string): Promise { try { // Try Tauri opener plugin first - const { open } = await import('@tauri-apps/plugin-opener'); + const { open } = await import("@tauri-apps/plugin-opener"); await open(url); } catch { // Fallback to browser API - window.open(url, '_blank'); + window.open(url, "_blank"); } } ``` @@ -305,9 +302,9 @@ The `telegram` npm package requires Node.js APIs. These are polyfilled: ```typescript // polyfills.ts -import { Buffer } from 'buffer'; -import process from 'process'; -import util from 'util'; +import { Buffer } from "buffer"; +import process from "process"; +import util from "util"; window.Buffer = Buffer; window.process = process; @@ -318,7 +315,7 @@ window.util = util; ```typescript // main.tsx -import './polyfills'; +import "./polyfills"; // ... rest of app ``` @@ -329,15 +326,15 @@ import './polyfills'; export default defineConfig({ resolve: { alias: { - buffer: 'buffer', - process: 'process/browser', - util: 'util' - } + buffer: "buffer", + process: "process/browser", + util: "util", + }, }, define: { - 'process.env': {}, - global: 'globalThis' - } + "process.env": {}, + global: "globalThis", + }, }); ``` @@ -410,10 +407,10 @@ Country list for phone number input. ```typescript interface Country { - code: string; // "US" - name: string; // "United States" - dialCode: string; // "+1" - flag: string; // "🇺🇸" + code: string; // "US" + name: string; // "United States" + dialCode: string; // "+1" + flag: string; // "🇺🇸" } export const countries: Country[]; @@ -422,7 +419,7 @@ export const countries: Country[]; **Usage:** ```typescript -import { countries } from '../data/countries'; +import { countries } from "../data/countries"; function PhoneInput() { const [country, setCountry] = useState(countries[0]); @@ -431,9 +428,9 @@ function PhoneInput() {