From 518d342021de07011d2b89690faae077e5cd67a3 Mon Sep 17 00:00:00 2001 From: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com> Date: Sat, 9 May 2026 09:23:31 +0800 Subject: [PATCH] docs(mobile): establish independence rules and tech-stack baseline - Refactor root CLAUDE.md sharing rules into a single Sharing Principles section, replacing scattered mentions across 10 places with one source of truth + minimal "(web + desktop)" qualifiers on existing sections - Add apps/mobile/CLAUDE.md with locked tech-stack baseline: Expo SDK 54, React Native 0.81, NativeWind 4 + Tailwind 3.4, react-native-reusables, TanStack Query 5, Zustand, expo-secure-store - Mobile pins React directly (does NOT track root catalog:) so the Expo SDK / RN release schedule isn't blocked by web/desktop upgrades - Visual tokens are mobile-owned (transcribed from packages/ui/styles/ tokens.css by hand, not imported); Tailwind v3.4 vs v4 mismatch makes file sharing impractical anyway - Document mobile build/release pipeline (main CI excludes mobile, separate mobile-verify and mobile-release workflows, EAS Update for OTA) Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 38 ++++++++++++++++++++++++++++---------- apps/mobile/CLAUDE.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 apps/mobile/CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md index c15a76a37..24cca1b5a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,11 +32,14 @@ Multica is an AI-native task management platform — like Linear, but with AI ag - `server/` — Go backend (Chi router, sqlc for DB, gorilla/websocket for real-time) - `apps/web/` — Next.js frontend (App Router) - `apps/desktop/` — Electron desktop app (electron-vite) -- `packages/core/` — Headless business logic (zero react-dom, all-platform reuse) +- `apps/mobile/` — Expo / React Native iOS app. See `apps/mobile/CLAUDE.md`. +- `packages/core/` — Headless business logic (zero react-dom) - `packages/ui/` — Atomic UI components (zero business logic) - `packages/views/` — Shared business pages/components (zero next/* imports, zero react-router imports) - `packages/tsconfig/` — Shared TypeScript configuration +What lives where for sharing purposes is documented in *Sharing Principles* below — read it once. + ### Key Architectural Decisions **Internal Packages pattern** — all shared packages export raw `.ts`/`.tsx` files (no pre-compilation). The consuming app's bundler compiles them directly. This gives zero-config HMR and instant go-to-definition. @@ -52,7 +55,7 @@ Multica is an AI-native task management platform — like Linear, but with AI ag The architecture relies on a strict split between server state and client state. Mixing them is the most common way to break it. - **TanStack Query owns all server state.** Issues, users, workspaces, inbox — anything fetched from the API lives in the Query cache. WS events keep it fresh via invalidation; no polling, no `staleTime` workarounds. -- **Zustand owns all client state.** UI selections, filters, drafts, modal state, navigation history. Stores live in `packages/core/` (never in `packages/views/`) so both apps share them. +- **Zustand owns all client state.** UI selections, filters, drafts, modal state, navigation history. Stores live in `packages/core/` (never in `packages/views/`) so they're shared. - **React Context** is reserved for cross-cutting platform plumbing — `WorkspaceIdProvider`, `NavigationProvider`. Don't reach for it for general state. - **Auth and workspace stores are the only stores allowed to call `api.*` directly**, because they manage critical state that must exist before queries can run. They're created via factory + injected dependencies, registered by the platform layer. @@ -69,6 +72,17 @@ The architecture relies on a strict split between server state and client state. - Selectors must return stable references. Returning a freshly built object or array on every call (e.g. `s => ({ a: s.a, b: s.b })` or `s => s.items.map(...)`) triggers infinite re-renders. Either select primitives separately or use shallow comparison. - Hooks that need workspace context should accept `wsId` as a parameter, not call `useWorkspaceId()` internally — this lets them work outside the `WorkspaceIdProvider` (e.g. in a sidebar that renders before workspace is loaded). +## Sharing Principles + +The monorepo splits into two share zones: + +- **Web and desktop** share business logic, components, hooks, stores, and views through `packages/core/`, `packages/ui/`, and `packages/views/`. Existing model — keep using it. +- **Mobile (`apps/mobile/`) is independent.** It shares only **types and pure functions** from `@multica/core/`, with `import type` for types (zero runtime coupling). UI, state, hooks, providers, i18n, React version, build pipeline, release cadence — all mobile-owned. + +Mobile is locked to the React version that Expo SDK / React Native ships (which lags React main by 6-12 months). Coupling mobile to the root `catalog:` React would block mobile from upgrading on its own schedule. + +See `apps/mobile/CLAUDE.md` for the mobile rules and tech-stack baseline. + ## Commands ```bash @@ -183,17 +197,17 @@ When adding a `Queries.Delete*` or `Queries.Update*` call, ask: "Where did this These are hard constraints. Violating them breaks the cross-platform architecture: -- `packages/core/` — zero react-dom, zero localStorage (use StorageAdapter), zero process.env, zero UI libraries. **All shared Zustand stores live here**, even view-related ones (filters, view modes) — stores are pure state, not UI. +- `packages/core/` — zero react-dom, zero localStorage (use StorageAdapter), zero process.env, zero UI libraries. **Shared Zustand stores live here**, even view-related ones (filters, view modes) — stores are pure state, not UI. - `packages/ui/` — zero `@multica/core` imports (pure UI, no business logic). - `packages/views/` — zero `next/*` imports, zero `react-router-dom` imports, zero stores. Use `NavigationAdapter` for all routing. - `apps/web/platform/` — the only place for Next.js APIs (`next/navigation`). - `apps/desktop/src/renderer/src/platform/` — the only place for react-router-dom navigation wiring. -### The No-Duplication Rule +### The No-Duplication Rule (web + desktop) -**If the same logic exists in both apps, it must be extracted to a shared package.** +**If the same logic exists in both web and desktop, it must be extracted to a shared package.** -This applies to everything: components, hooks, guards, providers, utility functions. The decision process: +This applies to everything between web and desktop: components, hooks, guards, providers, utility functions. The decision process: 1. Does this code depend on Next.js or Electron APIs? → Keep in the respective app. 2. Does it depend on `react-router-dom` or `next/navigation`? → Keep in app's `platform/` layer. @@ -201,9 +215,9 @@ This applies to everything: components, hooks, guards, providers, utility functi When the two apps need different behavior for the same concept (e.g., different loading UI), extract the shared logic into a component with props/slots for the differences. Don't duplicate the logic. -### Cross-Platform Development Rules +### Cross-Platform Development Rules (web + desktop) -When adding a new page or feature: +When adding a new page or feature for web/desktop: 1. **New page component** → add to `packages/views//`. Never import from `next/*` or `react-router-dom`. 2. **Wire it in both apps** → add a route in `apps/web/app/` (Next.js page file) AND in the desktop router. **Exception**: pre-workspace transition flows (create workspace, accept invite) are NOT routes on desktop — they're `WindowOverlay` state. See *Desktop-specific Rules → Route categories*. @@ -212,14 +226,18 @@ When adding a new page or feature: 5. **Platform-specific UI** → if a feature is web-only or desktop-only, keep it in the respective app. Use props slots (`extra`, `topSlot`) on shared layout components to inject platform-specific UI. 6. **New hooks that need workspace context** → accept `wsId` as parameter instead of reading from `useWorkspaceId()` Context, so they work both inside and outside `WorkspaceIdProvider`. -### CSS Architecture +### CSS Architecture (web + desktop) -Both apps share the same CSS foundation from `packages/ui/styles/`. +Web and desktop share the same CSS foundation from `packages/ui/styles/`. - **Design tokens** → use semantic tokens (`bg-background`, `text-muted-foreground`). Never use hardcoded Tailwind colors (`text-red-500`, `bg-gray-100`). - **Shared styles** → `packages/ui/styles/`. Never duplicate scrollbar styling, keyframes, or base layer rules in app CSS. - **`@source` directives** → both apps scan shared packages so Tailwind sees all class names. +## Mobile-specific Rules + +Rules for `apps/mobile/` live in `apps/mobile/CLAUDE.md`. Read it before touching anything in `apps/mobile/` — it covers what may be imported from `@multica/core/`, the React version policy, the build/release pipeline, and the locked tech-stack baseline. + ## Desktop-specific Rules These rules apply to `apps/desktop/` only. Web has different constraints (URL bar, SSR, no tabs) and doesn't share these concerns. Every rule in this section was added after a concrete bug — treat them as enforced, not suggestions. diff --git a/apps/mobile/CLAUDE.md b/apps/mobile/CLAUDE.md new file mode 100644 index 000000000..3616c8016 --- /dev/null +++ b/apps/mobile/CLAUDE.md @@ -0,0 +1,42 @@ +# Mobile App Rules (apps/mobile/) + +For cross-app sharing rules, see the root `CLAUDE.md` *Sharing Principles* section. This file documents the locked tech-stack baseline and the few mobile-specific rules — so AI doesn't suggest outdated alternatives. + +## What mobile may import from `packages/` + +- `import type` from `@multica/core/types/*` (zero runtime coupling) +- Pure functions from `@multica/core/` + +Everything else, mobile writes its own. + +## Tech-stack baseline (locked) + +Start minimal. Add to this list when actually adopted — do NOT pre-list libraries. + +- **Expo SDK 54** +- **React Native 0.81** +- **React 19.x** — whatever Expo SDK 54 ships. Pinned in `apps/mobile/package.json` directly, NOT via root `catalog:`. +- **TypeScript** strict +- **Expo Router 6** — file-based routing +- **NativeWind 4** + **Tailwind 3.4** — NativeWind 5 is unstable and doesn't support Expo Go; stay on v4. (Note: web/desktop use Tailwind v4 — versions intentionally differ.) +- **react-native-reusables (RNR)** — the shadcn equivalent for React Native. Uses NativeWind + RN-Primitives + CVA. Component API mirrors shadcn. +- **TanStack Query 5** — mobile owns its `QueryClient` with `AppState` focus listener + `NetInfo` online listener. +- **Zustand** — mobile-local state only. +- **expo-secure-store** — auth token persistence. + +When upgrading any of these, update this list. + +## Visual tokens (separate from web) + +Mobile maintains its own design tokens in `apps/mobile/tailwind.config.js`. You MAY reference `packages/ui/styles/tokens.css` (web/desktop tokens) as inspiration, but **do not import or symlink the file**. Tokens are transcribed by hand and may diverge for mobile (touch-friendly spacing, no hover states, native typography). + +Tailwind version mismatch (mobile v3.4 vs web v4) makes file sharing impractical anyway — this isolation is intentional. + +## Build & release + +- **Main CI** (`.github/workflows/ci.yml`) excludes mobile via `--filter='!@multica/mobile'`. Mobile failures do NOT block web/desktop PRs. +- **Mobile verify** (`.github/workflows/mobile-verify.yml`): triggered on `apps/mobile/**` or `packages/core/types/**` changes — runs typecheck/lint/test only, no IPA build. +- **Mobile release** (`.github/workflows/mobile-release.yml`): triggered by `mobile-v*.*.*` tag → `eas build` + `eas submit`. +- **OTA** — EAS Update for JS-only fixes that don't change the runtime version. Manual / on-demand push to preview/production channels. + +Mobile release cadence is decoupled from main `v*.*.*` tags (server / CLI / desktop).