ryOS ryOS / Docs
GitHub Launch

Hooks Architecture

ryOS provides 44 custom hooks organized by functionality, enabling audio playback, window management, media handling, undo/redo, realtime presence, background synchronization, and more.

Hook Categories Overview

graph TB
    subgraph Audio["Audio Hooks"]
        US[useSound]
        UTT[useTtsQueue]
        UCS[useChatSynth]
        UAR[useAudioRecorder]
        USB[useSoundboard]
        UTS[useTerminalSounds]
        UAT[useAudioTranscription]
        UV[useVibration]
    end
    
    subgraph Window["App/Window Hooks"]
        UWM[useWindowManager]
        ULA[useLaunchApp]
        UAS[useActivityState]
        UAL[useActivityLabel]
        UWI[useWindowInsets]
    end
    
    subgraph Media["Media Hooks"]
        UL[useLyrics]
        USC[useSongCover]
        UCP[useCoverPalette]
        UF[useFurigana]
        ULE[useLyricsErrorToast]
    end
    
    subgraph UndoRedo["Undo/Redo Hooks"]
        UUR[useUndoRedo]
        UGUR[useGlobalUndoRedo]
    end
    
    subgraph Utility["Utility Hooks"]
        UIM[useIsMobile]
        UIP[useIsPhone]
        UMQ[useMediaQuery]
        UT[useToast]
        UO[useOffline]
        ULP[useLongPress]
        USN[useSwipeNavigation]
        UW[useWallpaper]
        UCB[useCacheBustTrigger]
        USS[useSpotlightSearch]
        UBN[useBackgroundChatNotifications]
        UAC[useAutoCloudSync]
        ULS[useListenSync]
        USF[useStreamingFetch]
        UTH[useTranslatedHelpItems]
        URCS[useRealtimeConnectionStatus]
        UTL[useTelegramLink]
    end
    
    subgraph Effect["Effect Hooks"]
        UEL[useEventListener]
        ULR[useLatestRef]
        UIT[useInterval]
        UTO[useTimeout]
        URO[useResizeObserver]
    end
    
    subgraph Auth["Auth Hooks"]
        UA[useAuth]
    end

Audio Hooks

useSound

Web Audio API-based playback for UI sounds with caching and volume control.

interface UseSoundReturn {
  play: (options?: { volume?: number; fadeIn?: boolean }) => Promise<void>;
  stop: () => void;
  isPlaying: boolean;
}

// Usage
const { play: playClick } = useSound(Sounds.BUTTON_CLICK);
playClick({ volume: 0.8 });
Features:
  • AudioBuffer caching with LRU eviction
  • Load deduplication (prevents duplicate fetches)
  • Concurrent source limiting (16 mobile, 32 desktop)
  • Volume ramping to prevent clicks
  • Context change detection (re-caches when AudioContext recreated)
Available Sound Categories:
CategorySounds
WindowWINDOW_OPEN, WINDOW_CLOSE, WINDOW_EXPAND, WINDOW_COLLAPSE
InteractionBUTTON_CLICK, MENU_OPEN, MENU_CLOSE
AlertsALERT_SOSUMI, ALERT_BONK, ALERT_INDIGO
App-specificPHOTO_SHUTTER, VIDEO_TAPE, IPOD_CLICK_WHEEL

useTtsQueue

Gap-free text-to-speech with intelligent queuing and volume ducking.

interface UseTtsQueueReturn {
  speak: (text: string, options?: TtsOptions) => Promise<void>;
  stop: () => void;
  isSpeaking: boolean;
  queue: TtsQueueItem[];
}

// Usage
const { speak, stop, isSpeaking } = useTtsQueue();
await speak("Hello, world!", { provider: "openai", voice: "nova" });
Features:
  • AudioContext timeline scheduling for gap-free playback
  • Parallel fetching (up to 3 concurrent TTS requests)
  • Volume ducking: iPod reduced to 35%, chat synth to 60% during speech
  • Micro-fades (10ms) to prevent audio clicks

useChatSynth

Musical feedback for typing using Tone.js synthesis.

interface UseChatSynthReturn {
  playNote: () => void;
  setPreset: (preset: SynthPreset) => void;
  currentPreset: SynthPreset;
}

// Usage
const { playNote, setPreset } = useChatSynth();
setPreset("ethereal");
Presets:
PresetOscillatorCharacter
ClassicTriangleWarm, balanced
EtherealSineSoft, dreamy
DigitalSquareSharp, electronic
RetroSawtoothVintage, buzzy
Off-Disabled
Features:
  • Pentatonic scale (C4, D4, F4, G4, A4, C5, D5)
  • Effects chain: Filter → Tremolo → Reverb → PolySynth
  • 16 voice polyphony limit
  • Global instance persists across HMR

useAudioRecorder

Microphone recording with format detection.

interface UseAudioRecorderReturn {
  startRecording: () => Promise<void>;
  stopRecording: () => Promise<string>; // Returns base64
  isRecording: boolean;
  error: string | null;
}

// Usage
const { startRecording, stopRecording, isRecording } = useAudioRecorder();
await startRecording();
const base64Audio = await stopRecording();
Features:
  • MediaRecorder API with 200ms chunk intervals
  • Format detection: WebM (Chrome/Firefox), MP4 (Safari)
  • Base64 encoding for storage
  • Proper stream cleanup

useAudioTranscription

Voice-to-text with silence detection.

interface UseAudioTranscriptionReturn {
  startListening: () => Promise<void>;
  stopListening: () => Promise<void>;
  transcript: string;
  isListening: boolean;
  silenceLevel: number;
}

// Usage
const { startListening, transcript, isListening } = useAudioTranscription({
  onTranscript: (text) => console.log(text),
  silenceThreshold: 0.01,
  silenceDuration: 2000,
});
Features:
  • Adaptive silence detection
  • Auto-stop on silence
  • Volume level monitoring
  • Whisper API integration

App/Window Hooks

useWindowManager

Window positioning, dragging, resizing, and snap-to-edge.

interface UseWindowManagerReturn {
  windowPosition: { x: number; y: number };
  windowSize: { width: number; height: number };
  isDragging: boolean;
  resizeType: ResizeType;
  handleMouseDown: (e: MouseEvent) => void;
  handleResizeStart: (e: MouseEvent, type: ResizeType) => void;
  setWindowSize: (size: WindowSize) => void;
  setWindowPosition: (pos: WindowPosition) => void;
  maximizeWindowHeight: () => void;
  getSafeAreaBottomInset: () => number;
  snapZone: "left" | "right" | null;
  computeInsets: () => WindowInsets;
}

// Usage
const { windowPosition, handleMouseDown, snapZone } = useWindowManager({
  appId: "finder",
  instanceId: "finder-1",
});
Features:
  • Cascade positioning for new windows
  • Snap-to-edge (50% screen width)
  • Mobile-aware constraints
  • Sound integration for drag/resize

useLaunchApp

App launching with instance management.

interface UseLaunchAppReturn {
  launchApp: (appId: AppId, initialData?: unknown) => string;
  launchOrFocusApp: (appId: AppId) => void;
  closeApp: (instanceId: string) => void;
}

// Usage
const { launchApp, launchOrFocusApp } = useLaunchApp();
const instanceId = launchApp("textedit", { filePath: "/Documents/note.md" });
Features:
  • Multi-instance support for eligible apps
  • Restore minimized instances
  • Initial data passing
  • Instance ID generation

useWindowInsets

Theme-aware safe area calculation.

interface WindowInsets {
  top: number;      // Menu bar height
  bottom: number;   // Dock/taskbar height
  left: number;
  right: number;
}

// Usage
const insets = useWindowInsets();
const availableHeight = window.innerHeight - insets.top - insets.bottom;

Media Hooks

useLyrics

Lyrics fetching with translation support.

interface UseLyricsReturn {
  lyrics: LyricLine[];
  isLoading: boolean;
  error: string | null;
  hasLyrics: boolean;
  translation: LyricTranslation | null;
  refetch: () => void;
}

// Usage
const { lyrics, isLoading, translation } = useLyrics({
  songId: "abc123",
  title: "Song Title",
  artist: "Artist Name",
  enableTranslation: true,
});
Features:
  • Multiple lyrics sources (Kugou, etc.)
  • AI translation with streaming
  • Furigana/soramimi prefetch
  • Cache bust trigger support

useFurigana

Japanese text annotation (furigana and soramimi).

interface UseFuriganaReturn {
  furiganaMap: Map<string, FuriganaSegment[]>;
  soramimiMap: Map<string, SoramimiSegment[]>;
  furiganaProgress: number;
  soramimiProgress: number;
  isLoadingFurigana: boolean;
  isLoadingSoramimi: boolean;
}

// Usage
const { furiganaMap, soramimiMap, furiganaProgress } = useFurigana({
  songId: "abc123",
  lines: lyricsLines,
  enabled: true,
});
Features:
  • Progressive SSE streaming
  • Ordered loading (furigana before soramimi for Japanese)
  • Real-time progress updates

useSongCover

Album art fetching with caching.

interface UseSongCoverReturn {
  coverUrl: string | null;
  isLoading: boolean;
  error: string | null;
}

// Usage
const { coverUrl, isLoading } = useSongCover({
  title: "Song Title",
  artist: "Artist Name",
  fallbackUrl: "/default-cover.png",
});
Features:
  • In-memory cache
  • YouTube thumbnail fallback
  • Error handling with fallback

Utility Hooks

useIsMobile / useIsPhone

Device detection hooks.

// useIsMobile: Touch OR small screen
const isMobile = useIsMobile(); // Default breakpoint: 768px

// useIsPhone: Touch AND small screen  
const isPhone = useIsPhone();   // Default breakpoint: 640px

useOffline

Network status detection with polling.

interface UseOfflineReturn {
  isOffline: boolean;
  lastOnline: Date | null;
}

// Usage
const { isOffline } = useOffline();
if (isOffline) {
  showOfflineIndicator();
}
Features:
  • navigator.onLine monitoring
  • Periodic polling (5s intervals)
  • Last online timestamp

useLongPress

Touch long-press detection.

interface UseLongPressReturn {
  onTouchStart: (e: TouchEvent) => void;
  onTouchEnd: () => void;
  onTouchMove: () => void;
}

// Usage
const longPressHandlers = useLongPress({
  onLongPress: () => showContextMenu(),
  delay: 500, // ms
});

<div {...longPressHandlers} />

useSwipeNavigation

Horizontal swipe gesture detection.

interface UseSwipeNavigationReturn {
  handlers: {
    onTouchStart: (e: TouchEvent) => void;
    onTouchMove: (e: TouchEvent) => void;
    onTouchEnd: () => void;
  };
  isSwiping: boolean;
  swipeDirection: "left" | "right" | null;
}

// Usage
const { handlers, swipeDirection } = useSwipeNavigation({
  onSwipeLeft: () => nextApp(),
  onSwipeRight: () => prevApp(),
  threshold: 50,
});

useStreamingFetch

Generic SSE streaming with lifecycle management.

interface UseStreamingFetchReturn<T> {
  data: T | null;
  isLoading: boolean;
  error: string | null;
  progress: number;
}

// Usage
const { data, progress, isLoading } = useStreamingFetch<TranslationResult>({
  url: "/api/translate",
  body: { text, targetLang },
  onProgress: (p) => setProgress(p),
  onComplete: (result) => handleResult(result),
});
Features:
  • Abort controller integration
  • Stale request prevention
  • Offline detection
  • Progress tracking

useSpotlightSearch

Spotlight query orchestration with worker-backed indexing.

interface SpotlightSearchState {
  results: SpotlightResult[];
  isSearching: boolean;
}

// Usage
const { results, isSearching } = useSpotlightSearch(query);
Features:
  • Uses spotlightSearch.worker.ts and spotlightSearch.shared.ts for worker-backed indexing
  • Posts index/query messages to the worker
  • Defers initial indexing with requestIdleCallback for responsiveness
  • Subscribes to files, music, sites, video, calendar, and contacts stores and refreshes index incrementally
  • Ignores stale worker responses with request IDs
Searchable items: Apps, documents, applets, music, sites, video, Calendar events, Contacts, settings, terminal commands, and AI fallback.

useAutoCloudSync

Realtime cloud sync orchestration via Pusher or local WebSocket. Subscribes to sync channels for 11 domains (settings, files-metadata, files-images, files-trash, files-applets, songs, videos, stickies, calendar, contacts, custom-wallpapers) and applies remote updates in real time. Syncs dock icons, iPod/Karaoke settings, Stickies, wallpapers, images, contacts, videos library, and calendar data. Used by App.tsx for authenticated users with auto-sync enabled.

useBackgroundChatNotifications / useListenSync

Background orchestration hooks used by the desktop shell:

  • useBackgroundChatNotifications monitors chat activity and system notifications while apps run in background.
  • useListenSync keeps Listen Session playback/session state synchronized across app components.

useCacheBustTrigger

Force refresh detection for shared state.

interface UseCacheBustTriggerReturn {
  currentTrigger: number;
  isForceRequest: boolean;
  markHandled: () => void;
}

// Usage
const { isForceRequest, markHandled } = useCacheBustTrigger();

useEffect(() => {
  if (isForceRequest) {
    refetchData();
    markHandled();
  }
}, [isForceRequest]);

Auth Hook

useAuth

Complete authentication flow management.

interface UseAuthReturn {
  username: string | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  login: (username: string, password: string) => Promise<boolean>;
  logout: () => Promise<void>;
  verifyToken: () => Promise<boolean>;
  showPasswordDialog: boolean;
  setShowPasswordDialog: (show: boolean) => void;
}

// Usage
const { username, isAuthenticated, login, logout } = useAuth();

if (!isAuthenticated) {
  await login("alice", "password123");
}

Undo/Redo Hooks

useUndoRedo

Provides useRegisterUndoRedo and useInstanceUndoRedo for wiring app-level undo/redo to the central useUndoRedoStore.

// In app component — register handlers for this instance
useRegisterUndoRedo(instanceId, {
  undo: () => history.undo(),
  redo: () => history.redo(),
  canUndo: history.canUndo,
  canRedo: history.canRedo,
});

// In menu bar — read undo/redo state for Edit menu items
const { canUndo, canRedo, undo, redo } = useInstanceUndoRedo(instanceId);
Features:
  • Per-instance registration, automatically cleaned up on unmount
  • State updates propagated reactively to menu bars
  • Used by Finder, Paint, and TextEdit

useGlobalUndoRedo

Global keyboard listener dispatching Cmd/Ctrl+Z (undo) and Cmd/Ctrl+Shift+Z / Ctrl+Y (redo) to the foreground window's registered handlers. Mounted once at the top level in AppManager.

// In AppManager — mount once at the top level
useGlobalUndoRedo();

Realtime Hooks

useRealtimeConnectionStatus

Subscribes to Pusher connection state changes and returns "connected", "connecting", or "disconnected".

const connectionState = useRealtimeConnectionStatus();
// "connected" | "connecting" | "disconnected"

useTelegramLink

Manages Telegram account linking flow including link creation, deep-link opening, code copying, and disconnection.

const {
  telegramLinkedAccount,
  telegramLinkSession,
  handleCreateTelegramLink,
  handleDisconnectTelegramLink,
} = useTelegramLink({ username, authToken });

Effect Hooks

useEventListener

Type-safe event listener management with automatic cleanup.

// Window event
useEventListener("resize", handleResize);

// Document event
useEventListener("visibilitychange", handleVisibility, document);

// Element event via ref
const buttonRef = useRef<HTMLButtonElement>(null);
useEventListener("click", handleClick, buttonRef);
Features:
  • Automatic cleanup on unmount
  • Ref-based handler to avoid stale closures
  • Supports window, document, and element targets
  • TypeScript-safe event types

Also exports useCustomEventListener for custom events with typed payloads.

useLatestRef

Keeps a ref updated with the latest value without triggering re-renders.

const [count, setCount] = useState(0);
const countRef = useLatestRef(count);

// In callbacks - always gets current value
const handleClick = useCallback(() => {
  console.log('Current count:', countRef.current);
}, []); // No need to include count in deps
Use cases:
  • Avoiding stale closures in callbacks
  • Event handlers that need current state
  • Animation frame callbacks

Also exports usePrevious for tracking previous render values.

useInterval

Declarative interval management with automatic cleanup.

// Basic usage - runs every second
useInterval(() => setCount(c => c + 1), 1000);

// Conditional interval - pauses when delay is null
useInterval(() => tick(), isRunning ? 1000 : null);

// Immediate execution on mount
useInterval(() => fetchData(), 5000, { immediate: true });

useTimeout

Declarative timeout with manual control.

// Basic usage
useTimeout(() => setVisible(false), 3000);

// Conditional timeout
useTimeout(() => hideMessage(), showMessage ? 5000 : null);

// Manual control
const { clear, reset } = useTimeout(() => autoSave(), 10000);
// Later: clear() to cancel, reset() to restart

useResizeObserver

Element size observation with automatic cleanup.

// Basic usage - returns ref to attach
const ref = useResizeObserver<HTMLDivElement>((entry) => {
  setWidth(entry.contentRect.width);
});
return <div ref={ref}>...</div>;

// With debouncing
const ref = useResizeObserver<HTMLDivElement>(
  (entry) => setDimensions(entry.contentRect),
  { debounce: 100 }
);

Also exports:

  • useResizeObserverWithRef - for existing refs
  • useElementSize - simple hook returning [ref, { width, height }]

Common Patterns

Global AudioContext Management

Audio hooks share a global context:

import { getAudioContext, resumeAudioContext } from "@/lib/audioContext";

// Always resume before playback
await resumeAudioContext();
const ctx = getAudioContext();

Store Integration

Hooks use fine-grained Zustand selectors:

const speechVolume = useAudioSettingsStore((s) => s.speechVolume);
const masterVolume = useAudioSettingsStore((s) => s.masterVolume);

Stale Request Prevention

const currentIdRef = useRef(id);

useEffect(() => {
  currentIdRef.current = id;
  const controller = new AbortController();
  
  fetchData(id, controller.signal).then((data) => {
    // Check if still current
    if (id !== currentIdRef.current) return;
    setData(data);
  });
  
  return () => controller.abort();
}, [id]);

Volume Ramping

const targetVolume = volume * uiVolume * masterVolume;
const now = audioContext.currentTime;

gainNode.gain.setValueAtTime(gainNode.gain.value, now);
gainNode.gain.linearRampToValueAtTime(targetVolume, now + 0.01);

Hook Dependencies

graph TD
    subgraph "Stores"
        AS[useAudioSettingsStore]
        IS[useIpodStore]
        CS[useChatsStore]
        APS[useAppStore]
        URS[useUndoRedoStore]
    end
    
    subgraph "Audio Hooks"
        US[useSound] --> AS
        UTT[useTtsQueue] --> AS
        UTT --> IS
        UCS[useChatSynth] --> AS
        UCS --> UV[useVibration]
    end
    
    subgraph "Media Hooks"
        UL[useLyrics] --> IS
        UL --> UCB[useCacheBustTrigger]
        UF[useFurigana] --> UCB
    end
    
    subgraph "Window Hooks"
        UWM[useWindowManager] --> US
        UWM --> APS
    end
    
    subgraph "Undo/Redo Hooks"
        UGUR[useGlobalUndoRedo] --> APS
        UGUR --> URS
        UUR[useRegisterUndoRedo] --> URS
    end

Usage Recommendations

HookUse WhenNotes
useSoundUI feedbackUse Sounds enum for paths
useTtsQueueLong-form speechAuto-queues for seamless playback
useChatSynthTyping feedbackConfigurable presets
useWindowManagerAny draggable windowHandles snap-to-edge
useLaunchAppOpening appsHandles multi-instance
useLyricsSynced lyricsIncludes translation support
useFuriganaJapanese textWait for furigana before soramimi
useIsMobileTouch/small screenUse for layout decisions
useIsPhonePhones specificallyMore restrictive than isMobile
useOfflineNetwork-dependent featuresIncludes periodic polling
useEventListenerDOM event handlersAuto-cleanup, avoids stale closures
useLatestRefCallbacks needing current stateEliminates manual ref sync
useIntervalPolling, animationsPass null to pause
useTimeoutDelayed actionsReturns clear/reset controls
useResizeObserverResponsive componentsOptional debouncing
useRegisterUndoRedoApps with undo/redoRegister in app component, pairs with useGlobalUndoRedo
useInstanceUndoRedoMenu bars with Edit > Undo/RedoReads undo/redo state for a given instance
useRealtimeConnectionStatusShowing connection stateReturns Pusher connection status
useTelegramLinkTelegram integration UIFull link/unlink lifecycle

Related Documentation