# App L100

> A Claude skill from Leo-Atienza/atlas-claude.

- **Type:** Skill
- **Install:** `agentstack add skill-leo-atienza-atlas-claude-app-l100`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Leo-Atienza](https://agentstack.voostack.com/s/leo-atienza)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Leo-Atienza](https://github.com/Leo-Atienza)
- **Source:** https://github.com/Leo-Atienza/atlas-claude/tree/main/skills/_archived/app-l100

## Install

```sh
agentstack add skill-leo-atienza-atlas-claude-app-l100
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Universal App Conductor — Building Premium Apps Across All Platforms

## When to Use This Skill

**Auto-activate when:** building any native app — mobile (React Native/Expo), desktop (Tauri), universal (multi-platform), iOS/Swift, or any app requiring device hardware, offline-first, or on-device AI. This is the CONDUCTOR skill for all native/cross-platform development.

**This skill does NOT replace specialist skills.** It orchestrates them.

## The Skill Map

| Task | Load Skill | ID |
|------|-----------|-----|
| React Native performance & patterns | `react-native` | SK-016 |
| Expo deployment & builds | `expo-deployment` | (archived, auto-activate) |
| Expo CI/CD workflows | `expo-cicd-workflows` | (archived, auto-activate) |
| **Tauri desktop apps** | **`tauri-desktop`** | **SK-088** |
| **Camera, scanning, biometrics, sensors** | **`device-hardware-bridge`** | **SK-089** |
| **Offline-first, CRDT sync** | **`local-first-architecture`** | **SK-090** |
| **On-device AI, ML inference, RAG** | **`edge-intelligence`** | **SK-091** |
| **Multi-platform monorepo** | **`crossplatform-monorepo`** | **SK-092** |
| **Native animations & motion** | **`motion-native`** | **SK-097** |
| **2D/3D visual rendering (Skia, Rive, R3F)** | **`visual-native`** | **SK-098** |
| **Screen transitions & smooth scroll** | **`transition-native`** | **SK-099** |
| **Sensory design (haptics + sound + motion tokens)** | **`sensory-native`** | **SK-100** |
| Swift concurrency review | `swift-concurrency-pro` | SK-035 |
| SwiftUI code review | `swiftui-pro` | SK-037 |
| Swift testing | `swift-testing-pro` | SK-036 |
| Data fetching & caching | `tanstack-ecosystem` | SK-055 |
| Unit/component testing | `vitest-testing` | SK-056 |
| Advanced JS/TS patterns | `advanced-javascript` | SK-045 |
| Design aesthetics | `frontend-design` | SK-005 |
| E2E testing (mobile + desktop) | `e2e-testing` | SK-027 |

---

## Platform Decision Matrix

| Scenario | Recommendation |
|---|---|
| New mobile app, team knows React | Expo SDK 54 (Managed Workflow) |
| Need custom native SDK not in Expo | Expo (Bare Workflow) with config plugins |
| Deep native customization, existing native team | React Native bare |
| iOS-only, premium native feel critical | Swift/SwiftUI native |
| Universal app (web + iOS + Android) | Expo + Expo Router + NativeWind |
| Shared codebase with Next.js web app | Turborepo monorepo + Expo + Next.js (SK-092) |
| **Desktop app (Windows/macOS/Linux)** | **Tauri 2.0 (SK-088)** |
| **Desktop + mobile from one codebase** | **Tauri 2.0 with mobile targets (SK-088)** |
| **Multi-platform (mobile + desktop + web)** | **Turborepo monorepo (SK-092) + Expo + Tauri + Next.js** |
| **Offline-first app with sync** | **Local-First Architecture (SK-090) + framework of choice** |
| **App with on-device AI/ML** | **Edge Intelligence (SK-091) + framework of choice** |
| **Camera/scanning/sensor-heavy app** | **Device Hardware Bridge (SK-089) + framework of choice** |

---

## The Full Stack (2026)

```
Mobile:        Expo SDK 54 / Expo Router v4 / React Native 0.82+ (New Architecture mandatory)
Desktop:       Tauri 2.0 (Rust backend + system WebView, 96% smaller than Electron)
Animation:     Reanimated 4 (mobile) / Motion + GSAP (desktop/web)
Local-First:   PowerSync / TinyBase v5 / Legend State v3
On-Device AI:  llama.rn / MediaPipe / sqlite-vec (on-device RAG pipeline)
Hardware:      Vision Camera v5 / expo-camera / Tauri plugins
State:         Zustand v5 + TanStack Query (server) + Jotai v2 (atomic)
Monorepo:      Turborepo + shared packages
Build:         React Compiler (default), precompiled iOS XCFrameworks
```

### Expo SDK 54 Highlights

- **Precompiled React Native for iOS** — XCFrameworks reduce clean build ~120s → ~10s
- **React Compiler enabled by default** — automatic memoization
- **sqlite-vec** — on-device vector search for RAG pipelines
- **TextDecoderStream** — streaming AI responses (local or remote)
- **expo-app-integrity** — DeviceCheck (iOS) + Play Integrity (Android)
- **Liquid Glass (iOS 26)** — `expo-glass-effect`, native tabs with glass sheen
- **Edge-to-edge Android 16** — always enabled, cannot be disabled

---

## Tauri Thread Model (Desktop)

```
┌─────────────────────────┐
│   Frontend (WebView)     │  React/Svelte/Vue — your existing web code
│   (system WebView2/WKW)  │  Animations, UI state, user interaction
└────────┬────────────────┘
         │ IPC: Commands + Events + Channels
┌────────▼────────────────┐
│   Rust Core              │  Business logic, file system, crypto, plugins
│   (compiled, type-safe)  │  Heavy computation, system access
└────────┬────────────────┘
         │
┌────────▼────────────────┐
│   System APIs            │  OS notifications, tray, clipboard, updater
└─────────────────────────┘
```

**Performance:** 
      
        
      
    
  );
}
```

### Storage (MMKV — 30x faster than AsyncStorage)

```tsx
// lib/storage.ts
import { MMKV } from 'react-native-mmkv';

export const storage = new MMKV();

// Synchronous — no await needed
storage.set('user.token', token);
const token = storage.getString('user.token');
storage.delete('user.token');
```

### TanStack Query + MMKV Persistence (Offline-First)

```tsx
// lib/query.ts
import { QueryClient } from '@tanstack/react-query';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
import { storage } from './storage';

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000,
      gcTime: 24 * 60 * 60 * 1000,
      retry: 2,
    },
  },
});

export const persister = createSyncStoragePersister({
  storage: {
    getItem: (key) => storage.getString(key) ?? null,
    setItem: (key, value) => storage.set(key, value),
    removeItem: (key) => storage.delete(key),
  },
});
```

---

## Haptic Feedback — Every Interaction Gets a Tap

```tsx
// hooks/useHaptic.ts
import * as Haptics from 'expo-haptics';

export const useHaptic = () => ({
  tap: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium),
  light: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light),
  heavy: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy),
  select: () => Haptics.selectionAsync(),
  success: () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success),
  error: () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error),
  warning: () => Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning),
});
```

**When to use which:**

| Interaction | Haptic | Notes |
|---|---|---|
| Button tap | `tap()` (Medium) | Every interactive button |
| Toggle / switch | `light()` | On state change, not on press |
| Picker scroll step | `select()` | Each item tick |
| Form submit success | `success()` | One-time confirmation |
| Delete confirm | `warning()` | Before the destructive action |
| Auth/validation error | `error()` | |
| Drag start | `tap()` | |
| Drag drop / snap | `heavy()` | Landing feeling |
| Long press (context menu) | `heavy()` | |

---

## Animation Patterns

### Button with Press Feedback (Gesture Handler, NOT Pressable)

```tsx
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
import * as Haptics from 'expo-haptics';

function AnimatedButton({ onPress, children }: Props) {
  const scale = useSharedValue(1);

  const tap = Gesture.Tap()
    .onBegin(() => {
      'worklet';
      scale.value = withSpring(0.95, { damping: 20, stiffness: 400 });
    })
    .onFinalize((_e, success) => {
      'worklet';
      scale.value = withSpring(1, { damping: 15, stiffness: 300 });
      if (success) {
        runOnJS(Haptics.impactAsync)(Haptics.ImpactFeedbackStyle.Medium);
        runOnJS(onPress)();
      }
    });

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }));

  return (
    
      {children}
    
  );
}
```

**Why GestureDetector over Pressable:** `Pressable.onPressIn` routes through JS thread. `Gesture.Tap().onBegin` runs as a worklet on UI thread — zero-latency animation start.

### Screen Entrance (Moti — Declarative)

```tsx
import { MotiView } from 'moti';

function ScreenContent() {
  return (
    
      {/* Screen content */}
    
  );
}
```

### Staggered List Entrance (Moti)

```tsx
import { MotiView } from 'moti';

function StaggeredList({ items }: { items: Item[] }) {
  return (
    <>
      {items.map((item, index) => (
        
          
        
      ))}
    
  );
}
```

### Collapsing Header (Reanimated + ScrollHandler)

```tsx
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  useAnimatedScrollHandler,
  interpolate,
  Extrapolation,
} from 'react-native-reanimated';

const HEADER_MAX = 200;
const HEADER_MIN = 80;

function CollapsingHeaderScreen() {
  const scrollY = useSharedValue(0);

  const scrollHandler = useAnimatedScrollHandler({
    onScroll: (event) => {
      'worklet';
      scrollY.value = event.contentOffset.y;
    },
  });

  const headerStyle = useAnimatedStyle(() => ({
    height: interpolate(
      scrollY.value,
      [0, HEADER_MAX - HEADER_MIN],
      [HEADER_MAX, HEADER_MIN],
      Extrapolation.CLAMP,
    ),
  }));

  const titleStyle = useAnimatedStyle(() => ({
    opacity: interpolate(scrollY.value, [0, 60], [1, 0], Extrapolation.CLAMP),
    transform: [{
      scale: interpolate(scrollY.value, [0, 60], [1, 0.8], Extrapolation.CLAMP),
    }],
  }));

  return (
    <>
      
        Feed
      
      
        {/* Content */}
      
    
  );
}
```

### Bottom Sheet (Gorhom v5)

```tsx
import BottomSheet, { BottomSheetScrollView } from '@gorhom/bottom-sheet';
import { useMemo, useRef } from 'react';

function MapScreen() {
  const sheetRef = useRef(null);
  const snapPoints = useMemo(() => ['25%', '50%', '90%'], []);

  return (
    <>
      
      
        
          {/* Sheet content */}
        
      
    
  );
}
```

Gorhom v5 uses Reanimated v4 + Gesture Handler v2 natively. Velocity-based dismiss is built in.

### Swipe-to-Dismiss Card (Gesture Handler v2)

```tsx
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
  useSharedValue, useAnimatedStyle, withSpring, runOnJS,
} from 'react-native-reanimated';

function SwipeCard({ onDismiss }: { onDismiss: () => void }) {
  const translateX = useSharedValue(0);
  const DISMISS_THRESHOLD = 150;

  const pan = Gesture.Pan()
    .onUpdate((e) => {
      'worklet';
      translateX.value = e.translationX;
    })
    .onEnd((e) => {
      'worklet';
      if (Math.abs(e.translationX) > DISMISS_THRESHOLD || Math.abs(e.velocityX) > 800) {
        translateX.value = withSpring(e.translationX > 0 ? 500 : -500, { damping: 20 });
        runOnJS(onDismiss)();
      } else {
        translateX.value = withSpring(0, { damping: 20, stiffness: 300 });
      }
    });

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [
      { translateX: translateX.value },
      { rotate: `${translateX.value / 20}deg` },
    ],
    opacity: interpolate(Math.abs(translateX.value), [0, 200], [1, 0.5]),
  }));

  return (
    
      
        {/* Card content */}
      
    
  );
}
```

---

## List Performance — FlashList, Always

```tsx
import { FlashList } from '@shopify/flash-list';

 }
  estimatedItemSize={88}
  keyExtractor={(item) => item.id}
  getItemType={(item) => item.type}  // Critical for heterogeneous lists
/>
```

**Rules:**
- `React.memo` every list item (React Compiler now default in SDK 54 — handles this automatically for new projects)
- Pass primitives to items, not objects (`userId={user.id}` not `user={user}`)
- Stable callbacks — `useCallback` for `onPress` handlers passed to items
- `estimatedItemSize` is required — use average height across item types
- `getItemType` when items have different layouts — prevents recycling mismatches

**Performance comparison:**

| Component | 5000-item load | Scroll FPS | Memory |
|---|---|---|---|
| ScrollView | 1-3s freeze | 
```

Generate BlurHash on the server when images are uploaded. The hash is 20-30 characters — embed in API responses.

---

## Loading States — Skeleton, Never Spinner

```tsx
import { Skeleton } from 'moti/skeleton';
import { MotiView } from 'moti';

function FeedItemSkeleton() {
  return (
    
      
      
        
        
      
    
  );
}

// In the screen
function FeedScreen() {
  const { data, isLoading } = useQuery(feedQuery);

  if (isLoading) {
    return (
       }
        estimatedItemSize={72}
      />
    );
  }

  return ;
}
```

---

## Native Menus — Zeego (Not JS Overlays)

```tsx
import * as ContextMenu from 'zeego/context-menu';

  
    
  
  
     sharePost(post.id)}>
      Share
      
    
     deletePost(post.id)} destructive>
      Delete
      
    
  

```

Zeego renders native `UIMenu` on iOS and `PopupMenu` on Android. Never build a custom JS menu component.

---

## Navigation — Defer Heavy Work Past Transitions

```tsx
import { useFocusEffect } from 'expo-router';
import { InteractionManager } from 'react-native';

export default function FeedScreen() {
  useFocusEffect(
    useCallback(() => {
      const task = InteractionManager.runAfterInteractions(() => {
        fetchFeedData();
        initializeAnalytics();
      });
      return () => task.cancel();
    }, [])
  );
}
```

Without this, data fetching during a navigation slide-in competes for JS thread time and causes jank.

---

## Scroll — Never Track Position in State

```tsx
// WRONG — re-renders entire tree on every scroll pixel
const [scrollY, setScrollY] = useState(0);
 setScrollY(e.nativeEvent.contentOffset.y)} />

// CORRECT — stays on UI thread, zero re-renders
const scrollY = useSharedValue(0);
const handler = useAnimatedScrollHandler({
  onScroll: (event) => {
    'worklet';
    scrollY.value = event.contentOffset.y;
  },
});

```

---

## State Architecture

```
┌─────────────────────────────────────┐
│  Server State: TanStack Query       │  API data, cached + persisted to MMKV
│  (offline-first via MMKV persister) │
├─────────────────────────────────────┤
│  Client State: Zustand              │  Auth, settings, UI preferences
│  (persisted to MMKV)               │
├─────────────────────────────────────┤
│  UI State: Jotai atoms              │  Fine-grained: modals, filters, toggles
│  (ephemeral, not persisted)         │
├─────────────────────────────────────┤
│  Animation State: Reanimated        │  Shared values on UI thread
│  (never in React state)             │
└─────────────────────────────────────┘
```

**Zustand + MMKV persistence:**
```tsx
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { storage } from '@/lib/storage';

const mmkvStorage = createJSONStorage(() => ({
  getItem: (name) => storage.getString(name) ?? null,
  setItem: (name, value) => storage.set(name, value),
  removeItem: (name) => storage.delete(name),
}));

export const useAuthStore = create(
  persist(
    (set) => ({
      token: null as string | null,
      user: null as User | null,
      setAuth: (token: string, user: User) => set({ token, user }),
      clearAuth: () => set({ token: null, user: null }),
    }),
    { name: 'auth-storage', storage: mmkvStorage }
  )
);
```

---

## Native iOS/Swift — Quick Reference

### @Observable (iOS 17+) — NOT ObservableObject

```swift
@Observable
@MainActor
final class FeedViewModel {
    var posts: [Post] = []
    var isLoading = false

    func loadPosts() async {
        isLoading = true
        posts = try? await PostService.shared.fetchFeed() ?? []
        isLoading = false
    }
}

// View — no @StateObject, no @ObservedObject needed
struct FeedView: View {
    @State private var viewModel = FeedViewModel()
    var body: some View {
        List(viewModel.posts) { PostRow(post: $0) }
            .task { await viewModel.loadPosts() }
    }
}
```

### Navigation (matche

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Leo-Atienza](https://github.com/Leo-Atienza)
- **Source:** [Leo-Atienza/atlas-claude](https://github.com/Leo-Atienza/atlas-claude)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-leo-atienza-atlas-claude-app-l100
- Seller: https://agentstack.voostack.com/s/leo-atienza
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
