Skip to content
Merged
Show file tree
Hide file tree
Changes from 115 commits
Commits
Show all changes
166 commits
Select commit Hold shift + click to select a range
ec19041
docs(billing): specify Apple credit purchases
iscekic May 6, 2026
348bf02
feat(db): add Apple IAP purchase tables
iscekic May 6, 2026
724094f
feat(web): define Apple credit products
iscekic May 6, 2026
cd05810
feat(web): add Apple signed data verifier
iscekic May 6, 2026
f0681cb
feat(web): process Apple credit purchases
iscekic May 6, 2026
f877f8f
feat(web): expose Apple credit purchase APIs
iscekic May 6, 2026
ca2c87b
feat(web): handle Apple IAP notifications
iscekic May 6, 2026
00f815e
feat(mobile): add Apple credit purchase hooks
iscekic May 6, 2026
e3b14c3
feat(mobile): add Apple credit purchase UI
iscekic May 6, 2026
34ca77a
fix(mobile): stabilize Apple credit purchase hooks
iscekic May 6, 2026
72128b2
docs(mobile): document Apple credit purchase setup
iscekic May 6, 2026
471bbc0
fix: stabilize Apple credit purchase verification
iscekic May 6, 2026
d658a90
fix: address Apple IAP review feedback
iscekic May 6, 2026
1a57af8
fix(mobile): remove home welcome headline
iscekic May 6, 2026
1ef7981
fix(mobile): remove kilo chat debug logs
iscekic May 6, 2026
1124806
fix(mobile): enable expo iap config plugin
iscekic May 6, 2026
147dc4c
fix(mobile): handle apple purchases from storekit events
iscekic May 6, 2026
a140354
chore(mobile): align expo sdk dependencies
iscekic May 6, 2026
5070823
fix(mobile): recover unfinished apple credit purchases
iscekic May 6, 2026
0fc605a
fix(kilo-pass): remove one-time apple credit purchases
iscekic May 6, 2026
18aaf73
feat(kilo-pass): add app store subscriptions
iscekic May 6, 2026
8d3fa34
fix(kilo-pass): order app store subscriptions
iscekic May 6, 2026
33f1fa5
fix(mobile): make Kilo Pass purchase sheet native
iscekic May 6, 2026
b77479a
fix(mobile): remove web parity Kilo Pass copy
iscekic May 6, 2026
a3438c4
fix(mobile): add Kilo Pass explainer link
iscekic May 6, 2026
4b8c7dc
fix(mobile): update Kilo Pass teaser copy
iscekic May 6, 2026
5b2a063
fix(mobile): route web Kilo Pass management
iscekic May 6, 2026
39a64b3
fix(mobile): recover Kilo Pass store purchases
iscekic May 6, 2026
88d0a49
fix(mobile): sign out on unauthorized tRPC queries
iscekic May 6, 2026
0c5228a
fix(mobile): move KiloClaw skeleton to top
iscekic May 6, 2026
8806841
fix(mobile): add agent empty state CTA
iscekic May 6, 2026
5dafc8b
fix(kilo-pass): make store completion idempotent
iscekic May 6, 2026
96cd281
fix(web): show account on device authorization
iscekic May 6, 2026
9c418fa
docs: revert KiloClaw billing spec change
iscekic May 6, 2026
ae2b116
fix(mobile): open App Store Kilo Pass management
iscekic May 7, 2026
57eb120
fix(billing): expose monthly App Store Kilo Pass plans
iscekic May 7, 2026
cecd28e
fix(billing): track store Kilo Pass monthly streaks
iscekic May 7, 2026
d29cb41
fix(mobile): make Kilo Pass store sheet tappable
iscekic May 7, 2026
4431146
fix(mobile): present Kilo Pass as native modal
iscekic May 7, 2026
b418a52
docs(mobile): clarify implementation principles
iscekic May 7, 2026
b9871fa
fix(mobile): simplify Kilo Pass pricing cards
iscekic May 7, 2026
19397f6
fix(mobile): remove Kilo Pass info link
iscekic May 7, 2026
8302da3
fix(mobile): ignore App Store purchase cancellation
iscekic May 7, 2026
568a7dc
docs(mobile): require press feedback
iscekic May 7, 2026
0e13374
fix(mobile): add Kilo Pass press haptics
iscekic May 7, 2026
721a72a
fix(mobile): dismiss Kilo Pass after purchase
iscekic May 7, 2026
281bd38
fix(kilo-pass): remove yearly App Store products
iscekic May 7, 2026
e3108ad
fix(mobile): load Kilo Pass products as one query
iscekic May 7, 2026
f999170
fix(mobile): guard Kilo Pass purchase dismissal
iscekic May 7, 2026
def25f3
fix(kilo-pass): attach App Store purchases to users
iscekic May 7, 2026
6b883db
fix(mobile): keep Kilo Pass purchase on profile
iscekic May 7, 2026
f0899d8
fix(kilo-pass): handle App Store auto-renew cancellation
iscekic May 7, 2026
b9e0390
chore(web): update Apple server SDK
iscekic May 7, 2026
d6a3f63
refactor(web): centralize Apple Store SDK setup
iscekic May 7, 2026
be65b99
refactor(web): use Apple notification enums
iscekic May 7, 2026
daedd75
fix(web): ignore App Store consumption requests
iscekic May 7, 2026
a240269
fix(web): keep failed App Store renewals active
iscekic May 7, 2026
faed523
fix(kilo-pass): apply App Store upgrades
iscekic May 7, 2026
0dca3e9
fix(kilo-pass): prorate App Store upgrades
iscekic May 7, 2026
216db4e
Merge remote-tracking branch 'origin/main' into feat/apple-iap-person…
iscekic May 8, 2026
812ac4d
fix(mobile): dismiss Kilo Pass sheet after purchase
iscekic May 8, 2026
7ad755c
feat(kilo-pass): add dev App Store refund request
iscekic May 8, 2026
0cd199d
fix(mobile): coalesce Kilo Pass purchase completion
iscekic May 8, 2026
3412154
fix(kilo-pass): decline App Store refund requests
iscekic May 8, 2026
b4230e4
fix(mobile): recover Kilo Pass purchases on app start
iscekic May 8, 2026
83cd484
fix(kilo-pass): reverse app store refund credits
iscekic May 8, 2026
ae12aec
fix(kilo-pass): route store-managed pass actions
iscekic May 8, 2026
d8123dd
fix(mobile): show pending Kilo Pass cancellation
iscekic May 8, 2026
6a31f99
fix(mobile): show Kilo Pass cancellation date
iscekic May 8, 2026
421b2a6
fix(mobile): hide canceled Kilo Pass as active
iscekic May 8, 2026
93fdcbe
fix(kilo-pass): keep purchase sheet open after completion
iscekic May 8, 2026
8e63496
fix(mobile): quiet Kilo Pass ownership recovery
iscekic May 8, 2026
4fa4f29
fix(mobile): show App Store Kilo Pass ownership
iscekic May 8, 2026
bd0eddf
fix(kilo-pass): reset to profile after purchase
iscekic May 8, 2026
d9baead
fix(kilo-pass): make profile top route after purchase
iscekic May 8, 2026
54135fd
Merge remote-tracking branch 'origin/main' into feat/apple-iap-person…
iscekic May 8, 2026
72d41d1
fix(mobile): hide internal Kilo Pass ownership helpers
iscekic May 11, 2026
293b535
test(kilo-pass): freeze promo cutoff assertions
iscekic May 11, 2026
b0655cc
fix(kilo-pass): prevent store subscription reassignment
iscekic May 11, 2026
3fe1bad
fix(kilo-pass): serialize store completions per user
iscekic May 11, 2026
0bdaab8
fix(kilo-pass): reject expired app store transactions
iscekic May 11, 2026
93d4f93
fix(kilo-pass): scope app store notification updates
iscekic May 11, 2026
5ae9b33
fix(kilo-pass): reject mismatched app store renewals
iscekic May 11, 2026
cb59d7e
fix(kilo-pass): reverse issued app store credits
iscekic May 11, 2026
c52aca8
fix(kilo-pass): keep upgraded store issuance current
iscekic May 11, 2026
b65359b
fix(kilo-pass): compute store managed state from purchases
iscekic May 11, 2026
24bdd28
fix(mobile): centralize store purchase handling
iscekic May 11, 2026
4d89621
fix(user): scrub app store data on soft delete
iscekic May 11, 2026
8d38a8d
fix(kilo-pass): redact app store purchase payloads
iscekic May 11, 2026
6b3c276
fix(kilo-pass): handle app store auto-renew enabled
iscekic May 11, 2026
5c41dcb
fix(kilo-pass): claim app store notifications atomically
iscekic May 11, 2026
8fb5c9e
fix(kilo-pass): block stripe portal for store subscriptions
iscekic May 11, 2026
2cf53e7
fix(kilo-pass): expose nullable stripe subscription id
iscekic May 11, 2026
15ddd7b
fix(kilo-pass): sanitize app store completion errors
iscekic May 11, 2026
12be498
fix(mobile): handle unauthorized mutation errors
iscekic May 11, 2026
0eb3554
fix(mobile): show stripe kilo pass management on android
iscekic May 11, 2026
be9ea41
fix(mobile): use backend store product ids
iscekic May 11, 2026
4b3dd2d
fix(mobile): clear stale store product errors
iscekic May 11, 2026
b1b70d8
fix(mobile): skip ownership preflight for subscribe card
iscekic May 11, 2026
b2f58d9
fix(mobile): parse kilo pass backend timestamps
iscekic May 11, 2026
ee2def6
fix(mobile): label kilo pass card actions
iscekic May 11, 2026
f5d5fd5
fix(mobile): clarify kilo pass purchase loading states
iscekic May 11, 2026
262944d
fix(mobile): make profile modal content scrollable
iscekic May 11, 2026
d3a304b
fix(kilo-pass): include store upgrade adjustments in history
iscekic May 11, 2026
10815fe
Revert "fix(kilo-pass): include store upgrade adjustments in history"
iscekic May 11, 2026
d0a9f1f
test(kilo-pass): cover upgraded store credit history
iscekic May 11, 2026
1fa3d3e
fix(kilo-pass): index latest store purchases
iscekic May 11, 2026
95bc4d9
fix(mobile): cache store product metadata briefly
iscekic May 11, 2026
e167b7b
docs(mobile): update app store kilo pass setup
iscekic May 11, 2026
7fb5d67
fix(mobile): keep google play kilo pass state inert
iscekic May 11, 2026
2a6b025
fix(gastown): narrow container fetch typings
iscekic May 11, 2026
fb5d2d8
fix(kilo-pass): memoize app store sdk setup
iscekic May 11, 2026
f6dc7ad
fix(mobile): batch store recovery invalidation
iscekic May 11, 2026
3eae6db
fix(device-auth): redirect expired sessions to sign in
iscekic May 11, 2026
c9cc62e
fix(kilo-pass): label settings icon button
iscekic May 11, 2026
d64e1a5
fix(db): squash App Store migrations
iscekic May 11, 2026
71a34ed
fix(kilo-pass): handle expired App Store notifications
iscekic May 11, 2026
ed58564
fix(mobile): keep kilo pass card types internal
iscekic May 11, 2026
b404237
docs(mobile): explain dev storekit refund gate
iscekic May 11, 2026
a631f59
test(kilo-pass): clarify refund decline policy
iscekic May 11, 2026
4639ca3
refactor(kilo-pass): remove redundant apple notification cast
iscekic May 11, 2026
0fa60df
refactor(kilo-pass): remove notification user fallback
iscekic May 11, 2026
192d62b
refactor(kilo-pass): reuse apple environment normalization
iscekic May 11, 2026
1aae89b
refactor(kilo-pass): validate store payload redaction input
iscekic May 11, 2026
fdb8cb2
refactor(kilo-pass): share subscription accounting helpers
iscekic May 11, 2026
890318f
refactor(kilo-pass): dedupe router subscription state
iscekic May 11, 2026
af15eb7
fix(kilo-pass): reverse app store refunds from transaction price
iscekic May 11, 2026
26ce506
fix(kilo-pass): scope app store refund reversals to transaction
iscekic May 11, 2026
4206acc
fix(kilo-pass): ignore stale app store purchases after terminal events
iscekic May 11, 2026
91b6ded
fix(kilo-pass): expire stale store subscriptions from purchase expiry
iscekic May 11, 2026
ae7911b
fix(kilo-pass): preserve store subscription start date
iscekic May 11, 2026
7f665d2
fix(mobile): keep app store purchase locked until callback
iscekic May 11, 2026
0894945
fix(mobile): retry failed app store purchase recovery
iscekic May 11, 2026
b5348e7
fix(mobile): avoid subscribe card while kilo pass state loads
iscekic May 11, 2026
64f6b61
fix(mobile): show app store kilo pass status on android
iscekic May 11, 2026
cbf48aa
fix(mobile): show app store ownership mismatch before purchase
iscekic May 11, 2026
981c0ee
fix(mobile): ignore unknown app store purchase callbacks
iscekic May 11, 2026
f9b1de9
fix(mobile): hide stale kilo pass products on store errors
iscekic May 11, 2026
683a30c
fix(kilo-pass): avoid stripe controls for google play
iscekic May 11, 2026
113dcdd
fix(kilo-pass): rewrite store upgrades in original period
iscekic May 11, 2026
4e30742
fix(kilo-pass): retry in-flight app store notifications
iscekic May 11, 2026
1d929a0
fix(db): enforce kilo pass provider subscription ids
iscekic May 11, 2026
4be0f05
fix(db): constrain kilo pass store purchase ownership
iscekic May 11, 2026
7ca4208
fix(kilo-pass): include store upgrade ledger in history
iscekic May 11, 2026
b9c00ab
chore(db): squash kilo pass migrations
iscekic May 11, 2026
50d478f
test(kilo-pass): align ci fixtures with provider ids
iscekic May 12, 2026
3e2fad9
Merge remote-tracking branch 'origin/main' into feat/apple-iap-person…
iscekic May 12, 2026
a036e6e
Merge remote-tracking branch 'origin/main' into feat/apple-iap-person…
iscekic May 12, 2026
34fc5a2
chore: allow local network dev origins
iscekic May 12, 2026
b1f7ab9
fix(kilo-pass): handle non-USD App Store refunds without retry loop
iscekic May 12, 2026
b51c907
test(kilo-pass): assert exact EUR base-clawback amount
iscekic May 12, 2026
3e79a4b
fix(kilo-pass): surface store-managed subs in cancelAndRefundKiloPass…
iscekic May 12, 2026
11e6767
fix(admin): shorten store-managed badge label, move guidance to row n…
iscekic May 12, 2026
ec9ff8f
refactor(kilo-pass): atomically process Apple refund and renewal noti…
iscekic May 12, 2026
e4d06c0
refactor(kilo-pass): widen completeStoreKiloPassPurchase tx type to D…
iscekic May 12, 2026
9ada85c
refactor(kilo-pass): narrow completeStoreKiloPassPurchase tx param to…
iscekic May 12, 2026
cff6b26
fix(kilo-pass): distinguish missing App Store account token from acco…
iscekic May 12, 2026
532c8ce
chore(kilo-pass): document read-path safety-net expiration write
iscekic May 12, 2026
f3af56c
test(kilo-pass): cover store-managed subscriber guard on Stripe-only …
iscekic May 12, 2026
25fa52b
chore(kilo-pass): clarify mapAppleKiloPassTransaction expiry semantics
iscekic May 12, 2026
1d48d74
fix(mobile): clear purchase error toast dedup on auth change
iscekic May 12, 2026
5dbb6da
refactor(kilo-pass): move store-subscription expiry write into a cron
iscekic May 13, 2026
395a313
chore(db): drop 0124 migration to renumber after main merge
iscekic May 13, 2026
e74c8d5
Merge remote-tracking branch 'origin/main' into feat/apple-iap-person…
iscekic May 13, 2026
a34718f
chore(db): regenerate kilo-pass store migration as 0125 after main merge
iscekic May 13, 2026
a3939a1
fix(db): order FK after referenced unique index in 0125 migration
iscekic May 13, 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
9 changes: 8 additions & 1 deletion .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ STRIPE_KILO_PASS_TIER_49_MONTHLY_PRICE_ID=price_test_tier_49_monthly
STRIPE_KILO_PASS_TIER_49_YEARLY_PRICE_ID=price_test_tier_49_yearly
STRIPE_KILO_PASS_TIER_199_MONTHLY_PRICE_ID=price_test_tier_199_monthly
STRIPE_KILO_PASS_TIER_199_YEARLY_PRICE_ID=price_test_tier_199_yearly
# App Store Kilo Pass subscriptions
APPLE_IAP_ENVIRONMENT=Sandbox
APPLE_APP_APPLE_ID=
APPLE_ROOT_CERTIFICATES_PEM=
APPLE_IAP_KEY_ID=
APPLE_IAP_ISSUER_ID=
APPLE_IAP_PRIVATE_KEY=
# KiloClaw pricing (test values)
STRIPE_KILOCLAW_EARLYBIRD_PRICE_ID=price_test_kiloclaw_earlybird
STRIPE_KILOCLAW_EARLYBIRD_COUPON_ID=coupon_test_kiloclaw_earlybird
Expand Down Expand Up @@ -152,4 +159,4 @@ ABUSE_SERVICE_CF_ACCESS_CLIENT_SECRET=
# Feature flags
KILOCLAW_BILLING_ENFORCEMENT=false
# Debug options
DEBUG_SHOW_DEV_UI=1
DEBUG_SHOW_DEV_UI=1
22 changes: 21 additions & 1 deletion apps/mobile/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,18 @@ npx expo install --dev <package-name> # devDependencies

After installing or upgrading dependencies, run `pnpx expo-doctor` and fix any issues it reports (version mismatches, duplicate deps, etc.).

## Implementation Principles

- Implement features in the simplest boring way that preserves the requested behavior. Avoid speculative abstractions, defensive layers, and "just in case" code paths.
- Keep code DRY when behavior or contracts are actually shared. Prefer one shared helper, schema, or type over duplicated local copies.
- Define shared types at the ownership boundary instead of recreating the same shape in mobile code. Use existing package exports, tRPC router output types, or a new shared contract when multiple surfaces need the same shape.
- Parse untrusted HTTP inputs with Zod at the boundary where data enters the system. After data has crossed a trusted tRPC or shared-package boundary, rely on TypeScript rather than re-parsing the same value in the mobile app.
- Do not add defense-in-depth validation inside mobile components or hooks unless the data source is genuinely untrusted or the extra check handles a real user-visible failure mode.

## Data Fetching

- When you need data from the backend, **always add a new tRPC procedure** rather than copying data or inventing client-side alternatives. The app uses tRPC with React Query — adding a procedure is cheap and keeps the source of truth on the server.
- When a component takes backend data as props, derive the prop types from the tRPC router's return types (e.g., `NonNullable<ReturnType<typeof useMyQuery>['data']>`) instead of manually copying type definitions. This keeps types in sync with the backend automatically.
- When a component takes backend data as props, derive the prop types from the tRPC router's return types (e.g., `NonNullable<ReturnType<typeof useMyQuery>['data']>`) or an existing shared type instead of manually copying type definitions. This keeps types in sync with the backend automatically.
- **Never use `new Date()` on any date or timestamp string from the backend.** Hermes cannot reliably parse PostgreSQL timestamps (`2026-03-13 14:30:00+00`) or date-only strings (`2026-09-26`). Always use `parseTimestamp()` from `@/lib/utils` — it handles both formats.

### Mutations
Expand Down Expand Up @@ -77,6 +85,18 @@ After installing or upgrading dependencies, run `pnpx expo-doctor` and fix any i

## UX Patterns

### Native Feel

- Prefer native-feeling platform experiences styled with Kilo tokens and existing app components. Start with the simplest platform-expected interaction, then apply Kilo spacing, color, type, and icon conventions.
- For platform primitives such as sheets, alerts, pickers, tabs, gestures, and keyboard behavior, use established native or app-standard components that preserve expected gestures and accessibility.
- Avoid custom replacements that need workarounds, omit standard gestures, or add complexity without a product reason. A plain sheet that behaves correctly on iOS and Android is better than a bespoke sheet that looks clever but feels broken.

### Press Feedback

- Every pressable surface must notify the user when the press gesture lands. Use the feedback that best fits the surface: pressed opacity, native ripple, haptics, state change, navigation transition, loading state, or another platform-appropriate response.
- Do not add redundant feedback. If the press immediately causes a clear visual transition or native control response, that can be enough; if the surface otherwise feels inert, add explicit pressed styling or haptics.
- Keep feedback native-feeling and lightweight. Avoid custom animations or haptic patterns that make simple list rows, cards, or buttons feel heavier than the action deserves.

### Icons

- Use `lucide-react-native` for all icons. Never use emoji as UI elements.
Expand Down
25 changes: 25 additions & 0 deletions apps/mobile/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,28 @@ Generally speaking, you only need a new dev build if making dependency/native ch
3. create a new dev build using `pnpm build:ios`
4. `pnpm start`
5. open installed app on your phone

## App Store Kilo Pass Subscriptions

App Store Kilo Pass subscriptions require an EAS development build or TestFlight
build with the in-app purchase capability enabled. Expo Go is not supported for
this feature.

Configured auto-renewable subscription product IDs:

- `kilopass.tier19.monthly.v1`
- `kilopass.tier49.monthly.v1`
- `kilopass.tier199.monthly.v1`

Use App Store Connect sandbox tester accounts for local and TestFlight sandbox
verification. Configure App Store Server Notifications V2 to post to
`/api/kilo-pass/apple/notifications`.

Backend environment variables:

- `APPLE_IAP_ENVIRONMENT`
- `APPLE_APP_APPLE_ID`
- `APPLE_ROOT_CERTIFICATES_PEM`
- `APPLE_IAP_KEY_ID`
- `APPLE_IAP_ISSUER_ID`
- `APPLE_IAP_PRIVATE_KEY`
1 change: 1 addition & 0 deletions apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ const config: ExpoConfig = {
],
'expo-apple-authentication',
'expo-audio',
'expo-iap',
'expo-sharing',
'expo-video',
'expo-asset',
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const cloudAgentSdkPath = path.resolve(webSrc, 'lib', 'cloud-agent-sdk');
const config = getSentryExpoConfig(__dirname);

// Allow Metro to resolve workspace files and pnpm's real package paths
config.watchFolders = [monorepoRoot];
config.watchFolders = [...new Set([...(config.watchFolders || []), monorepoRoot])];

// Let SDK dependencies (jotai, zod, etc.) resolve from the monorepo root node_modules
config.resolver.nodeModulesPaths = [
Expand Down
69 changes: 39 additions & 30 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,47 +31,48 @@
"@react-native-community/netinfo": "11.5.2",
"@rn-primitives/portal": "^1.3.0",
"@rn-primitives/slot": "^1.2.0",
"@sentry/react-native": "~7.11.0",
"@sentry/react-native": "~8.10.0",
"@shopify/flash-list": "2.0.2",
"@tailwindcss/postcss": "^4.2.2",
"@tanstack/react-query": "catalog:",
"@trpc/client": "catalog:",
"@trpc/tanstack-react-query": "catalog:",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"expo": "~55.0.12",
"expo-apple-authentication": "~55.0.12",
"expo-application": "~55.0.13",
"expo-asset": "~55.0.13",
"expo-audio": "~55.0.12",
"expo": "~55.0.23",
"expo-apple-authentication": "~55.0.13",
"expo-application": "~55.0.14",
"expo-asset": "~55.0.17",
"expo-audio": "~55.0.14",
"expo-blur": "~55.0.14",
"expo-build-properties": "~55.0.12",
"expo-clipboard": "~55.0.12",
"expo-constants": "~55.0.12",
"expo-build-properties": "~55.0.13",
"expo-clipboard": "~55.0.13",
"expo-constants": "~55.0.16",
"expo-crypto": "~55.0.14",
"expo-dev-client": "~55.0.23",
"expo-document-picker": "~55.0.12",
"expo-font": "~55.0.6",
"expo-haptics": "~55.0.13",
"expo-image": "~55.0.8",
"expo-image-picker": "~55.0.17",
"expo-insights": "55.0.15",
"expo-linking": "~55.0.11",
"expo-location": "~55.1.8",
"expo-notifications": "~55.0.17",
"expo-router": "~55.0.11",
"expo-secure-store": "~55.0.12",
"expo-sharing": "~55.0.17",
"expo-splash-screen": "~55.0.16",
"expo-status-bar": "~55.0.5",
"expo-tracking-transparency": "~55.0.12",
"expo-video": "~55.0.14",
"expo-web-browser": "~55.0.13",
"expo-dev-client": "~55.0.32",
"expo-document-picker": "~55.0.13",
"expo-font": "~55.0.7",
"expo-haptics": "~55.0.14",
"expo-iap": "^4.2.4",
"expo-image": "~55.0.10",
"expo-image-picker": "~55.0.20",
"expo-insights": "55.0.16",
"expo-linking": "~55.0.15",
"expo-location": "~55.1.9",
"expo-notifications": "~55.0.22",
"expo-router": "~55.0.14",
"expo-secure-store": "~55.0.13",
"expo-sharing": "~55.0.18",
"expo-splash-screen": "~55.0.20",
"expo-status-bar": "~55.0.6",
"expo-tracking-transparency": "~55.0.13",
"expo-video": "~55.0.16",
"expo-web-browser": "~55.0.15",
"jotai": "^2.18.1",
"lucide-react-native": "^1.7.0",
"nativewind": "5.0.0-preview.3",
"react": "19.2.0",
"react-native": "0.83.4",
"react-native": "0.83.6",
"react-native-appsflyer": "^6.17.9",
"react-native-css": "3.0.6",
"react-native-gesture-handler": "~2.30.0",
Expand All @@ -80,11 +81,12 @@
"react-native-safe-area-context": "~5.6.2",
"react-native-screens": "~4.23.0",
"react-native-svg": "15.15.3",
"react-native-worklets": "0.7.2",
"react-native-worklets": "0.7.4",
"sonner-native": "^0.23.1",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.2",
"ulid": "3.0.1"
"ulid": "3.0.1",
"zod": "catalog:"
},
"devDependencies": {
"@sentry/cli": "catalog:",
Expand All @@ -100,5 +102,12 @@
"injected": true
}
},
"expo": {
"install": {
"exclude": [
"@sentry/react-native"
]
}
},
"private": true
}
18 changes: 14 additions & 4 deletions apps/mobile/src/app/(app)/(tabs)/(1_kiloclaw)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { type Href, useRouter } from 'expo-router';
import { useCallback, useState } from 'react';
import { View } from 'react-native';
import { Platform, View } from 'react-native';
import Animated, { FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

import { EmptyStateContent } from '@/components/kiloclaw/empty-state-content';
import { getKiloClawEntryDecision } from '@/components/kiloclaw/instance-entry-state';
Expand All @@ -16,10 +17,12 @@ import { useKiloClawMobileOnboardingState } from '@/lib/hooks/use-kiloclaw-queri
import { useThemeColors } from '@/lib/hooks/use-theme-colors';
import { useUnreadCounts } from '@/lib/hooks/use-unread-counts';
import { chatSandboxPath } from '@/lib/kilo-chat-routes';
import { getTabBarOverlayHeight } from '@/lib/tab-bar-layout';

export default function KiloClawTab() {
const router = useRouter();
const colors = useThemeColors();
const { bottom } = useSafeAreaInsets();
const [manualRefreshing, setManualRefreshing] = useState(false);
const instancesQuery = useAllKiloClawInstances();
const { data: instances } = instancesQuery;
Expand All @@ -30,6 +33,9 @@ export default function KiloClawTab() {
useForegroundInvalidateKiloclawState();

const showInstanceSkeleton = entryDecision.kind === 'loading' || onboardingQuery.isPending;
const emptyStateContainerStyle = {
paddingBottom: getTabBarOverlayHeight(bottom, Platform.OS),
};

const handleRefresh = useCallback(() => {
void (async () => {
Expand Down Expand Up @@ -94,15 +100,19 @@ export default function KiloClawTab() {
className="px-[22px]"
headerRight={<ProfileAvatarButton />}
/>
<Animated.View layout={LinearTransition} className="flex-1 items-center justify-center px-4">
<Animated.View layout={LinearTransition} className="flex-1 px-4">
{showInstanceSkeleton ? (
<Animated.View exiting={FadeOut.duration(150)} className="w-full gap-3 px-4">
<Animated.View exiting={FadeOut.duration(150)} className="w-full gap-3 px-4 pt-5">
<Skeleton className="h-16 w-full rounded-xl" />
<Skeleton className="h-16 w-full rounded-xl" />
<Skeleton className="h-16 w-full rounded-xl" />
</Animated.View>
) : (
<Animated.View entering={FadeIn.duration(200)}>
<Animated.View
entering={FadeIn.duration(200)}
className="flex-1 items-center justify-center"
style={emptyStateContainerStyle}
>
<EmptyStateContent
foregroundColor={colors.foreground}
state={onboardingQuery.data}
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/src/app/(app)/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';

import { BlurBar } from '@/components/ui/blur-bar';
import { useThemeColors } from '@/lib/hooks/use-theme-colors';
import { ANDROID_TAB_BAR_EXTRA_PADDING, TAB_BAR_BASE_HEIGHT } from '@/lib/tab-bar-layout';

const ANDROID_TAB_BAR_EXTRA_PADDING = 4;
const TAB_BAR_ITEM_CONTENT_WIDTH = 64;
const TAB_BAR_ICON_STYLE = {
alignItems: 'center',
Expand Down Expand Up @@ -64,7 +64,7 @@ export default function TabsLayout() {
elevation: 0,
position: 'absolute',
...(Platform.OS === 'android' && {
height: 50 + bottom + ANDROID_TAB_BAR_EXTRA_PADDING,
height: TAB_BAR_BASE_HEIGHT + bottom + ANDROID_TAB_BAR_EXTRA_PADDING,
}),
},
}}
Expand Down
Loading
Loading