Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9113efe
feat: HN news reader with offline-first feed and premium UI
maximcoding Mar 24, 2026
b798a5e
refactor(home): expert-level cleanup — tokens, memo, shared utils, de…
maximcoding Mar 25, 2026
d5e24b3
refactor(nav): migrate to static config (React Navigation 7)
maximcoding Mar 25, 2026
666333c
fix(lint): apply biome format fixes across changed files
maximcoding Mar 25, 2026
446e52e
refactor(shared): promote SectionHeader and useShimmer to shared layer
maximcoding Mar 25, 2026
46da4d7
refactor(auth): replace manual boolean useState with useToggle
maximcoding Mar 25, 2026
d1b137a
refactor(auth): fix service layer issues
maximcoding Mar 25, 2026
4432b17
Scope hooks and constants to feature/session boundaries
maximcoding Mar 25, 2026
004901d
Apply React Native best practices: Pressable and StyleSheet
maximcoding Mar 25, 2026
2f8fda0
Fix inline callbacks and accessibility in settings screens
maximcoding Mar 25, 2026
a3a5052
Move HalfSheet to shared/components/ui (correct layer)
maximcoding Mar 25, 2026
3728e9a
Move GlobalModal to shared/components/ui (correct layer)
maximcoding Mar 25, 2026
6aecf2d
Rename bootstrap route API to initial route; remove dead navigation/o…
maximcoding Mar 25, 2026
d7595d2
Update navigation rules to React Navigation v7 + project state
maximcoding Mar 25, 2026
ee3dc54
Track React Navigation v8 upgrade in TODO (currently alpha)
maximcoding Mar 25, 2026
5f332ba
Navigation dynamic config, theme cleanup, agent/rule updates
maximcoding Mar 25, 2026
e6ab8cf
change splash screen
maximcoding Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ src/
├── shared/ # Cross-cutting code — must NOT import from features
│ ├── components/ui/ # Button, Text, ScreenWrapper, …
│ ├── hooks/
│ ├── constants/ # shared non-config constants (not storage keys)
│ ├── constants/ # shared non-config constants (not storage keys; tag arrays belong in feature api/keys.ts)
│ ├── services/api|storage/
│ ├── stores/
│ ├── theme/
Expand Down
18 changes: 13 additions & 5 deletions .claude/agents/code-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,28 @@ You are a senior code reviewer for this React Native TypeScript project. Your st
- [ ] Strict mode compliance — no implicit `any`, no untyped function returns on exported APIs
- [ ] Zod schemas validate every API response in feature services before returning domain models

### Code Quality
- [ ] No magic numbers in logic — numeric literals with meaning (timeouts, limits, sizes, counts, offsets) must be named constants defined at module or config level
- [ ] No magic strings in logic — non-i18n string literals used as identifiers, keys, or config values must be named constants (e.g. storage keys from `src/config/constants.ts`, route names from `src/navigation/routes.ts`)
- [ ] Functions do one thing — no function exceeds ~40 lines or mixes concerns (data fetching + transformation + UI logic); extract helpers when a function grows beyond a single clear responsibility
- [ ] No deeply nested callbacks or conditionals — flatten with early returns and extracted helpers

### UI & Styling
- [ ] All screens use `ScreenWrapper` as root element
- [ ] No raw hex colors, numeric spacing, or font sizes — `useTheme()` tokens only
- [ ] `StyleSheet.create()` used; inline styles only for dynamically computed values
- [ ] No raw hex colors, numeric spacing, or font sizes — `useTheme()` tokens only; brand colors via `theme.brand.*`
- [ ] `StyleSheet.create()` used; inline styles only for values that are dynamically computed at render time — not for static overrides
- [ ] Repeated style patterns extracted into shared `StyleSheet` entries or shared style constants — no copy-pasted style blocks across components
- [ ] Shared UI components accept strings as props — no hardcoded user-facing copy

### i18n
- [ ] All user-facing strings use `useT('<feature>')` with the correct per-feature namespace
- [ ] Namespace matches the feature directory name (lowercase)
- [ ] All user-facing strings use `useT()` (no argument) and access keys via `t('feature.key')`
- [ ] Key prefix matches the feature directory name (lowercase)

### State & Data
- [ ] Server state via React Query; local UI state via `useState`; global UI state via Zustand in `src/shared/stores/`
- [ ] Query keys defined in feature `api/keys.ts` using `[feature, entity, id?, params?]` format
- [ ] Mutations include `meta.tags` for targeted invalidation
- [ ] Tag arrays (e.g. `AUTH_SESSION_TAGS`) exported from the feature's `api/keys.ts` — not defined inline in hooks or placed in `src/shared/constants/`
- [ ] Mutations include `meta.tags` for targeted invalidation; invalidation uses `invalidateByTags` with the feature's own `tagMap` only — no cross-feature tagMap references
- [ ] MMKV key strings imported from `src/config/constants.ts`, not hardcoded

### React Native Specifics
Expand Down
229 changes: 0 additions & 229 deletions .claude/agents/rn-architect.md

This file was deleted.

75 changes: 63 additions & 12 deletions .claude/rules/navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,19 @@ Global rules: [AGENTS.md](../../AGENTS.md). Claude stack summary: [CLAUDE.md](..
# Rules — navigation

## Structure
- **Root stack** (`src/navigation/root/root-navigator.tsx`): `ROOT_ONBOARDING`, `ROOT_AUTH`, `ROOT_APP`.
- **Bootstrap:** `src/session/useBootstrapRoute.ts` determines the initial route.
- **Routes:** all constants in `src/navigation/routes.ts` — never inline route strings.
- **ParamLists:** per-feature at `src/features/<name>/navigation/param-list.ts`; root shell at `src/navigation/root-param-list.ts`.
- **`NavigationContainer`** lives inside `src/navigation/NavigationRoot.tsx` — do not add a second one.
- **Navigator** (`src/navigation/root/root-navigator.tsx`): JSX `<Stack.Navigator>` + `<Stack.Screen>` pattern. Export `RootNavigator` (the root stack component) and `HomeTabs` (tab navigator component). Create navigators at module level: `const Stack = createNativeStackNavigator<RootStackParamList>()`.
- **Root entry** `src/navigation/NavigationRoot.tsx`: single `<NavigationContainer>` with `ref={navigationRef}` — render exactly once, no second instance.
- **Initial route:** `useInitialRoute()` from `src/session/useInitialRoute.ts` — sync `useState` initializer that reads MMKV once, resolves `ROOT_ONBOARDING | ROOT_AUTH | ROOT_APP`.
- **Routes:** all constants in `src/navigation/routes.ts` — never inline route string literals anywhere.
- **ParamLists:** `RootStackParamList` and `HomeTabParamList` in `src/navigation/root-param-list.ts`; declared manually to avoid circular deps. Global augmentation declared once there:
```ts
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
```
- **Imperative navigation** outside the React tree: use `navigationRef` from `src/navigation/helpers/navigation-helpers.ts`.

## Provider order (must match `App.tsx`)
```
Expand All @@ -22,18 +30,61 @@ GestureHandlerRootView
ErrorBoundary
QueryProvider
OfflineBanner
NavigationRoot
NavigationRoot ← contains NavigationContainer + RootNavigator
```

## Dynamic navigator rules (React Navigation v7)
- Create navigator instances (`const Stack = createNativeStackNavigator<T>()`) at module scope — not inside components.
- Add new screens as `<Stack.Screen name={ROUTES.X} component={ScreenComponent} />` inside the appropriate navigator JSX.
- Share `screenOptions` via the `screenOptions` prop on `<Stack.Navigator>` — do not repeat options on individual screens if they apply to all.
- For auth gating or feature flags: conditionally render `<Stack.Screen>` in JSX — do not call `navigation.navigate()` after auth state changes.
- Nested navigators (e.g. tabs inside a stack screen): create a dedicated component (e.g. `HomeTabs`) and register it as the screen component.

## Params
- Params must be **JSON-serializable** — required for state persistence and deep linking.
- Pass **IDs only**, never full data objects. Fetch data from React Query cache using the ID inside the screen.
- Do not use reserved param keys: `screen`, `params`, `initial`, `state`.
- To update params from within a screen: `navigation.setParams({...})` (merge) or `navigation.replaceParams({...})` (replace).
- To pass data back to a previous screen: `navigation.popTo('ScreenName', { result })`.
- To navigate to a nested screen with params: `navigation.navigate('Parent', { screen: 'Child', params: { id } })`.

## Navigation actions
- `navigate()` — standard transition; no-op if already on that screen (safe, no duplication).
- `push()` — forces a new instance of the same screen; use when multiple instances are needed.
- `goBack()` — standard back; hardware back and swipe gestures call this automatically.
- `popTo('ScreenName')` — jump back to a specific screen, skipping intermediates.
- `popToTop()` — reset a stack to its root screen.

## Screen lifecycle
- Use `useFocusEffect(useCallback(() => { ... return cleanup }, []))` for side effects that must run on focus (data refresh, analytics). Always return a cleanup function.
- Use `useIsFocused()` when a component must re-render on focus state change.
- Use `navigation.addListener('focus' | 'blur', cb)` inside `useEffect` — always return the unsubscribe result.
- Do not use plain `useEffect` for focus-aware operations without listener subscriptions.

## TypeScript
- ParamList entries must use `type`, never `interface`.
- Do not annotate `useNavigation<T>()` with a specific type — declare the global `RootParamList` instead.
- Do not annotate `useRoute<T>()` — use `route` from screen props (`NativeStackScreenProps<RootStackParamList, 'RouteName'>`).
- Use `CompositeScreenProps` for screens inside nested navigators that need access to parent navigation.
- Use `NavigatorScreenParams<ChildParamList>` when a parent ParamList entry wraps a nested navigator.

## Half-sheet modals
Use `presentation: 'transparentModal'`, `animation: 'none'`, `gestureEnabled: false` (see `HALF_SHEET_OPTIONS` in `root-navigator.tsx`).
- Register with `presentation: 'transparentModal'`, `animation: 'none'`, `gestureEnabled: false` (see `HALF_SHEET_OPTIONS` in `root-navigator.tsx`).
- Use `HalfSheet` from `src/shared/components/ui/HalfSheet.tsx` as the content wrapper.

## Must
- All route constants from `src/navigation/routes.ts` — never use inline string literals for routes.
- New screens: register in `routes.ts` and add the ParamList entry to the feature's `navigation/param-list.ts`.
- Use `navigationRef` from `src/navigation/helpers/navigation-helpers.ts` for imperative navigation outside React tree.
- All route constants from `src/navigation/routes.ts` — never inline strings.
- New screens: add constant to `routes.ts`, add `ParamList` entry to `root-param-list.ts`, register in `root-navigator.tsx`.
- `navigationRef` for imperative navigation outside the React tree.
- `useFocusEffect` + `useCallback` for focus-scoped side effects.
- `InteractionManager.runAfterInteractions` for heavy non-UI work triggered by navigation.

## Must not
- Do not nest a second `NavigationContainer`.
- Do not navigate from `src/shared/**` directly — navigate only via helpers or callbacks passed in as props.
- Do not render a second `<NavigationContainer>` — one root only.
- Do not call `navigation.navigate()` after an auth state change — conditional screen rendering handles it.
- Do not nest navigators of the same type (tabs-in-tabs, stack-in-stack at the same level).
- Do not create a nested navigator solely for code organization — group screens under a shared `screenOptions` prop instead.
- Do not navigate from `src/shared/**` directly — pass callbacks as props or use `navigationRef` helpers.
- Do not add navigation logic inside `src/shared/components/ui/` components.
- Do not pass full data objects as params — pass IDs and fetch data inside the screen.
- Do not leave empty directories under `src/navigation/` — remove the folder when the last file is deleted.
1 change: 1 addition & 0 deletions .claude/rules/react-query.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Never hardcode numeric stale/gc values — always reference a `Freshness` profil

## Must not
- No React Query keys inline in components — always use `api/keys.ts`.
- No tag arrays (e.g. `['auth:me', 'auth:session']`) inline in hooks — export named arrays from the feature's `api/keys.ts` and import from there.
- No `invalidateQueries()` without a targeted key — use tag-based invalidation.
- No magic `staleTime` / `gcTime` numbers — use `Freshness` profiles.
- No server data duplicated in Zustand.
2 changes: 1 addition & 1 deletion .claude/rules/shared-services.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ shared/services/
- All HTTP calls must go through `http/http.client.ts` (exported `httpClient`) / `http/api.ts` helpers or a `transport/` adapter — never bare `fetch`.
- Every adapter must pass responses through `src/shared/utils/normalize-error.ts` for consistent error shapes.
- React Query client configuration (staleTime, retry, persistence) must live in `query/policy/`, `query/client/`, or `query/persistence/` — not scattered across feature hooks.
- Tag-based cache invalidation must use `query/helpers/invalidate-by-tags.ts` and tag constants from `query/tags.ts`.
- Tag-based cache invalidation must use `query/helpers/invalidate-by-tags.ts`. The `Tag`, `TagMap`, and `KeyGetter` types live in `query/tags.ts`; feature tag arrays (e.g. `AUTH_SESSION_TAGS`) and tagMaps belong in each feature's `api/keys.ts`.
- MMKV key strings must be imported from `src/config/constants.ts`.
- Sentry calls (`captureException`, `captureMessage`) must go through `monitoring/sentry.ts` helpers — do not call the Sentry SDK directly in feature code.
- The mock transport adapter (`transport/adapters/mock.adapter.ts`) is dev-only; it must be gated by `flags.USE_MOCK` from `src/config/constants.ts`.
Expand Down
29 changes: 29 additions & 0 deletions .claude/skills/rn-code-reviewer/CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# RN Starter Review Checklist

Use this as a concise pass/fail checklist during reviews.

## Correctness
- [ ] Logic changes handle happy path and edge cases.
- [ ] Async flows handle loading, success, and error states.
- [ ] Errors are not swallowed; context is preserved.

## Architecture and imports
- [ ] `src/shared` does not import feature code.
- [ ] Imports use `@/` aliases, not deep relative paths.
- [ ] New feature code is placed under `src/features/<feature>/...`.

## RN UI and theming
- [ ] No raw colors/spacing/fonts in UI code.
- [ ] Styling uses theme tokens and `StyleSheet.create()`.
- [ ] Platform-specific behavior is handled where required.

## Data and state
- [ ] Server state uses React Query patterns from feature `api/keys.ts`.
- [ ] Mutations include targeted invalidation patterns.
- [ ] No server/domain data is moved into Zustand global stores.
- [ ] No direct `fetch` usage; transport/API layer is used.

## Tests
- [ ] Non-trivial behavior changes include tests.
- [ ] Missing tests are called out with exact suggested scenarios.
- [ ] Residual risk and untested paths are explicitly noted.
82 changes: 82 additions & 0 deletions .claude/skills/rn-code-reviewer/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
name: rn-code-reviewer
description: Reviews React Native bare-workflow changes in this starter for correctness, regressions, architecture boundaries, React Query usage, theming, and test coverage. Use when reviewing PRs, diffs, or requested code reviews.
---

# RN Code Reviewer

Project-specific code review workflow for this React Native starter.

## When to apply

Use this skill when:
- Reviewing pull requests or local diffs
- Asked to "review" code quality, bugs, or regressions
- Validating RN architecture and repo conventions before merge

## Review priorities

Always prioritize findings in this order:
1. Correctness and behavioral regressions
2. Security/privacy risks and error handling gaps
3. Architecture and boundary violations
4. Performance concerns with real user impact
5. Missing or weak tests

## Required checks

### 1) Correctness and risk
- Verify changed logic matches intended behavior and handles edge cases.
- Flag silent failures, swallowed errors, or lossy error handling.
- Check async flows for loading, success, and error-state handling.

### 2) Repository architecture constraints
- Enforce feature/shared boundaries (`src/shared` must not import from features).
- Enforce alias imports (`@/`); flag deep relative imports.
- Verify feature code stays under `src/features/<feature>/...` with expected folders.

### 3) React Native UI conventions
- Flag raw colors/spacing/fonts in UI; require theme tokens.
- Prefer `StyleSheet.create()` over broad inline styles (except dynamic values).
- Verify platform-specific behavior is handled where needed (`Platform.select`, native differences).

### 4) Data/state conventions
- Server state should use React Query (feature-level `api/keys.ts` key patterns).
- Mutations should use targeted invalidation (`meta.tags` / invalidation paths).
- Flag server data leaking into Zustand stores.
- Flag direct `fetch` usage (project transport layer should be used).

### 5) Tests and verification
- Ensure non-trivial logic changes include tests or justify why not.
- Suggest concrete missing tests (what behavior, where to add).
- Highlight risky untested paths before merge.

## Output format

Return findings first, ordered by severity:

1. `Critical` — must fix before merge
2. `Major` — high-impact issues, should fix
3. `Minor` — quality/maintainability improvements

For each finding include:
- File path
- Why it is a problem (risk/regression)
- Specific fix direction

Then include:
- Open questions/assumptions
- Brief change summary (secondary)
- Residual testing gaps

## Review behavior rules

- Do not lead with a broad summary before findings.
- Be explicit about potential regressions and user impact.
- Prefer actionable, minimal suggestions over large refactors.
- If no issues are found, explicitly state "No findings" and note residual risks or test gaps.

## Additional reference

Use this checklist for quick pass/fail scanning:
- [CHECKLIST.md](CHECKLIST.md)
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ When changing SVGs, run `npm run gen:icons`. When changing i18n keys, run `npm r
- **src/i18n/** — useT, locales, extraction.
- **src/shared/components/ui/** — Reusable UI primitives (Button, Text, ScreenWrapper, …). If a component needs sharing, it either belongs here (truly generic) or stays in the feature that owns it — there is no intermediate category.
- **src/shared/hooks/** — Shared hooks.
- **src/shared/constants/** — Shared non-config constants (e.g. query invalidation tag lists). Storage key names and env-backed flags stay in **`src/config/`**.
- **src/shared/constants/** — Shared non-config constants. Storage key names and env-backed flags stay in **`src/config/`**. Tag arrays for cache invalidation (e.g. `AUTH_SESSION_TAGS`) belong in each feature's `api/keys.ts` — not here.
- **src/shared/services/api/** — HTTP, transport, query client, network, offline.
- **src/shared/services/monitoring/** — Optional crash reporting (e.g. Sentry init); see `docs/OPERATIONS.md#sentry`.
- **src/shared/services/storage/** — MMKV, cache, Zustand persistence adapter.
Expand Down Expand Up @@ -73,6 +73,7 @@ Use path alias `@/` only (e.g. `@/navigation/`, `@/session/`, `@/config/`, `@/i1
- No server data in Zustand; no feature logic in `src/shared/components/ui/` or `src/shared/stores/`.
- No deep relative imports; path aliases only (`check:imports`).
- When adding/changing SVGs: run `npm run gen:icons` and keep `check:icons` passing.
- No empty directories in `src/`. When moving or deleting files, remove the containing folder if it becomes empty. Empty folders signal dead code and confuse codebase navigation.

## When adding

Expand Down
1 change: 0 additions & 1 deletion App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export default function App() {
// Android: exit app from root-level leaves (main tabs, login, onboarding).
useBackButtonHandler(
routeName =>
routeName === ROUTES.HOME_TABS ||
routeName === ROUTES.TAB_HOME ||
routeName === ROUTES.TAB_SETTINGS ||
routeName === ROUTES.AUTH_LOGIN ||
Expand Down
Binary file modified assets/bootsplash/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/bootsplash/logo@1,5x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/bootsplash/logo@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/bootsplash/logo@3x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/bootsplash/logo@4x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions assets/icons.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
// AUTO-GENERATED FILE — DO NOT EDIT MANUALLY
// Run: npm run gen:icons

import Check from '@assets/svgs/check.svg'
import Globe from '@assets/svgs/globe.svg'
import Home from '@assets/svgs/home.svg'
import Info from '@assets/svgs/info.svg'
import Layers from '@assets/svgs/layers.svg'
import Logout from '@assets/svgs/logout.svg'
import Moon from '@assets/svgs/moon.svg'
import Settings from '@assets/svgs/settings.svg'
import Sun from '@assets/svgs/sun.svg'
import User from '@assets/svgs/user.svg'

export enum IconName {
CHECK = 'CHECK',
GLOBE = 'GLOBE',
HOME = 'HOME',
INFO = 'INFO',
LAYERS = 'LAYERS',
LOGOUT = 'LOGOUT',
MOON = 'MOON',
SETTINGS = 'SETTINGS',
SUN = 'SUN',
USER = 'USER',
}

export const AppIcon = {
[IconName.CHECK]: Check,
[IconName.GLOBE]: Globe,
[IconName.HOME]: Home,
[IconName.INFO]: Info,
[IconName.LAYERS]: Layers,
[IconName.LOGOUT]: Logout,
[IconName.MOON]: Moon,
[IconName.SETTINGS]: Settings,
[IconName.SUN]: Sun,
[IconName.USER]: User,
} as const

Expand Down
3 changes: 3 additions & 0 deletions assets/svgs/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/svgs/globe.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading