From 34c5c4ca8ececc01173f4e45eb4db93efe1f71bd Mon Sep 17 00:00:00 2001 From: Kevin Lago Date: Tue, 28 Jul 2026 18:02:17 -0400 Subject: [PATCH] docs(#260): rewrite CLAUDE.md against the app that actually exists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLAUDE.md calls itself the authoritative project reference, read at the start of every session. It was describing a shell removed months ago, which is not a cosmetic problem: several #234 children were written from its picture of the app and target files no route reaches (#236, and the plan half of #245). - **"There are no automated tests" was the most damaging line.** There are ~51 test files and 500+ tests behind `npm test`, and CI runs them (install · typecheck · test · export). An agent trusting that sentence skips the gate that guards the whole payload contract. Records the RN-free constraint too: tests run under `tsx --test`, so a tested module must not import react-native — the reason pure logic belongs in `src/lib/**`. - **Folder structure** rewritten from the real tree. The old one invented eight files (`find`/`edit`/`git`/`run`/`files`/`settings` tabs, `(live)`, `(fleet)`, `ChatScreen`) and omitted everything that now carries the app — `tunnel/`, `mirror/`, `pages/`, `kit/`, `graph/`, `sessions/`, `alerts/`. - **What This App Is** reframed: a read-only desktop mirror plus session chat, a local planner, and a surviving repo-client half — not the AI-first IDE the old text promised. Adds the demo-data note (#250) so first-run behaviour is not a mystery. - **Key Data Flows** now documents the mirror path (store_state → MirrorContext → page selector → component), the SEPARATE plan_state/plan_event live-plan path, and the two-layer contract parity harness including the PENDING_DOMAINS → DECODERS rule. - **Known Issues** replaced. Every previous entry was already fixed — duplicate IconBtn/Surface, ChatScreen, ThemeContext, llm.ts, gitClient.ts, the duplicated underscore workflows — which trained the reader to skip the section that should hold the real traps. It now leads with the 24 orphaned source files and the grep that finds them, plus the live traps: RN-free tests, designPage.ts not being plain ASCII, CSS colour values RN cannot parse, and syncing against base-studio-code `develop` rather than `main`. - Agent tool list corrected: ten tools, not the five listed. Every path referenced in the new file was verified to exist. Closes #260 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01RAPZC6xEyCrH9qSKUKLQdy --- CLAUDE.md | 344 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 192 insertions(+), 152 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3199a2e..fb16464 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,8 +13,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co # Start dev server (use Expo Go or a dev build on device) npx expo start -# Type-check (no tests exist — this is the primary correctness gate) -npx tsc --noEmit +# The two correctness gates — run BOTH before pushing +npx tsc --noEmit # or: npm run typecheck +npm test # tsx --test "src/**/*.test.ts" # EAS builds (requires EAS CLI and login) eas build --platform ios --profile development # dev client build @@ -26,19 +27,41 @@ eas update --branch preview # push to preview channel eas update --branch main # push to production channel ``` -There are no automated tests. `tsc --noEmit` is the only local correctness check. +**There is a real test suite — ~51 files, 500+ tests, run with `npm test`.** It is fast (a +couple of seconds) and RN-free: tests run under `tsx --test`, so a module a test imports must +not pull in `react-native`. That is why pure logic lives in `src/lib/**` and why, for example, +the stage-status colour map sits in `lib/planner/colors.ts` rather than in the component that +renders it — a `.tsx` cannot be imported from a `.ts` test. + +CI (`.github/workflows/ci.yml`) runs four jobs on every PR: `install` → `typecheck` · `test` · +`export`. The `test` job is the enforcement arm of the payload-parity harness (#246); without it +the contract fixtures were developer-local only. --- ## What This App Is -**Mobile Studio Code (MSC)** is an AI-first mobile IDE for iOS, built with Expo / React Native. -It lets a developer work on a GitHub repository entirely from an iPhone — browsing files, -editing code, committing, and — most importantly — directing an embedded Claude AI agent -to do the heavy lifting. +**Mobile Studio Code (MSC)** is an iOS companion to the **base-studio-code** desktop app, built +with Expo / React Native. It ships to the App Store as `base-studio-code` (see `app.json`). + +It is primarily a **read-only mirror**. The desktop publishes its state over an encrypted Noise +tunnel as `store_state` frames; the phone renders them. Five tabs — **Glance · Planner · Skills · +UI · Automations** — plus session chat, an alerts inbox, and a local project planner. + +Three things it can actually *do*, as opposed to display: + +1. **Session chat** — the one sanctioned mutation path into the desktop's panes. +2. **The local planner** (`app/(planner)/planner.tsx`) — fully local project planning that can + sync to the desktop. +3. **A GitHub repo client** — `app/repo.tsx` plus `lib/github.ts` / `lib/agent.ts`, surviving from + the app's origin as a standalone mobile IDE. Still wired, no longer the centre of gravity. -The guiding principle: **the AI should be able to do everything. The user should only have -to describe intent.** +The guiding principle is unchanged: **the AI should be able to do everything. The user should only +have to describe intent.** + +> **When the desktop is not paired, every mirror tab is empty.** `lib/mirror/demoData.ts` supplies +> representative demo projections so first run is exercisable (#250 — an App Store review +> requirement); real frames take over the moment a desktop connects. --- @@ -51,14 +74,20 @@ enemy. If a workflow requires more than two taps from the user, ask whether the absorb that complexity instead. ### AI is the primary actor -The user sets direction. Claude executes. Browsing, editing, committing — all of it can -and should be driven through the chat interface. The manual tabs (Files, Edit, Git) exist -as power-user overrides, not the primary workflow. +The user sets direction. Claude executes. The mirror tabs show what the desktop is doing; when +the user wants something *done*, the path is chat — `app/(sessions)/chat.tsx` against an +addressable desktop session. + +### Read-only until proven otherwise +The phone mirrors; it does not edit the desktop's state. Every new surface starts read-only. A +mutation needs a deliberate frame on the wire (`plan_confirm`, session input) — never a local +write that hopes to be reconciled later. ### Trust the agent loop -The agent has tools: `list_directory`, `read_file`, `write_file`, `read_issue`, -`comment_on_issue`. These are intentionally minimal. New capability should be added as -agent tools before it is added as UI. +The repo-client agent (`lib/agent.ts`) has ten tools: `list_directory`, `read_file`, `grep_file`, +`write_file`, `push_changes`, `pull_changes`, `read_remote_file`, `resolve_conflict`, +`read_issue`, `comment_on_issue`. They are intentionally minimal. New capability should be added +as an agent tool before it is added as UI. ### Invisible complexity Context optimisation, retry logic, session checkpointing, conflict resolution — none of @@ -86,158 +115,169 @@ Errors should be actionable, not technical. ## Folder Structure +Only the load-bearing paths. When this disagrees with the tree, the tree wins — and fix this file. + ``` / -├── app/ # Expo Router file-based routing -│ ├── _layout.tsx # Root layout: theme, session, nav guards, ambient Orbs background -│ ├── repo.tsx # Repo picker: search GitHub repos, clone (download) selected repo -│ ├── (tabs)/ # Main app — bottom tab navigator -│ │ ├── _layout.tsx # Tab definitions + custom BottomTabBar -│ │ ├── index.tsx # Files tab — collapsible folder tree, search, recents -│ │ ├── find.tsx # Find tab — full-text search across repo files -│ │ ├── edit.tsx # Edit tab — code viewer/editor for the currently open file -│ │ ├── run.tsx # Run tab — tunnel mirror of base-studio-code's PTY sessions (pairing/QR) -│ │ ├── git.tsx # Git tab — pull/push, changed files list, AI commit message draft -│ │ ├── files.tsx # Hidden route (href: null) — legacy/unused -│ │ └── settings.tsx # Hidden route (href: null) — surfaced via SettingsScreen -│ ├── (planner)/planner.tsx # Local project planner (PlannerContext + aiLoop + pipelines) -│ ├── (sync)/sync.tsx # Planner file-reconcile conflict editor (plan_sync_* path) -│ ├── (fleet)/fleet.tsx # Fleet/coordination view over the tunnel -│ └── (live)/live.tsx # Read-only mirror of the desktop's LIVE planning session (#1245) +├── app/ # Expo Router file-based routing +│ ├── _layout.tsx # Root layout: theme, tunnel, mirror, live-plan, alerts providers +│ ├── repo.tsx # GitHub repo picker + PAT entry +│ ├── (tabs)/ # The five-tab mirror shell; lands on Glance +│ │ ├── _layout.tsx # tab defs + custom BottomTabBar + AlertToast overlay +│ │ ├── index.tsx # Glance — domain "glance" +│ │ ├── plan.tsx # Planner — renders (planner)/planner embedded (NOT a mirror) +│ │ ├── skills.tsx # Skills — domain "skills" +│ │ ├── ui.tsx # UI — segmented: "components" + "themes" +│ │ └── automations.tsx # Automations — segmented: "automations" + "mcp" +│ ├── (sessions)/ # roster.tsx + chat.tsx — the ONE mutation path into the desktop +│ ├── (alerts)/inbox.tsx # Notification inbox (domain "alerts", FCM deep links) +│ ├── (more)/ # more · connection (pairing/QR) · providers · theme · security +│ ├── (planner)/planner.tsx # Local project planner (PlannerContext + pipelines) +│ └── (sync)/sync.tsx # Planner file-reconcile conflict editor (plan_sync_* path) │ ├── src/ -│ ├── lib/ # Core logic (no UI) -│ │ ├── agent.ts # Agent loop: tool definitions, tool runner, system prompt builder, -│ │ │ # retry logic (exponential backoff), iteration cap (25), checkpoint -│ │ │ # saves between iterations -│ │ ├── anthropic.ts # Raw Anthropic API client: streaming chat, commit message drafting -│ │ ├── contextOptimizer.ts # Context window management: stale read eviction, history compaction -│ │ │ # (summarises old turns when history exceeds ~60k chars) -│ │ ├── errorBus.ts # Global error event bus (lightweight pub/sub) -│ │ ├── fs.ts # All file system access via expo-file-system: read, write, manifest -│ │ │ # I/O, task index I/O, pending checkpoint I/O -│ │ ├── gitClient.ts # (Minimal) git utility helpers -│ │ ├── github.ts # GitHub REST API: verify PAT, download repo (tree + blobs), -│ │ │ # pull (diff remote tree vs manifest), push modified files, -│ │ │ # fetch issues, post comments -│ │ ├── llm.ts # Legacy LLM wrapper (predates agent.ts; used by ChatScreen) -│ │ ├── session.tsx # SessionProvider + useSession hook — single source of truth for: -│ │ │ # auth state, manifest, open file, pull/push, task management, -│ │ │ # chat/agent lifecycle, retry + cancel signals -│ │ ├── storage.ts # Secure key-value store (expo-secure-store) for PAT + API key -│ │ ├── syntax.ts # Regex-based syntax highlighting token parser -│ │ ├── tasks.ts # Task CRUD helpers: create, bootstrap, migrate legacy chat, -│ │ │ # patch index entries -│ │ └── types.ts # All shared TypeScript types (ChatMessage, Manifest, Task, -│ │ # LinkedIssue, ToolDefinition, etc.) +│ ├── lib/ +│ │ ├── tunnel.ts # TunnelClient: Noise transport, frame decode, mutation senders +│ │ ├── TunnelContext.tsx # Provider + useTunnel() +│ │ ├── tunnel/ # Transport internals — ALL pure + unit-tested +│ │ │ ├── noise.ts pairing.ts reconnect.ts input.ts paneSize.ts +│ │ │ ├── storeState.ts # store_state + store_state_chunk reassembly (rev-keyed) +│ │ │ ├── livePlan.ts # plan_state / plan_event reducer → the LIVE plan +│ │ │ ├── LivePlanContext.tsx +│ │ │ ├── *.fixtures.json # CONTRACT: byte-identical with base-studio-code +│ │ │ └── *.fixtures.test.ts# the parity harness (frame + payload) — see below +│ │ ├── mirror/ # Mirrored state → view models +│ │ │ ├── MirrorContext.tsx # useMirrorDomain(domain) → { data, rev, synced } +│ │ │ ├── payload.ts # selector contract: NEVER throw on missing/extra/mistyped +│ │ │ ├── state.ts demoData.ts feed.ts themeMap.ts +│ │ │ └── securityView.ts automationsView.ts mcpView.ts +│ │ ├── pages/ # Per-domain page selectors (pure, React-free) +│ │ │ ├── glancePage.ts skillsPage.ts designPage.ts orgPage.ts +│ │ │ └── blueprintsPage.ts plannerBoard.ts ← currently ORPHANED (see Tech Debt) +│ │ ├── graph/ # Pure graph engine: layout, cycles, routing, org + glance adapters +│ │ ├── kit/ # Design-system port: spec resolve, baseline JSON specs +│ │ ├── planner/ # Local planner: context, pipelines, publish, colors, sync +│ │ ├── sessions/ # roster, layout, input gating, nav +│ │ ├── alerts/ # AlertsContext, model, FCM routing, read state +│ │ ├── providers/ # Multi-provider LLM clients (anthropic, google, openai-compatible) +│ │ ├── agent.ts github.ts fs.ts tasks.ts session.tsx storage.ts # the repo-client half +│ │ └── types.ts # Wire types + STORE_DOMAINS │ │ │ ├── components/ -│ │ ├── ui/ # Shared UI primitives -│ │ │ ├── BottomTabBar.tsx # Custom tab bar with Claude avatar shortcut to chat -│ │ │ ├── ClaudeAvatar.tsx # Animated Claude "orb" avatar shown in tab bar + chat -│ │ │ ├── IconBtn.tsx # Pressable icon button -│ │ │ ├── Icons.tsx # SVG icon components -│ │ │ ├── IssueLinkSheet.tsx # Bottom sheet for linking a GitHub issue to a task -│ │ │ ├── Orbs.tsx # Ambient animated background blobs (theme-aware) -│ │ │ ├── Surface.tsx # Glass/blur surface card primitive -│ │ │ ├── TaskSheet.tsx # Bottom sheet for task management (create, switch, archive) -│ │ │ ├── ThemePicker.tsx # Theme selection UI -│ │ │ ├── TokenText.tsx # Syntax-highlighted text renderer (uses syntax.ts tokens) -│ │ │ └── TopPill.tsx # Top status pill (branch name, modified count, etc.) -│ │ ├── CodeLine.tsx # Single line of syntax-highlighted code -│ │ ├── CodeView.tsx # Scrollable code viewer built from CodeLine rows -│ │ ├── IconBtn.tsx # (duplicate root-level copy — prefer ui/ version) -│ │ ├── Surface.tsx # (duplicate root-level copy — prefer ui/ version) -│ │ └── TabIcons.tsx # Tab bar icon definitions -│ │ -│ ├── screens/ -│ │ ├── ChatScreen.tsx # Legacy standalone chat screen (uses llm.ts, not agent.ts) -│ │ └── SettingsScreen.tsx # Settings UI: theme picker, auth reset, repo clear -│ │ -│ ├── data/ # Static data / assets (contents not examined) -│ ├── theme.ts # Theme definitions (dark/light palettes, code colours, glass flag) -│ ├── theme/ # Theme sub-module (index re-exports) -│ ├── ThemeContext.tsx # Legacy ThemeContext (predates theme.ts; used by ChatScreen) -│ └── codeContent.ts # Static code sample (used in onboarding/demo) +│ │ ├── shell/ # MirrorScaffold (domain → synced/awaiting/demo), headers +│ │ ├── glance/ skills/ design/ automations/ security/ sessions/ # one mirror per domain +│ │ ├── kit/ # SpecHost / KitRenderer — renders baseline JSON specs +│ │ ├── graph/ # GraphCanvas (RN-SVG), drill-back +│ │ ├── planner/ # Local planner UI (several files here are ORPHANED) +│ │ └── ui/ # Primitives: Surface, Tag, Btn, BottomTabBar, ThemePicker… +│ └── theme.ts # THEMES.dark / THEMES.light + useTheme() │ -├── .github/ -│ ├── ISSUE_TEMPLATE/ # GitHub issue templates -│ ├── pull_request_template.md -│ └── workflows/ -│ ├── expo-preview.yml # ✅ canonical name -│ ├── expo_preview.yml # ⚠️ old underscore name — to be deleted -│ ├── issue-branch-check.yml # ✅ canonical name -│ ├── issue_branch_check.yml # ⚠️ old underscore name — to be deleted -│ └── .gitkeep -│ -├── App.tsx # Expo entry point (delegates to app/_layout.tsx via router) -├── app.json # Expo config: name "Mobile Studio Code", bundle ID, plugins -├── package.json # Dependencies -├── tsconfig.json # TypeScript config -├── babel.config.js # Babel config -└── CLAUDE.md # This file +├── .github/workflows/ # ci.yml (install·typecheck·test·export) · preview.yml · update.yml +├── app.json # Expo config — display name "base-studio-code" +└── CLAUDE.md # This file ``` --- ## Key Data Flows -### Session stages -`loading` → `ready` (app usable) - -`StageGate` in `app/_layout.tsx` only holds a spinner until secrets + any saved -manifest load; it does not gate navigation. The app boots into the tabs and -lands on the **Run** tab (`unstable_settings.initialRouteName` in -`app/(tabs)/_layout.tsx`). Repo selection is **not** a launch gate — the user -opens the repo picker (`/repo`) on demand from the **Git** tab (its "Switch" -action, or the "Pick a repo" button in the no-repo empty state). The repo -picker also hosts GitHub PAT entry and a "Plan a project" entry into the -planner (`/(planner)/planner`). There is no onboarding screen — credentials -are persisted via `storage.ts` and surfaced errors if missing. - -### Agent loop (per message send) -1. User sends message via chat UI in `edit.tsx` (the main chat interface) -2. `session.tsx` → `runAgent()` in `agent.ts` -3. Agent calls Anthropic API (`anthropic.ts`), receives tool calls -4. `runTool()` executes each tool (read/write file, list dir, read/comment issue) -5. Results fed back; loop continues until `end_turn` or iteration cap (25) -6. Context optimiser trims stale reads + compacts old history as needed -7. Checkpoint saved to disk between iterations (survives app backgrounding) - -### Pull / Push (GitHub API — no native git) -- **Pull**: fetch remote tree → compare SHAs vs manifest → download changed blobs → - skip locally-modified files (surface as conflicts) -- **Push**: for each `modified=true` file in manifest → `PUT /repos/{repo}/contents/{path}` - with the file's last-known SHA → update manifest SHA on success - -### Task system -Each task has isolated `turns` (UI display) and `history` (Anthropic API messages). -Switching tasks gives the agent a completely fresh context window. Tasks persist to -`{repoDir}/.msc-tasks/{taskId}.json`; the index lives at `{repoDir}/.msc-task-index.json`. +### Boot +`app/_layout.tsx` mounts the provider stack (theme → tunnel → mirror → live plan → alerts) and +holds a spinner only until secrets load. The app lands on **Glance**. There is no onboarding and +no launch gate: pairing happens on demand from **More → Connection**, repo selection from +`/repo`. + +### The mirror path (how nearly every screen gets its data) +``` +desktop ──store_state {domain, rev, json}──▶ tunnel.ts + │ (over-cap domains arrive as + │ store_state_chunk and are reassembled + │ by tunnel/storeState.ts, keyed by rev) + ▼ + MirrorContext ──useMirrorDomain(d)──▶ MirrorScaffold + │ + lib/pages/*.ts selector ◀────┘ + │ + component renders the VM +``` +`STORE_DOMAINS` (`lib/types.ts`) is the vocabulary: `glance`, `plan`, `org`, `blueprints`, +`skills`, `components`, `themes`, `automations`, `mcp`, `alerts`, `security`. + +**Selectors must never throw** on a missing, extra, or mistyped field (`lib/mirror/payload.ts`). +A drifted payload must degrade to a blank section, never a crash, on a phone in the field. + +### The live plan (a SEPARATE path — do not confuse it with the `plan` domain) +`plan_state` / `plan_event` **frames** → `tunnel/livePlan.ts` (pure reducer, keyed by projectId) +→ `LivePlanContext` → `BlueprintStageBar` / `(planner)/planner.tsx`. It uses its own status +vocabulary (`SectionRenderStatus`), not the `plan` domain's `StageStatus`. **The `plan` store +domain currently has no consumer at all.** + +### Contract parity (the thing that keeps mobile and desktop honest) +Two fixture files are **byte-identical** with base-studio-code (verified by git blob hash), and +two test files decode them into our typed models and deep-equal back: + +- `tunnelProtocol.fixtures.json` — FRAME shapes. +- `storePayloads.fixtures.json` — per-domain PAYLOAD shapes (#246). Generated desktop-side from + the real builders. **Layer A** re-encodes only the fields we read (an unknown field fails the + deep-equal; a missing required field throws by name). **Layer B** smoke-tests that the + canonical payload yields a non-degenerate view — per field, never just "not empty" — because a + tolerant selector renders a blank page rather than failing. + +A domain is either in `DECODERS` (reconciled) or in `PENDING_DOMAINS` with its issue number. +**Moving a domain from `PENDING_DOMAINS` into `DECODERS` is the definition of done for its +issue.** Changing a fixture is a coordinated two-repo PR pair. + +### Session chat (the one mutation path) +`(sessions)/roster.tsx` lists addressable desktop panes; `chat.tsx` streams one and sends input, +subject to the desktop's input grant (`tunnel/inputGrant`, `sessions/inputGate.ts`). --- ## Known Issues & Tech Debt -- **Duplicate components**: `IconBtn.tsx` and `Surface.tsx` exist at both - `src/components/` and `src/components/ui/`. The `ui/` versions are current; - the root-level copies are stale. -- **Legacy ChatScreen**: `src/screens/ChatScreen.tsx` uses the old `llm.ts` wrapper - and `ThemeContext.tsx` instead of the current `theme.ts`. Not wired into the main - tab navigation. -- **Legacy ThemeContext**: `src/ThemeContext.tsx` is a parallel theme system only used - by `ChatScreen.tsx`. Everything else uses `src/theme.ts`. -- **Run tab is the tunnel mirror**: `app/(tabs)/run.tsx` mirrors base-studio-code's PTY - sessions over the Noise relay (the phone has no shell of its own — there is nothing to - run locally). It also hosts the **Fleet** / **Live** / **Plan** shortcuts. -- **Workflow filename duplication**: Both `expo_preview.yml` / `expo-preview.yml` and - `issue_branch_check.yml` / `issue-branch-check.yml` exist. The underscore versions - should be removed; only the hyphenated versions are canonical. -- **New-file push limitation**: Files written by the agent that have never been on remote - (`sha: null`) rely on the GitHub `PUT /contents` API creating them. If the manifest - SHA state desyncs (e.g. after a failed push), recovery requires a pull first. -- **No delete-file tool**: The agent can write files but cannot delete them. Removing a - file from a repo requires manual action (or a future `delete_file` tool). -- **No branch creation**: The app works on whatever branch was selected at clone time. - Creating or switching branches is not possible from within the app. -- **No diff view**: The Git tab shows which files changed but not what changed within them. +### Orphaned code — read this before starting any issue + +**24 source files are unreachable from any `app/` route.** They compile, they have passing +tests, and nothing renders them. This is the single biggest trap in the repo: issues get written +against them and the work lands somewhere no user can see. It has already happened — see #236 +and the plan half of #245, both invalidated by `54c13fb` (2026-07-25), which removed the Planner +tab's segment strip and orphaned every surface it mounted. + +**Before implementing an issue, confirm its target is reachable:** + +```bash +# does anything import it? +grep -rn "ComponentName" --include=*.tsx --include=*.ts src app +``` + +Currently orphaned: + +| Area | Files | +|---|---| +| planner UI | `BlueprintsSection` `LivePlanBoard` `ChatTab` `PlanTab` `PlannerChrome` `PreviewTab` `atoms` | +| page selectors | `blueprintsPage.ts` `plannerBoard.ts` · `orgPage.ts` *(deliberate — built for #233's Teams segment)* | +| repo-client leftovers | `github/BranchGraph` `github/LanguageBar` `github/charts` `diff.ts` `githubCache.ts` `githubPulse.ts` `syntax.ts` | +| other | `fleet/CoordInboxCard` `fleet/WorkerCard` `ui/IssueLinkSheet` `ui/TaskSheet` `ui/TopPill` `planner/seed.ts` `screens/GraphDemoScreen.tsx` | + +Test-only files (`tunnel/fixtureDecode.ts`, the `*.fixtures.json`, `graph/vitestShim.ts`) are +also unreachable from `app/` — that is correct and expected. + +### Live traps + +- **Tests must not import React Native.** `npm test` runs under `tsx --test`. Put logic that + needs testing in `src/lib/**`, not in a `.tsx`. +- **`src/lib/pages/designPage.ts` is not plain ASCII** — `grep` reports it as a binary file. Use + `grep -a`, or the Read tool. +- **Desktop `tech`/`hue` values are CSS** (`var(--accent)`, `oklch(...)`). React Native can parse + neither. Never bind a mirrored colour string to a `color` prop without normalising it. +- **Sync against base-studio-code `develop`, not `main`.** `main` is far behind; every contract + reference our code cites exists only on `develop`. +- **`plan` is exempt from the payload harness** (`UNPROJECTED_DOMAINS`) — it is published by the + planner, not `useStoreProjector`. + +### Platform limits (unchanged) + +- **No delete-file tool** — the agent can write files but not remove them. +- **No branch creation** — the app works on whatever branch was selected at clone time. +- **New-file push** relies on `PUT /contents` creating the path; if the manifest SHA desyncs, + recovery requires a pull first.