Client
The client is a React Native app built with Expo SDK 56. It runs on iOS and Android.
Tech stack
Section titled “Tech stack”| Concern | Library |
|---|---|
| Framework | Expo SDK 56 / React Native 0.85 |
| Routing | Expo Router — file-based, tab + stack navigation |
| State management | Zustand |
| Calendar component | @musubi/calendar — forked in-repo, extended with recurrence |
| Animations | Reanimated v4 + Gesture Handler |
| Auth client | Better Auth |
| Fonts | Inter Tight, Noto Serif, Shippori Mincho B1 |
File structure
Section titled “File structure”apps/client/├── app/│ ├── _layout.tsx # Root layout — font loading, auth guard, splash│ ├── (auth)/│ │ ├── welcome.tsx # Landing screen — server URL + sign in / sign up│ │ ├── sign-in.tsx # Email + password sign-in│ │ └── sign-up.tsx # Registration (step 1 of 3)│ ├── (tabs)/│ │ ├── _layout.tsx # Tab navigator + initial data fetch│ │ ├── index.tsx # Main calendar view│ │ ├── agenda.tsx # Upcoming events list│ │ ├── calendars.tsx # Manage calendars│ │ └── settings.tsx # User settings│ └── invite/│ └── [token].tsx # Deep-link invite acceptance├── components/│ ├── calendar/ # Calendar-specific UI (modals, filter bar, skeletons…)│ └── ... # General components├── constants/│ ├── colors.ts # Named colour swatches (appColors array)│ ├── theme.ts # StyleSheet, fonts, calendarTheme│ └── const.ts # Japanese month/day name arrays├── hooks/│ ├── useVisibleEvents.ts # Filters + sorts events by active calendars│ ├── useEventsStream.ts # SSE connection for real-time event updates│ └── useModalAnimation.ts # Shared bottom-sheet animation hook├── services/│ └── api.ts # Typed HTTP client for all API endpoints├── store/│ ├── useEventsStore.ts # Events state + CRUD│ ├── useCalendarsStore.ts # Calendars state, active filter, solo mode│ └── useSettingsStore.ts # User settings (view mode, week start, kanji)└── contexts/ └── ServerContext.tsx # Server URL + auth client instanceScreens
Section titled “Screens”Welcome (app/(auth)/welcome.tsx)
Section titled “Welcome (app/(auth)/welcome.tsx)”The first screen new users see. Lets them set a custom server URL (for self-hosters), then navigate to sign-in or sign-up.
Sign Up (app/(auth)/sign-up.tsx)
Section titled “Sign Up (app/(auth)/sign-up.tsx)”Three-step account creation. Step 1 (currently implemented): name, email, and passphrase. Step 2 and 3 are planned — see What’s Coming.
Sign In (app/(auth)/sign-in.tsx)
Section titled “Sign In (app/(auth)/sign-in.tsx)”Email and passphrase. Both fields are wired for platform autofill (textContentType on iOS, autoComplete on Android) so password managers including Bitwarden work out of the box.
Home — Calendar (app/(tabs)/index.tsx)
Section titled “Home — Calendar (app/(tabs)/index.tsx)”The main screen. Displays a calendar in day, week, or month view. Recurring events are expanded from their RRULE strings for the visible date range. Tap an event to see details; use the FAB to create a new one. Long-press a calendar filter pill to isolate (solo) that calendar.
Agenda (app/(tabs)/agenda.tsx)
Section titled “Agenda (app/(tabs)/agenda.tsx)”A scrollable list of upcoming events across all active calendars, grouped by date. Only shows events from today onwards.
Calendars (app/(tabs)/calendars.tsx)
Section titled “Calendars (app/(tabs)/calendars.tsx)”Create calendars, view members, send invite links, and manage calendar settings.
Settings (app/(tabs)/settings.tsx)
Section titled “Settings (app/(tabs)/settings.tsx)”Set the default calendar view, week start day, and toggle the kanji header decoration.
Invite (app/invite/[token].tsx)
Section titled “Invite (app/invite/[token].tsx)”Handles deep links (https://musubi.frgtn.dev/invite/<token>). Shows the calendar you’re being invited to, with member count and upcoming events. If you’re already a member of that calendar, the screen skips straight to the app.
Calendar component (@musubi/calendar)
Section titled “Calendar component (@musubi/calendar)”Musubi uses a forked version of react-native-big-calendar, maintained in packages/calendar/. The fork adds:
- Recurrence expansion —
expandRecurringEvents()turns RRULE strings into individual occurrences for the current view window - Enriched events optimisation — pre-computes a date-keyed lookup map so per-cell rendering does O(1) lookups instead of scanning the full event array
Use Calendar from @musubi/calendar, not the npm package directly:
import { Calendar } from '@musubi/calendar';State management
Section titled “State management”Three Zustand stores cover all app state:
useEventsStore — the list of events fetched from the server, and the CRUD actions that call the API and update the store.
useCalendarsStore — the list of calendars, which ones are currently shown (activeCals), and solo mode (long-pressing a filter pill to isolate one calendar).
useSettingsStore — user preferences persisted via the API.
Real-time updates
Section titled “Real-time updates”useEventsStream opens a persistent SSE connection to GET /api/stream. The server pushes event payloads whenever something changes in a calendar you’re a member of. The hook calls the appropriate store actions to keep local state current.
Colour palette
Section titled “Colour palette”The app uses a fixed dark palette. Named tokens live in constants/theme.ts:
| Token | Hex | Usage |
|---|---|---|
fg | #e8e4d9 | Primary text |
fg2 | 72% of fg | Secondary text |
fg3 | 48% of fg | Muted text, labels |
fg4 | 28% of fg | Placeholders |
accent | #c8553d | Buttons, errors, highlights |
bg | #0c0c0e | Main background |
bg1 | #131316 | Tab bar, cards |
bg2 | #1a1a1e | Elevated surfaces |
bg3 | #222226 | Pills, input backgrounds |
Never hardcode hex values in components. Always use colors from constants/theme.ts.
Adding a screen
Section titled “Adding a screen”- Create a file in the appropriate
app/directory - Export a default React component as the screen
- If it’s a new tab, add it to
app/(tabs)/_layout.tsx - Use
styles.screenas the root container style — it handles background colour and flex