Install
$ agentstack add skill-leo-atienza-atlas-claude-app-l100 ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
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)
// 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
// 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)
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)
import { MotiView } from 'moti';
function ScreenContent() {
return (
{/* Screen content */}
);
}
Staggered List Entrance (Moti)
import { MotiView } from 'moti';
function StaggeredList({ items }: { items: Item[] }) {
return (
<>
{items.map((item, index) => (
))}
);
}
Collapsing Header (Reanimated + ScrollHandler)
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)
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)
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
import { FlashList } from '@shopify/flash-list';
}
estimatedItemSize={88}
keyExtractor={(item) => item.id}
getItemType={(item) => item.type} // Critical for heterogeneous lists
/>
Rules:
React.memoevery list item (React Compiler now default in SDK 54 — handles this automatically for new projects)- Pass primitives to items, not objects (
userId={user.id}notuser={user}) - Stable callbacks —
useCallbackforonPresshandlers passed to items estimatedItemSizeis required — use average height across item typesgetItemTypewhen 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)
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
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
// 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:
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
@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
- Source: Leo-Atienza/atlas-claude
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.