From 9fa551a2f9ee4317fd97eec01e6128841bc554de Mon Sep 17 00:00:00 2001 From: Kuba Date: Thu, 26 Mar 2026 09:34:58 +0100 Subject: [PATCH 1/3] Add React Native implementation skill with references and documentation --- .github/agents/tsh-software-engineer.agent.md | 4 + .../tsh-ensuring-accessibility/SKILL.md | 1 + .../skills/tsh-implementing-forms/SKILL.md | 1 + .../skills/tsh-implementing-frontend/SKILL.md | 1 + .../tsh-implementing-react-native/SKILL.md | 176 +++++++ .../references/react-native-navigation.md | 436 ++++++++++++++++ .../references/react-native-patterns.md | 465 ++++++++++++++++++ .../references/react-native-performance.md | 331 +++++++++++++ .github/skills/tsh-writing-hooks/SKILL.md | 1 + website/docs/agents/software-engineer.md | 7 + website/docs/skills/overview.md | 2 + .../skills/react-native-implementation.md | 104 ++++ 12 files changed, 1529 insertions(+) create mode 100644 .github/skills/tsh-implementing-react-native/SKILL.md create mode 100644 .github/skills/tsh-implementing-react-native/references/react-native-navigation.md create mode 100644 .github/skills/tsh-implementing-react-native/references/react-native-patterns.md create mode 100644 .github/skills/tsh-implementing-react-native/references/react-native-performance.md create mode 100644 website/docs/skills/react-native-implementation.md diff --git a/.github/agents/tsh-software-engineer.agent.md b/.github/agents/tsh-software-engineer.agent.md index 3577855a..ff4dc2b9 100644 --- a/.github/agents/tsh-software-engineer.agent.md +++ b/.github/agents/tsh-software-engineer.agent.md @@ -105,6 +105,10 @@ Pre-existing uncommitted changes in the working tree are intentional and OUTSIDE - when writing SQL queries, designing database schemas, creating migrations, implementing ORM-based data access, optimising query performance, or working with transactions and locking. Applies to PostgreSQL, MySQL, MariaDB, SQL Server, and Oracle. + +- for React Native mobile UI tasks: platform-specific components, native styling with design tokens, navigation, gestures, animations, and Figma-to-native workflow. + + - to follow TSH backend standards when building REST/GraphQL APIs, implementing CRUD endpoints, DataGrid filtering/pagination, database handling, authentication (JWT), external service adapters, testing strategies, logging, and Docker setup. Applies to Node.js, PHP, .NET, Java, and Go backends. diff --git a/.github/skills/tsh-ensuring-accessibility/SKILL.md b/.github/skills/tsh-ensuring-accessibility/SKILL.md index 9c50b74a..7f9b4f9f 100644 --- a/.github/skills/tsh-ensuring-accessibility/SKILL.md +++ b/.github/skills/tsh-ensuring-accessibility/SKILL.md @@ -218,6 +218,7 @@ Accessibility: ## Connected Skills - `tsh-implementing-frontend` — for component composition patterns that support accessible structure +- `tsh-implementing-react-native` — for React Native accessibility (VoiceOver, TalkBack, accessibilityRole, accessibilityLabel) - `tsh-implementing-forms` — for accessible form field patterns, labels, and error announcements - `tsh-reviewing-frontend` — for accessibility spot-checks during code review - `tsh-optimizing-frontend` — for performance optimizations that also impact accessibility (loading speed, interaction responsiveness) diff --git a/.github/skills/tsh-implementing-forms/SKILL.md b/.github/skills/tsh-implementing-forms/SKILL.md index 12350889..372f3494 100644 --- a/.github/skills/tsh-implementing-forms/SKILL.md +++ b/.github/skills/tsh-implementing-forms/SKILL.md @@ -147,6 +147,7 @@ Form: ## Connected Skills - `tsh-implementing-frontend` — for component composition patterns and framework-specific references (form library integration, validation library choice) +- `tsh-implementing-react-native` — for React Native form implementation (KeyboardAvoidingView, mobile input patterns) - `tsh-ensuring-accessibility` — for WCAG compliance in form fields, labels, and error announcements - `tsh-writing-hooks` — for custom form-related hooks/composables (useFormField, useMultiStepForm) - `tsh-reviewing-frontend` — for form-specific review criteria during code review diff --git a/.github/skills/tsh-implementing-frontend/SKILL.md b/.github/skills/tsh-implementing-frontend/SKILL.md index 9717956f..947b2861 100644 --- a/.github/skills/tsh-implementing-frontend/SKILL.md +++ b/.github/skills/tsh-implementing-frontend/SKILL.md @@ -159,3 +159,4 @@ The patterns above are framework-agnostic. For framework-specific implementation - `tsh-implementing-forms` — for form-specific component patterns and validation - `tsh-writing-hooks` — for custom hook patterns used within components - `tsh-reviewing-frontend` — for frontend-specific code review of implemented components +- `tsh-implementing-react-native` — for React Native mobile projects using similar component patterns diff --git a/.github/skills/tsh-implementing-react-native/SKILL.md b/.github/skills/tsh-implementing-react-native/SKILL.md new file mode 100644 index 00000000..6afce31f --- /dev/null +++ b/.github/skills/tsh-implementing-react-native/SKILL.md @@ -0,0 +1,176 @@ +--- +name: tsh-implementing-react-native +description: "React Native component patterns, platform-specific code, native styling with design tokens, navigation structure, gesture handling, and Figma-to-native workflow. Use when implementing mobile UI components, translating Figma designs into React Native code, managing component state in mobile apps, or integrating with a design system in React Native." +--- + +# Implementing React Native + +Provides patterns for building reusable, composable React Native components with design system integration, platform-aware code, and a structured Figma-to-native workflow. + + + + +Define what the UI should look like based on state, not how to manipulate native views. Describe the desired outcome through components and state declarations. Let React Native and the Fabric renderer handle view reconciliation. Compose complex screens from simple, predictable building blocks. + + + +Build complex screens by composing small, focused components rather than creating monolithic ones. Each component should have a single clear responsibility. Prefer children, slots, and compound component patterns over deep prop trees. Mobile screens have more layout constraints — leverage composition to handle platform variations without branching logic inside components. + + + +Always use design tokens (colors, spacing, typography, radii) from the project's design system. Never hardcode visual values. Map Figma specs to existing tokens. If no exact token match exists, find the closest and document the deviation — do not invent tokens without approval. In React Native this means referencing a shared theme object or token constants — never inline raw numbers or hex colors in `StyleSheet.create()`. + + + +Write shared code by default. Introduce platform-specific behavior only when the platforms genuinely differ (e.g., status bar handling, haptic feedback, platform-specific APIs). Use `Platform.select()` or `.ios.tsx` / `.android.tsx` file extensions for targeted divergence. If more than ~30% of a component is platform-specific, split into separate platform files rather than littering the component with conditionals. + + + +If design context, tokens, or specifications are missing or unclear, stop and ask the user. Do not proceed with assumptions about visual implementation. Missing information produces wrong UI — asking produces correct UI. + + + + +## Implementation Process + +Use the checklist below and track progress: + +``` +Progress: +- [ ] Step 1: Gather design context +- [ ] Step 2: Plan component structure +- [ ] Step 3: Implement components +- [ ] Step 4: Organize modules +- [ ] Step 5: Verify implementation +``` + +**Step 1: Gather design context** + +- Extract specs from Figma (via MCP tool if available). Identify components, spacing, typography, colors, hit areas, and interaction states. +- Map every Figma value to an existing design token in the codebase: + 1. Extract the raw value from Figma (e.g., `#3B82F6`, `16`). + 2. Search the codebase for a matching token (theme file, token constants, or a design system package). + 3. If a token exists — use it. + 4. If no exact match — find the closest existing token and document the deviation. + 5. If truly new — flag it and ask the user before creating. +- Identify all states the design implies: default, pressed, focused, disabled, loading, error, empty. +- Identify platform-specific design differences. Check if the design has separate iOS and Android frames. Note where platform conventions diverge (e.g., back navigation, status bar style, bottom sheet behavior). +- Check touch target sizes — minimum 44×44 points on iOS, 48×48 dp on Android (Material Design guideline). + +**Step 2: Plan component structure** + +- Decide component boundaries: what is a reusable component vs. screen-specific layout. +- Identify the props interface for each component — typed, with sensible defaults. +- Determine state needs using the State Decision Framework table below. +- Search the codebase for existing similar components. Extend or compose existing components rather than duplicating. +- Sketch the component tree: parent → children relationships, data flow direction, and where state lives. +- Identify which components need platform-specific rendering and decide the branching strategy (inline `Platform.select`, separate files, or a wrapper component). + +**Step 3: Implement components** + +Follow these patterns for every component: + +- **Composition**: Use composition patterns — content projection (`children`), render delegation, compound components — to keep components flexible. Avoid prop sprawl — if a component accepts more than ~7 props, it likely needs decomposition. +- **Typed props**: Define explicit TypeScript types for all props. Never use `any`. Co-locate types in `ComponentName.types.ts`. +- **Named exports only**: Use `export { ComponentName }` — no default exports. This ensures consistent imports and simplifies refactoring. +- **StyleSheet.create()**: Always use `StyleSheet.create()` for styles. It validates style properties at creation time and enables native-side optimizations. Never pass raw style objects repeatedly. +- **Three UI states**: Every data-dependent component must handle loading (skeleton or activity indicator), error (meaningful message + recovery action), and empty (helpful message when no data). +- **Error boundaries**: Wrap screen-level or feature-level components with an error boundary to catch JS rendering errors gracefully. Use `react-native-error-boundary` or a custom `ErrorBoundary` class component. For Expo projects, `expo-error-recovery` can restore state after fatal JS errors. Note: error boundaries do NOT catch errors in event handlers, async code, or native crashes — handle those separately. +- **Design tokens**: All visual values (colors, spacing, typography, shadows, radii) must come from the design system theme. Zero hardcoded values in style definitions. +- **Platform touch feedback**: Use `Pressable` as the standard touchable component. Configure `android_ripple` for Material ripple on Android and opacity/highlight feedback on iOS. Avoid deprecated `TouchableOpacity`, `TouchableHighlight`, `TouchableWithoutFeedback`. +- **Safe areas**: Use `SafeAreaView` or equivalent safe area hooks from `react-native-safe-area-context` for screens that render near device edges (notches, home indicators, status bars). + +For framework-specific patterns (React Native with Expo, React Navigation, Reanimated), load the appropriate reference from `./references/`. + +**Step 4: Organize modules** + +Apply barrel file rules from the Barrel File Guidelines table below: + +- Create `index.ts` barrel files at public API boundaries — folders whose exports are consumed by other modules. +- Use named re-exports only: `export { Button } from './Button'`. +- Skip barrels for internal utility folders that serve a single parent component. +- Verify the barrel doesn't re-export unused internals. In React Native projects, unused re-exports still increase the JS bundle parsed by Hermes. + +**Step 5: Verify implementation** + +- If a calling workflow provides a verification loop (e.g., the Engineering Manager runs `tsh-ui-reviewer` automatically during `/tsh-implement`), defer to that workflow — do not duplicate verification here. +- If no verification workflow is active, use the `tsh-ui-verifying` skill directly to compare the implementation against the Figma design. +- Walk through each interaction state (pressed, focused, disabled, error, loading, empty) and verify correctness on both platforms. +- Test on both iOS and Android — styles that look correct on one platform may differ on the other (shadows, elevation, font rendering, status bar overlaps). +- Verify touch targets meet minimum size requirements (44×44 pt iOS / 48×48 dp Android). +- Check with platform-specific accessibility tools (VoiceOver on iOS, TalkBack on Android). + +## State Decision Framework + +| State type | When to use | Example | +| ------------ | --------------------------- | ------------------------------------------- | +| Local state | UI-only, single component | Modal visibility, input value, toggle state | +| Lifted state | Shared between 2-3 siblings | Filter applied to a sibling list | +| Context / DI | Deeply nested consumption | Theme, locale, auth status | +| Global store | Complex cross-cutting state | Multi-step form wizard, shopping cart | +| Server cache | Remote data with caching | API responses, paginated lists | + +## Barrel File Guidelines + +| Rule | Description | +| ------------------ | ------------------------------------------------------------------ | +| Create barrel when | Folder exports are consumed by OTHER modules (public API boundary) | +| Avoid barrel when | Internal utils serving a single parent component — import directly | +| Re-export style | Named re-exports only: `export { Button } from './Button'` | +| Never wildcard | Avoid `export * from` — breaks tree shaking, hides API surface | +| Keep flat | One level deep — no barrel importing another barrel | +| Test with build | Verify barrel doesn't pull unused code into bundle | + +## Component Checklist + +``` +Component: +- [ ] Single responsibility — one clear purpose +- [ ] Typed props — explicit types, sensible defaults +- [ ] Named export — no default exports +- [ ] Design tokens — no hardcoded visual values +- [ ] Error state — handles failure gracefully +- [ ] Loading state — shows skeleton or activity indicator +- [ ] Empty state — meaningful message when no data +- [ ] Composition — uses children/slots, not prop sprawl +- [ ] StyleSheet.create() — no inline style objects +- [ ] Pressable — uses Pressable, not deprecated Touchable* +- [ ] Safe areas — respects notches and system bars +- [ ] Touch targets — minimum 44×44 pt (iOS) / 48×48 dp (Android) +- [ ] Platform tested — verified on both iOS and Android +``` + +## Anti-Patterns + +| Anti-Pattern | Instead Do | +| ------------------------------------------------ | ----------------------------------------------------------------------------------- | +| Hardcoded colors/spacing (`#3B82F6`, `16`) | Use design tokens (`theme.colors.primary500`, `theme.spacing.md`) | +| Monolithic screen component (300+ lines) | Split into composed sub-components | +| Props drilling through 4+ levels | Use context or composition pattern | +| Duplicating existing component | Extend existing with variants | +| Inline style objects (`style={{ padding: 16 }}`) | Use `StyleSheet.create()` and reference by key | +| `export default` | Named exports for consistency and refactoring | +| `any` type for props | Explicit type definitions | +| Barrel file for internal utils | Direct imports for single-consumer folders | +| `TouchableOpacity` / `TouchableHighlight` | Use `Pressable` with platform-appropriate feedback | +| `Platform.OS === 'ios' ? ... : ...` everywhere | Use `Platform.select()` or platform-specific file extensions for cleaner separation | +| Raw `View` as touchable with `onPress` | Use `Pressable` — raw `View` has no accessibility role or touch feedback | +| Percentage-based dimensions for everything | Use flex layout; reserve percentages for specific layout needs | + +## Framework-Specific Patterns + +The patterns above are framework-agnostic. All projects use Expo — load the references below for Expo-specific tooling and library details: + +- **Expo + React Native Core**: See `./references/react-native-patterns.md` — Expo modules, New Architecture, styling patterns, platform-specific code, gestures, animations, keyboard handling. +- **Navigation (Expo Router)**: See `./references/react-native-navigation.md` — Expo Router file-based routing (default), React Navigation patterns (legacy projects), deep linking, screen organization. +- **Performance**: See `./references/react-native-performance.md` — FlashList/FlatList optimization, Hermes, memory management, startup time, bundle analysis. + +## Connected Skills + +- `tsh-ui-verifying` — for verifying implementation against Figma designs +- `tsh-technical-context-discovering` — for understanding project conventions before implementing +- `tsh-ensuring-accessibility` — to ensure components meet accessibility standards on both platforms +- `tsh-optimizing-frontend` — for general performance considerations (memoization, code splitting concepts apply) +- `tsh-implementing-forms` — for form-specific component patterns and validation (schema-first validation applies in RN) +- `tsh-writing-hooks` — for custom hook patterns used within components +- `tsh-reviewing-frontend` — for code review of implemented components diff --git a/.github/skills/tsh-implementing-react-native/references/react-native-navigation.md b/.github/skills/tsh-implementing-react-native/references/react-native-navigation.md new file mode 100644 index 00000000..ee957de9 --- /dev/null +++ b/.github/skills/tsh-implementing-react-native/references/react-native-navigation.md @@ -0,0 +1,436 @@ +# React Native Navigation Patterns + +Navigation-specific patterns for the `tsh-implementing-react-native` skill. Load this reference when implementing screen navigation, deep linking, or route structure in React Native. + +## Table of Contents + +- [Navigation Libraries](#navigation-libraries) +- [Screen Organization](#screen-organization) +- [React Navigation Patterns](#react-navigation-patterns) +- [Expo Router Patterns](#expo-router-patterns) +- [Deep Linking](#deep-linking) +- [Navigation State and TypeScript](#navigation-state-and-typescript) +- [Anti-Patterns](#anti-patterns) + +## Navigation Libraries + +**Expo Router is the standard** for all Expo projects. It provides file-based routing (like Next.js), automatic deep linking, typed routes, and web parity with zero configuration. + +| Library | Routing Model | When to use | +| -------------------- | ------------- | ---------------------------------------------------------- | +| Expo Router 4+ | File-based | **Default** — all new Expo projects | +| React Navigation 7+ | Imperative | Legacy projects already using it (do not migrate mid-project) | + +Expo Router is built on top of React Navigation, so all React Navigation primitives (native-stack, tabs, drawers) work under the hood. The patterns below for React Navigation are included for projects that already use it — for new code, always use Expo Router. + +Do not mix both in the same project. + +> **React Navigation 7 note**: React Navigation 7 introduced a **static API** alongside the existing dynamic API. The static API uses `createNativeStackNavigator({ screens: { ... } })` with screen configuration objects instead of JSX `` elements. Both APIs are supported — match the project's existing pattern. The examples in this reference use the dynamic (JSX) API as it's more widely adopted. If the project uses the static API, adapt accordingly. + +## Screen Organization + +### File-based routing (Expo Router) + +``` +app/ +├── _layout.tsx # Root layout (providers, fonts, splash) +├── index.tsx # Home / entry screen +├── (auth)/ # Auth route group (not shown in URL) +│ ├── _layout.tsx # Auth stack layout +│ ├── sign-in.tsx +│ └── sign-up.tsx +├── (tabs)/ # Tab group +│ ├── _layout.tsx # Tab bar configuration +│ ├── home.tsx +│ ├── search.tsx +│ └── profile.tsx +├── settings/ +│ ├── index.tsx # /settings +│ ├── notifications.tsx # /settings/notifications +│ └── [id].tsx # /settings/:id (dynamic segment) +└── +not-found.tsx # 404 catch-all +``` + +### Imperative routing (React Navigation) + +``` +src/ +├── navigation/ +│ ├── RootNavigator.tsx # Entry: auth check → AuthStack or MainTabs +│ ├── AuthStack.tsx # Sign in, sign up, forgot password +│ ├── MainTabs.tsx # Bottom tab navigator +│ ├── HomeStack.tsx # Stack within the Home tab +│ ├── ProfileStack.tsx # Stack within the Profile tab +│ └── types.ts # All route param types +├── screens/ +│ ├── auth/ +│ │ ├── SignInScreen.tsx +│ │ └── SignUpScreen.tsx +│ ├── home/ +│ │ ├── HomeScreen.tsx +│ │ └── DetailScreen.tsx +│ └── profile/ +│ ├── ProfileScreen.tsx +│ └── SettingsScreen.tsx +``` + +## React Navigation Patterns + +### Navigator composition + +React Navigation uses a nested navigator model. Common structure: + +```typescript +// RootNavigator.tsx +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const Stack = createNativeStackNavigator(); + +const RootNavigator = () => ( + + + {isAuthenticated ? ( + + ) : ( + + )} + {/* Modal screens accessible from anywhere */} + + + + + +); +``` + +### Native stack + +Always use `@react-navigation/native-stack` (not `@react-navigation/stack`). Native stack uses platform-native navigation transitions (UINavigationController on iOS, Fragment on Android) for better performance and native feel. + +### Typed navigation + +Define param types for all routes and pass them as generics to navigators and hooks: + +```typescript +// navigation/types.ts +type RootStackParamList = { + Main: undefined; + Auth: undefined; + CreatePost: undefined; +}; + +type HomeStackParamList = { + Home: undefined; + Detail: { id: string; title: string }; + Search: { query?: string }; +}; + +// In screens — typed navigation hook +import { useNavigation } from '@react-navigation/native'; +import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; + +type HomeScreenNavProp = NativeStackNavigationProp; + +const HomeScreen = () => { + const navigation = useNavigation(); + + const handlePress = (item: Item) => { + navigation.navigate('Detail', { id: item.id, title: item.title }); + }; +}; +``` + +### Screen options + +Configure headers and tabs declaratively via `screenOptions`: + +```typescript +const MainTabs = () => ( + ({ + tabBarIcon: ({ focused, color, size }) => { + const icon = tabIcons[route.name]; + return ; + }, + tabBarActiveTintColor: theme.colors.primary500, + tabBarInactiveTintColor: theme.colors.textSecondary, + headerShown: false, + })} + > + + + + +); +``` + +## Expo Router Patterns + +### Layouts + +Layouts define the navigation structure for a route group: + +```typescript +// app/(tabs)/_layout.tsx +import { Tabs } from 'expo-router'; + +const TabLayout = () => ( + + , + }} + /> + , + }} + /> + +); + +export default TabLayout; +``` + +Note: Expo Router layout files use `export default` — this is the one exception to the "named exports only" rule, as the file-based router requires default exports. + +### Typed routes + +Expo Router generates route types from the file structure automatically. Enable typed routes in `tsconfig.json`: + +```json +{ + "compilerOptions": { + "strict": true + }, + "include": [".expo/types/**/*.ts", "**/*.ts", "**/*.tsx"] +} +``` + +This gives you compile-time route validation — `router.push()` and `` only accept valid routes: + +```typescript +import { useRouter, useLocalSearchParams, Link } from 'expo-router'; + +const DetailScreen = () => { + const { id } = useLocalSearchParams<{ id: string }>(); + const router = useRouter(); + + const handleBack = () => router.back(); + // Typed — TypeScript errors if route doesn't exist + const handleNavigate = () => router.push('/settings/notifications'); + + // In JSX — href is also type-checked + return Go; +}; +``` + +### Route groups + +Group routes with parentheses `(groupName)` to organize without affecting the URL: + +- `(auth)/` — Authentication screens grouped together, using a stack layout +- `(tabs)/` — Tab-based screens grouped together, using a tab layout +- `(settings)/` — Settings-related screens grouped, sharing a common header + +### Modal routes + +Present screens as modals by configuring the layout: + +```typescript +// app/_layout.tsx + + + + +``` + +## Deep Linking + +### Expo Router + +Deep linking is automatic. The file path IS the URL. A file at `app/settings/[id].tsx` responds to `myapp://settings/123` with zero configuration. + +Configure the URL scheme in `app.json`: + +```json +{ + "expo": { + "scheme": "myapp" + } +} +``` + +For universal links (HTTPS links that open the app), configure `intentFilters` (Android) and `associatedDomains` (iOS) in `app.json`. + +### React Navigation + +Configure linking in the `NavigationContainer`: + +```typescript +const linking: LinkingOptions = { + prefixes: ['myapp://', 'https://myapp.com'], + config: { + screens: { + Main: { + screens: { + Home: 'home', + Detail: 'detail/:id', + }, + }, + Auth: { + screens: { + SignIn: 'sign-in', + }, + }, + }, + }, +}; + + + {/* ... */} + +``` + +### Deep link testing + +Test deep links during development: + +```bash +# iOS Simulator +npx uri-scheme open "myapp://detail/123" --ios + +# Android Emulator +adb shell am start -a android.intent.action.VIEW -d "myapp://detail/123" + +# Expo Go +npx uri-scheme open "exp://127.0.0.1:8081/--/detail/123" +``` + +## Navigation State and TypeScript + +### Navigation-aware data loading + +Load screen data in the screen component, triggered by focus: + +```typescript +import { useFocusEffect } from '@react-navigation/native'; // or expo-router +import { useCallback } from 'react'; + +const ProfileScreen = () => { + const { data, refetch } = useProfile(); + + // Refetch when screen comes into focus (e.g., returning from edit screen) + useFocusEffect( + useCallback(() => { + refetch(); + }, [refetch]) + ); +}; +``` + +### Preventing navigation with unsaved changes + +```typescript +import { usePreventRemove } from '@react-navigation/native'; + +const EditScreen = () => { + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + + usePreventRemove(hasUnsavedChanges, ({ data }) => { + Alert.alert( + 'Discard changes?', + 'You have unsaved changes. Are you sure you want to leave?', + [ + { text: 'Stay', style: 'cancel' }, + { text: 'Discard', style: 'destructive', onPress: () => data.action() }, + ] + ); + }); +}; +``` + +### Screen tracking / analytics + +Centralize screen tracking at the navigator level: + +```typescript +// React Navigation + { + const currentRoute = getActiveRouteName(state); + analytics.trackScreen(currentRoute); + }} +> +``` + +```typescript +// Expo Router — use the pathname hook +import { usePathname } from 'expo-router'; + +// In root layout +const pathname = usePathname(); +useEffect(() => { + analytics.trackScreen(pathname); +}, [pathname]); +``` + +## Anti-Patterns + +| Anti-Pattern | Why | Fix | +| ----------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------- | +| `@react-navigation/stack` (JS-based stack) | JS-driven transitions, slower than native | Use `@react-navigation/native-stack` | +| Untyped `navigation.navigate('Screen')` | No param validation, runtime crashes | Define `ParamList` types, use typed hooks | +| Nesting 4+ navigators deep | Complex state, hard to reason about, slow transitions | Flatten: use groups and modal presentations | +| Fetching data in `useEffect` without focus check | Stale data when returning to screen | Use `useFocusEffect` for screen data that can go stale | +| Passing large objects as route params | Serialized into navigation state, memory overhead | Pass IDs only, fetch data in the destination screen | +| `navigation.reset()` for logout | Leaves screens in memory, animations break | Conditional rendering in root navigator based on auth | +| Mixing Expo Router and React Navigation | Conflicting navigation states, unpredictable behavior | Pick one and use it exclusively | +| Default export for non-layout components | Inconsistent imports, harder refactoring | Named exports (except Expo Router `_layout.tsx` files) | +| Hardcoded screen names as strings everywhere | Typo-prone, no refactoring support | Use route name constants or typed navigation | +| Custom modal via `View` overlay | No native transition, no gesture dismissal, a11y gaps | Use `presentation: 'modal'` in stack or `@gorhom/bottom-sheet` | + +## Bottom Sheet Pattern + +`@gorhom/bottom-sheet` is the standard library for bottom sheet navigation in React Native. It integrates with Reanimated and Gesture Handler for native-quality 60 fps interactions. + +```typescript +import BottomSheet from '@gorhom/bottom-sheet'; +import { useCallback, useMemo, useRef } from 'react'; + +const FilterSheet = () => { + const bottomSheetRef = useRef(null); + const snapPoints = useMemo(() => ['25%', '50%', '90%'], []); + + const handleOpen = useCallback(() => { + bottomSheetRef.current?.expand(); + }, []); + + return ( + + {/* Sheet content */} + + ); +}; +``` + +Key rules: +- Use `@gorhom/bottom-sheet` — not custom `Animated.View` overlays. It handles gesture competition, keyboard avoidance, and accessibility. +- Define `snapPoints` with `useMemo` to avoid re-creating the array on every render. +- Use `index={-1}` to start the sheet closed; control it programmatically via the ref. +- For sheets containing scrollable content, use `BottomSheetScrollView` or `BottomSheetFlatList` instead of standard `ScrollView` / `FlatList` to avoid gesture conflicts. diff --git a/.github/skills/tsh-implementing-react-native/references/react-native-patterns.md b/.github/skills/tsh-implementing-react-native/references/react-native-patterns.md new file mode 100644 index 00000000..769cef76 --- /dev/null +++ b/.github/skills/tsh-implementing-react-native/references/react-native-patterns.md @@ -0,0 +1,465 @@ +# React Native Implementation Patterns + +React Native-specific patterns for the `tsh-implementing-react-native` skill. Load this reference when working with React Native in Expo projects. + +## Table of Contents + +- [Project Structure](#project-structure) +- [Component Composition](#component-composition) +- [Styling Patterns](#styling-patterns) +- [Platform-Specific Code](#platform-specific-code) +- [Pressable and Touch Feedback](#pressable-and-touch-feedback) +- [Safe Areas and Layout](#safe-areas-and-layout) +- [Gestures and Animations](#gestures-and-animations) +- [New Architecture and Expo Modules](#new-architecture-and-expo-modules) +- [Anti-Patterns](#anti-patterns) + +## Project Structure + +All projects use Expo as the standard framework. Follow the project's existing structure. If starting fresh, use this layout: + +``` +src/ +├── app/ # Expo Router file-based routes (or screens/ for React Navigation) +├── components/ +│ ├── ui/ # Reusable design system primitives (Button, Text, Card) +│ └── features/ # Feature-specific composed components +├── hooks/ # Custom hooks +├── services/ # API clients, storage, analytics +├── stores/ # Global state (Zustand, Jotai, etc.) +├── theme/ # Design tokens, theme provider +│ ├── tokens.ts # Colors, spacing, typography, radii +│ └── index.ts # Theme provider and hooks +├── types/ # Shared TypeScript types +└── utils/ # Pure utility functions +``` + +Key conventions: +- Co-locate component types: `Button.types.ts` next to `Button.tsx`. +- Co-locate component styles: `Button.styles.ts` next to `Button.tsx` (for complex components), or inline `StyleSheet.create()` at the bottom of the component file (for simpler components). +- One component per file. The filename matches the component name. + +## Component Composition + +React Native implements the generic composition patterns as follows: + +| Generic Pattern | React Native Implementation | +| ------------------- | --------------------------------------------------------------------------------------- | +| Content projection | `children` prop — renders child components inside a container `View` | +| Render delegation | Render props: `renderItem={(item) => }` | +| Compound components | Shared context between parent and sub-components (`Select` + `Select.Option`) | +| Slots | Named props accepting `ReactNode`: `leftIcon`, `rightIcon`, `header`, `footer` | + +Prefer `children` for single content projection. Use named `ReactNode` props (slots) when multiple projection points are needed — this is very common in RN for list headers, footers, empty states, and icon slots. Use compound components for tightly coupled UI groups (tabs, accordion, select). + +### Text must always be in `` + +In React Native, all text content MUST be wrapped in a `` component. Unlike web, you cannot place raw strings inside ``. This is the most common mistake for web developers transitioning to RN. + +```typescript +// WRONG — crashes at runtime +Hello world + +// CORRECT +Hello world +``` + +This applies to conditional text too — `{count > 0 && {count}}` not `{count > 0 && count}`. + +### Typed component pattern + +```typescript +import { type ReactNode } from 'react'; +import { View, StyleSheet } from 'react-native'; + +type CardProps = { + children: ReactNode; + header?: ReactNode; + footer?: ReactNode; + variant?: 'elevated' | 'outlined' | 'filled'; +}; + +const Card = ({ children, header, footer, variant = 'elevated' }: CardProps) => ( + + {header && {header}} + {children} + {footer && {footer}} + +); + +export { Card }; +export type { CardProps }; +``` + +## Styling Patterns + +### StyleSheet.create() + +Always use `StyleSheet.create()`. It validates style keys at creation time and enables optimizations on the native side. + +```typescript +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: theme.spacing.md, + backgroundColor: theme.colors.background, + }, + title: { + ...theme.typography.heading2, + color: theme.colors.textPrimary, + }, +}); +``` + +### Theme and design tokens + +Centralize tokens in a theme object. Access them via a theme hook or direct import — match the project's existing pattern. + +```typescript +// theme/tokens.ts +const tokens = { + colors: { + primary500: '#3B82F6', + primary600: '#2563EB', + background: '#FFFFFF', + surface: '#F8FAFC', + textPrimary: '#0F172A', + textSecondary: '#64748B', + error: '#EF4444', + success: '#22C55E', + }, + spacing: { + xs: 4, + sm: 8, + md: 16, + lg: 24, + xl: 32, + xxl: 48, + }, + radii: { + sm: 4, + md: 8, + lg: 16, + full: 9999, + }, + typography: { + heading1: { fontSize: 32, lineHeight: 40, fontWeight: '700' as const }, + heading2: { fontSize: 24, lineHeight: 32, fontWeight: '600' as const }, + body: { fontSize: 16, lineHeight: 24, fontWeight: '400' as const }, + caption: { fontSize: 12, lineHeight: 16, fontWeight: '400' as const }, + }, +} as const; +``` + +### Dynamic / conditional styles + +Combine static styles with dynamic values using style arrays. Keep the static base in `StyleSheet.create()` and overlay dynamic parts: + +```typescript + +``` + +For variant-based styling, map variants to pre-created stylesheet entries rather than computing styles inline: + +```typescript +const variantStyles = StyleSheet.create({ + elevated: { elevation: 4, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4 }, + outlined: { borderWidth: 1, borderColor: theme.colors.border }, + filled: { backgroundColor: theme.colors.surface }, +}); + +// Usage — compose base + variant + +``` + +### Responsive sizing + +React Native uses density-independent pixels. Avoid percentage-based layouts as default — prefer Flexbox: + +| Need | Approach | +| --------------------------------- | ------------------------------------------------------------ | +| Fill available space | `flex: 1` | +| Distribute children evenly | `flex: 1` on each child inside a flex container | +| Responsive columns | `flexDirection: 'row'`, `flexWrap: 'wrap'`, fixed item width | +| Screen-relative sizing (rare) | `useWindowDimensions()` hook | +| Tablet / foldable layout adaption | `useWindowDimensions()` + breakpoint constants | + +## Platform-Specific Code + +### Platform.select() + +For small, value-level differences: + +```typescript +import { Platform, StyleSheet } from 'react-native'; + +const styles = StyleSheet.create({ + shadow: Platform.select({ + ios: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + }, + android: { + elevation: 4, + }, + default: {}, + }), +}); +``` + +### Platform file extensions + +For larger divergence (different component trees, different native APIs), use file extensions: + +``` +StatusBarManager.tsx # Shared logic, if any +StatusBarManager.ios.tsx # iOS-specific implementation +StatusBarManager.android.tsx # Android-specific implementation +``` + +Metro bundler automatically resolves the correct file. Both files must export the same public API (same named exports and types). + +### Decision guide + +| Divergence level | Strategy | +| -------------------------------------- | ------------------------------------- | +| Single value differs (shadow, spacing) | `Platform.select()` inline | +| A few lines differ | `Platform.OS === 'ios'` conditional | +| 30%+ of component differs | Separate `.ios.tsx` / `.android.tsx` | +| Entirely different native API | Separate files + shared types/hook | + +## Pressable and Touch Feedback + +`Pressable` is the standard touchable primitive. It replaces the deprecated `TouchableOpacity`, `TouchableHighlight`, `TouchableNativeFeedback`, and `TouchableWithoutFeedback`. + +```typescript +import { Pressable, StyleSheet } from 'react-native'; + +const ActionButton = ({ onPress, label, disabled }: ActionButtonProps) => ( + [ + styles.button, + pressed && styles.buttonPressed, + disabled && styles.buttonDisabled, + ]} + accessibilityRole="button" + accessibilityLabel={label} + accessibilityState={{ disabled }} + > + {label} + +); + +const styles = StyleSheet.create({ + button: { + minHeight: 48, + minWidth: 48, + paddingHorizontal: theme.spacing.lg, + paddingVertical: theme.spacing.md, + borderRadius: theme.radii.md, + backgroundColor: theme.colors.primary500, + alignItems: 'center', + justifyContent: 'center', + }, + buttonPressed: { + backgroundColor: theme.colors.primary600, + }, + buttonDisabled: { + opacity: 0.5, + }, + buttonText: { + ...theme.typography.body, + color: theme.colors.onPrimary, + fontWeight: '600', + }, +}); +``` + +Key rules: +- Always set `minHeight` and `minWidth` to meet touch target minimums (44 pt iOS / 48 dp Android). +- Always provide `accessibilityRole` and `accessibilityLabel` on pressable elements. +- Use `android_ripple` for Material Design feedback on Android. +- Use the `pressed` state from the style function for iOS feedback (opacity change, background change). + +## Safe Areas and Layout + +Use `react-native-safe-area-context` (included in Expo) for safe area management: + +```typescript +import { SafeAreaView } from 'react-native-safe-area-context'; + +// Screen-level wrapper — pads content away from notches, status bar, home indicator +const SettingsScreen = () => ( + + {/* Screen content */} + +); +``` + +- Use `SafeAreaView` at the screen level, not on every component. +- Specify `edges` explicitly when you only need specific safe areas (e.g., `['top']` on screens with a bottom tab bar that already handles the bottom inset). +- For finer-grained control, use the `useSafeAreaInsets()` hook to read inset values and apply them as padding to specific views. +- Do NOT wrap `FlatList` / `ScrollView` in `SafeAreaView` — use `contentContainerStyle` padding with inset values instead, to avoid double-padding issues. + +### Keyboard avoidance + +Use `react-native-keyboard-controller` (included in Expo) for keyboard handling. It provides Reanimated-powered smooth animations and works reliably on both platforms — unlike the built-in `KeyboardAvoidingView` which has known inconsistencies on Android. + +```typescript +import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'; + +const FormScreen = () => ( + + {/* Form fields — automatically scroll into view when keyboard appears */} + +); +``` + +For fine-grained control, use the `useKeyboardHandler` hook to react to keyboard events with worklet callbacks: + +```typescript +import { useKeyboardHandler } from 'react-native-keyboard-controller'; +import { useSharedValue } from 'react-native-reanimated'; + +const height = useSharedValue(0); + +useKeyboardHandler({ + onMove: (event) => { + 'worklet'; + height.value = event.height; + }, +}); +``` + +> **Fallback**: If `react-native-keyboard-controller` cannot be used, the built-in `KeyboardAvoidingView` with `behavior={Platform.OS === 'ios' ? 'padding' : 'height'}` works as a basic alternative. + +## Gestures and Animations + +### React Native Gesture Handler + +Use `react-native-gesture-handler` for all gesture recognition. It runs gestures on the native thread, avoiding JS thread bottlenecks. + +```typescript +import { Gesture, GestureDetector } from 'react-native-gesture-handler'; + +const panGesture = Gesture.Pan() + .onUpdate((event) => { + // Runs on UI thread with Reanimated worklets + translateX.value = event.translationX; + }) + .onEnd(() => { + translateX.value = withSpring(0); + }); + + + + +``` + +### React Native Reanimated + +Use `react-native-reanimated` for animations. It runs animations on the UI thread via worklets, keeping 60/120 fps even when the JS thread is busy. + +```typescript +import Animated, { + useSharedValue, + useAnimatedStyle, + withSpring, + withTiming, +} from 'react-native-reanimated'; + +const opacity = useSharedValue(0); + +// Trigger animation +opacity.value = withTiming(1, { duration: 300 }); + +// Derive animated style +const animatedStyle = useAnimatedStyle(() => ({ + opacity: opacity.value, +})); + + +``` + +Key patterns: +- Use `useSharedValue` instead of `useRef` or `useState` for animated values. +- Use `useAnimatedStyle` to derive styles from shared values — runs on the UI thread. +- Use `withSpring`, `withTiming`, `withDecay` for animation drivers. +- Use `runOnJS` to call JS thread functions from worklets (e.g., updating React state after an animation completes). +- Never read `sharedValue.value` in the component render body — only in worklets or event handlers. + +### Layout animations + +Reanimated provides `entering` and `exiting` props for declarative mount/unmount animations: + +```typescript +import Animated, { FadeIn, FadeOut, SlideInRight } from 'react-native-reanimated'; + +{isVisible && ( + + + +)} +``` + +## New Architecture and Expo Modules + +React Native New Architecture (Fabric + TurboModules) is the default since RN 0.76+. Key implications: + +- **Fabric renderer**: Synchronous and concurrent-capable rendering. Layout is computed synchronously when needed, eliminating layout "flashes" from async bridge communication. +- **TurboModules**: Native modules are lazily loaded and use JSI (JavaScript Interface) for synchronous, typesafe communication — no serialized JSON bridge. +- **Codegen**: TypeScript specs generate native interfaces. When writing native modules, define types in TypeScript and let Codegen produce the native bindings. + +For Expo developers, the New Architecture is transparent — it's enabled by default since Expo SDK 52. Be aware of: + +- Third-party libraries must support New Architecture. Check compatibility before adding dependencies. Expo provides a [directory](https://reactnative.directory/) with compatibility indicators. +- If a library doesn't support New Architecture, the interop layer handles most cases automatically, but performance-sensitive native modules may need updates. + +### Expo modules + +When native functionality is needed, prefer Expo modules over community packages when available: + +| Need | Expo Module | Notes | +| ------------------ | --------------------------- | ------------------------------------------ | +| Camera | `expo-camera` | | +| File system | `expo-file-system` | | +| Image picker | `expo-image-picker` | | +| Image display | `expo-image` | Replaces `Image` from RN core | +| Haptics | `expo-haptics` | | +| Secure storage | `expo-secure-store` | | +| Local auth (bio) | `expo-local-authentication` | | +| Notifications | `expo-notifications` | | +| Location | `expo-location` | | +| Video | `expo-video` | | +| Audio | `expo-audio` | Replaces deprecated `expo-av` for audio | +| Splash screen | `expo-splash-screen` | | +| Fonts | `expo-font` | Load custom fonts at startup | +| Constants | `expo-constants` | App config, device info | +| OTA updates | `expo-updates` | Over-the-air JS bundle updates | +| Linking / URLs | `expo-linking` | Deep link handling, URL opening | +| In-app browser | `expo-web-browser` | Open URLs in system browser / auth flows | +| Navigation | `expo-router` | File-based routing (see navigation ref) | + +Expo modules use the New Architecture natively and are maintained as part of the Expo SDK. Always prefer an Expo module over a community alternative when one exists — it guarantees compatibility with the current SDK and New Architecture. + +## Anti-Patterns + +| Anti-Pattern | Why | Fix | +| ----------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------------------------- | +| Inline style objects `style={{ padding: 16 }}` | New object every render, skips native optimizations | Use `StyleSheet.create()` | +| `TouchableOpacity` / `TouchableHighlight` | Deprecated, inconsistent cross-platform | Use `Pressable` | +| Animated API from `react-native` core | Runs on JS thread, drops frames under load | Use `react-native-reanimated` | +| `PanResponder` for gestures | JS thread gesture handling, laggy | Use `react-native-gesture-handler` | +| `ScrollView` for long dynamic lists | Renders all items, memory explosion | Use `FlatList` or `FlashList` | +| `useEffect` + `Animated.timing` for animations | Extra render cycle, JS thread animation | `useSharedValue` + `withTiming` from Reanimated | +| Reading `sharedValue.value` during render | Breaks UI thread isolation, stale values | Read only in `useAnimatedStyle` worklets or event handlers | +| Wrapping `FlatList` in `SafeAreaView` | Double padding, content hidden below safe area | Use `contentContainerStyle` with `useSafeAreaInsets()` padding | +| `Platform.OS === 'ios' ? ... : ...` in 10+ places | Scattered conditionals, hard to maintain | Use `Platform.select()`, or split into platform-specific files | +| Hardcoded pixel values | No design consistency, hard to update | Reference theme tokens | +| `setTimeout` for animation sequencing | Unreliable timing, can't be cancelled properly | Use `withDelay`, `withSequence` from Reanimated | +| Raw string outside `` | Runtime crash — RN requires all text in `` | Always wrap text content in `` components | diff --git a/.github/skills/tsh-implementing-react-native/references/react-native-performance.md b/.github/skills/tsh-implementing-react-native/references/react-native-performance.md new file mode 100644 index 00000000..8ae0ba8e --- /dev/null +++ b/.github/skills/tsh-implementing-react-native/references/react-native-performance.md @@ -0,0 +1,331 @@ +# React Native Performance Patterns + +Performance-specific patterns for the `tsh-implementing-react-native` skill. Load this reference when optimizing React Native app performance — list rendering, startup time, memory, animations, and bundle size. + +## Table of Contents + +- [Measure Before Optimizing](#measure-before-optimizing) +- [List Rendering](#list-rendering) +- [Image Optimization](#image-optimization) +- [Startup Time](#startup-time) +- [JS Bundle Optimization](#js-bundle-optimization) +- [Memoization Patterns](#memoization-patterns) +- [Memory Management](#memory-management) +- [Hermes Engine](#hermes-engine) +- [Performance Checklist](#performance-checklist) +- [Anti-Patterns](#anti-patterns) + +## Measure Before Optimizing + +Never optimize without data. Profile first: + +| Tool | What it measures | Platform | +| ------------------------------ | ------------------------------------------ | -------- | +| React DevTools Profiler | Component render times, re-render reasons | Both | +| React Native DevTools | JS/UI thread frame rates, component tree | Both | +| Expo Dev Tools | Performance monitoring, network inspector | Both | +| Xcode Instruments | CPU, memory, energy, UI hangs | iOS | +| Android Studio Profiler | CPU, memory, network, energy | Android | +| `react-native-performance` | TTI, component render marks | Both | +| `performance.mark()/.measure()`| Custom operation timing in Hermes | Both | +| React Native DevMenu → Perf Monitor | Real-time JS/UI frame rate overlay | Both | + +Enable the Performance Monitor overlay (shake → "Perf Monitor") as a first check. Both JS and UI threads should consistently hit 60 fps (or 120 fps on ProMotion/high-refresh devices). + +## List Rendering + +Long lists are the most common performance bottleneck in React Native apps. + +### FlashList (recommended) + +`@shopify/flash-list` is the recommended replacement for `FlatList`. It recycles views (like native `RecyclerView` / `UICollectionView`) instead of unmounting and remounting them. + +```typescript +import { FlashList } from '@shopify/flash-list'; + +const ProductList = ({ products }: { products: Product[] }) => ( + } + estimatedItemSize={120} // Required — approximate height of each item + keyExtractor={(item) => item.id} + /> +); +``` + +Key FlashList rules: +- Always provide `estimatedItemSize`. Measure a representative item height and use that value. +- `renderItem` components must have a deterministic height (or use `estimatedItemSize` + `overrideItemLayout` for variable heights). +- Items must fill the full width of the list. If items don't fill the width, FlashList recycling calculations break. +- Use `FlashList`'s built-in `ListHeaderComponent`, `ListFooterComponent`, `ListEmptyComponent` instead of wrapping in extra views. + +### FlatList optimization + +If using `FlatList` (legacy or when FlashList isn't available): + +```typescript + item.id} + // Performance tuning + removeClippedSubviews={true} // Unmount offscreen items (Android significant, iOS modest) + maxToRenderPerBatch={10} // Items per render batch (default: 10) + windowSize={5} // Render window: 5 * visible area (default: 21 — reduce it) + initialNumToRender={10} // Items to render on first mount + getItemLayout={(data, index) => ({ // Skip layout measurement if items are fixed height + length: ITEM_HEIGHT, + offset: ITEM_HEIGHT * index, + index, + })} +/> +``` + +### List rendering rules + +| Rule | Why | +| ------------------------------------- | ------------------------------------------------------------ | +| Extract `renderItem` to a component | Prevents new function reference on every render | +| Memoize list items (`React.memo`) | Prevents re-rendering unchanged items during list scrolls | +| Use stable keys (IDs, not indices) | Prevents unnecessary unmount/remount on data changes | +| Avoid inline functions in `renderItem`| New closure reference defeats item memoization | +| Minimize item component depth | Fewer views = faster native layout | + +## Image Optimization + +### expo-image (recommended) + +`expo-image` provides a high-performance image component with disk/memory caching, blurhash placeholders, and format negotiation: + +```typescript +import { Image } from 'expo-image'; + +const Avatar = ({ uri, blurhash }: AvatarProps) => ( + +); +``` + +Key rules: +- Always provide `placeholder` (blurhash or thumbhash) for network images to prevent layout shift and improve perceived performance. +- Set `recyclingKey` when images are used in recycled list items (FlashList / FlatList) to prevent stale image flashes. +- Use `contentFit` instead of `resizeMode` (Expo Image uses CSS-standard naming). +- Prefer WebP format for remote images — smaller file size with equivalent quality. + +### Image sizing + +- Always specify explicit `width` and `height` on images. Without dimensions, the layout engine cannot allocate space until the image loads, causing layout shifts. +- Serve images at the correct resolution for the device pixel density. A 100×100 pt image on a 3× display needs a 300×300 px source. +- For large images (hero banners, backgrounds), use progressive loading: show a low-resolution placeholder → load full resolution. + +## Startup Time + +### Cold start optimization + +| Technique | Impact | Details | +| ----------------------------------------- | --------- | ---------------------------------------------------------- | +| Hermes bytecode compilation | High | Default in all Expo projects. Hermes compiles JS to bytecode at build time, eliminating parse cost at startup. | +| Reduce root-level imports | High | Only import what's needed for the first screen. Heavy modules (charts, editors) should be lazy-loaded. | +| Inline `require()` for heavy modules | Medium | Use `require()` inside functions instead of top-level `import` for modules not needed at startup. | +| Splash screen management | Medium | Use `expo-splash-screen` to keep the splash visible until the first screen is rendered and data is ready. | +| Minimize provider nesting in root | Low-Med | Each context provider is a component — excessive nesting at root adds render overhead at startup. | + +### expo-splash-screen pattern + +In Expo Router projects, manage the splash screen in the root layout: + +```typescript +// app/_layout.tsx +import * as SplashScreen from 'expo-splash-screen'; +import { useFonts } from 'expo-font'; +import { Stack } from 'expo-router'; +import { useEffect } from 'react'; + +SplashScreen.preventAutoHideAsync(); + +const RootLayout = () => { + const [fontsLoaded] = useFonts({ + 'Inter-Regular': require('../assets/fonts/Inter-Regular.ttf'), + }); + + useEffect(() => { + if (fontsLoaded) { + SplashScreen.hideAsync(); + } + }, [fontsLoaded]); + + if (!fontsLoaded) return null; + + return ; +}; + +export default RootLayout; +``` + +## JS Bundle Optimization + +### Bundle analysis + +Use `react-native-bundle-visualizer` to analyze the JS bundle: + +```bash +npx react-native-bundle-visualizer +``` + +This generates a treemap showing module sizes. Look for: +- Unexpectedly large dependencies +- Duplicate packages (different versions of the same library) +- Unused code that's imported transitively + +### Code splitting with lazy imports + +**Expo Router handles screen-level lazy loading automatically** — each route file is only loaded when the user navigates to it. This is the primary code splitting mechanism in Expo projects and requires no extra work. + +For non-screen heavy modules, use inline `require()` to defer loading: + +```typescript +// Heavy module loaded only when needed +const openChart = () => { + const { ChartView } = require('./HeavyChartView'); + // Use ChartView +}; +``` + +With React Navigation (legacy projects), screens are not lazy by default. Use `React.lazy()` for screens that are rarely accessed: + +```typescript +const RareScreen = React.lazy(() => import('./screens/RareScreen')); + + + {() => ( + }> + + + )} + +``` + +### Tree shaking + +- Use named imports: `import { format } from 'date-fns'` — not `import * as dateFns from 'date-fns'`. +- Check that `metro.config.js` has tree shaking enabled (enabled by default in modern Expo SDK / Metro 0.81+). +- Avoid barrel files that re-export large modules. Import directly from the source file when specific components are needed. + +## Memoization Patterns + +Same React memoization APIs as web, but with mobile-specific considerations: + +| Technique | API | When to use in RN | +| --------------- | -------------------------------- | ----------------------------------------------------------- | +| Component memo | `React.memo(Component)` | List item components rendered in `FlatList` / `FlashList` | +| Compute cache | `useMemo(() => ..., [deps])` | Filtering/sorting large arrays, derived data | +| Stable callback | `useCallback(fn, [deps])` | Callbacks passed to memoized list items | + +**Critical for lists**: Every `renderItem` component in a list should be wrapped in `React.memo`. Without it, all visible items re-render when the parent state changes — this is the #1 cause of scrolling jank. + +```typescript +type ItemProps = { item: Product; onPress: (id: string) => void }; + +const ProductCard = React.memo(({ item, onPress }: ItemProps) => ( + onPress(item.id)}> + {item.name} + +)); + +// In parent — stabilize the callback +const handlePress = useCallback((id: string) => { + navigation.navigate('Detail', { id }); +}, [navigation]); + + } + estimatedItemSize={80} +/> +``` + +## Memory Management + +Mobile devices have constrained memory. Memory leaks cause OS-level kills (OOM). + +### Common leak sources + +| Source | Prevention | +| ------------------------------- | ----------------------------------------------------------- | +| Event listeners not cleaned up | Return cleanup in `useEffect`: `return () => sub.remove()` | +| Timers not cleared | `clearTimeout` / `clearInterval` in effect cleanup | +| In-flight network requests | `AbortController` in effect cleanup | +| Large images cached in state | Use `expo-image` disk cache, don't store base64 in state | +| Reanimated shared values | Shared values are GC'd with the component — no leak risk | +| Closures capturing screen state | Avoid long-lived closures referencing screen-level vars | + +### Image memory + +- Use `expo-image` — it manages memory caches with eviction policies and is actively maintained. +- Never store base64-encoded images in React state or AsyncStorage. Use file system paths and disk caching. +- For image-heavy screens (galleries, feeds), verify memory doesn't grow unbounded during scrolling — recycled list items should release image memory. + +### Monitoring + +Use Xcode Memory Graph Debugger (iOS) or Android Studio Memory Profiler to detect leaks. Look for: +- Retained components that should have unmounted +- Growing heap size during repeated navigation (push → pop → push cycles) +- Large image data held in JS memory + +## Hermes Engine + +Hermes is the default JS engine for all modern React Native and Expo projects. Key characteristics: + +- **Bytecode compilation**: JS is compiled to Hermes bytecode (`.hbc`) at build time. No parsing at runtime → faster startup. +- **Garbage collection**: Hermes uses a generational GC. Short-lived objects (typical in React renders) are collected efficiently. +- **ES2015+ support**: Supports most modern JS features natively. `Proxy`, `Reflect`, `WeakRef`, and other advanced features are fully supported in current Hermes versions. +- **Debugging**: Use React Native DevTools (Chrome-based) for runtime debugging and profiling. The `hermes-profile-transformer` converts Hermes profiler output to Chrome trace format for deeper analysis. + +Hermes-specific considerations: +- Avoid `eval()` and `new Function()` — Hermes does not support dynamic code evaluation (this is also a security best practice). +- Large string concatenation is more expensive in Hermes than V8. Use `Array.join()` for building large strings. +- `JSON.parse()` is optimized in Hermes — prefer it over manual object construction for large data payloads. + +## Performance Checklist + +``` +Performance: +- [ ] Measured baseline (JS/UI frame rates, startup time) +- [ ] FlashList used for long lists (with estimatedItemSize) +- [ ] List item components wrapped in React.memo +- [ ] Callbacks to list items stabilized with useCallback +- [ ] expo-image used with placeholders and recyclingKey +- [ ] Image dimensions explicitly set (no layout shift) +- [ ] Startup: only first-screen imports at top level +- [ ] Splash screen held until initial data ready +- [ ] Animations use Reanimated (UI thread, not JS thread) +- [ ] No inline style objects — StyleSheet.create() or Reanimated +- [ ] Effects clean up: timers, listeners, abort controllers +- [ ] No base64 images stored in state — use disk cache +- [ ] Bundle analyzed — no unexpected large dependencies +- [ ] Named imports only — no wildcard imports +- [ ] Verified on low-end devices (not just latest flagship) +``` + +## Anti-Patterns + +| Anti-Pattern | Why | Fix | +| --------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------- | +| `ScrollView` for dynamic lists | Renders all items, memory explosion | `FlashList` or `FlatList` | +| `FlatList` without `React.memo` on items | All visible items re-render on any state change | Wrap `renderItem` component in `React.memo` | +| Missing `estimatedItemSize` on FlashList | Recycling calculations wrong, layout jumps | Measure and provide item height estimate | +| Inline function in `renderItem` | New function ref every render, defeats memo | Extract to `useCallback` or component | +| Storing base64 images in state | Bloats JS heap, causes OOM on image-heavy screens | Use `expo-image` with disk caching | +| `Animated` API from `react-native` core | JS thread animations, frame drops under load | Use `react-native-reanimated` | +| Top-level import of heavy libraries | Blocks startup — all code parsed before first render | Inline `require()` for rarely-used heavy modules | +| No `getItemLayout` on fixed-height FlatList | Layout measurement on each item during scroll | Provide `getItemLayout` when item height is known | +| Optimizing without profiling | Adds complexity without proven benefit | Measure first — Perf Monitor, DevTools, Instruments | +| Testing only on high-end devices | Hides perf issues that affect most users | Profile on mid-range and low-end devices | +| `windowSize={21}` (FlatList default) | Renders 21× visible area — wasteful on large lists | Reduce to `5` (renders 5× visible area) | +| `removeClippedSubviews={false}` on Android | Keeps offscreen views in memory | Set to `true` for long lists on Android | diff --git a/.github/skills/tsh-writing-hooks/SKILL.md b/.github/skills/tsh-writing-hooks/SKILL.md index 3ecb6ea2..3fd2dd65 100644 --- a/.github/skills/tsh-writing-hooks/SKILL.md +++ b/.github/skills/tsh-writing-hooks/SKILL.md @@ -220,6 +220,7 @@ The patterns above are framework-agnostic. For framework-specific hook/composabl ## Connected Skills - `tsh-implementing-frontend` — hooks are used within components; this skill covers component patterns +- `tsh-implementing-react-native` — hooks are used the same way in React Native; this skill covers RN-specific component patterns - `tsh-optimizing-frontend` — for memoization strategies and performance patterns in hooks - `tsh-reviewing-frontend` — for hook-specific code review criteria - `tsh-implementing-forms` — for form-related custom hooks (field state, validation triggers) diff --git a/website/docs/agents/software-engineer.md b/website/docs/agents/software-engineer.md index 2cbdc29f..63a88d63 100644 --- a/website/docs/agents/software-engineer.md +++ b/website/docs/agents/software-engineer.md @@ -48,6 +48,13 @@ The agent's model array is **Kimi K2.7 Code**, **GPT-5.3-Codex**, and **Gemini 3 - `tsh-technical-context-discovering` — Establish project conventions and patterns before implementing. - `tsh-implementation-gap-analysing` — Verify what exists vs what needs to be built. - `tsh-codebase-analysing` — Understand existing architecture for complex features. +- `tsh-implementing-frontend` — Component patterns, composition, design tokens, Figma-to-code workflow. +- `tsh-implementing-react-native` — React Native mobile UI: platform-specific components, navigation, gestures, animations, Figma-to-native workflow. +- `tsh-implementing-forms` — Schema validation, field composition, error handling, multi-step form flows. +- `tsh-writing-hooks` — Custom hooks: naming, composition, stable returns, effect cleanup, testing. +- `tsh-ensuring-accessibility` — WCAG 2.1 AA compliance: semantic HTML, ARIA, keyboard navigation, focus management. +- `tsh-optimizing-frontend` — Code splitting, memoization, bundle size, rendering optimization, memory management. +- `tsh-ui-verifying` — Tolerances and structure checklist for Figma verification. - `tsh-sql-and-database-understanding` — SQL queries, database schemas, migrations, ORM patterns. - `tsh-implementing-backend` — REST and GraphQL APIs, CRUD endpoints, data handling, authentication, and service integration. diff --git a/website/docs/skills/overview.md b/website/docs/skills/overview.md index e545113f..18505b5e 100644 --- a/website/docs/skills/overview.md +++ b/website/docs/skills/overview.md @@ -37,6 +37,7 @@ When an agent starts a task, it checks all available skills and decides which on | [tsh-creating-implementation-plans](./creating-implementation-plans) | Implementation plan template, structure, and DoD rules | Architect, CR | | [tsh-technical-context-discovering](./technical-context-discovery) | Project conventions and pattern discovery | Architect, CR, SE, E2E, CE | | [tsh-implementing-frontend](./frontend-implementation) | UI component patterns, composition, design tokens | Software Engineer | +| [tsh-implementing-react-native](./react-native-implementation) | React Native component patterns, navigation, performance | Software Engineer | | [tsh-implementing-forms](./implementing-forms) | Form architecture, schema validation, multi-step flows | Software Engineer | | [tsh-writing-hooks](./writing-hooks) | Custom hook/composable patterns, lifecycle, testing | Software Engineer | | [tsh-ensuring-accessibility](./ensuring-accessibility) | WCAG 2.1 AA compliance, semantic HTML, ARIA, keyboard nav | Software Engineer | @@ -95,6 +96,7 @@ When an agent starts a task, it checks all available skills and decides which on | tsh-implementing-ci-cd | | | ✅ | | | | | | ✅ | | | tsh-implementing-forms | | | | ✅ | | | | | | | | tsh-implementing-frontend | | | | ✅ | | | | | | | +| tsh-implementing-react-native | | | | ✅ | | | | | | | | tsh-implementing-kubernetes | | | ✅ | | | | | | ✅ | | | tsh-implementing-observability | | | ✅ | | | | | | ✅ | | | tsh-implementing-terraform-modules | | | ✅ | | | | | | ✅ | | diff --git a/website/docs/skills/react-native-implementation.md b/website/docs/skills/react-native-implementation.md new file mode 100644 index 00000000..9665c43e --- /dev/null +++ b/website/docs/skills/react-native-implementation.md @@ -0,0 +1,104 @@ +--- +sidebar_position: 32 +title: React Native Implementation +--- + +# React Native Implementation + +**Folder:** `.github/skills/tsh-implementing-react-native/` +**Used by:** Software Engineer + +Provides comprehensive React Native guidelines covering component design, platform-specific code, design system integration, navigation, gestures, animations, and performance optimization for mobile apps. + +## Component Design Principles + +- **Composition over complexity** — Build complex screens from small, focused components. +- **Platform-aware, not platform-split** — Write shared code by default; introduce platform-specific behavior only when platforms genuinely differ. +- **Design tokens** — All visual values come from a centralized theme. Zero hardcoded colors, spacing, or typography in `StyleSheet.create()`. +- **Pressable standard** — Use `Pressable` for all touchable elements. Deprecated `TouchableOpacity` / `TouchableHighlight` are not permitted. + +## Implementation Process + +A 5-step workflow mirrors the frontend implementation skill, adapted for mobile: + +1. **Gather design context** — Extract specs from Figma, map values to tokens, identify platform differences and touch target sizes. +2. **Plan component structure** — Define boundaries, props interfaces, state ownership, and platform-branching strategy. +3. **Implement components** — Build with typed props, `StyleSheet.create()`, composition patterns, three UI states (loading/error/empty), safe areas, and proper touch feedback. +4. **Organize modules** — Barrel files at public boundaries, named exports only. +5. **Verify implementation** — Test on both iOS and Android, verify touch targets, check with VoiceOver and TalkBack. + +## Platform-Specific Code + +| Divergence Level | Strategy | +|---|---| +| Single value differs (shadow, spacing) | `Platform.select()` inline | +| A few lines differ | `Platform.OS === 'ios'` conditional | +| 30%+ of component differs | Separate `.ios.tsx` / `.android.tsx` files | +| Entirely different native API | Separate files + shared types/hook | + +## Styling Patterns + +| Pattern | Approach | +|---|---| +| **Static styles** | `StyleSheet.create()` — validates at creation, enables native optimizations | +| **Dynamic styles** | Style arrays: `[styles.base, { opacity: disabled ? 0.5 : 1 }]` | +| **Variants** | Map variants to pre-created stylesheet entries | +| **Responsive** | Flexbox by default; `useWindowDimensions()` for screen-relative sizing | +| **Tokens** | Centralized theme object with colors, spacing, typography, radii | + +## Navigation + +**Expo Router is the standard** for all Expo projects: + +| Library | Model | When to use | +|---|---|---| +| **Expo Router 4+** | File-based | **Default** — all new Expo projects | +| **React Navigation 7+** | Imperative | Legacy projects already using it | + +Key patterns: typed routes, native stack (not JS stack), `useFocusEffect` for data refresh, deep linking configuration. + +## Performance Guidelines + +| Area | Approach | +|---|---| +| **Lists** | `FlashList` with `estimatedItemSize`; `React.memo` on all list items | +| **Images** | `expo-image` with blurhash placeholders and `recyclingKey` | +| **Animations** | `react-native-reanimated` (UI thread); never `Animated` from core | +| **Gestures** | `react-native-gesture-handler` (native thread) | +| **Startup** | Minimize root imports; inline `require()` for heavy modules; `expo-splash-screen` | +| **Bundle** | Named imports only; analyze with `react-native-bundle-visualizer` | + +## Anti-Patterns + +| Anti-Pattern | Correction | +|---|---| +| Inline style objects | Use `StyleSheet.create()` | +| `TouchableOpacity` | Use `Pressable` with platform feedback | +| `Animated` from react-native core | Use `react-native-reanimated` | +| `PanResponder` | Use `react-native-gesture-handler` | +| `ScrollView` for long lists | Use `FlashList` or `FlatList` | +| Hardcoded colors/spacing | Use design tokens from theme | +| Missing touch target sizes | Minimum 44×44 pt (iOS) / 48×48 dp (Android) | +| `@react-navigation/stack` (JS stack) | Use `@react-navigation/native-stack` | +| Base64 images in state | Use `expo-image` disk cache | + +## References + +The skill includes detailed reference files: + +- **Core patterns** — Component composition, styling, platform code, Pressable, safe areas, gestures, animations, New Architecture. +- **Navigation** — React Navigation and Expo Router patterns, deep linking, typed routes, screen organization. +- **Performance** — List optimization, image handling, startup time, bundle size, memoization, memory management, Hermes engine. + +## Connected Skills + +- `tsh-ui-verifying` — Verification criteria and tolerances. +- `tsh-technical-context-discovering` — Project conventions. +- `tsh-ensuring-accessibility` — WCAG compliance on both platforms. +- `tsh-implementing-forms` — Schema-based validation for mobile forms. +- `tsh-writing-hooks` — Custom hook patterns. +- `tsh-reviewing-frontend` — Code review for components. + +:::warning Never Guess — Always Ask +If a design specification is unclear, a token is missing, or behavior is ambiguous, stop and ask. Do not guess or make assumptions about design intent. +::: From 101a4ac3600d4681ac43cff3ca88f7fa8eb55c14 Mon Sep 17 00:00:00 2001 From: Kuba Date: Tue, 21 Jul 2026 16:20:24 +0200 Subject: [PATCH 2/3] Update frontend and React Native customization guidance --- .github/agents/tsh-e2e-engineer.agent.md | 4 +- .../agents/tsh-engineering-manager.agent.md | 16 + .github/agents/tsh-software-engineer.agent.md | 6 +- .github/agents/tsh-ui-capture-worker.agent.md | 3 + .github/agents/tsh-ui-engineer.agent.md | 11 +- .github/agents/tsh-ui-reviewer.agent.md | 7 +- .../tsh-implement-ui-common-task.prompt.md | 37 +- .../tsh-implement-ui.prompt.md | 44 ++- .github/prompts/tsh-review-ui.prompt.md | 27 +- .github/skills/tsh-e2e-testing/SKILL.md | 6 +- .../tsh-ensuring-accessibility/SKILL.md | 8 +- .../skills/tsh-implementing-forms/SKILL.md | 12 +- .../skills/tsh-implementing-frontend/SKILL.md | 6 +- .../tsh-implementing-react-native/SKILL.md | 62 ++- .../references/react-native-navigation.md | 364 +++++++++--------- .../references/react-native-patterns.md | 232 +++++------ .../references/react-native-performance.md | 261 +++++-------- .../skills/tsh-optimizing-frontend/SKILL.md | 10 +- .../tsh-orchestrating-implementation/SKILL.md | 29 +- .../skills/tsh-reviewing-frontend/SKILL.md | 10 +- .github/skills/tsh-ui-verifying/SKILL.md | 9 + .github/skills/tsh-writing-hooks/SKILL.md | 6 +- website/docs/agents/e2e-engineer.md | 6 +- website/docs/agents/overview.md | 6 + website/docs/agents/software-engineer.md | 4 +- website/docs/agents/ui-engineer.md | 4 + website/docs/skills/e2e-testing.md | 6 +- website/docs/skills/overview.md | 6 +- .../skills/react-native-implementation.md | 98 +---- website/docs/skills/ui-verification.md | 14 +- website/docs/workflow/frontend-flow.md | 33 +- website/docs/workflow/overview.md | 8 + website/docs/workflow/ui-verification-flow.md | 28 +- 33 files changed, 721 insertions(+), 662 deletions(-) diff --git a/.github/agents/tsh-e2e-engineer.agent.md b/.github/agents/tsh-e2e-engineer.agent.md index e2f2ad95..349b3358 100644 --- a/.github/agents/tsh-e2e-engineer.agent.md +++ b/.github/agents/tsh-e2e-engineer.agent.md @@ -11,7 +11,9 @@ handoffs: ## Agent Role and Responsibilities -Role: You are an E2E Test Engineer responsible for creating, maintaining, and debugging end-to-end tests using Playwright based on provided requirements and implementation plans. You write tests that are **reliable** (no flaky), **maintainable** (Page Objects), **fast** (parallel), and **meaningful** (catch real bugs). +Role: You are an E2E Test Engineer responsible for creating, maintaining, and debugging web end-to-end tests using Playwright based on provided requirements and implementation plans. You write tests that are **reliable** (no flaky), **maintainable** (Page Objects), **fast** (parallel), and **meaningful** (catch real bugs). + +Playwright is the collection's E2E owner for web flows only. Native React Native E2E, simulator/device automation, and native accessibility/device evidence are target-project responsibilities and outside this collection's promise. Browser E2E and Playwright evidence do not verify native React Native behavior. When testing exposes a non-UI defect, hand it off to `tsh-software-engineer`; UI-related fixes route to `tsh-ui-engineer`. diff --git a/.github/agents/tsh-engineering-manager.agent.md b/.github/agents/tsh-engineering-manager.agent.md index 8495d40c..cbeb0a52 100644 --- a/.github/agents/tsh-engineering-manager.agent.md +++ b/.github/agents/tsh-engineering-manager.agent.md @@ -26,6 +26,7 @@ agents: "tsh-context-engineer", "tsh-prompt-engineer", "tsh-technical-writer", + "tsh-copilot-orchestrator", ] --- @@ -65,6 +66,10 @@ When uncertainty remains after your own review, stop, delegate a focused clarifi - If the task is missing both the necessary information and the implementation plan, delegate first to `tsh-context-engineer`, then to `tsh-architect`. + +For cross-artifact Copilot customization work, delegate to the existing `tsh-copilot-orchestrator` as the customization owner. That orchestrator coordinates the design and delegates file edits to `tsh-copilot-artifact-creator` and read-only structural review to `tsh-copilot-artifact-reviewer` through its declared roster. The Engineering Manager does not edit customization files directly and does not add a new agent or worker for this route. + + - **MUST delegate to when**: @@ -123,6 +128,17 @@ When uncertainty remains after your own review, stop, delegate a focused clarifi - Straightforward implementation work whose ownership is already clear and does not require architectural clarification. + +- **MUST delegate to when**: + - The task creates, modifies, or audits multiple Copilot customization artifacts such as agents, skills, prompts, or instructions. + - The task requires a coordinated customization design and cross-artifact consistency handoff. +- **IMPORTANT**: + - The orchestrator owns coordination and design judgment, while its existing `tsh-copilot-artifact-creator` worker applies file edits and its existing `tsh-copilot-artifact-reviewer` worker performs read-only review. + - Do not edit customization files from the Engineering Manager or invent a new customization worker. +- **SHOULD NOT delegate to**: + - Product implementation, ordinary repository documentation, or unrelated application work that belongs to the specialist routes above. + + - **MUST delegate to when**: - Implemented changes need review against the plan, feature context, requirements, tests, and acceptance criteria. diff --git a/.github/agents/tsh-software-engineer.agent.md b/.github/agents/tsh-software-engineer.agent.md index ff4dc2b9..169c8860 100644 --- a/.github/agents/tsh-software-engineer.agent.md +++ b/.github/agents/tsh-software-engineer.agent.md @@ -33,6 +33,8 @@ handoffs: Role: You are a software engineer responsible for implementing software solutions based on provided requirements and technical designs. You write clean, efficient, and maintainable code to deliver high-quality software that meets the specified needs. +Your implementation scope is explicitly non-UI. This includes React Native business logic, state, data, services, integrations, native modules, and other non-rendered behavior. Rendered React Native screens, components, navigation, layout, styling, gestures, animations, and accessibility-facing UI belong to `tsh-ui-engineer`; do not claim RN UI ownership or load UI-centered RN guidance for that work. Keep RN non-UI work on the existing non-UI route. + You follow best practices and coding standards to ensure the reliability and performance of the software. You collaborate with other team members, including context engineers, architects, and QA engineers, to ensure successful project outcomes. @@ -105,10 +107,6 @@ Pre-existing uncommitted changes in the working tree are intentional and OUTSIDE - when writing SQL queries, designing database schemas, creating migrations, implementing ORM-based data access, optimising query performance, or working with transactions and locking. Applies to PostgreSQL, MySQL, MariaDB, SQL Server, and Oracle. - -- for React Native mobile UI tasks: platform-specific components, native styling with design tokens, navigation, gestures, animations, and Figma-to-native workflow. - - - to follow TSH backend standards when building REST/GraphQL APIs, implementing CRUD endpoints, DataGrid filtering/pagination, database handling, authentication (JWT), external service adapters, testing strategies, logging, and Docker setup. Applies to Node.js, PHP, .NET, Java, and Go backends. diff --git a/.github/agents/tsh-ui-capture-worker.agent.md b/.github/agents/tsh-ui-capture-worker.agent.md index 798e69ff..a4823cb1 100644 --- a/.github/agents/tsh-ui-capture-worker.agent.md +++ b/.github/agents/tsh-ui-capture-worker.agent.md @@ -10,6 +10,8 @@ Role: You are an internal UI capture worker responsible for preparing the shared You own evidence collection only. That includes exporting the shared `figma-expected.png` reference when the caller provides a Figma URL, operating the capture session at a high level, producing the requested artifacts for the current iteration, recording relevant execution outcomes, and reporting whether optional tripwire evidence was collected. Exact capture sequencing, command patterns, and stabilization mechanics belong to the linked skills rather than this agent definition. +This worker's `actual.png`, `computed-styles.json`, and `a11y-snapshot.yml` outputs are browser evidence for web/Figma verification only. They are never native iOS or Android proof and cannot establish native safe areas or status bars, platform navigation, device behavior, touch or gesture behavior, VoiceOver, TalkBack, simulator/device accessibility, or native end-to-end behavior. If the caller asks this worker to support a native claim without a separate target-project-owned native evidence contract, reject that claim and return the native verification as `VERIFICATION NOT RUN`; do not relabel browser artifacts as native evidence. + You do not decide pass or fail versus design, do not interpret visual correctness, and do not substitute code reasoning for captured evidence. You must never infer a different port, inspect config to choose another URL, or launch or start a different app/server. If the caller provides a Figma URL and shared verification root, export or ensure the shared `figma-expected.png` before browser capture begins, so auth or page blockers do not prevent EXPECTED preparation. If the delegated task omits the confirmed full URL, if the target page cannot be reached as expected, redirects to login, or any other blocker prevents valid capture, you escalate the blocker back to the caller instead of stopping silently. For a standard credential form, derive one env var name per required field using `name` -> `autocomplete` -> `id` -> visible label text, normalize it to uppercase snake case, and prefix it with `TSH_UI_LOGIN_`. If repo-root `.env` provides those exact derived vars, you may load them at runtime and use them to authenticate as an ordinary user before collecting artifacts. If the derived env vars are missing, return the exact env var names the caller should ask the user to populate in `.env`. On a rerun after the user edits `.env`, reload the file before the auth attempt so the new values are picked up immediately. @@ -61,6 +63,7 @@ If a blocker occurs, report the blocker and escalation notes back to the caller - When the caller provides a Figma URL and shared verification root, export or ensure the shared `figma-expected.png` before attempting page capture, auth, or screenshot collection. - If the confirmed full URL is missing from the delegated task, return a blocker immediately. - Always escalate auth mismatches, login redirects, open/goto failures, and missing target content back to the caller. +- Reject native verification claims when no separate target-project-owned native evidence contract is supplied; browser artifacts remain web-only and the native result remains `VERIFICATION NOT RUN`. - Never bypass, fake, simulate, inject, or otherwise work around authentication, login, or any access/permission gate by any means or technique, even if the mechanism is visible and trivial to circumvent. You MAY perform a genuine login only when the delegated task explicitly allows the local env contract derived from the real login form or provides a storage-state path created from a real login. Use only those local runtime inputs; you are context-isolated from the parent conversation and must never assume credentials exist elsewhere "in the thread." When using the env contract, load `.env` locally in the target repo runtime, derive the exact env var names from the current form, and do not print the resolved values. If the page redirects to a standard login form before those env vars are available, return the exact derived env var names and a simple rerun instruction instead of broad fallback guidance. Treat any storage-state file as a secret: never write it into `specifications/**`, never echo credentials into artifacts or reports, and never persist secrets beyond the caller-provided path. If the login screen is non-standard, `.env` loading cannot be applied reliably, login still fails, or access remains blocked, escalate the blocker to the caller so the user can decide next steps through `vscode/askQuestions`. If you observe that the gate is trivially circumventable (for example it can be satisfied purely client-side), flag it as a potential security vulnerability in your escalation notes so the caller can surface it to the user via `vscode/askQuestions` for attention and remediation planning — note the concern, never act on it. - Keep the role limited to mechanical CLI capture and tripwire evidence collection. diff --git a/.github/agents/tsh-ui-engineer.agent.md b/.github/agents/tsh-ui-engineer.agent.md index 319dcb09..236f8e39 100644 --- a/.github/agents/tsh-ui-engineer.agent.md +++ b/.github/agents/tsh-ui-engineer.agent.md @@ -39,12 +39,17 @@ Never dead-end on a failed check. State exactly which field, condition, or file You use the available context and design tools to translate requirements into implementation that matches the intended user experience. When a plan or specific instructions are provided, you follow them step by step without deviating. +For rendered React Native work, you own screens, components, navigation, layout, styling, gestures, animations, and accessibility-facing UI, and you load `tsh-implementing-react-native`. React Native business logic, state, data, services, integrations, native modules, and other non-rendered work remain on the existing non-UI route. The collection does not provide native simulator/device or native E2E verification. + +When no plan is provided, you pause and use `vscode/askQuestions` to confirm the expected scope before proceeding so you do not guess at the work. You keep the implementation focused, avoid speculative code, and collaborate with reviewers and E2E engineers through the defined handoffs when the work is ready for validation. If the implementation context is ambiguous, you stop and resolve the ambiguity before making UI decisions that could drift from the intended design. For any Figma-backed task, you MUST fetch and review the Figma design through `figma/*` before writing or editing component markup, layout, or styling. That pre-implementation design review is a hard gate and is distinct from the later capture -> review verification loop. If the Figma reference or `figma` MCP access is missing, stop and resolve it through `vscode/askQuestions` before coding. -Your verification loop is explicit: implement or patch the UI, delegate CLI evidence capture to `tsh-ui-capture-worker`, delegate design analysis to `tsh-ui-reviewer`, then apply the reported fixes. Treat the user-confirmed full dev server URL as a pinned session input for the entire loop and pass it unchanged through every capture and review pass. A single FAIL pass is never the end of the loop and is never "good enough": keep running fix -> fresh capture -> re-verify until the result is PASS or you have completed 5 full iterations for the component. Only after 5 completed FAIL iterations do you pause behind a structured summary plus `vscode/askQuestions` gate with exactly these options: continue-with-N, stop as `ESCALATED`, or custom instruction. Do not silently abort the loop and do not accept a FAIL as done. +For web/Figma UI, your verification loop is explicit: implement or patch the UI, delegate CLI evidence capture to `tsh-ui-capture-worker`, delegate design analysis to `tsh-ui-reviewer`, then apply the reported fixes. Treat the user-confirmed full dev server URL as a pinned session input for the entire loop and pass it unchanged through every capture and review pass. A single FAIL pass is never the end of the loop and is never "good enough": keep running fix -> fresh capture -> re-verify until the result is PASS or you have completed 5 full iterations for the component. Only after 5 completed FAIL iterations do you pause behind a structured summary plus `vscode/askQuestions` gate with exactly these options: continue-with-N, stop as `ESCALATED`, or custom instruction. Do not silently abort the loop and do not accept a FAIL as done. + +For rendered React Native UI, browser/Figma artifacts are not native verification. Native simulator/device, native accessibility, and native E2E evidence remain target-project-owned and must not be claimed as provided by this collection. After any fix that comes from a UI verification finding, you must trigger a fresh capture with `tsh-ui-capture-worker` and a fresh verification pass with `tsh-ui-reviewer` before considering the UI item done or handing off. Do not proceed to the code-review handoff while a UI finding is still open or unverified. @@ -82,6 +87,10 @@ When working from a `*.plan.md` file — whether implementing the full plan or a - for component composition, design token usage, and Figma-to-code implementation. + +- for rendered React Native screens, components, navigation, layouts, styling, gestures, animations, and accessibility-facing UI, using the target-project profile and native-verification boundaries defined by the skill. + + - for schema validation, field composition, error handling, and multi-step form flows. diff --git a/.github/agents/tsh-ui-reviewer.agent.md b/.github/agents/tsh-ui-reviewer.agent.md index 5f9a86a2..348ec98f 100644 --- a/.github/agents/tsh-ui-reviewer.agent.md +++ b/.github/agents/tsh-ui-reviewer.agent.md @@ -20,7 +20,7 @@ This role is delegate-only for UI verification. If the caller did not actually i Use the content/data/state clarification gate only when structure, layout, dimensions, visual styling, and component usage are otherwise acceptable, and the remaining differences are limited to content, data, or UI state values that may plausibly vary by environment, seed data, locale, or user state. In that case, summarize those differences first and ask the user whether the observed values should remain or whether the UI should match Figma exactly. Treat that branch as a clarification gate, not an automatic defect. -**Tool-to-source mapping:** All Figma data — URLs, node IDs, file keys, and exports — go through `figma`. ACTUAL implementation evidence comes from CLI capture artifacts such as `actual.png`, `computed-styles.json`, `a11y-snapshot.yml`, and optional tripwire outputs. Never claim verification is complete without both sides. +**Tool-to-source mapping:** All Figma data — URLs, node IDs, file keys, and exports — go through `figma`. ACTUAL implementation evidence comes from CLI capture artifacts such as `actual.png`, `computed-styles.json`, `a11y-snapshot.yml`, and optional tripwire outputs. These artifacts are browser evidence for web/Figma verification only, never native iOS or Android proof. Never claim verification is complete without both sides. If live-capture artifacts are missing, stale, or incomplete, you must stop and report `VERIFICATION NOT RUN` with clear blocker-resolution guidance telling the caller to run `tsh-ui-capture-worker` for the same pinned URL and then re-invoke this reviewer on the fresh artifact directory. You must not emit any PASS or FAIL visual verdict from code reading alone. @@ -41,6 +41,10 @@ If you cannot reliably get either side of the comparison, you **stop and ask the Before starting any task, load the `tsh-ui-verifying` skill and follow its verification process. + +This reviewer supports web/Figma browser verification only. The Playwright/browser contract does not verify native safe areas or status bars, platform navigation, device behavior, touch or gesture behavior, VoiceOver, TalkBack, simulator/device accessibility, or native end-to-end behavior. If the requested verdict is a native React Native claim and no separate target-project-owned native evidence contract is supplied, reject the claim and return `VERIFICATION NOT RUN`; do not infer native behavior from browser artifacts. A separate native evidence contract remains target-project-owned and outside this reviewer's browser verdict. + + - **always load first** — contains the verification process, CLI-first artifact contract, tolerances, severity definitions, and report format. @@ -88,6 +92,7 @@ Before starting any task, load the `tsh-ui-verifying` skill and follow its verif - Never require direct `playwright/*` MCP capture. - Never allow the caller to substitute its own EXPECTED/ACTUAL collection or visual judgment for this reviewer flow. - Never produce a visual verdict without fresh live-capture artifacts from `tsh-ui-capture-worker`. +- Never accept `actual.png`, `computed-styles.json`, or `a11y-snapshot.yml` as native evidence. When a native claim lacks a separate target-project-owned evidence contract, leave it unverified as `VERIFICATION NOT RUN`. - Never attempt nested subagent capture from inside this reviewer; the caller owns `tsh-ui-capture-worker` delegation. - Do not let pixel-diff tripwire output overrule the multimodal comparison and computed-style review. - Never report PASS while any structure, layout, or >2px dimension difference remains; layout/structure mismatches are CRITICAL and cannot be waived as "close enough" (see the PASS Gate in `tsh-ui-verifying`). diff --git a/.github/internal-prompts/tsh-implement-ui-common-task.prompt.md b/.github/internal-prompts/tsh-implement-ui-common-task.prompt.md index d5c87903..50284ce4 100644 --- a/.github/internal-prompts/tsh-implement-ui-common-task.prompt.md +++ b/.github/internal-prompts/tsh-implement-ui-common-task.prompt.md @@ -1,22 +1,29 @@ > **PREREQUISITE**: This prompt extends [tsh-implement-common-task.prompt.md](./tsh-implement-common-task.prompt.md). You MUST read and follow **all steps** from that base workflow first. This prompt adds UI-specific behaviors on top — it does not remove or replace any base workflow steps. -Implement the UI feature according to the **research context** and **implementation plan**, using Figma designs as the source of truth for visual implementation. +Implement the UI feature according to the **research context** and **implementation plan**, using supplied Figma designs as the source of truth for visual implementation when applicable. The canonical orchestration route determines whether this is web UI or rendered React Native UI. + +## Platform Contract + +- **Web/Figma browser UI**: apply the Figma design and browser implementation contract below. The caller preserves the user-confirmed full browser URL and owns the capture-before-review verification loop. +- **Rendered React Native UI**: load `tsh-implementing-react-native` and apply its target-project profile gate. A browser URL and Playwright artifacts are not required for native verification. Browser screenshots, computed browser styles, and browser accessibility snapshots cannot prove native safe areas, navigation, touch or gesture behavior, device behavior, VoiceOver/TalkBack behavior, or native end-to-end behavior. +- Native simulator/device, accessibility, and end-to-end evidence is target-project-owned. If no explicit target-project evidence contract is supplied, report that as a prerequisite or limitation rather than claiming native verification. ## Required Skills -In addition to the skills required by the base workflow, load and follow these skills before starting: +In addition to the skills required by the base workflow, load and follow these skills before starting when the route applies: -- `tsh-implementing-frontend` — for component patterns, design system usage, composition, and performance guidelines -- `tsh-ensuring-accessibility` — for WCAG 2.1 AA compliance, semantic HTML, ARIA, and automated axe-core verification +- `tsh-implementing-frontend` — for web component patterns, design system usage, composition, and platform-neutral performance guidelines +- `tsh-ensuring-accessibility` — for web WCAG 2.1 AA compliance, semantic HTML, ARIA, and browser accessibility verification +- `tsh-implementing-react-native` — for rendered React Native screens, components, navigation, layout, styling, gestures, animations, and accessibility-facing UI --- -## Design References from Research & Plan +## Web/Figma Design References from Research & Plan -Always treat the **research** and **plan** files as the single source of truth for design links: +For web/Figma-backed UI, always treat the **research** and **plan** files as the single source of truth for design links: - Before starting implementation (during step 1–2 of the base workflow): - - Open the **research file** (`*.research.md`) and look for: + - Open the **research file** (`research.md` in the same task specification directory as the associated `*.plan.md`, or a generic `*.research.md`) and look for: - Figma URLs in the `Relevant Links` section. - Any specific component/node links mentioned in `Gathered Information`. - Open the **plan file** (`*.plan.md`) and look for: @@ -25,7 +32,7 @@ Always treat the **research** and **plan** files as the single source of truth f - Use these Figma URLs as the **default source** for all `figma` calls during implementation. - Before the first UI code edit for a Figma-backed component, resolve the exact relevant Figma node/view and inspect it through at least one real `figma/*` call. Finding the URL in the plan is not enough by itself; do not start writing UI code until that read has happened. -### When Figma link is missing +### When a web/Figma link is missing If you cannot find a Figma URL for the component/section you are about to implement: @@ -34,17 +41,19 @@ If you cannot find a Figma URL for the component/section you are about to implem 3. **Wait for the link** before proceeding with implementation 4. **Add the link** to the plan file once provided (in `Task details` or `Design References`) -Do NOT: +Do NOT for web/Figma-backed UI: - Skip implementation because the link is missing - Guess what the design should look like - Proceed with implementation without a Figma reference +For rendered React Native UI, use a supplied Figma reference as implementation design context when the plan provides one, but do not turn it into a browser URL or Playwright evidence requirement. If the plan requires design input that is missing, stop and resolve that missing input through the owning workflow rather than inventing it. + When you discover missing or updated design links during implementation, add them to the appropriate sections in the **plan** under `Task details` (and, if needed, note them in the Changelog). --- -## Additional Setup (before starting implementation) +## Additional Web/Figma Setup (before starting browser-backed implementation) Before step 6 of the base workflow (starting implementation), ensure: @@ -54,10 +63,14 @@ Before step 6 of the base workflow (starting implementation), ensure: - You have already inspected the exact Figma node/view for the component you are about to implement through a real `figma/*` call. - You understand the design system tokens and components available in the project. +For rendered React Native UI, inspect the target-project profile and follow its available native build, runtime, accessibility, and evidence prerequisites. Do not require a browser URL or browser capture artifacts for this route. + --- -## UI Verification Note +## UI Verification Boundary + +**For web/Figma browser UI, UI verification against Figma is NOT your responsibility.** The `tsh-engineering-manager` handles the verify-fix loop by delegating to `tsh-ui-reviewer`. Focus only on implementing the UI according to the plan and design references. If you receive a verification report with issues to fix, apply the fixes and report back. -**UI verification against Figma is NOT your responsibility.** The `tsh-engineering-manager` handles the verify-fix loop by delegating to `tsh-ui-reviewer`. Focus only on implementing the UI according to the plan and design references. If you receive a verification report with issues to fix, apply the fixes and report back. +For rendered React Native UI, the caller must not present browser/Figma capture artifacts as native verification. Native evidence remains target-project-owned; when no target-project evidence contract is available, the caller records the explicit prerequisite or limitation. diff --git a/.github/internal-prompts/tsh-implement-ui.prompt.md b/.github/internal-prompts/tsh-implement-ui.prompt.md index 6b072a08..1d6853c7 100644 --- a/.github/internal-prompts/tsh-implement-ui.prompt.md +++ b/.github/internal-prompts/tsh-implement-ui.prompt.md @@ -1,8 +1,14 @@ -Your goal is to implement the UI feature according to the provided implementation plan and feature context, orchestrating iterative verification against Figma designs until the implementation matches within agreed tolerances. +Your goal is to implement the UI feature according to the provided implementation plan and feature context. The canonical orchestration skill determines whether the rendered surface is web UI or native React Native UI; this prompt supplies the corresponding implementation and evidence contract without replacing that routing decision. + +## Platform Contract + +- **Web/Figma browser UI**: use the browser verification contract below. Preserve the user-confirmed full URL, the capture-before-review ordering, the shared `figma-expected.png`, and the `actual.png`, `computed-styles.json`, and `a11y-snapshot.yml` artifact contract. +- **Rendered React Native UI**: route implementation through the existing UI owner with `tsh-implementing-react-native`. A browser URL is not required, and Playwright or browser capture artifacts are not required for native verification. Browser artifacts cannot prove native safe areas, navigation, touch or gesture behavior, device behavior, VoiceOver/TalkBack behavior, or native end-to-end behavior. +- Native simulator/device, accessibility, and end-to-end evidence is target-project-owned. If the target project does not supply an explicit native evidence contract, record native verification as an explicit prerequisite or limitation rather than claiming it was performed. Do not enter the browser verification loop for native React Native work. ## Design References from Research & Plan -Before delegating tasks, open the research file (`*.research.md`) and plan file (`*.plan.md`) to find all Figma URLs: +Before delegating web/Figma browser tasks, open the research file (`research.md` in the same task specification directory as the associated `*.plan.md`, or a generic `*.research.md`) and plan file (`*.plan.md`) to find all Figma URLs: - In the **research file**, look for: - Figma URLs in the `Relevant Links` section. @@ -11,30 +17,30 @@ Before delegating tasks, open the research file (`*.research.md`) and plan file - Figma URLs and design references in `Task details`. - A structured "Design References" subsection mapping views/components to Figma URLs or node IDs. -Use these URLs when delegating to both `tsh-ui-engineer` (implementation context) and `tsh-ui-reviewer` (verification target). +Use these URLs when delegating web/Figma browser work to `tsh-ui-engineer` (implementation context) and `tsh-ui-reviewer` (verification target). For native React Native work, pass a Figma URL only as implementation design context when the plan provides one; do not turn it into a browser verification requirement. ### When Figma link is missing -If you cannot find a Figma URL for a component/section that needs verification: +If you cannot find a Figma URL for a web/Figma component or section that needs verification: 1. **Stop** — do not delegate implementation or verification for that component 2. **Ask the user** to provide the Figma link for the specific section 3. **Wait for the link** before proceeding 4. **Add the link** to the plan file once provided (in `Task details` or `Design References`) -Do NOT skip verification or delegate without a Figma reference. +Do NOT skip web verification or delegate web/Figma review without a Figma reference. Native React Native work follows the target project's design and evidence inputs; this prompt does not invent a browser substitute. ## Workflow -1. **Review the plan** — Review the implementation plan and feature context thoroughly. Identify which tasks are UI implementation tasks (need Figma verification) and which are non-visual tasks. Extract all Figma URLs from the research/plan files. +1. **Review the plan** — Review the implementation plan and feature context thoroughly. Identify web UI tasks (which use Figma/browser verification), rendered React Native UI tasks (which do not use the browser verification contract), and non-visual tasks. Extract all relevant Figma URLs from the research/plan files. 2. **Delegate codebase analysis (if needed)** — Check if the plan file (`*.plan.md`) contains a populated **"Technical Context"** section. If it does, skip this step — the context was already captured during planning. If the section is missing or empty, use `tsh-architect` agent to perform codebase analysis and technical context discovery to establish project conventions, coding standards, architecture patterns, and existing codebase patterns before implementing. -3. **Confirm dev server URL** — **Use `vscode/askQuestions` now** to ask the user for the exact full dev server URL that should be used for verification (e.g., "What exact URL should UI verification use for this page?"). Do not defer this to later — you need the confirmed URL before any verification can start. Do not guess from running processes, project config, or port scans — multiple services may run on different ports. Once confirmed, treat that full URL as a pinned session input and use it unchanged for all subsequent verifications in this session. +3. **Confirm the web verification URL when applicable** — For web/Figma browser work, **use `vscode/askQuestions` now** to ask the user for the exact full dev server URL that should be used for verification (e.g., "What exact URL should UI verification use for this page?"). Do not defer this to later, guess from running processes, project config, or port scans, or switch ports after confirmation. Once confirmed, treat that full URL as a pinned session input and use it unchanged for all subsequent web captures and reviews. This URL is not required for native React Native work; use the target project's native evidence contract when supplied, otherwise record the native verification prerequisite or limitation. -4. **Delegate UI implementation** — For each UI implementation task, delegate to `tsh-ui-engineer` using [tsh-implement-ui-common-task.prompt.md](./tsh-implement-ui-common-task.prompt.md). Pass the relevant Figma URLs, component context, and plan section. For non-Figma frontend and backend tasks, use [tsh-implement-common-task.prompt.md](./tsh-implement-common-task.prompt.md). +4. **Delegate UI implementation** — For each UI implementation task, delegate to `tsh-ui-engineer` using [tsh-implement-ui-common-task.prompt.md](./tsh-implement-ui-common-task.prompt.md). Pass the relevant Figma URLs, component context, and plan section. For native React Native work, pass the target-project profile and any supplied native evidence contract as context; do not require a browser URL. For non-Figma frontend and backend tasks, use [tsh-implement-common-task.prompt.md](./tsh-implement-common-task.prompt.md). -5. **Delegate UI verification** — After each UI implementation task completes, first delegate capture to `tsh-ui-capture-worker` using the pinned user-confirmed full dev server URL from step 3, the Figma URL, the shared verification root, and the current iteration artifact directory. Require the capture worker to prepare or ensure the shared `figma-expected.png` before ACTUAL browser capture starts. Then delegate verification to `tsh-ui-reviewer` using `runSubagent` with [tsh-review-ui.prompt.md](../prompts/tsh-review-ui.prompt.md). Pass: the Figma URL, the same pinned full dev server URL, the component/section name, and the exact artifact directory produced by `tsh-ui-capture-worker`. Forward that exact URL unchanged through every delegate, retry, and capture pass. The ui-reviewer will compare the Figma design against the running implementation and return a structured report. **Note:** The reviewer consumes the shared Figma EXPECTED reference plus caller-provided ACTUAL live-capture artifacts (`actual.png`, `computed-styles.json`, `a11y-snapshot.yml`) produced earlier by `tsh-ui-capture-worker`. No agent in this loop may launch, start, or switch to another local app/server or port once the URL is confirmed. +5. **Delegate web/Figma browser verification** — For web/Figma browser work only, after each UI implementation task completes, first delegate capture to `tsh-ui-capture-worker` using the pinned user-confirmed full dev server URL from step 3, the Figma URL, the shared verification root, and the current iteration artifact directory. Require the capture worker to prepare or ensure the shared `figma-expected.png` before ACTUAL browser capture starts. Then delegate verification to `tsh-ui-reviewer` using `runSubagent` with [tsh-review-ui.prompt.md](../prompts/tsh-review-ui.prompt.md). Pass: the Figma URL, the same pinned full dev server URL, the component/section name, and the exact artifact directory produced by `tsh-ui-capture-worker`. Forward that exact URL unchanged through every delegate, retry, and capture pass. The ui-reviewer will compare the Figma design against the running implementation and return a structured report. **Note:** The reviewer consumes the shared Figma EXPECTED reference plus caller-provided ACTUAL live-capture artifacts (`actual.png`, `computed-styles.json`, `a11y-snapshot.yml`) produced earlier by `tsh-ui-capture-worker`. No agent in this loop may launch, start, or switch to another local app/server or port once the URL is confirmed. - In every delegation, explicitly require the reviewer response to include: `Verification Result`, `Component`, exact `Artifact Directory`, per-file artifact status, and blocker-resolution guidance. Treat an empty response or a response missing any of those fields as an invalid verification result and re-run the reviewer once with the same pinned URL, the same fresh artifact directory, and a stricter handoff. - The caller/orchestrator must not treat its own lack of `figma` tool access as a Figma blocker for UI verification. If a Figma URL is available, delegate review first and let `tsh-ui-reviewer` determine whether `figma` MCP is actually unavailable in its own runtime. @@ -42,14 +48,16 @@ Do NOT skip verification or delegate without a Figma reference. - This step is **delegate-only**. The main/orchestrating agent must not perform UI verification itself and must not substitute code review, type checks, or browser inspection for the delegated `tsh-ui-capture-worker` + `tsh-ui-reviewer` flow. - If `tsh-ui-reviewer` cannot be invoked, if `figma` is unavailable to the reviewer, or if `tsh-ui-capture-worker` cannot be invoked by the caller, treat that as a blocker in the UI gate and report `VERIFICATION NOT RUN`. Do not self-execute a fallback verification path in the caller. -6. **Handle verification results**: +6. **Handle web/Figma verification results**: - If **PASS** → mark the task and its verification step as complete in the plan. Move to the next task. - If **FAIL** → this is NOT a stopping point. Delegate the fix to `tsh-ui-engineer` — pass the **complete** verification report and explicitly instruct the engineer to fix **ALL** listed differences, not just the first one. After the fix, delegate a **fresh capture** to `tsh-ui-capture-worker` using the same pinned full URL and the same Figma URL to ensure the shared `figma-expected.png` still exists (or refresh it if the node changed) and to regenerate `actual.png`, `computed-styles.json`, and `a11y-snapshot.yml`, and only then re-delegate verification to `tsh-ui-reviewer` on those new artifacts. Re-verification must never run on stale artifacts. Then loop again: a single FAIL pass never ends the loop — keep running fix → fresh capture → re-verify until the result is PASS or you have completed **5 full iterations** for this component. Only after 5 completed iterations with remaining mismatches do you open the structured gate below. - If **VERIFICATION NOT RUN** → treat it as a **pre-verification blocker**, not as a failed verification iteration. Resolve the blocker with the user through `vscode/askQuestions` if needed (missing confirmed URL, auth, wrong page state, unreachable page, incomplete artifacts); do not use a freeform text request as a substitute. Regenerate capture via `tsh-ui-capture-worker` using the same pinned full URL, and re-run verification. This state does **not** consume the 5-iteration budget and does **not** enter the post-5-iteration continue/stop/custom gate. Only treat it as `ESCALATED` if the user explicitly acknowledges an unresolved blocker. - If **VERIFICATION NOT RUN** because `tsh-ui-reviewer` reported `figma` MCP unavailability, only then may the caller ask the user to enable Figma MCP or provide an exported reference image. The caller must not raise that blocker based on its own tool availability. - If **VERIFICATION NOT RUN** because the reviewer step itself was not delegated or could not access its required tools/subagents, that is an orchestration blocker. Resolve it through `vscode/askQuestions`; do not let the main agent attempt the verification step directly. -- After 5 failed iterations with remaining mismatches → pause behind a **structured** `vscode/askQuestions` gate. Present a structured summary containing exactly these fields: component or section name, Figma URL, remaining mismatches, what was attempted in each iteration, suspected root cause. + - After 5 failed iterations with remaining mismatches → pause behind a **structured** `vscode/askQuestions` gate. Present a structured summary containing exactly these fields: component or section name, Figma URL, remaining mismatches, what was attempted in each iteration, suspected root cause. + + - For rendered React Native UI, do not use this web/Figma result handling. Native verification is target-project-owned; when no target-project evidence contract is supplied, record `VERIFICATION NOT RUN` or an equivalent explicit native limitation and identify the missing prerequisite. Browser artifacts cannot upgrade that limitation to a native PASS. - The `vscode/askQuestions` gate must offer exactly 3 choices: `continue-with` an explicit additional iteration count, stop and accept the current state as acknowledged `ESCALATED`, or provide a custom instruction. - Record the user's decision and the resulting outcome in the plan's Changelog. - If the extra iteration budget is exhausted and gaps remain, run the same structured `vscode/askQuestions` gate again. @@ -73,9 +81,9 @@ Do NOT skip verification or delegate without a Figma reference. 11. **Delegate code review** — Delegate to `tsh-code-reviewer` agent via [tsh-review.prompt.md](../prompts/tsh-review.prompt.md). Include E2E test execution as part of the review. The code reviewer runs all quality gates (unit, integration, E2E tests, linting, build). -## Verification Loop (MANDATORY — never stop after one pass) +## Web/Figma Verification Loop (MANDATORY - never stop after one pass) -For each UI component, run this loop explicitly: +For each web/Figma browser UI component, run this loop explicitly. Rendered React Native UI does not enter this loop: ```text iteration = 0 @@ -98,7 +106,7 @@ Hard rules for weaker models: - Every iteration regenerates fresh ACTUAL artifacts and ensures the shared `figma-expected.png` exists (refresh only if the Figma node changed); never reuse pre-fix ACTUAL evidence. - `VERIFICATION NOT RUN` (capture / auth / URL blocker) does not consume the 5-iteration budget. -## Verification Rules +## Web/Figma Verification Rules 1. Every UI component must be verified by `tsh-ui-reviewer` — minimum once per component, no exceptions 2. Fix all reported differences — do not skip or rationalize @@ -106,9 +114,9 @@ Hard rules for weaker models: 4. Maximum 5 iterations per component — escalate if still failing 5. Check confidence level — LOW confidence means tool data may be incomplete -## Verification Gate — Do Not Proceed Without Real Verification +## Web/Figma Verification Gate - Do Not Proceed Without Real Verification -**Default to asking (judgment rule, not a checklist):** the blocker cases named in this prompt are only examples. Whenever anything is missing, broken, ambiguous, inconsistent, or unexpected — including situations not listed here — and you cannot complete a real verification with the full artifact base, stop and resolve it through `vscode/askQuestions`. Do not guess, improvise, fabricate, or proceed on partial evidence. Think about whether the evidence actually supports the verdict before continuing. +**Default to asking (judgment rule, not a checklist):** the blocker cases named in this prompt are only examples. Whenever anything is missing, broken, ambiguous, inconsistent, or unexpected - including situations not listed here - and you cannot complete a real web/Figma verification with the full artifact base, stop and resolve it through `vscode/askQuestions`. Do not guess, improvise, fabricate, or proceed on partial evidence. Think about whether the evidence actually supports the verdict before continuing. Before proceeding from a UI verification step to the next task or to code review, confirm that the `tsh-ui-reviewer` actually performed a **real Figma comparison with live-capture artifacts**. A valid verification report must contain: @@ -127,11 +135,11 @@ If `tsh-ui-reviewer` was never actually invoked, if `tsh-ui-capture-worker` was If `tsh-ui-reviewer` returns an empty response, or omits the explicit verdict or artifact-directory contract, treat that exactly like an invalid verification report: rerun once with the same pinned URL and a stricter handoff demanding those fields; if it still fails, keep the item at `VERIFICATION NOT RUN` and resolve the orchestration blocker through `vscode/askQuestions`. -**Never proceed to code review with unverified UI components.** UI verification is a separate gate that must clear first, and it must be reported separately from code review. If verification cannot be completed for a component, document it in the plan's Changelog and get explicit user approval before moving to code review. Type checks, build, unit/integration tests, and code review are NOT UI verification and never substitute for the live-capture + Figma comparison — a layout/CSS/sizing change is not "done" just because it compiles or because code review found the code clean. +**Never proceed to code review with unverified web UI components.** Web UI verification is a separate gate that must clear first, and it must be reported separately from code review. If web verification cannot be completed for a component, document it in the plan's Changelog and get explicit user approval before moving to code review. Type checks, build, unit/integration tests, and code review are NOT UI verification and never substitute for the live-capture + Figma comparison - a layout/CSS/sizing change is not "done" just because it compiles or because code review found the code clean. For native React Native UI, do not claim that this browser gate ran; use target-project-owned evidence or document the explicit limitation. After every UI fix, repeat the capture and reviewer pass on fresh artifacts before treating the item as done. Do not reuse stale evidence, and do not merge the UI gate into the code-review step. -## Fallback: When `tsh-ui-reviewer` Returns Errors +## Web/Figma Fallback: When `tsh-ui-reviewer` Returns Errors If `tsh-ui-reviewer` consistently returns LOW confidence or tool errors: diff --git a/.github/prompts/tsh-review-ui.prompt.md b/.github/prompts/tsh-review-ui.prompt.md index c1d11634..f311e91a 100644 --- a/.github/prompts/tsh-review-ui.prompt.md +++ b/.github/prompts/tsh-review-ui.prompt.md @@ -5,23 +5,27 @@ argument-hint: "[Figma URL or pinned node link] [exact full dev server URL] [com --- -Perform exactly one UI verification pass comparing the current implementation against the Figma design. Report all differences found and never fix code in this workflow. +Perform exactly one web/Figma browser verification pass comparing the current browser implementation against the Figma design. Report all differences found and never fix code in this workflow. This prompt does not provide native React Native verification. + + +- This prompt supports web-compatible Figma verification using a user-confirmed browser URL and the caller-provided browser capture artifacts. +- For rendered React Native UI, do not require a browser URL or Playwright artifacts and do not present browser artifacts as proof of native safe areas, navigation, touch or gesture behavior, device behavior, VoiceOver/TalkBack behavior, or native end-to-end behavior. +- Native simulator/device, accessibility, and end-to-end evidence is target-project-owned. When no explicit target-project evidence contract is supplied, represent native verification as `VERIFICATION NOT RUN` with an explicit target-project prerequisite or limitation; do not claim a native PASS from this prompt. + This prompt can run standalone or as the delegated reviewer step from `/tsh-implement`, but in both modes it must return an explicit, non-empty verdict. An empty response is invalid. -- This verification step is delegate-only. If the caller cannot actually invoke `tsh-ui-reviewer` with its required tools (`figma`), or cannot provide fresh `tsh-ui-capture-worker` artifacts for the current pass, the step is blocked and must return `VERIFICATION NOT RUN`. +- This web verification step is delegate-only. If the caller cannot actually invoke `tsh-ui-reviewer` with its required tools (`figma`), or cannot provide fresh `tsh-ui-capture-worker` artifacts for the current pass, the step is blocked and must return `VERIFICATION NOT RUN`. - The caller must not try to perform UI verification itself as a fallback. -- The caller must run `tsh-ui-capture-worker` first for the current pass and provide the resulting artifact directory to this reviewer. +- For web/Figma browser work, the caller must run `tsh-ui-capture-worker` first for the current pass and provide the resulting artifact directory to this reviewer. This ordering does not apply to native React Native work because this prompt is not a native verifier. -- Required: Figma URL or pinned Figma node link for the exact component or section being verified. -- Required: user-confirmed exact full dev server URL for the page under verification. -- Required: component or section name. -- Required for delegated verification: task ID and iteration number, or an explicit artifact directory path containing `actual.png`, `computed-styles.json`, and `a11y-snapshot.yml` for this pass. -- If the caller supplies a user-confirmed full URL, use that exact URL unchanged. If no URL is supplied in a standalone run, confirm one with the user before capture. +- For web/Figma browser verification: Figma URL or pinned Figma node link for the exact component or section; a user-confirmed exact full browser URL; the component or section name; and, for delegated verification, a task ID and iteration number or an explicit artifact directory containing `actual.png`, `computed-styles.json`, and `a11y-snapshot.yml` for this pass. +- If the caller supplies a user-confirmed browser URL, use that exact URL unchanged. If no browser URL is supplied in a standalone web run, confirm one with the user before capture. +- For rendered React Native UI: do not request a browser URL or browser artifact directory. If this prompt is invoked despite the native boundary, return `VERIFICATION NOT RUN` and identify the target-project native evidence contract as the required prerequisite when none is supplied. @@ -33,11 +37,11 @@ Before starting, load and follow these skills: Follow the 5-step verification process defined in the `tsh-ui-verifying` skill. The skill contains the complete workflow. For this prompt, enforce these additional rules: -1. Validate inputs first: Figma URL, user-confirmed full dev server URL, component name, and artifact-directory context. +1. Validate inputs first for web/Figma browser work: Figma URL, user-confirmed full browser URL, component name, and artifact-directory context. If the supplied implementation is rendered React Native UI, stop this browser workflow and return the native limitation described in ``; do not ask for browser inputs. 2. Get EXPECTED from Figma via `figma`. - MANDATORY: export the node image and save it as the shared `specifications//ui-verification/figma-expected.png` reference before comparison, or reuse that shared file when the Figma URL/node is unchanged. - If Figma cannot be fetched or saved, report `VERIFICATION NOT RUN`. -3. Get ACTUAL from implementation through caller-provided `tsh-ui-capture-worker` CLI artifacts written into `specifications//ui-verification/iteration-/`. +3. Get ACTUAL from a web implementation through caller-provided `tsh-ui-capture-worker` CLI artifacts written into `specifications//ui-verification/iteration-/`. - Required artifact set: `actual.png`, `computed-styles.json`, `a11y-snapshot.yml`. - If the caller did not provide the artifact directory or the artifact set is incomplete, return `VERIFICATION NOT RUN` and instruct the caller to run `tsh-ui-capture-worker` first for the same pinned URL. 4. Compare following the skill's verification categories and tolerances. @@ -102,12 +106,15 @@ Return a Markdown report that always contains every section below in this order, ``` Never omit `Verification Result`, `Component`, `Artifact Directory`, `Artifact Status`, or `Blocker Resolution`. If a value cannot be known, write `UNKNOWN` and explain why. + +For a native React Native limitation report, retain the same report headings but set `Verification Result` to `VERIFICATION NOT RUN`, mark the browser artifacts as `not-applicable`, set the browser URL and artifact directory to `UNKNOWN` or `not-applicable`, and state in `Blocker Resolution` that native evidence is target-project-owned and the missing evidence contract is the next prerequisite. Do not use this report to imply that browser evidence verifies native behavior. - Missing or blocked capture must report `VERIFICATION NOT RUN` with blocker-resolution guidance. - Do not ask for credentials, session details, or blocker-resolution input in plain assistant text before invoking `vscode/askQuestions`. - Do not improvise a fallback verification in the caller. +- This prompt's URL, Figma, and artifact requirements apply only to web-compatible browser verification; native React Native verification remains outside this collection-owned contract. diff --git a/.github/skills/tsh-e2e-testing/SKILL.md b/.github/skills/tsh-e2e-testing/SKILL.md index 6553d0f6..3dec5e1c 100644 --- a/.github/skills/tsh-e2e-testing/SKILL.md +++ b/.github/skills/tsh-e2e-testing/SKILL.md @@ -1,11 +1,15 @@ --- name: tsh-e2e-testing -description: 'E2E testing patterns, verification procedures, and CI readiness checklists using Playwright. Use for writing, debugging, or reviewing end-to-end tests, fixing flaky tests, creating Page Objects, mocking external APIs.' +description: 'E2E testing patterns, verification procedures, and CI readiness checklists for web flows using Playwright. Use for writing, debugging, or reviewing web end-to-end tests, fixing flaky tests, creating Page Objects, mocking external APIs.' user-invocable: false --- # E2E Testing Patterns & Practices +## Platform Boundary + +Playwright is the collection's E2E owner for web flows only. Native React Native E2E, simulator/device automation, and native accessibility/device evidence are target-project responsibilities and outside this collection's promise. Browser E2E and Playwright evidence do not verify native React Native behavior. + ## Page Object Pattern ```typescript diff --git a/.github/skills/tsh-ensuring-accessibility/SKILL.md b/.github/skills/tsh-ensuring-accessibility/SKILL.md index 7f9b4f9f..cf4f114d 100644 --- a/.github/skills/tsh-ensuring-accessibility/SKILL.md +++ b/.github/skills/tsh-ensuring-accessibility/SKILL.md @@ -8,10 +8,14 @@ user-invocable: false Provides WCAG 2.1 AA compliance patterns for building inclusive frontend interfaces with proper semantic markup, keyboard navigation, focus management, and screen reader support. + +The concrete semantic HTML, ARIA, DOM focus, CSS contrast, browser zoom/reflow, browser devtools, and axe-core checks in this skill are web-file guidance. Apply platform-neutral principles where valid, but skip or adapt those web-only checks for native React Native files. For RN files, load `tsh-implementing-react-native` after its target-project profile gate. RN-specific accessibility, forms, lifecycle/hooks, performance, and review guidance comes from that skill and its existing references; do not duplicate it wholesale here. Browser artifacts cannot close native accessibility verification, and packages, versions, and tooling remain target-profile dependent. + + -Start with the correct HTML element. `