TypeScript/React Development in Mikoto
Activation
TRIGGER when: working on frontend code in apps/client/, packages/mikoto.js/, or any TypeScript/React files.
Project Structure
apps/client/src/
├── components/
│ ├── atoms/ # Small, single-purpose (Avatar, SpaceIcon)
│ ├── molecules/ # Compound components (markdown, editors)
│ ├── surfaces/ # Page/tab views (MessageSurface, DocumentChannel)
│ ├── modals/ # Modal dialogs
│ ├── sidebars/ # Sidebar components
│ ├── tabs/ # Tab UI components
│ ├── ui/ # Chakra UI component re-exports
│ ├── icons/ # Custom FontAwesome icons
│ └── design/ # Design system components
├── views/ # Top-level views (MainView, AuthView)
├── hooks/ # Custom React hooks
├── store/ # Jotai atoms, LocalDB
└── functions/ # Utility functions (fileUpload, notify)
packages/
├── mikoto.js/ # Core API client library
│ ├── managers/ # Resource managers (SpaceManager, ChannelManager)
│ ├── WebsocketApi.ts # WebSocket connection
│ ├── MikotoClient.ts # Main client class
│ ├── AuthClient.ts # Authentication
│ └── api.gen.ts # Auto-generated types from OpenAPI
├── mikoto-ui/ # UI component library
├── permcheck/ # Permission checking utilities
├── lexical-markdown/ # Markdown editing plugin
└── tsconfig/ # Shared TypeScript config
Component Patterns
All components are functional with typed props interfaces:
interface AvatarProps {
src?: string | null;
userId?: string;
size?: number;
}
export function Avatar({
src,
userId,
size,
...rest
}: AvatarProps & React.HTMLAttributes<HTMLImageElement>) {
// implementation
}
Styling
Both Emotion styled-components and Chakra UI are used together:
import { chakra } from '@chakra-ui/react';
import styled from '@emotion/styled';
// Emotion styled component
const Wrapper = styled.div`
display: flex;
background: var(--chakra-colors-gray-800);
`;
// Chakra factory
const Card = chakra('div', {
base: { p: '4', bg: 'bg.panel' },
});
Surface System (Tab/Panel Management)
Components map to surface kinds for dynamic rendering:
const surfaceMap = {
textChannel: MessageSurface,
voiceChannel: lazy(() => import('./Voice')),
documentChannel: lazy(() => import('./Documents')),
search: SearchSurface,
spaceSettings: SpaceSettingsSurface,
// ...
};
- Surfaces are lazy-loaded with
React.lazy()andSuspense - DockView manages multi-pane layout
- Tabs identified by
kind/keyformat
Modal Pattern
// Modals use useModalKit() hook
// State managed through modalState atom
// Content rendered in Chakra DialogRoot
Context Menu Pattern
// useContextMenu() hook
// Position-aware with auto-flip near viewport edge
// Dismisses on outside click or Escape
State Management
Three state tools are used for different purposes:
Jotai (UI/Client State)
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
// Simple atom
const rightBarOpenState = atom(false);
// Persisted atom
const themeState = atomWithStorage('theme', 'dark');
// Atom family (parameterized)
const tabNameFamily = atomFamily((id: string) => atom(''));
// Usage
const [value, setValue] = useAtom(rightBarOpenState);
const value = useAtomValue(rightBarOpenState);
const setValue = useSetAtom(rightBarOpenState);
Valtio (Reactive Data/Managers)
Used in mikoto.js for reactive object proxies:
import { proxy, useSnapshot } from 'valtio';
import { proxyMap } from 'valtio/utils';
// CachedManager uses proxyMap for collections
// useMaybeSnapshot() for conditional proxy observation
React Query (Server State)
// QueryClient configured with structuralSharing: false
// (due to Mikoto.js class objects)
LocalDB (Typed localStorage)
import { LocalDB } from '@/store/LocalDB';
// Runtime type-safe wrapper using Zod
const db = new LocalDB('key', schema, initFunction);
db.get(); // typed
db.set(value); // validated
API Client (mikoto.js)
MikotoClient
import { useMikoto } from '@/hooks';
function MyComponent() {
const mikoto = useMikoto();
// REST calls use bracket notation
const space = await mikoto.rest['spaces.get'](undefined, {
params: { spaceId: id },
});
// WebSocket operations
mikoto.ws.send('typing.start', { channelId });
}
Manager Pattern
Managers wrap REST/WebSocket with caching:
// SpaceManager, ChannelManager, UserManager, etc.
// Extend CachedManager<T> for cached resources
// Methods: _get(), _insert(), _delete(), values()
// Auto-subscribe to WebSocket events for real-time updates
Context Hooks
export function useMikoto(): MikotoClient; // Main client
export function useAuthClient(): AuthClient; // Auth client
Routing
React Router v6 with browser router:
/ → MainView (shell)
/spaces, /friends, /discover, /settings → Top-level views
/space/:spaceRef → Space view
/space/:spaceRef/channel/:channelId → Channel view
/space/:spaceRef/settings → Space settings
/login, /register, /forgotpassword → Auth views
/invite/:inviteCode → Invite handler
:spaceRefaccepts both UUIDs and @handles- Routes wrapped with
MikotoClientProviderfor auth
Custom Hooks
// Context access
useMikoto(); // MikotoClient instance
useAuthClient(); // AuthClient instance
// Data fetching
useFetchMember(space); // Load space members
// UI utilities
useContextMenu(fn); // Right-click menus
useContextMenuX(); // Extended context menu
useModalKit(); // Modal management
useTabkit(); // Tab operations
useInterval(cb, ms); // setInterval wrapper
useIsMobile(); // Mobile detection
useErrorElement(); // Error boundary helper
TypeScript Conventions
Config
- Strict mode:
true - Target:
es2020 - Module resolution:
bundler - JSX:
react-jsx(new transform) - Path alias:
@/*→./src/*
Type Patterns
// Props: always interface
interface ButtonProps {
variant?: 'solid' | 'outline';
size?: 'sm' | 'md' | 'lg';
}
// Unions/mapped types: use type
type ConnectionState = 'connecting' | 'reconnecting' | 'disconnected';
// Zod for runtime validation
import { z } from 'zod';
const schema = z.object({ name: z.string() });
// Generic constraints
class CachedManager<T extends { id: string }> { ... }
Strict Safety
- No implicit
any - Optional chaining (
?.) and nullish coalescing (??) used throughout - Type guards with
iskeyword - Discriminated unions for state types
File Naming
| Type | Convention | Example |
|---|---|---|
| Components | PascalCase | Avatar.tsx, UserArea.tsx |
| Hooks | camelCase with use prefix |
useInterval.ts |
| Utilities | camelCase | fileUpload.ts |
| Types/Classes | PascalCase | LocalDB.ts, MikotoClient.ts |
| Directories | camelCase or kebab-case | atoms/, mikoto-ui/ |
Import Conventions
// Path alias (always use for app imports)
import { Avatar } from '@/components/atoms/Avatar';
import { Button, Dialog } from '@/components/ui';
// Barrel exports from index files
import { useAuthClient, useMikoto } from '@/hooks';
Key Dependencies
| Category | Libraries |
|---|---|
| UI | @chakra-ui/react v3, @emotion/styled, framer-motion |
| State | jotai, valtio, @tanstack/react-query v5 |
| Editors | lexical, slate, y.js |
| Real-time | socket.io-client, livekit-client |
| Forms | react-hook-form, zod |
| Layout | dockview-react, re-resizable, react-virtuoso |
| Icons | @fortawesome/react-fontawesome, react-icons |
| Routing | react-router-dom v6 |
Development Commands
pnpm dev # Vite dev server (from apps/client/)
moon :typecheck # Monorepo-wide type check
moon :lint # ESLint
moon :lint.fix # Auto-fix lint issues
moon :format # Prettier
moon :test # Vitest
moon :generate # Regenerate API types from OpenAPI schema
Testing
import { expect, test } from 'vitest';
test('description', () => {
expect(result).toBe(expected);
});
Best Practices
- Use
@/imports for all app-internal imports - No casts - prefer to check the types/use zod validators, over using
as(especially noany) - Type all props with named interfaces, not inline types
- Use Jotai for UI state, Valtio for data proxies, React Query for server state
- Lazy load surfaces and heavy components with
React.lazy() - Use existing hooks — check
@/hooksbefore writing new state logic - Use barrel exports — add new components to relevant
index.ts - Run
moon :typecheckafter all frontend changes - Use Chakra semantic tokens (
bg.subtle,fg.muted,border) for theme compatibility - Prefer Chakra style props over inline CSS, but Emotion
styledis acceptable for complex components
Converted and distributed by TomeVault — claim your Tome and manage your conversions.