For the complete documentation index, see llms.txt. This page is also available as Markdown.

Frontend (app/src/)

The React + Vite frontend (`app/src/`) - architecture, state, services, providers, routing, components, hooks.

The OpenHuman desktop UI: a Vite + React 19 tree under app/src/ (pnpm workspace openhuman-app). It uses Redux Toolkit with persistence for session state, talks to the in-process Rust core over JSON-RPC (coreRpcClient โ†’ local HTTP, with the Tauri relay_http_rpc command as a fallback relay) and socket.io (socketService), and reaches the cloud backend via REST (apiClient). Heavy logic lives in the core, not here.

This is one consolidated reference. Use the table of contents above (or your reader's outline) to jump between sections.

Quick reference

Section
Covers

Provider chain, build, layout, conventions

Redux Toolkit slices, selectors, persistence

apiClient, socketService, coreRpcClient

ThemeProvider, CoreState, Socket, ChatRuntime providers

HashRouter, route guards, main routes

UI / settings component patterns

Shared hooks, helpers, config

Scale

Metric
Value

TypeScript / TSX files under app/src/

~1700 (find app/src -name '*.ts' -o -name '*.tsx' | wc -l to refresh)

Test runner

Vitest (app/test/vitest.config.ts)

Directory layout

app/src/
โ”œโ”€โ”€ App.tsx                 # Provider chain + HashRouter shell (desktop + mobile shells)
โ”œโ”€โ”€ AppRoutes.tsx           # Desktop route table (AppRoutesIOS.tsx for mobile)
โ”œโ”€โ”€ main.tsx                # Entry (polyfills, Sentry, store, styles)
โ”œโ”€โ”€ store/                  # Redux slices, selectors, userScopedStorage persistence
โ”œโ”€โ”€ providers/              # ThemeProvider, CoreStateProvider, SocketProvider, ChatRuntimeProvider
โ”œโ”€โ”€ services/               # apiClient, socketService, coreRpcClient, transport/, api/* (~50 modules)
โ”œโ”€โ”€ lib/                    # AI prompt loaders, i18n, MCP helpers, platform, tunnel crypto
โ”œโ”€โ”€ pages/                  # Route-level screens (incl. onboarding/, ios/, dev/)
โ”œโ”€โ”€ features/               # Feature verticals (human/, conversations/, meet/, voice/)
โ”œโ”€โ”€ components/             # Shared UI (incl. settings/, layout/shell/, accounts/)
โ”œโ”€โ”€ agentworld/             # tiny.place Agent World surface (/agent-world/*)
โ”œโ”€โ”€ hooks/                  # App hooks
โ”œโ”€โ”€ utils/                  # Config, Tauri command wrappers, routing utilities
โ””โ”€โ”€ assets/                 # Icons and static assets

Architecture overview

System architecture

OpenHumanโ€™s desktop UI is a React 19 app (app/src/) that:

  • Uses Redux Toolkit with persistence for session-related state

  • Connects to the backend with REST (apiClient) and to the local core with Socket.io (socketService โ†’ core socket endpoint)

  • Calls the Rust core (embedded in the Tauri host as a tokio task) over HTTP via coreRpcClient (JSON-RPC methods implemented in repo root src/openhuman/); non-loopback plain-http runtimes are relayed through the Tauri relay_http_rpc command

  • Leaves AI prompts to the core: bundled src/openhuman/agent/prompts (repo root) ship as Tauri resources and are read core-side, not by the frontend

  • Uses a minimal MCP-style helper layer under lib/mcp/ (transport, validation)

Entry points

File
Purpose

app/src/main.tsx

React root, polyfills, Sentry boundary, store, global styles

app/src/App.tsx

Provider chain (see below) + desktop/mobile shells, Settings modal overlay

app/src/AppRoutes.tsx

HashRouter routes, ProtectedRoute / PublicRoute / DefaultRedirect guards

Provider chain

Generated from app/src/App.tsx by scripts/generate-architecture-docs.mjs. Do not edit by hand โ€” run pnpm docs:generate to refresh.

#
Component
Role

1

Sentry.ErrorBoundary

Crash boundary; renders ErrorFallbackScreen

2

Provider

Redux store; enables useAppSelector / dispatch app-wide

3

PersistGate

Holds UI until persisted Redux slices rehydrate

4

ThemeProvider

Theme tokens and dark-mode handling

5

I18nProvider

Localization context consumed via useT

6

BootCheckGate

Blocks render until the core boot snapshot resolves

7

CoreStateProvider

Core app snapshot: auth, session, onboarding state

8

SocketProvider

Core socket.io events; desktop only (mobile uses the TunnelTransport relay)

9

ChatRuntimeProvider

Chat runtime events, tool timeline, and approvals

10

Router

HashRouter navigation for all routes

11

CommandProvider

Command palette context

12

ServiceBlockingGate

Blocks the shell until required services are configured

Why this order

  1. Redux Provider is outermost so useAppSelector / dispatch work everywhere.

  2. PersistGate rehydrates persisted slices before children assume stable auth/session.

  3. BootCheckGate / CoreStateProvider resolve the core boot snapshot (auth, onboarding) before feature providers mount.

  4. SocketProvider (desktop only) and ChatRuntimeProvider depend on that core state for realtime events and approvals.

  5. Router supplies navigation to all routes.

Module relationships (simplified)

Services layer (conceptual)

Runtime config precedence

The desktop app does not bake the core RPC URL or the API host into the bundle as a hard requirement. At runtime the app resolves them in this order (highest first):

  1. Welcome-screen RPC URL field, saved via utils/configPersistence and restored on next launch. End users configure a self-hosted core address here, not by hand-editing config.toml or .env files.

  2. Tauri core_rpc_url command, the port the embedded core is listening on for this process.

  3. VITE_OPENHUMAN_CORE_RPC_URL, build-time fallback for development.

  4. The hardcoded http://127.0.0.1:7788/rpc default.

Once the RPC handshake succeeds, services/backendUrl calls openhuman.config_resolve_api_url to pull api_url (and other safe client fields) from the loaded core Config. VITE_BACKEND_URL is only used as a web fallback when the app runs outside Tauri.

Components that need the backend URL should call useBackendUrl() (or getBackendUrl() from non-React code), they must not import the static BACKEND_URL constant from utils/config, which represents the build-time value only.

State Management

The application uses Redux Toolkit with Redux-Persist. There is no single root persist config: each slice that persists wraps its own reducer with persistReducer in store/index.ts, whitelisting exactly the fields that should survive a restart.

Storage backends

  • userScopedStorage (store/userScopedStorage.ts) โ€” the default storage for persisted slices. Blobs are keyed ${userId}:persist:<key> so state never leaks across users on logout/login (#900).

  • Plain localStorage โ€” used only for pre-login, device-wide slices (coreMode, locale, theme) that must survive user switches.

Slices

Authoritative list = the reducer map in store/index.ts. One-line purposes:

Slice
Purpose
Persisted?

accounts

Connected web-app (CEF webview) accounts + rail ordering

accounts, order, lastActiveAccountId (not the active id)

agentProfiles

Agent profile data

no

announcement

Harness-init announcement banner, seen ids

shownIds

backendMeet

Backend-driven Google Meet call state (join/leave, transcript, replies)

no

channelConnections

Messaging channel connections (WhatsApp, Slack, โ€ฆ)

connections + migration/default-channel fields

chatRuntime

Streaming buffers, tool timelines, inference status, artifacts

only artifactsByThread (ready snapshots)

companion

Companion overlay state

no

connectivity

navigator.onLine + backend/core health status

no

coreMode

Pre-login core mode selection (embedded / self-hosted / cloud)

mode (plain localStorage)

layout

Two-pane layout geometry (sidebar visibility, dragged widths)

panels

locale

UI language

current (plain localStorage)

mascot

Mascot appearance / voice selection

color, voiceId, customMascotGifUrl, selectedMascotId

notifications

Notification items + preferences

items, preferences

persona

Cosmetic persona display name + description (SOUL.md lives in the core)

displayName, description

providerSurfaces

Provider webview surface state

no

ptt

Push-to-talk hotkey + session prefs (isHeld deliberately excluded)

shortcut, speakReplies, showOverlay

socket

Per-user socket connection status / socket ids

no (reconnects on boot)

theme

Theme mode, font size, message view mode, custom themes

plain localStorage

thread

Chat thread list + per-thread message caches

only selectedThreadId

userErrors

User-actionable runtime errors (#3931)

no (in-memory only)

Ephemeral chat state (streaming buffers, tool timelines) must not survive a restart โ€” the UI would try to resume a turn whose live driver is gone. The one exception, agent-generated artifacts, goes through the artifactsReadyOnlyTransform in store/index.ts (pure logic in store/artifactsPersistFilter.ts).

Typed hooks

File: store/hooks.ts

Best practices

  1. Always use typed hooks โ€” useAppDispatch and useAppSelector.

  2. Use selectors for derived state โ€” see store/socketSelectors.ts, store/connectivitySelectors.ts, store/userErrorsSelectors.ts.

  3. Whitelist persistence per slice โ€” never persist transient/loading state; add a per-slice persistReducer in store/index.ts.

  4. Prefer Redux over ad-hoc localStorage โ€” plain localStorage is reserved for the pre-login slices noted above.

  5. In dev / E2E builds the store is exposed as window.__OPENHUMAN_STORE__ so WDIO specs can assert backing state; production bundles do not expose it.


Services Layer

The application uses singleton services for external communication. This prevents connection leaks and provides consistent API access.

Service architecture

API Client (services/apiClient.ts)

Fetch-based HTTP REST client for backend communication with typed request/response handling and error handling. The backend URL is resolved at runtime (services/backendUrl), not baked in.

Domain API modules (services/api/)

~50 domain-scoped modules, one per feature surface, each wrapping either backend REST endpoints or core RPC methods. Representative examples:

  • authApi / userApi โ€” auth + user profile

  • threadApi, threadGoalApi, threadUsageApi โ€” chat threads

  • agentProfilesApi, agentTeamApi, agentWorkApi, subagentApi โ€” agents

  • skillsApi, skillRegistryApi, flowsApi, workflowRunsApi, todosApi โ€” skills & automation

  • channelConnectionsApi, mcpClientsApi, mcpSetupApi, tunnelsApi โ€” connections

  • memoryTimelineApi, memoryFreshnessApi, graphCentralityApi, namespaceOverviewApi โ€” memory/graph

  • billingApi, creditsApi, referralApi, rewardsApi, inviteApi โ€” commerce

  • voiceSettingsApi, voiceInstallApi, aiSettingsApi, modelCouncilApi โ€” AI/voice config

For the full list, ls app/src/services/api/. New feature surfaces get their own module here rather than growing apiClient.

Socket Service (services/socketService.ts)

Socket.io client singleton connected to the local core's socket endpoint (base URL derived from the resolved RPC URL via coreSocket.ts; authenticated with the core RPC token). It ingests realtime core events โ€” chat/meet/channel/companion updates โ€” and dispatches them into Redux (socketSlice, backendMeetSlice, channelConnectionsSlice, companionSlice, connectivitySlice). It also hosts the MCP-style transport (SocketIOMCPTransportImpl from lib/mcp).

Keep socketService and the core socket behavior aligned (the "dual socket sync" rule in AGENTS.md). Connection lifecycle is owned by providers/SocketProvider.tsx; on mobile the provider is not mounted at all โ€” events arrive through the TunnelTransport relay instead.

Core RPC (services/coreRpcClient.ts)

The Rust core runs in-process inside the Tauri host (no sidecar). The UI calls JSON-RPC methods on it over local HTTP:

How a call flows:

  1. URL + token resolution โ€” the RPC URL follows the precedence in Runtime config precedence; the per-launch bearer token comes from the Tauri core_rpc_token command (or the stored token for self-hosted cores).

  2. Direct fetch โ€” the webview fetch()es the JSON-RPC envelope straight to the core (loopback http or any https URL).

  3. Shell relay fallback โ€” plain http:// to a non-loopback host is active mixed content and Chromium blocks it (#3865). rpcUrlNeedsShellRelay() detects this and routes the call through invoke('relay_http_rpc', { url, token, body }), implemented in app/src-tauri/src/core_rpc.rs, which returns { status, body } re-wrapped as a Response.

  4. Transport override โ€” iOS/remote connection profiles install a CoreTransport (setActiveCoreTransport) so the same callCoreRpc surface rides LAN/tunnel/cloud transports.

Errors are classified into a stable CoreRpcError.kind (auth_expired, transport, timeout, rate_limited, โ€ฆ) โ€” callers branch on kind, never on message regexes. An auth_expired classification broadcasts core-rpc-auth-expired, which CoreStateProvider turns into a session clear.

Best Practices

  1. Use singletons โ€” never create multiple service instances.

  2. Keep Tauri IPC and RPC calls in services โ€” do not scatter invoke() or raw fetches through components.

  3. Clean up on unmount โ€” disconnect in useEffect cleanup.

  4. Handle errors via CoreRpcError.kind โ€” retry only transient failures.


Providers

React context providers (app/src/providers/) manage service lifecycle and expose core-owned state. The full nesting (including gates that live in components/) is the generated provider chain above. There is no UserProvider, AIProvider, or SkillProvider โ€” auth/user state lives in CoreStateProvider, AI configuration lives in the Rust core, and skills execute in the core (the frontend QuickJS skills engine was removed).

ThemeProvider (providers/ThemeProvider.tsx)

Applies theme tokens and dark-mode handling from the persisted theme slice (mode, font size, custom themes).

CoreStateProvider (providers/CoreStateProvider.tsx)

The authoritative auth/session/onboarding context. Fetches the core app snapshot (fetchCoreAppSnapshot() RPC), exposes it via useCoreState() ({ snapshot, isBootstrapping, refresh }), and clears the session on the global core-rpc-auth-expired event. It follows a turn-boundary refetch contract: after every agent reply completes (chat_done in ChatRuntimeProvider) it refetches the user state (debounced 750ms) and merges it into the snapshot via patchSnapshot โ€” see providers/README.md.

SocketProvider (providers/SocketProvider.tsx)

Owns the socket.io connection to the local core: connects once core state is ready, updates the socket slice, and tears down on unmount. Desktop only โ€” App.tsx skips it on mobile, where events arrive through the TunnelTransport relay.

ChatRuntimeProvider (providers/ChatRuntimeProvider.tsx)

Subscribes to chat runtime socket events (message streaming, tool calls, subagent lifecycle, approval requests) and reduces them into the chatRuntime slice โ€” per-thread tool timelines, streaming buffers, artifacts, and approval state consumed by the chat surface and the mascot.

Gates and shell-level contexts (in components/)

  • BootCheckGate (components/BootCheckGate/) โ€” blocks render until the core boot snapshot resolves.

  • CommandProvider (components/commands/) โ€” command palette context.

  • ServiceBlockingGate (components/daemon/) โ€” blocks the shell until required services are configured.

Context vs Redux

Use Context For
Use Redux For

Service instances (socket, client)

Serializable state (status, data)

Methods (emit, on, off)

Persisted state (sessions, tokens)

Derived values

Complex state logic

Example: SocketProvider owns the socket instance; Redux stores connection status in socketSlice.


Human Mascot Surface

The mascot appears on two surfaces, deliberately. /human (app/src/features/human/HumanPage.tsx) is the dedicated full-bleed stage with a right-rail chat. /chat carries the same mascot docked on its composer, where it expands into a voice stage in place. Both read one set of mascot preferences from mascotSlice โ€” colour, voice, speak-replies, dismissal โ€” so the two can never disagree about the same setting.

app/src/features/human/chatMascot/ owns the chat-side surface:

Module
Role

ChatMascotContext.tsx

Shared dock/stage refs and the send binding. Every value is stable โ€” see the re-render note below.

ChatMascotDock.tsx

The small mascot standing on the composer's input box. An anchor + hit area; it draws nothing.

ChatMascotStage.tsx

The scaled-up voice surface: MicComposer, input-device selector, speak-replies switch, collapse button.

ChatMascotOverlay.tsx

The single Rive instance, moved between dock and stage with a transform.

geometry.ts

Pure dock โ‡„ stage transform maths (inscribedSquare, lerpBox, boxTransform).

Clicking the dock expands the mascot into a right-hand stage column while the transcript and the text composer stay live in the left column, so voice and text are the same conversation. pages/Accounts.tsx animates the column width; ChatMascotOverlay re-measures both anchors per frame so the mascot stays glued to a destination that is still moving. Expanded/collapsed and the speak-replies preference are persisted in mascotSlice.

Two invariants worth keeping. The mascot re-renders at ~60fps during TTS lipsync, so (a) it is rendered as a leaf with nothing beneath it, and (b) the mascot context value is deliberately non-reactive โ€” reactive state lives in Redux or in the send-binding external store instead. A reactive context value would reconcile the whole chat tree every frame, which is the stall #5357 had to fix. And the overlay only mounts while the agent account is selected: HTML paints behind the native CEF provider webviews, so a fixed overlay left alive under WhatsApp/Slack would be an invisible canvas still burning frames.

The mascot face comes from useHumanMascot, which subscribes to chat lifecycle events for thinking, speaking, acknowledgement, and error states, plus a listening pose driven by MicComposer's onRecordingChange.

Sub-agent delegation is visualized by SubMascotLayer. It does not introduce a new socket protocol. Instead, it reads the selected or active thread's chatRuntime.toolTimelineByThread entries that ChatRuntimeProvider already builds from subagent_spawned, subagent_completed, subagent_failed, subagent_iteration_start, subagent_tool_call, and subagent_tool_result.

Lifecycle mapping:

Runtime timeline state
Sub-mascot state

running

Small colored mascot in a thinking face with a short activity bubble

success

Same mascot resolves to a happy face and completion bubble

error

Same mascot resolves to a concerned face and failure bubble

Activity bubble text is intentionally compact: current child tool call, child iteration, the delegation prompt excerpt, or final status. The thread timeline remains the authoritative detailed view; sub-mascots are only the glanceable orchestration layer around the main mascot.


Pages & Routing

The application uses HashRouter with protected and public route guards. Desktop routes live in app/src/AppRoutes.tsx; on mobile (iOS/Android) AppRoutesIOS.tsx renders a reduced Human/Chat/Settings set instead.

Route map

Current desktop routes (read AppRoutes.tsx for the authoritative table โ€” the file is heavily commented with the rationale for each redirect):

Back-compat redirects (all Navigate replace, query params preserved):

There is no /login route โ€” authentication flows through the Welcome page, the /auth callback, and deep links. Desktop Settings is not an inline route: when the URL is /settings/*, AppShellDesktop keeps rendering the background location and mounts SettingsModal on top (see Settings). Note that /agents does not exist; the agent-social surface is /agent-world/*.

Route guards

All three guards read useCoreState() (not Redux auth state) and render RouteLoadingScreen while bootstrapping:

  • ProtectedRoute (components/ProtectedRoute.tsx) โ€” ({ children, requireAuth = true, redirectTo }); without a session token, navigates to redirectTo || '/'. Onboarding gating is not done here โ€” an effect in AppShellDesktop (App.tsx) forces non-onboarding routes back to /onboarding while onboarding_completed is false, and bounces off it once complete.

  • PublicRoute (components/PublicRoute.tsx) โ€” redirects signed-in users to /home (which forwards to /chat).

  • DefaultRedirect (components/DefaultRedirect.tsx) โ€” signed out โ†’ /; signed in but onboarding incomplete โ†’ /onboarding; otherwise โ†’ /chat. Waits for snapshot.currentUser to avoid the post-login race.

Onboarding Flow (pages/onboarding/)

A routed stepper (Onboarding.tsx mounts nested routes inside OnboardingLayout):

Each custom step offers Default (let OpenHuman manage it) vs Configure (inline controls, or a deep-link callout to Settings for domains not yet embedded). Pages live in pages/onboarding/pages/; the legacy Composio/skills/context-gathering steps (pages/onboarding/steps/) are retired from the default flow but remain on disk. Completion is tracked by the core's onboarding_completed flag, enforced by the AppShell onboarding gate. After onboarding, AppWalkthrough (Joyride) runs the post-onboarding tour.

Settings

Settings is a full /settings/* URL surface, presented on desktop as a modal overlay and on iOS as a full page. The old SettingsPanelLayout / useSettingsAnimation / ProfilePanel modal system is gone.

  • components/settings/settingsRouteRegistry.ts โ€” single declarative source of truth for every settings destination (id/route slug, i18n keys, section, sidebar navGroup, devOnly, search keywords). Navigation menus, breadcrumbs, and settings search all derive from it.

  • components/settings/settingsRouteElements.tsx โ€” maps registry entries to panel <Route> elements.

  • components/settings/modal/ โ€” SettingsModal (mounted by AppShellDesktop whenever the path is a settings path; settingsOverlay.ts computes { settingsOpen, baseLocation } so the page behind stays rendered), SettingsModalFrame (backdrop / Esc / focus / close), SettingsModalLayout (routed two-column layout).

  • components/settings/layout/ โ€” two-pane chrome: SettingsLayout, SettingsSidebar (grouped by SettingsNavGroup: general, assistant, data, connections, knowledge & memory, agents & autonomy, models & inference, automation & integrations, diagnostics & logs), SettingsSubNav, SettingsIndexRedirect.

  • components/settings/panels/ โ€” ~50 leaf panels (AccountPanel, AppearancePanel, AIPanel, AgentsPanel, AgentAccessPanel, AutonomyPanel, BillingPanel, CronJobsPanel, IntegrationsPanel, McpServerPanel, NotificationsTabbedPanel, PrivacyPanel, DeveloperOptionsPanel, โ€ฆ). Adding a panel = add the component + a registry entry; nav, breadcrumbs, and search pick it up automatically.

  • components/settings/search/ โ€” settings search bar + registry-derived index.

HashRouter vs BrowserRouter

The app uses HashRouter for desktop compatibility:

Why HashRouter:

  1. Tauri deep links work with hash-based URLs

  2. No server configuration needed

  3. Works with file:// protocol

  4. Prevents 404 on direct URL access

Deep links are handled before routing:

The listener intercepts openhuman:// URLs (e.g. auth handoff), exchanges tokens through the Rust side (bypassing CORS), stores the session, and navigates to the right route. See utils/desktopDeepLinkListener.ts.


Components

Shared UI lives in app/src/components/; feature-specific UI lives in app/src/features/<vertical>/. Highlights:

Conventions:

  • Modal via portal โ€” shell modals (Settings, link modal) render above routed content; the Settings modal uses the backgroundLocation pattern rather than unmounting the page underneath.

  • Controlled modals โ€” parents own isOpen state and pass onClose.

  • i18n everywhere โ€” all user-facing text goes through useT() (lib/i18n/I18nContext); CI enforces locale parity.

  • No dynamic imports in production app/src code โ€” static import / import type only.


Hooks & Utilities

Custom Hooks (hooks/)

~40 app-level hooks. Representative examples:

  • useUser โ€” thin wrapper over useCoreState(); returns { user: snapshot.currentUser, isLoading, error, refetch }. There is no standalone user store.

  • useBackendUrl โ€” runtime backend URL resolution (see Runtime config precedence).

  • useThreadQueries โ€” chat thread fetching.

  • useDaemonHealth / useDaemonLifecycle โ€” core service health.

  • useDictationHotkey / usePttHotkey โ€” global hotkey managers.

  • useDeveloperMode, useMediaQuery, useEscapeKey, useStickToBottom โ€” UI utilities.

  • Feature hooks: useFlowRunProgress, useWorkflowBuilderChat, useConsciousItems, useSubconscious, useIntelligenceStats, useCostDashboard, โ€ฆ.

Feature-local hooks live next to their feature under features/*/.

Utilities

Configuration (utils/config.ts)

Centralized build-time environment variable access โ€” never read import.meta.env directly elsewhere. These constants only carry the value baked into the bundle; for the runtime URL the app actually talks to, see services/backendUrl and hooks/useBackendUrl.

Do not import BACKEND_URL directly to make API calls. Resolve the URL at runtime so the core's api_url (via openhuman.config_resolve_api_url) takes effect:

Handles incoming openhuman:// deep links via the Tauri deep-link plugin: parses the URL, performs the Rust-side token exchange (bypasses CORS), stores the session, and navigates. Set up lazily from main.tsx so the Tauri IPC bridge is ready first.

URL Opener (utils/openUrl.ts)

Cross-platform URL opening โ€” tries the Tauri opener plugin, falls back to window.open. Always use this instead of raw window.open so links open in the system browser.

Tauri command wrappers (utils/tauriCommands/)

Typed wrappers around invoke(...), including the bridge-gap-aware isTauri() guard (checks __TAURI_INTERNALS__.invoke is actually wired, not merely that the app runs under Tauri). Use it โ€” never check window.__TAURI__ directly.

Polyfills (polyfills.ts)

Node.js globals (Buffer, process, util) polyfilled for the browser. Several browser-side modules use Node APIs โ€” e.g. voice/PTT audio encoding (features/voice/pttAudio.ts, wavEncoder.ts), mascot Rive asset caching (features/human/Mascot/), the Meet mascot frame producer, and tool-timeline formatting.

Two layers provide them:

  1. vite-plugin-node-polyfills in app/vite.config.ts (buffer, process, util, os, crypto, stream, plus Buffer/process/global globals).

  2. polyfills.ts, imported first in main.tsx, which synchronously assigns Buffer/process/util onto globalThis/window/global/self before any dependent module executes.

Best Practices

Hook dependencies & cleanup

Always include dependencies and always clean up subscriptions.

Error handling

Wrap Tauri/utility calls in try-catch with a fallback:

Type safety

Use TypeScript generics for API and RPC calls:


Last updated