fix(core): keep parked highlights when a forced refresh fails - #136
Merged
cameronapak merged 5 commits intoAug 14, 2026
Conversation
The drain treated a leftover token as a mint, then un-painted on the
second 401. getAccessToken({ force: true }) reports whether the mint
landed. Also skip the sign-in prompt while auth is loading, align
platform-core to 2.6.2, and pin expo-application in the example app.
Co-authored-by: Cursor <cursoragent@cursor.com>
A type alias sat in the middle of the import block and tripped import/first under --max-warnings=0. Co-authored-by: Cursor <cursoragent@cursor.com>
A tap during the loading window skipped the sign-in sheet. When bootstrap found no session the write reverted and the tap was lost. Hold the intent until auth settles, then prompt or apply. Co-authored-by: Cursor <cursoragent@cursor.com>
bmanquen
reviewed
Aug 14, 2026
Collaborator
There was a problem hiding this comment.
Superseded — comments reposted below in Conventional Comments form.
bmanquen
reviewed
Aug 14, 2026
bmanquen
left a comment
Collaborator
There was a problem hiding this comment.
Two-axis review of highlights...HEAD, spec = this PR's description.
Standards: 2 hard (docs now contradict the code), 6 judgement calls.
Spec: 1 partial, 3 implementation concerns, 2 scope items.
Worst in each axis, kept separate rather than reranked against each other:
- Standards —
AGENTS.md:119anddocs/adr/0017:127both quote the sign-in gate expression verbatim, and this branch changed it. AGENTS.md was edited here forgetAccessToken({ force }), so the doc-sync habit was applied to core and skipped for UI. - Spec — change 2's loading-window guard sits behind the cached-grant short-circuit, so the exact case bc12822 describes still reverts silently for any consumer that isn't
BibleReader.
Comments follow Conventional Comments. Two blocking, and 2 file-level comments posted alongside this review for findings whose lines fall outside the diff hunks.
bmanquen
reviewed
Aug 14, 2026
bmanquen
reviewed
Aug 14, 2026
A returning grant is seeded before the token lands. Writing on that hint still reverts if bootstrap finds no session. Wait only while loading and unsigned, and remint after a force joins an in-flight refresh so the drain does not un-paint on a leftover. Co-authored-by: Cursor <cursoragent@cursor.com>
The force path called refreshToken inside its own useCallback. ESLint treats that as access before declaration. Co-authored-by: Cursor <cursoragent@cursor.com>
bmanquen
approved these changes
Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Parked highlights stay painted when a forced token refresh fails. The sign-in prompt stays closed while auth is still loading.
Changes
getAccessToken({ force: true })reportsrefresh-failed.getAccessTokenaccepts{ force: true }so a caller can tell a mint from a leftover token.@youversion/platform-coreat 2.6.2.expo-application, and the docs match that.Start here: change 1 — a leftover unexpired token is not a mint.
Flow
Under the hood
The drain used
refreshNow()after a 401. That call swallows a failed refresh and leaves the old token in place. The next send uses the same stale token, the server refuses again, and the drain un-paints a real write.getAccessToken({ force: true })reports whether this call minted a token.refresh-failedtakes ordinary backoff. The drain reverts only after a minted token is refused a second time.Test plan
pnpm --filter @youversion/platform-react-native-expo-core exec jest src/auth/__tests__/auth-provider.test.tsx src/highlights/__tests__/highlight-queue-drain.test.ts src/highlights/__tests__/use-highlight-permission-flow.test.tsx --no-coverage(127 passed)pnpm --filter @youversion/platform-react-native-expo-ui exec jest src/native/__tests__/bible-reader-highlights-prompts.test.tsx --no-coverage(18 passed)Needs manual check: a parked write with a stale token and no network must stay painted after the drain wakes.
Made with Cursor
Greptile Summary
The PR preserves parked highlights when a forced token refresh fails and completes the earlier fix for highlight taps made during auth bootstrap.
Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart LR Tap[Highlight tap] --> Gate{Auth gate} Gate -->|settling| Hold[Hold intent] Hold -->|session restored| Apply[Apply highlight] Hold -->|signed out| Prompt[Open sign-in prompt] Gate -->|ready| Apply Gate -->|signed out| Prompt Refusal[Queue write receives 401/403] --> Force[Force token mint] Force -->|refresh failed| Keep[Keep parked write and paint] Force -->|mint succeeded| Retry[Retry write] Retry -->|refused again| Drop[Drop write and revert paint]Reviews (4): Last reviewed commit: "fix(core): remint after a join without c..." | Re-trigger Greptile
Context used (3)