Skip to content

perf(mobile): reuse the Kilo Pass store-product cache on re-entry - #6570

Merged
iscekic merged 1 commit into
mainfrom
kwf/mobile-perf-kilo-pass-purchase-ab6f
Sep 23, 2026
Merged

iscekic merged 1 commit into
mainfrom
kwf/mobile-perf-kilo-pass-purchase-ab6f

Conversation

@iscekic

@iscekic iscekic commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Changelog for users

  • Re-entering Kilo Pass paints the tier tiles from the cached store catalog instead of showing the tier skeletons again.
  • Kilo Pass opens the native store connection while the purchase presentation is still loading, so a slow store connect no longer adds a second wait after the network answers.

Changelog for maintainers

  • src/components/kilo-pass/kilo-pass-native-iap-owner.tsx:409 — accepted. The auth-epoch effect now seeds previousAuthEpochRef with the first epoch, so a re-entry inside the 5-minute window keeps the store-product entry; an actual account change still clears both the native entry and the shared backend catalog.
  • src/components/kilo-pass/kilo-pass-subscription-screen.tsx:538 — accepted. The IAP owner is now the route entry: it wraps the loading, error, non-native, and native-IAP variants and mounts before the presentation resolves; only KiloPassNativeIapContent stays gated on presentation.kind === 'native_iap' && isIapPlatform.
  • The backend catalog now carries the store cache window through backendStoreKiloPassProductsQueryOptions (staleTime 5 min), so the hook's loader and the owner's recovery fallback share one cache entry instead of each issuing getMobileStoreProducts.
  • The product loading gate (isStoreKiloPassProductsLoading) paints cached products without waiting for the store connection; the skeleton shows only while the chain is unsettled with nothing to paint, and a connection error still replaces it with the retry state.
  • Review the owner first: it stays the single useIAP call site, but it now mounts for non-native and unavailable presentations — confirm no purchase action is reachable from those variants.
  • Unit tests cover one owner mount per variant including loading, first-mount cache retention, the epoch-change clears, the loading gate, and the 5-minute catalog stale time.

E2E proof

[e1] Open Kilo Pass, leave, re-enter it — the second (and third) visit must issue zero kiloPass.getMobileStoreProducts and zero native fetchProducts, tiles from cache — Android emulator-5554, two cold-launch runs of the corrected e1 scene (SCENE e1 OK): e1-live-counts.txt records 'Kilo Pass visit 1: getMobileStoreProducts=1', 'Kilo Pass visit 2: getMobileStoreProducts=0', 'Kilo Pass visit 3: getMobileStoreProducts=0', quoted from e1-live-nextjs.log line 15 'POST /api/trpc/kiloPass.getMobileStoreProducts,kiloPass.getPurchasePresentation?batch=1' and lines 31/45 'POST /api/trpc/kiloPass.getPurchasePresentation?batch=1' (confirmed in e1-live2-nextjs.log lines 17/31/47); native fetchProducts and the cache-painted tiles are not observable on this no-Play-Store…

/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-proof-4d094d6-db34/e2e-backend/backend.log
  scope: [ 'usr_1', null ],
  revision: 2,
  trailing: false,
  status: 'happy',
  running: 2,
  needsInput: 1,
  idle: 0,
  needsApproval: 1
}
 ✓ src/lib/glanceable-delivery.test.ts (83 tests) 2585ms
uncaught exception; source = Uncaught (in promise); stack = Error: Secret "INTERNAL_API_SECRET_PROD" not found
<redacted>
stderr | ../../../../../../../../../Object.buildSnapshot (/home/igor_kilocode_ai/.local/share/kwf/wt/kwf-fix-proof-4d094d6-db34/services/notifications/src/lib/glanceable-delivery-deps.ts:34:17)
INTERNAL_API_SECRET missing; skipping glanceable aggregate delivery
 Test Files  18 passed (18)
      Tests  375 passed (375)
   Start at  23:55:18
   Duration  17.68s (transform 46.18s, setup 1.22s, import 126.33s, tests 6.05s, environment 2ms)
No projects matched the filters in "/home/igor_kilocode_ai/.local/share/kwf/wt/kwf-fix-proof-4d094d6-db34"
No projects matched the filters in "/home/igor_kilocode_ai/.local/share/kwf/wt/kwf-fix-proof-4d094d6-db34"
No projects matched the filters in "/home/igor_kilocode_ai/.local/share/kwf/wt/kwf-fix-proof-4d094d6-db34"
No projects matched the filters in "/home/igor_kilocode_ai/.local/share/kwf/wt/kwf-fix-proof-4d094d6-db34"
No projects matched the filters in "/home/igor_kilocode_ai/.local/share/kwf/wt/kwf-fix-proof-4d094d6-db34"
No projects matched the filters in "/home/igor_kilocode_ai/.local/share/kwf/wt/kwf-fix-proof-4d094d6-db34"
[e1] Open Kilo Pass, leave, re-enter it — the second (and third) visit must issu -> pass :: Android emulator-5554, two cold-launch runs of the corrected e1 scene (SCENE e1 OK): e1-live-counts.txt records 'Kilo Pass visit 1: getMobileStoreProducts=1', 'Kilo Pass visit 2: getMobileStoreProducts=0', 'Kilo Pass visit 3: getMobileStoreProducts=0', quoted from e1-live-nextjs.log line 15 'POST /api/trpc/kiloPass.getMobileStoreProducts,kiloPass.getPurchasePresentation?batch=1' and lines 31/45 'POST /api/trpc/kiloPass.getPurchasePresentation?batch=1' (confirmed in e1-live2-nextjs.log lines 17/31/47); native fetchProducts and the cache-painted tiles are not observable on this no-Play-Store emulator (digest 'Google Play products unavailable') and are left to the visual reviewer (e1.png).
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-proof-4d094d6-db34/e2e-mobile-app/e1-live-counts.txt
live run 2026-09-23 android emulator-5554, cold launch, scene e1 (3 Kilo Pass entries)
log: e1-live-nextjs.log (pnpm dev:capture nextjs --follow)
entry 1  log line 7: POST /api/trpc/kiloPass.getPurchasePresentation,kiloPass.getState,user.getContextBalance,user.getCreditBlocks?batch=1 200 in 143ms (next.js: 70ms, proxy.ts: 13ms, application-code
           kiloPass.getMobileStoreProducts in this request: 0
entry 2  log line 15: POST /api/trpc/kiloPass.getMobileStoreProducts,kiloPass.getPurchasePresentation?batch=1 200 in 78ms (next.js: 39ms, proxy.ts: 17ms, application-code: 21ms)
           kiloPass.getMobileStoreProducts in this request: 1
entry 3  log line 31: POST /api/trpc/kiloPass.getPurchasePresentation?batch=1 200 in 63ms (next.js: 30ms, proxy.ts: 16ms, application-code: 17ms)
           kiloPass.getMobileStoreProducts in this request: 0
entry 4  log line 45: POST /api/trpc/kiloPass.getPurchasePresentation?batch=1 200 in 63ms (next.js: 34ms, proxy.ts: 13ms, application-code: 16ms)
           kiloPass.getMobileStoreProducts in this request: 0
Profile-card batch (cache miss, before any Kilo Pass entry): getMobileStoreProducts=0
Kilo Pass visit 1: getMobileStoreProducts=1
Kilo Pass visit 2: getMobileStoreProducts=0
Kilo Pass visit 3: getMobileStoreProducts=0
total kiloPass.getMobileStoreProducts in the whole run: 1
SECOND RUN (independent confirmation), 2026-09-23, android emulator-5554, cold launch, same scene, log: e1-live2-nextjs.log
entry 1  log line 9: POST /api/trpc/kiloPass.getPurchasePresentation,kiloPass.getState,user.getContextBalance,user.getCreditBlocks?batch=1 200 in 56ms - getMobileStoreProducts=0
entry 2  log line 17: POST /api/trpc/kiloPass.getMobileStoreProducts,kiloPass.getPurchasePresentation?batch=1 200 in 100ms - getMobileStoreProducts=1 (Kilo Pass visit 1)
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-proof-4d094d6-db34/e2e-mobile-app/e1-live-nextjs.log
{"type":"trpc_timing","surface":"trpc","durationMs":45,"ok":true,"client":"mobile","platform":"android","version":"1.0.12","requestId":"req-mudcbr8z-evxi3qirmp6","path":"kiloPass.getState","procedureT
{"type":"trpc_timing","surface":"trpc","durationMs":47,"ok":true,"client":"mobile","platform":"android","version":"1.0.12","requestId":"req-mudcbr8z-evxi3qirmp6","path":"user.getCreditBlocks","procedu
{"type":"trpc_timing","surface":"trpc","durationMs":47,"ok":true,"client":"mobile","platform":"android","version":"1.0.12","requestId":"req-mudcbr8z-evxi3qirmp6","path":"kiloPass.getPurchasePresentati
 POST /api/trpc/user.getNotificationPreferences,user.getAuthProviders,user.getContextBalance,user.getCreditBlocks,organizations.list,kiloPass.getPurchasePresentation,kiloPass.getState?batch=1 200 in 1
{"type":"trpc_timing","surface":"trpc","durationMs":0,"ok":true,"client":"mobile","platform":"android","version":"1.0.12","requestId":"req-mudcbsue-xweevvgxq3b","path":"user.getMe","procedureType":"qu
 POST /api/trpc/user.getMe 200 in 52ms (next.js: 21ms, proxy.ts: 16ms, application-code: 15ms)
{"type":"trpc_timing","surface":"trpc","durationMs":3,"ok":true,"client":"mobile","platform":"android","version":"1.0.12","requestId":"req-mudcbsuk-aoq1ap5bw57","path":"kiloPass.getPurchasePresentatio
 POST /api/trpc/kiloPass.getPurchasePresentation?batch=1 200 in 63ms (next.js: 30ms, proxy.ts: 16ms, application-code: 17ms)
{"type":"trpc_timing","surface":"trpc","durationMs":1,"ok":true,"client":"mobile","platform":"android","version":"1.0.12","requestId":"req-mudcc3hk-a57cqmsvuxw","path":"user.getMe","procedureType":"qu
 POST /api/trpc/user.getMe 200 in 100ms (next.js: 53ms, proxy.ts: 10ms, application-code: 38ms)
/home/igor_kilocode_ai/.local/share/kwf/sections/kwf-fix-proof-4d094d6-db34/e2e-mobile-app/e1-live2-nextjs.log
{"type":"trpc_timing","surface":"trpc","durationMs":50,"ok":true,"client":"mobile","platform":"android","version":"1.0.12","requestId":"req-mudcgfav-ij1b4rhg3x","path":"organizations.list","procedureT
 POST /api/trpc/user.getNotificationPreferences,user.getAuthProviders,user.getContextBalance,user.getCreditBlocks,organizations.list,kiloPass.getPurchasePresentation,kiloPass.getState?batch=1 200 in 1
{"type":"trpc_timing","surface":"trpc","durationMs":0,"ok":true,"client":"mobile","platform":"android","version":"1.0.12","requestId":"req-mudcgh1v-2dm695vr7ay","path":"user.getMe","procedureType":"qu
 POST /api/trpc/user.getMe 200 in 61ms (next.js: 20ms, proxy.ts: 13ms, application-code: 29ms)
{"type":"trpc_timing","surface":"trpc","durationMs":8,"ok":true,"client":"mobile","platform":"android","version":"1.0.12","requestId":"req-mudcgh25-cjl7ky200n","path":"kiloPass.getPurchasePresentation
 POST /api/trpc/kiloPass.getPurchasePresentation?batch=1 200 in 73ms (next.js: 31ms, proxy.ts: 14ms, application-code: 28ms)
{"type":"trpc_timing","surface":"trpc","durationMs":53,"ok":true,"client":"mobile","platform":"android","version":"1.0.12","requestId":"req-mudcgmiv-hy3pez7eef7","path":"activeSessions.list","procedur
 POST /api/trpc/activeSessions.list 200 in 146ms (next.js: 44ms, proxy.ts: 12ms, application-code: 91ms)
{"type":"trpc_timing","surface":"trpc","durationMs":0,"ok":true,"client":"mobile","platform":"android","version":"1.0.12","requestId":"req-mudcgrs8-18mlxpshc4r","path":"user.getMe","procedureType":"qu
 POST /api/trpc/user.getMe 200 in 96ms (next.js: 47ms, proxy.ts: 9ms, application-code: 40ms)
Owner request
## Open findings (not fixed here) - [e1] Open Kilo Pass, leave, re-enter it: run `pnpm dev:capture nextjs`, count the store-product chain — the second visit must issue zero `kiloPass.getMobileStoreProducts` requests and zero native fetchProducts, and the tier tiles must paint from cache without a skeleton phase; quote the per-visit co - [e1] $KILO_WORKFLOW/e2e/appium.sh script /home/igor_kilocode_ai/.local/share/kwf/sections/mobile-perf-kilo-pass-purchase-ab6f/e2e-mobile-app/repro-e1.json --out /home/igor_kilocode_ai/.local/share/kwf/sections/mobile-perf-kilo-pass-purchase-ab6f/e2e-mobile-app/repro-e1 - [e1] Open Kilo Pass, leave, re-enter it — second visit must issue zero kiloPass.getMobileStoreProducts and zero native fetchProducts, tiles paint from cache without a skeleton: On android emulator-5554 the parked replay ran (SCENE e1 OK) but re-entry did not hit the cache: e1-visit-counts.txt shows - not fully verified: some optional checks did not run - not proved live: e1.png is no longer on the host that took it, so no publish can carry it - the '## E2E proof' section carries no log excerpt, so nothing shows the change was driven end to end

e1

@iscekic
iscekic marked this pull request as draft September 22, 2026 18:22
Comment thread apps/mobile/src/lib/kilo-pass/store-products-state.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The PR branch was rewritten to a single commit (6a56b99), so this is a full re-review of the 9-file delta rather than an incremental one; the Kilo Pass store-product cache reuse, the reconnect error clear, the owner-at-route-entry reorder, and the vitest project split are all sound, and no new defects were found.

Files Reviewed (9 files)
  • apps/mobile/src/components/kilo-pass/kilo-pass-native-iap-owner.tsx - the auth-epoch effect is guarded by previousAuthEpochRef and clears both the native entry and the shared backend catalog only on a real epoch bump; the shared backendStoreKiloPassProductsQueryOptions is safe because getMobileStoreProducts takes no input and the epoch bump removes it
  • apps/mobile/src/components/kilo-pass/kilo-pass-subscription-screen.test.ts - mount assertions updated for the owner-at-entry reorder; no defects
  • apps/mobile/src/components/kilo-pass/kilo-pass-subscription-screen.tsx - the owner now wraps every variant; the only useKiloPassNativeIap consumers that can start a purchase or restore are KiloPassNativeIapContent, so no store action is reachable from web_management/unavailable
  • apps/mobile/src/lib/kilo-pass/store-products-state.test.ts - covers the empty-catalog gate and the post-timeout state; no defects
  • apps/mobile/src/lib/kilo-pass/store-products-state.ts - isStoreKiloPassProductsLoading waits only when there is nothing to paint, so cached products skip the skeleton and a [] cache still waits for the store; no defects
  • apps/mobile/src/lib/kilo-pass/use-store-kilo-pass-products.mounted.test.tsx - deterministic under staleTime: Infinity; the reconnect case asserts the timeout message drops once the store connects
  • apps/mobile/src/lib/kilo-pass/use-store-kilo-pass-products.ts - the new connected effect clears the stale bounded-timeout message so a slow-but-successful re-entry does not keep the false retry card over cached tiles; no defects
  • apps/mobile/src/lib/kilo-pass/use-store-kilo-pass-purchase.test.tsx - new tests lock the silent recovery path and the 5-minute catalog stale time; no defects
  • apps/mobile/vitest.pure.config.ts - the !(*.mounted) glob keeps only use-store-kilo-pass-purchase.test.tsx in the pure project while the mounted project collects the new file, so no test runs twice or is dropped
Previous Review Summaries (3 snapshots, latest commit 4d094d6)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 4d094d6)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (9 files)
  • apps/mobile/src/components/kilo-pass/kilo-pass-native-iap-owner.tsx
  • apps/mobile/src/components/kilo-pass/kilo-pass-subscription-screen.tsx
  • apps/mobile/src/components/kilo-pass/kilo-pass-subscription-screen.test.ts
  • apps/mobile/src/lib/kilo-pass/store-products-state.ts
  • apps/mobile/src/lib/kilo-pass/store-products-state.test.ts
  • apps/mobile/src/lib/kilo-pass/use-store-kilo-pass-products.ts
  • apps/mobile/src/lib/kilo-pass/use-store-kilo-pass-products.mounted.test.tsx
  • apps/mobile/src/lib/kilo-pass/use-store-kilo-pass-purchase.test.tsx
  • apps/mobile/vitest.pure.config.ts

Previous review (commit 6b2ad0d)

Status: No Issues Found | Recommendation: Merge

The incremental delta (dcac7be..6b2ad0d) resolves both prior findings: the loading gate now treats a cached empty catalog as an empty surface (data != null && data.length > 0), and the widened owner mount is documented, with a unit test locking the silent recovery path. No new defects found in the changed code.

Files Reviewed (4 files)
  • apps/mobile/src/components/kilo-pass/kilo-pass-subscription-screen.tsx - explanatory comment only; no defects
  • apps/mobile/src/lib/kilo-pass/store-products-state.ts - empty cached catalog now waits for the store connection; bounded by the 8s connection timeout, so it cannot loop the skeleton
  • apps/mobile/src/lib/kilo-pass/store-products-state.test.ts - new cases cover the empty-catalog gate while disconnected and once connected
  • apps/mobile/src/lib/kilo-pass/use-store-kilo-pass-purchase.test.tsx - new test locks silent recovery when no inline error surface is mounted

Previous review (commit dcac7be)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/components/kilo-pass/kilo-pass-subscription-screen.tsx 560 Unconditional IAP owner mount runs the store connect, ownership lookup, recovery mutations, and a toast path for web_management/unavailable variants that previously never mounted the owner.

SUGGESTION

File Line Issue
apps/mobile/src/lib/kilo-pass/store-products-state.ts 36 data === undefined also counts a cached empty array as paintable, so an empty cached catalog paints the unavailable panel before the store connects.
Files Reviewed (7 files)
  • apps/mobile/src/components/kilo-pass/kilo-pass-native-iap-owner.tsx - auth-epoch guard verified safe (auth transitions clear the query cache); store side effects now reach non-native variants
  • apps/mobile/src/components/kilo-pass/kilo-pass-subscription-screen.tsx - 1 issue (WARNING)
  • apps/mobile/src/components/kilo-pass/kilo-pass-subscription-screen.test.ts - assertions updated consistently with the reorder; no defects
  • apps/mobile/src/lib/kilo-pass/store-products-state.ts - 1 issue (SUGGESTION)
  • apps/mobile/src/lib/kilo-pass/store-products-state.test.ts - new predicate covered; no defects
  • apps/mobile/src/lib/kilo-pass/use-store-kilo-pass-products.ts - shared 5-min catalog cache verified safe for appAccountToken
  • apps/mobile/src/lib/kilo-pass/use-store-kilo-pass-purchase.test.tsx - mocks/harness exercise the epoch-change and staleTime paths; no defects

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@iscekic
iscekic force-pushed the kwf/mobile-perf-kilo-pass-purchase-ab6f branch from dcac7be to 6b2ad0d Compare September 22, 2026 19:11
@iscekic
iscekic marked this pull request as ready for review September 22, 2026 19:32
@iscekic

iscekic commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator Author

kilo-review — independent audit of the published diff.

Status: 1 Issues

File Line Issue
apps/mobile/src/lib/kilo-pass/use-store-kilo-pass-products.ts 117 The new gate paints cached tier tiles while the store reconnects, but the 8s connection timeout still sets storeErrorMessage with cached data present, and nothing clears it when the store later connects, so a slow-but-successful re-entry swaps the tiles for a false 'Could not connect' retry card that persists.

@iscekic
iscekic marked this pull request as draft September 22, 2026 20:08
@iscekic

iscekic commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Accepted. Pushed db4071c (kwf kwf-fix-review-58ed).

What changed:

  • src/components/kilo-pass/kilo-pass-native-iap-owner.tsx:409 — accepted. The auth-epoch effect now seeds previousAuthEpochRef with the first epoch, so a re-entry inside the 5-minute window keeps the store-product entry; an actual account change still clears both the native entry and the shared backend catalog.
  • src/components/kilo-pass/kilo-pass-subscription-screen.tsx:538 — accepted. The IAP owner is now the route entry: it wraps the loading, error, non-native, and native-IAP variants and mounts before the presentation resolves; only KiloPassNativeIapContent stays gated on presentation.kind === 'native_iap' && isIapPlatform.
  • apps/mobile/src/lib/kilo-pass/use-store-kilo-pass-products.ts:117 — accepted. The finding is correct: the only clear was the products-success effect, and with the catalog already cached the query stays successful, so it never re-ran and the bounded connection message stayed painted over the tiles. A new effect on connected clears storeErrorMessage when the store connection lands, so a slow but successful re-entry drops the stale retry card; a later product failure still surfaces through the query's authored error, and a store that never connects keeps the bounded retry card.
  • The backend catalog now carries the store cache window through backendStoreKiloPassProductsQueryOptions (staleTime 5 min), so the hook's loader and the owner's recovery fallback share one cache entry instead of each issuing getMobileStoreProducts.
  • The product loading gate (isStoreKiloPassProductsLoading) paints cached products without waiting for the store connection; the skeleton shows only while the chain is unsettled with nothing to paint, and a connection error still replaces it with the retry state.
  • Review the owner first: it stays the single useIAP call site, but it now mounts for non-native and unavailable presentations — confirm no purchase action is reachable from those variants.
  • Unit tests now cover the reconnect clear: one mounted test drives the bounded wait, connects, and expects the error to drop and the cached tiles to return; a second keeps the retry card when the store never answers.
  • The pure vitest project no longer collects src/lib/kilo-pass/**/*.mounted.test.tsx; the new mounted test runs only in the mounted project, so it does not execute twice.

@iscekic
iscekic force-pushed the kwf/mobile-perf-kilo-pass-purchase-ab6f branch from db4071c to 4d094d6 Compare September 22, 2026 21:39
@iscekic
iscekic marked this pull request as ready for review September 22, 2026 22:01
@iscekic

iscekic commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator Author

This description names a scenario the proof did not capture:

  • not proved live: e1.png is no longer on the host that took it, so no publish can carry it

A repeated proof run rebuilds the same evidence, so no proof run is dispatched for a named gap. Merging with this gap open is your decision.

@iscekic iscekic added the human-ready The PR is ready for human review. label Sep 23, 2026
@iscekic iscekic self-assigned this Sep 23, 2026
@iscekic
iscekic force-pushed the kwf/mobile-perf-kilo-pass-purchase-ab6f branch from 4d094d6 to 6a56b99 Compare September 23, 2026 04:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants