KPBoards
Skip to main content
KPBoards

Web and mobile apps, shipped.

KPBoards — building web apps and mobile apps. Selected work, build-log teardowns, and hire-me for your next build.

Quick links

  • Home
  • Blog
  • Work
  • Open source
  • Stack
  • Services
  • Playbooks
  • About
  • Contact

Legal

  • Privacy notice
  • Terms of service
  • Cookie policy
  • Affiliate disclosure
  • Refund policy
  • DMCA / Copyright

Contact

hello@kpboards.com+84 901 430 110
Copyright 2026 KPBoards. All rights reserved.
Privacy noticeTerms of serviceCookie policy
Back to open source
Open SourceFeatured

expo-ecc-starter — Expo SDK 55 starter wired for Claude Code (mobile)

Production-ready Expo SDK 55 + React Native 0.83 + TypeScript strict starter: auth scaffold + SecureStore, Zustand, TanStack Query, RHF + Zod, theme system (light/dark/system), Jest + Maestro E2E, AgentShield — plus Everything Claude Code baked in (15 subagents, 15 slash commands, 22 skills) so week one of every new mobile project is no longer scaffolding.

stars
1 stars
forks
0 forks
open issues
10 open issues
License
MIT
Primary language
TypeScript
Last commit
Last commit: 4 days ago
View on GitHubHomepage
expo-ecc-starter — Expo SDK 55 starter wired for Claude Code (mobile)

On this page

  • Overview
  • Why I built it
  • Architecture
  • Installation
  • Usage
  • Roadmap
  • Links

Overview#

expo-ecc-starter is a batteries-included boilerplate for Expo SDK 55 + React Native 0.83 mobile apps in production. The first week of every new mobile project goes to re-wiring the same twenty things: auth + SecureStore, deep-link allowlists, theme persistence, RHF on RN, error boundaries, navigation guards, lint hooks, EAS profiles. This repo does all of it — modern, opinionated, out of the way — so day one is product code.

This is the mobile sibling of react-ecc-starter (the web version). Same Everything Claude Code (ECC) philosophy — different platform: 15 subagents, 15 slash commands, 22 skills, layered rules, AgentShield 1.4, and an EXPO_PUBLIC_* secret guard — all audited and ready for real-world use.

  • Build: Expo SDK 55 + React 19.2 + React Native 0.83 + TypeScript 5.9 strict (noUncheckedIndexedAccess + exactOptionalPropertyTypes enabled).
  • Routing: Expo Router 6 file-based with (auth) / (modal) / (tabs) groups, route-level Stack.Protected guards.
  • Server state: TanStack Query v5 + Zod — schema-validated, typed errors, mobile-aware retry.
  • Client state: Zustand v5, auth store backed by SecureStore (never AsyncStorage).
  • UI: No design-system bloat (no shadcn / NativeWind / Tamagui) — Pressable + theme tokens + Lucide icons, primitives owned in-repo.
  • Auth: Mock sign-in / sign-up + Stack.Protected + useAuthStore, tokens stored only in expo-secure-store; swap in a real provider when ready.
  • Tests: Jest 29 + React Native Testing Library + Maestro E2E (Detox fallback for bridge inspection); 60% coverage threshold.
  • Security: AgentShield 1.4 on every CI build, pre-commit blocks EXPO_PUBLIC_* secret leaks, deep links validated against an allowlist.

Why I built it#

After shipping react-ecc-starter for the web, every new mobile project rebuilt the same five things: auth + token storage, light/dark/system theme, navigation guards, RHF forms over RN inputs, deep-link routing. That's 1-2 weeks of work each time — and each time risks wiring tokens to the wrong place (AsyncStorage instead of SecureStore).

The biggest difference from other boilerplates: mechanically enforced, not trusted to code review. SecureStore-only token storage is enforced via lint rules + a CI gate, not convention. Deep links are validated against a hard-coded allowlist, not a runtime check. Pre-commit blocks any EXPO_PUBLIC_* outside the whitelist — secrets never make it into a commit.

The other half is Everything Claude Code (ECC) in its mobile flavor: 15 audited subagents + 15 slash commands + 22 skills (a11y-architect for iOS/Android, react-native-build-resolver for Metro/EAS/Reanimated, e2e-runner for Maestro/Detox, security-reviewer for mobile OWASP). Open Claude Code inside the project and review, scaffold, and debug patterns are already there. Day one is product code; week one ships a screen.

Architecture#

Organised around Expo Router's file-based routing with strict state / theme / native-module boundaries.

  • app/ — Expo Router 6 file-based routes with (auth) / (modal) / (tabs) groups, _layout.tsx per group, +not-found.tsx.
  • src/components/ — Themed primitives (Button, Card, Input, Spinner) plus composites, no external design-system dependency.
  • src/hooks/ — Custom hooks (use-prefixed); React Native APIs never leak to the UI layer.
  • src/stores/ — Zustand stores; the auth store is SecureStore-backed (tokens never live in a memory dump).
  • src/providers/ — QueryClientProvider, ThemeProvider, SafeAreaProvider composed at root.
  • src/theme/ — Tokens (color, spacing, typography), light/dark/system selector, persisted via AsyncStorage (preferences only — never tokens).
  • src/lib/ — Typed HTTP, Zod schemas, deep-link validator, pure utilities (no RN deps).
  • .claude/ — 15 subagents + 15 slash commands + 22 skills (mobile ECC), audited by AgentShield 1.4.
  • .maestro/ — Maestro E2E flows (smoke tests), invoked from CI and the pre-merge gate.

Pre-commit chain (Husky, no --no-verify escape): code-reviewer → typescript-reviewer → a11y-architect → e2e-runner → security-reviewer. AgentShield 1.4 runs on every CI build; Jest coverage threshold is 60%; force-push to main is blocked.

Installation#

Get the project running locally in a couple of commands.

Clone the templatebash
bunx degit pldkhoi/expo-ecc-starter my-app
cd my-app
Install dependencies (sets up Husky hooks)bash
bun install
Interactive rebrand + start dev serverbash
bun init        # interactive: app name, bundle id, scheme
bun dev         # Metro on :8081
# bun ios | bun android | bun web
One-time setup for Maestro E2Ebash
curl -Ls "https://get.maestro.mobile.dev" | bash
bun e2e:maestro

Usage#

Common patterns and snippets to get started fast.

Protected route with Stack.Protected + Zustand auth storetsx
// app/(auth)/_layout.tsx
import { Redirect, Stack } from 'expo-router';
import { useAuthStore } from '@/stores/auth-store';

export default function AuthLayout() {
  const isAuthenticated = useAuthStore((s) => s.isAuthenticated);

  if (!isAuthenticated) {
    return <Redirect href="/sign-in" />;
  }

  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Protected guard={isAuthenticated}>
        <Stack.Screen name="(tabs)" />
        <Stack.Screen name="(modal)" options={{ presentation: 'modal' }} />
      </Stack.Protected>
    </Stack>
  );
}
Secure token storage with expo-secure-store (never AsyncStorage)tsx
// src/stores/auth-store.ts
import { create } from 'zustand';
import * as SecureStore from 'expo-secure-store';

const TOKEN_KEY = 'auth.token';

interface AuthState {
  token: string | null;
  isAuthenticated: boolean;
  signIn: (token: string) => Promise<void>;
  signOut: () => Promise<void>;
  hydrate: () => Promise<void>;
}

export const useAuthStore = create<AuthState>((set) => ({
  token: null,
  isAuthenticated: false,
  signIn: async (token) => {
    await SecureStore.setItemAsync(TOKEN_KEY, token, {
      keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
    });
    set({ token, isAuthenticated: true });
  },
  signOut: async () => {
    await SecureStore.deleteItemAsync(TOKEN_KEY);
    set({ token: null, isAuthenticated: false });
  },
  hydrate: async () => {
    const token = await SecureStore.getItemAsync(TOKEN_KEY);
    set({ token, isAuthenticated: token !== null });
  },
}));
Themed component using the theme token systemtsx
// src/components/themed-button.tsx
import { Pressable, Text, StyleSheet } from 'react-native';
import { useTheme } from '@/theme/use-theme';

interface ThemedButtonProps {
  label: string;
  onPress: () => void;
  variant?: 'primary' | 'secondary';
}

export function ThemedButton({ label, onPress, variant = 'primary' }: ThemedButtonProps) {
  const { tokens } = useTheme();

  return (
    <Pressable
      onPress={onPress}
      accessibilityRole="button"
      accessibilityLabel={label}
      style={({ pressed }) => [
        styles.base,
        {
          backgroundColor:
            variant === 'primary' ? tokens.color.brand : tokens.color.surfaceMuted,
          opacity: pressed ? 0.85 : 1,
          minHeight: 48,
        },
      ]}
    >
      <Text style={{ color: tokens.color.onBrand, fontSize: tokens.typography.body }}>
        {label}
      </Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  base: {
    paddingHorizontal: 20,
    paddingVertical: 12,
    borderRadius: 12,
    alignItems: 'center',
    justifyContent: 'center',
  },
});

Roadmap#

  • Expand .maestro/ flows: onboarding carousel, paywall, deep-link settings, biometric unlock.
  • Add sample screens: 3-step onboarding, RevenueCat-ready paywall, settings with deep-link sharing.
  • More ECC subagents: release-orchestrator for auto-changelog + tag, eas-build-resolver focused on EAS Build / Reanimated failure modes.
  • EAS Build profile docs (development / preview / production) + a bun release script.
  • Expo SDK 55 → SDK 56 migration guide as soon as the RC stabilises, with auto-fix scripts for common breaking changes.

Links#

  • github.com/pldkhoi/expo-ecc-starter
  • https://github.com/pldkhoi/expo-ecc-starter