# ɳChat Architecture **Version**: 1.0.9 **Last Updated**: 2026-04-18 ## Overview ɳChat (nself-chat) is a FOSS team communication platform built as a **reference implementation** of what can be achieved with the [nSelf CLI](https://github.com/nself/nself) backend infrastructure. This document describes the architectural decisions, patterns, and setup instructions for both standalone and monorepo deployments. --- ## Table of Contents 1. [Core Principles](#core-principles) 2. [System Architecture](#system-architecture) 3. [Deployment Models](#deployment-models) 4. [Per-App RBAC/ACL](#per-app-rbacacl) 5. [Authentication Architecture](#authentication-architecture) 6. [Database Schema](#database-schema) 7. [Frontend Architecture](#frontend-architecture) 8. [Backend Services](#backend-services) 9. [Security Model](#security-model) 10. [Monorepo Setup Guide](#monorepo-setup-guide) --- ## Core Principles ### 1. Backend Exclusivity ɳChat uses **nSelf CLI exclusively** for all backend operations: - ✅ Database: PostgreSQL via nSelf - ✅ GraphQL: Hasura via nSelf - ✅ Authentication: Nhost Auth via nSelf - ✅ Storage: MinIO/S3 via nSelf - ✅ Search: MeiliSearch via nSelf - ✅ Real-time: WebSocket subscriptions via Hasura **We do NOT use**: Custom Express servers, Firebase, Supabase Auth, or any non-nSelf backend services. ### 2. Deployment Flexibility The application supports two deployment models: **Standalone** ``` user@host:~$ git clone nself-chat user@host:~$ cd nself-chat user@host:~$ nself start user@host:~$ pnpm dev ``` One app, one backend, independent deployment. **Monorepo ("One of Many")** ``` monorepo/ ├── backend/ # Shared nSelf backend ├── nchat/ # This app ├── example/ # Another app └── nfamily/ # Another app ``` Multiple apps, one backend, shared authentication, per-app roles. ### 3. Per-App RBAC/ACL In monorepo deployments, users authenticate once but can have **different roles in different apps**: - Admin in ɳChat, regular user in Example App - Owner in ɳFamily, guest in ɳChat - Moderator in Example App, member in ɳFamily This is implemented via three PostgreSQL tables: - `apps` - Registry of all applications - `app_user_roles` - User roles per app - `app_role_permissions` - Permissions per role per app --- ## System Architecture ### High-Level Diagram ``` ┌─────────────────────────────────────────────────────────────────┐ │ Frontend Layer │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Web │ │ Mobile │ │ Desktop │ │ Admin │ │ │ │ Next.js │ │Capacitor │ │ Electron │ │ Panel │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ │ │ └─────────────┴──────────────┴─────────────┘ │ │ │ │ └──────────────────────────┼───────────────────────────────────────┘ │ ┌──────────▼──────────┐ │ Nginx Reverse │ │ Proxy │ └──────────┬──────────┘ │ ┌──────────────────┼──────────────────┐ │ │ │ ┌───────▼────────┐ ┌──────▼──────┐ ┌────────▼────────┐ │ Hasura │ │ Nhost Auth │ │ MinIO Storage │ │ GraphQL API │ │ Service │ │ (S3-compat) │ └───────┬────────┘ └──────┬──────┘ └────────┬────────┘ │ │ │ └──────────────────┼──────────────────┘ │ ┌───────▼────────┐ │ PostgreSQL │ │ Database │ └────────────────┘ ``` ### Technology Stack **Frontend** - Framework: Next.js 15.1.6 (App Router) - UI: React 19 + Radix UI + Tailwind CSS - State: Zustand + Apollo Client - Real-time: GraphQL subscriptions + Socket.io - Editor: TipTap 2.11.2 - Forms: React Hook Form + Zod **Backend (via nSelf CLI)** - Database: PostgreSQL 15+ with 60+ extensions - GraphQL: Hasura Engine - Auth: Nhost Authentication - Storage: MinIO (S3-compatible) - Search: MeiliSearch - Cache: Redis - Monitoring: Grafana + Prometheus + Loki --- ## Deployment Models ### Standalone Deployment **Use Case**: Independent installation, single team/organization. **Directory Structure**: ``` nself-chat/ ├── backend/ # nSelf CLI project │ ├── migrations/ # DB migrations │ ├── docker-compose.yml │ └── .env ├── frontend/ # Next.js app │ ├── src/ │ ├── platforms/ │ └── package.json └── README.md ``` **Setup**: ```bash # 1. Initialize backend cd backend nself init nself start # 2. Start frontend cd ../frontend pnpm install pnpm dev ``` **Environment**: ```bash # frontend/.env.local NEXT_PUBLIC_APP_ID=nchat NEXT_PUBLIC_GRAPHQL_URL=http://api.localhost/v1/graphql NEXT_PUBLIC_AUTH_URL=http://auth.localhost/v1/auth NEXT_PUBLIC_STORAGE_URL=http://storage.localhost/v1/storage ``` --- ### Monorepo Deployment ("One of Many") **Use Case**: Multiple applications sharing authentication and users. **Directory Structure**: ``` monorepo/ ├── backend/ # Shared nSelf backend │ ├── migrations/ │ │ ├── 001_create_users.sql │ │ ├── 002_add_nchat_tables.sql │ │ ├── 003_add_example_tables.sql │ │ └── 004_add_per_app_rbac.sql │ └── docker-compose.yml ├── nchat/ # ɳChat app │ ├── frontend/ │ └── package.json ├── example/ # Example application │ ├── frontend/ │ └── package.json └── nfamily/ # ɳFamily app (example) ├── frontend/ └── package.json ``` **Setup**: ```bash # 1. Initialize shared backend cd backend nself init --demo nself start # 2. Run migrations for all apps nself exec postgres psql -U postgres -d nself < migrations/001_create_users.sql nself exec postgres psql -U postgres -d nself < migrations/002_add_nchat_tables.sql nself exec postgres psql -U postgres -d nself < migrations/003_add_example_tables.sql nself exec postgres psql -U postgres -d nself < migrations/004_add_per_app_rbac.sql # 3. Start each app cd ../nchat/frontend && pnpm dev cd ../example/frontend && pnpm dev --port 3001 cd ../nfamily/frontend && pnpm dev --port 3002 ``` **App Environment Configuration**: ```bash # nchat/frontend/.env.local NEXT_PUBLIC_APP_ID=nchat NEXT_PUBLIC_APP_NAME=ɳChat NEXT_PUBLIC_GRAPHQL_URL=http://api.localhost/v1/graphql NEXT_PUBLIC_AUTH_URL=http://auth.localhost/v1/auth # example/frontend/.env.local NEXT_PUBLIC_APP_ID=example NEXT_PUBLIC_APP_NAME="Example App" NEXT_PUBLIC_GRAPHQL_URL=http://api.localhost/v1/graphql NEXT_PUBLIC_AUTH_URL=http://auth.localhost/v1/auth # nfamily/frontend/.env.local NEXT_PUBLIC_APP_ID=nfamily NEXT_PUBLIC_APP_NAME=ɳFamily NEXT_PUBLIC_GRAPHQL_URL=http://api.localhost/v1/graphql NEXT_PUBLIC_AUTH_URL=http://auth.localhost/v1/auth ``` --- ## Per-App RBAC/ACL ### Overview The per-app RBAC system allows users to have **different roles in different applications** while sharing a single user account and authentication session. ### Database Schema **`apps` Table** ```sql CREATE TABLE public.apps ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), app_id TEXT UNIQUE NOT NULL, -- 'nchat', 'example', 'nfamily' app_name TEXT NOT NULL, -- 'ɳChat', 'Example App', 'ɳFamily' app_url TEXT, -- 'https://chat.nself.org' is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); ``` **`app_user_roles` Table** ```sql CREATE TABLE public.app_user_roles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), app_id TEXT NOT NULL REFERENCES apps(app_id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, role TEXT NOT NULL, -- 'owner', 'admin', 'moderator', 'member', 'guest' granted_by UUID REFERENCES auth.users(id), granted_at TIMESTAMPTZ DEFAULT NOW(), expires_at TIMESTAMPTZ, -- Optional expiration created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(app_id, user_id, role) ); ``` **`app_role_permissions` Table** ```sql CREATE TABLE public.app_role_permissions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), app_id TEXT NOT NULL REFERENCES apps(app_id) ON DELETE CASCADE, role TEXT NOT NULL, permission TEXT NOT NULL, -- 'channels.create', 'messages.delete' resource TEXT, -- Optional: specific resource ID created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(app_id, role, permission, resource) ); ``` ### Role Types | Role | Description | Example Permissions | |------|-------------|-------------------| | **owner** | Complete control | All permissions, billing, settings | | **admin** | User/channel management | Manage users, create/delete channels, moderation | | **moderator** | Content moderation | Delete messages, warn/timeout users, pin content | | **member** | Standard user | Send messages, join channels, upload files | | **guest** | Limited access | View channels, read messages (no posting) | ### Permission Format Permissions use dot notation: - `app.admin` - Full app administration - `users.manage` - User management - `users.ban` - Ban users - `channels.create` - Create channels - `channels.delete` - Delete channels - `messages.send` - Send messages - `messages.delete` - Delete own messages - `messages.delete.any` - Delete any message - `files.upload` - Upload files - `settings.manage` - Manage app settings - `billing.manage` - Manage billing ### Helper Functions ```sql -- Check if user has role in app SELECT user_has_app_role( 'user-uuid', 'nchat', 'admin' ); -- Check if user has permission in app SELECT user_has_app_permission( 'user-uuid', 'nchat', 'channels.delete', NULL -- resource ID (optional) ); -- Get all user roles for app SELECT * FROM get_user_app_roles( 'user-uuid', 'nchat' ); ``` ### Frontend Integration **Hook Usage**: ```typescript import { useAppPermissions } from '@/hooks/use-app-permissions' function DeleteChannelButton({ channelId }: { channelId: string }) { const { hasPermission, isAdmin, loading } = useAppPermissions() if (loading) return if (!hasPermission('channels.delete')) return null return ( ) } ``` **Context Access**: ```typescript import { useAuth } from '@/contexts/auth-context' function UserProfile() { const { user } = useAuth() return (

{user.displayName}

Roles in {process.env.NEXT_PUBLIC_APP_NAME}:

) } ``` ### GraphQL Queries ```graphql # Get user's roles in current app query GetUserAppRoles($userId: uuid!, $appId: String!) { app_user_roles( where: { user_id: { _eq: $userId } app_id: { _eq: $appId } _or: [ { expires_at: { _is_null: true } } { expires_at: { _gt: "now()" } } ] } ) { role granted_at expires_at } } # Grant a role to a user mutation GrantUserRole( $appId: String! $userId: uuid! $role: String! $grantedBy: uuid ) { insert_app_user_roles_one( object: { app_id: $appId user_id: $userId role: $role granted_by: $grantedBy } ) { id role } } ``` --- ## Authentication Architecture ### Single Sign-On (SSO) in Monorepo When multiple apps share a backend: 1. **User logs in to ɳChat** → Nhost creates session 2. **User visits Example App** → Session is valid (same auth service) 3. **User visits ɳFamily** → Session is valid (same auth service) All apps use the same: - `auth.users` table - Nhost JWT tokens - Session storage ### App-Specific Context While authentication is shared, **app context is unique**: ```typescript // User logs in once user.id = "123" user.email = "alice@example.com" // Context varies by app // In ɳChat: user.appRoles = ["admin"] user.appContext.permissions = ["channels.delete", "users.ban"] // In Example App (same user): user.appRoles = ["member"] user.appContext.permissions = ["videos.view", "videos.upload"] // In ɳFamily (same user): user.appRoles = ["owner"] user.appContext.permissions = ["*"] // All permissions ``` ### Development Mode For local development, ɳChat supports a **test authentication mode**: ```bash # Enable test auth (8 predefined users) NEXT_PUBLIC_USE_DEV_AUTH=true ``` Test users: - `owner@nself.org` (owner role) - `admin@nself.org` (admin role) - `moderator@nself.org` (moderator role) - `member@nself.org` (member role) - `guest@nself.org` (guest role) Password for all: `password123` **IMPORTANT**: Never use `NEXT_PUBLIC_USE_DEV_AUTH=true` in production! --- ## Database Schema ### Core Tables | Table | Purpose | Rows (typical) | |-------|---------|----------------| | `auth.users` | User accounts | 1K-1M+ | | `nchat_channels` | Chat channels | 10-10K | | `nchat_messages` | Messages | 100K-10M+ | | `nchat_roles` | Role definitions | 5-20 | | `nchat_role_permissions` | RBAC permissions | 50-200 | | `apps` | App registry | 1-50 | | `app_user_roles` | Per-app roles | 1K-1M+ | | `app_role_permissions` | Per-app permissions | 100-1K | ### Migration Strategy **Standalone**: ```bash cd backend nself exec postgres psql -U postgres -d nself < migrations/init.sql ``` **Monorepo**: ```bash # Order matters - run in sequence cd backend nself exec postgres psql -U postgres -d nself < migrations/001_create_users.sql nself exec postgres psql -U postgres -d nself < migrations/002_add_per_app_rbac.sql nself exec postgres psql -U postgres -d nself < migrations/003_add_nchat_tables.sql nself exec postgres psql -U postgres -d nself < migrations/004_add_example_tables.sql ``` ### Row-Level Security (RLS) All tables use RLS policies: ```sql -- Example: Messages are viewable by channel members CREATE POLICY "Users can view messages in their channels" ON nchat_messages FOR SELECT USING ( channel_id IN ( SELECT channel_id FROM nchat_channel_members WHERE user_id = auth.uid() ) ); -- Example: Apps are viewable by active app users CREATE POLICY "Users can view their app roles" ON app_user_roles FOR SELECT USING (user_id = auth.uid()); ``` --- ## Frontend Architecture ### Directory Structure ``` frontend/ ├── src/ │ ├── app/ # Next.js App Router │ │ ├── api/ # API routes │ │ ├── auth/ # Auth pages │ │ ├── chat/ # Main chat UI │ │ ├── setup/ # Setup wizard │ │ └── settings/ # User settings │ ├── components/ │ │ ├── chat/ # Chat components │ │ ├── ui/ # Radix UI wrappers │ │ └── layout/ # Header, Sidebar │ ├── contexts/ │ │ ├── auth-context.tsx # Auth state │ │ └── app-config-context.tsx │ ├── hooks/ │ │ ├── use-channels.ts │ │ ├── use-messages.ts │ │ └── use-app-permissions.ts # RBAC hook │ ├── graphql/ │ │ ├── queries/ │ │ ├── mutations/ │ │ └── app-rbac.ts # RBAC queries │ ├── types/ │ │ ├── app-rbac.ts # RBAC types │ │ └── index.ts │ └── lib/ │ ├── apollo-client.ts │ └── utils.ts ├── platforms/ │ ├── mobile/ # Capacitor (iOS/Android) │ ├── desktop/ # Electron/Tauri │ └── README.md ├── public/ # Static assets ├── tests/ # Jest + Playwright └── package.json ``` ### State Management **Global State (Zustand)**: - User preferences - Theme settings - UI state (modals, sidebars) **Server State (Apollo Client)**: - Channels - Messages - Users - App configuration **Real-time (GraphQL Subscriptions)**: ```typescript subscription OnNewMessage($channelId: uuid!) { nchat_messages( where: { channel_id: { _eq: $channelId } } order_by: { created_at: desc } limit: 1 ) { id content user { id displayName avatarUrl } created_at } } ``` --- ## Shared Packages (Monorepo) Six workspace packages under `packages/` provide shared logic consumed by the web frontend, Electron desktop, and Capacitor mobile platforms. | Package | npm name | Status | Depends on | |---------|----------|--------|------------| | `packages/core` | `@nself-chat/core` | Active — logger stub, shared types, utils | (none) | | `packages/api` | `@nself-chat/api` | Active — Apollo client factory, ApolloProvider re-export | `@nself-chat/core` | | `packages/state` | `@nself-chat/state` | Active — Zustand stores | `@nself-chat/core`, `@nself-chat/api` | | `packages/ui` | `@nself-chat/ui` | Active — shared React components, global CSS | `@nself-chat/core` | | `packages/config` | `@nself-chat/config` | Active — runtime configuration | `@nself-chat/core` | | `packages/testing` | `@nself-chat/testing` | Stub — testing utilities added in future sprint | (none) | ### Dependency Direction Packages are strictly layered. Dependencies only flow downward: ``` @nself-chat/state ──> @nself-chat/api ──> @nself-chat/core @nself-chat/ui ──────────────────────────────────────────> @nself-chat/core @nself-chat/config ──────────────────────────────────────────> @nself-chat/core @nself-chat/testing ──> (no workspace deps) ``` No package may import from `frontend/` or from a platform (`desktop/`, `mobile/`). Platforms consume packages — never the reverse. ### Build Packages are resolved via Vite aliases (source-level) during platform builds — no pre-build step required for local development. Each package ships `src/index.ts` as its source entry. ```bash # Build all packages (TypeScript declarations + JS) pnpm --filter "@nself-chat/*" build # Build specific platform (resolves packages via vite aliases) pnpm --filter @nself-chat/desktop build pnpm --filter @nself-chat/mobile build ``` ### Adding a New Package 1. Create `packages//` with `package.json` (`name: "@nself-chat/"`), `src/index.ts`, and `tsconfig.json` extending `../../tsconfig.json`. 2. Add `"@nself-chat/": "workspace:*"` to any consumer's `package.json` `dependencies`. 3. Add a vite alias in the consumer's `vite.config.ts`: `'@nself-chat/': resolve(__dirname, '../../packages//src')`. 4. Add a `paths` entry in the consumer's `tsconfig.json`: `"@nself-chat//*": ["../../../packages//src/*"]`. --- ## Backend Services ### nSelf CLI Services | Service | Port | Purpose | |---------|------|---------| | Hasura | 8080 | GraphQL API | | Auth | 4000 | Authentication | | PostgreSQL | 5432 | Database | | MinIO | 9000/9001 | S3 storage | | MeiliSearch | 7700 | Full-text search | | Redis | 6379 | Cache/sessions | | Grafana | 3000 | Monitoring | | Prometheus | 9090 | Metrics | | Loki | 3100 | Logs | ### Service Communication ``` Frontend ─┬─> Hasura (8080) ────> PostgreSQL (5432) ├─> Auth (4000) ───────> PostgreSQL (5432) ├─> MinIO (9000) ──────> S3 buckets └─> MeiliSearch (7700) ─> Search indices ``` All services run via Docker Compose generated by nSelf CLI: ```bash cd backend nself start # Start all services nself stop # Stop all services nself status # Show status nself logs hasura # View logs nself urls # List all service URLs ``` --- ## Security Model ### Authentication Flow 1. **User submits credentials** → Frontend sends to Nhost Auth 2. **Nhost validates** → Returns JWT access token + refresh token 3. **Frontend stores tokens** → Secure HTTP-only cookies (production) 4. **Subsequent requests** → Include JWT in Authorization header 5. **Hasura validates JWT** → Decodes user ID, enforces RLS ### Authorization Layers **Layer 1: Hasura JWT Validation** - All requests include JWT - Hasura verifies signature - Extracts `x-hasura-user-id` and `x-hasura-role` **Layer 2: Row-Level Security (RLS)** - PostgreSQL policies enforce data access - Uses `auth.uid()` from JWT claims - Cannot be bypassed by GraphQL **Layer 3: Application Permissions (RBAC)** - Frontend checks `app_user_roles` and `app_role_permissions` - UI hides/disables unauthorized actions - Backend validates via RLS policies ### Rate Limiting ```yaml # Hasura console > API Limits api_limits: depth_limit: global: 10 per_role: user: 7 anonymous: 5 node_limit: global: 50 per_role: user: 30 anonymous: 10 rate_limit: global: max_reqs_per_min: 120 unique_params: IP per_role: user: max_reqs_per_min: 60 unique_params: ["x-hasura-user-id"] ``` --- ## Monorepo Setup Guide ### Step 1: Create Directory Structure ```bash mkdir -p monorepo/{backend,nchat,example,nfamily} cd monorepo ``` ### Step 2: Initialize Shared Backend ```bash cd backend nself init --demo # Follow prompts: # - Enable: PostgreSQL, Hasura, Auth, MinIO, MeiliSearch, Redis # - Enable: Monitoring (Grafana + Prometheus + Loki) # - Set admin password # - Configure domain (e.g., api.localhost) ``` ### Step 3: Clone Apps ```bash cd .. git clone https://github.com/nself/nself-chat.git nchat # Set EXAMPLE_APP_REPOSITORY to your own second app repository git clone "${EXAMPLE_APP_REPOSITORY:?Set your second app repository URL}" example git clone https://github.com/nself/nself-family.git nfamily ``` ### Step 4: Run Migrations ```bash cd backend # Core tables (users, auth) nself exec postgres psql -U postgres -d nself < ../nchat/backend/db/migrations/20260212_add_per_app_rbac.sql # App-specific tables nself exec postgres psql -U postgres -d nself < ../nchat/backend/db/migrations/init_nchat.sql nself exec postgres psql -U postgres -d nself < ../example/backend/db/migrations/init_example.sql nself exec postgres psql -U postgres -d nself < ../nfamily/backend/db/migrations/init_nfamily.sql ``` ### Step 5: Configure Apps ```bash # ɳChat cd ../nchat/frontend cp .env.example .env.local # Edit NEXT_PUBLIC_APP_ID=nchat # Example App cd ../../example/frontend cp .env.example .env.local # Edit NEXT_PUBLIC_APP_ID=example # ɳFamily cd ../../nfamily/frontend cp .env.example .env.local # Edit NEXT_PUBLIC_APP_ID=nfamily ``` ### Step 6: Start Services ```bash # Terminal 1: Backend cd backend nself start # Terminal 2: ɳChat cd nchat/frontend pnpm dev # Terminal 3: Example App cd example/frontend pnpm dev --port 3001 # Terminal 4: ɳFamily cd nfamily/frontend pnpm dev --port 3002 ``` ### Step 7: Register Apps Visit Hasura console (http://localhost:8080) and run: ```sql INSERT INTO public.apps (app_id, app_name, app_url, is_active) VALUES ('nchat', 'ɳChat', 'http://localhost:3000', true), ('example', 'Example App', 'http://localhost:3001', true), ('nfamily', 'ɳFamily', 'http://localhost:3002', true); ``` ### Step 8: Assign Initial Roles ```sql -- Make first user an owner in all apps INSERT INTO public.app_user_roles (app_id, user_id, role) VALUES ('nchat', 'user-uuid-here', 'owner'), ('example', 'user-uuid-here', 'owner'), ('nfamily', 'user-uuid-here', 'owner'); ``` --- ## Troubleshooting ### Issue: "User has no roles in this app" **Solution**: Assign default role to new users via trigger: ```sql CREATE OR REPLACE FUNCTION assign_default_app_role() RETURNS TRIGGER AS $$ BEGIN -- Assign 'member' role in all active apps INSERT INTO public.app_user_roles (app_id, user_id, role) SELECT app_id, NEW.id, 'member' FROM public.apps WHERE is_active = true; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER on_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE FUNCTION assign_default_app_role(); ``` ### Issue: "Permission denied for app_user_roles" **Solution**: Grant Hasura access: ```sql GRANT SELECT ON public.app_user_roles TO hasura; GRANT SELECT ON public.app_role_permissions TO hasura; GRANT EXECUTE ON FUNCTION public.user_has_app_role TO hasura; GRANT EXECUTE ON FUNCTION public.user_has_app_permission TO hasura; ``` ### Issue: "Apps not showing in GraphQL" **Solution**: Track tables in Hasura: ```bash cd backend nself exec hasura hasura-cli metadata reload ``` Or via console: 1. Visit http://localhost:8080 2. Data tab → Schema → public 3. Track tables: `apps`, `app_user_roles`, `app_role_permissions` --- ## Additional Resources - [nSelf CLI Documentation](https://nself.org/docs) - [ɳChat README](./README.md) - [Hasura Docs](https://hasura.io/docs/) - [Nhost Auth Docs](https://docs.nhost.io/authentication) - [GraphQL Best Practices](https://graphql.org/learn/best-practices/) --- ## Changelog **v0.9.2** (February 12, 2026) - Added per-app RBAC/ACL system - Updated README with FOSS mission - Created ARCHITECTURE.md - Added monorepo setup documentation **v0.9.0** (February 6, 2026) - Restructured frontend to nself-family pattern - Fixed all TypeScript errors - Achieved 98%+ test pass rate - Production-ready build --- ## Desktop Platform — Tauri 2 **Added:** S12 (P103 sprint). Replaces the Electron shell at `platforms/desktop/`. ### Stack | Layer | Technology | |---|---| | Renderer | React/Vite (`@nself-chat/web` workspace package) | | Shell | Rust + Tauri 2 | | IPC | Tauri `invoke()` commands (18 channels) | | Packaging | Tauri `bundle` → DMG (macOS), MSI/NSIS (Windows), AppImage/deb (Linux) | | Distribution | GitHub Releases + S3 updater feed at `packages.nself.org` | ### Workspace package `nchat/desktop/` — `@nself-chat/desktop`. All Tauri 2 source lives here, separate from the legacy Electron shell at `platforms/desktop/` (removed in T19). ### Key features - Native menus (File/Edit/View/Window) with keyboard shortcuts - System tray with hide-to-tray on macOS close - Deep-link handler for `nchat://chat/` and `nchat://invite/` - Window state persistence (size, position, maximized, fullscreen) - Auto-updater with semver downgrade guard - macOS dock badge via `app_set_badge_count` - Optional crash reporting via sentry-tauri (DSN from env) ### Build ```bash cd nchat && pnpm install cd desktop && pnpm tauri:dev # dev mode cd desktop && pnpm tauri:build # release build cd desktop && pnpm test # vitest unit tests cd desktop && pnpm ipc-parity # IPC channel parity check cd desktop && pnpm test:e2e # Playwright e2e (requires built binary) ``` ### CI workflows | Workflow | Trigger | Notes | |---|---|---| | `.github/workflows/desktop-macos.yml` | tag push, PR, dispatch | arm64 + x64 matrix; bundle size gate (≤90 MB); e2e job | | `.github/workflows/desktop-linux.yml` | tag push, PR, dispatch | Ubuntu 22.04; libwebkit2gtk-4.1-dev; Wayland DMA-BUF disabled | | `.github/workflows/desktop-windows.yml` | tag push, PR, dispatch | x64; EV signing via SSL.com eSigner | Full documentation: [DESKTOP.md](./DESKTOP.md) --- ## License MIT License - See [LICENSE](./LICENSE) file for details. --- **Questions?** Open an issue on [GitHub](https://github.com/nself/nself-chat/issues).