Repository navigation
AI provider credits + mobile empty-state stability & Cairn Stacker easter egg - #98
Conversation
Mobile: replace the wrapping model chips in AI settings with a search box
and a bounded scrollable list so providers that return 50+ models (e.g.
OpenRouter) are easy to browse. Filters by name, shows a match count, and
pins the selected model in view.
Both: show a provider's remaining credits when it exposes them. Adds an
ai:fetchKeyInfo IPC handler (GET {base}/v1/key, OpenRouter shape) resolved
in the main process, surfaced via a CreditsBadge in ProviderManager on
desktop and getKeyInfo() + a credits card in AiSettingsForm on mobile.
Returns null on any non-2xx/parse/network failure so it hides gracefully
for providers without a credits endpoint (OpenAI, Ollama, LM Studio).
Changelogs: add changelogs/v2.5.10.md; append to mobile v0.1.3.md.
… backgrounding - sync: report peerOpsApplied as the ops that ACTUALLY changed the DB (applyRemote().applied.length), not the raw peer-oplog read count. The data-changed signal keys off this, so a quiet 25s/post-write sync cycle no longer fires every screen's re-query + embeddings catch-up. Adds a distinct peerOpsRead field (mirrors desktop-sync); Sync screen shows it when >applied. - graph/webview: remount the WKWebView (key bump) on content-process termination instead of the unreliable reload() — iOS reclaims a backgrounded web view's process, and reload() on a dead process often no-ops/restores a blank doc (why swapping layout type 'fixed' it). AppState-aware: rebuild on the next foreground if killed while backgrounded. onLoadEnd re-pushes live force-mode toggles so a rebuild can't revert them. Same fix for the note math/diagram WebViewRenderer. - tools/registry: memoise the parsed community manifest by raw string (skip repeated JSON.parse + schema re-validation); use prebuilt id Sets for install checks and useMemo maps for per-row tool defs so unrelated re-renders stop re-parsing SQLite per catalog entry. Changelog: mobile v0.1.3.
- Calendar: full-height layout — month/week grid flexes to fill (week rows share height, no grid scroll); selected-day list + Unscheduled tray move to a fixed-height (~35%) bottom band that scrolls independently, padded above the tab bar. Tray chips render inline (dropped the capped inner scroll). - Light theme: darker border token so bordered inputs/cards read as defined edges instead of flat fills; AI-settings helper text bumped to textSecondary for WCAG AA contrast. - Chat AI-settings icon: brain -> gearshape cog (it opens a settings sheet). - Board cards: padding 10 -> 12 for consistent rhythm.
Chat and Search empty states lived inside their scroll/list containers, so they drifted when the keyboard opened (Search re-anchored on every keyboard resize via useWindowDimensions). Add a shared 'pinned' overlay mode to EmptyState that renders as a fixed-position sibling of the scroll view, using one anchoring system for both tabs instead of two hand-tuned ones. The pinned overlay initially used pointerEvents='none', which killed the 5-tap easter-egg trigger on the Cairn icon (and would have killed Chat's 'Set up AI' button). Switch to 'box-none' so blank overlay area passes touches through while the icon + action buttons stay tappable. Five quick taps now opens a hidden mini-games menu offering Breakout plus a new on-brand Cairn Stacker: a namesake stacking game where a stone slides across the top and you tap to drop it; overhang is sliced off so the tower narrows, perfect drops chain a combo, and each stone is a real workspace entity coloured like the knowledge graph.
…render loop The 'lines changed since you last viewed this note' banner flashed and vanished on open: clearing the one-shot change mark re-ran the highlight effect and wiped the highlight it had just set. Track the last processed mark per note so our own clear doesn't erase the highlight, drop the 6.5s auto-timeout, and clear highlights on first edit (line numbers shift). Settings -> AI / Coding Agent could throw 'getSnapshot should be cached' (infinite render loop) because the saved-providers selector built a fresh setModel closure every render, defeating useShallow snapshot caching. Select the stable store actions instead and build the per-surface setModel wrapper outside the selector.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
💤 Files with no reviewable changes (1)
🚧 Files skipped from review as they are similar to previous changes (6)
📝 WalkthroughWalkthroughThis release adds provider credit lookup and display, searchable mobile model selection, pinned empty states, a Stacker mini-game, calendar scrolling changes, WebView recovery, sync metrics, cached tool computations, persistent note-change highlights, and stable provider settings selectors. ChangesProvider credits and model browsing
Pinned empty states and Cairn games
Calendar grid and scrolling layout
Mobile recovery, synchronization, and cached tools
Note highlights and provider selector stability
Supporting visual updates
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
mobile/app/(tabs)/chat/index.tsxESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. mobile/app/settings/tools.tsxESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox. mobile/src/components/AiSettingsForm.tsxESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The set-state-in-effect disable is no longer needed — the setChangedLines([]) reset is now inside a conditional, so the rule doesn't flag it. The stray directive tripped --max-warnings 0 in CI.
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (3)
mobile/src/components/StackerGame.tsx (1)
440-440: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHardcoded
#ffffor the stone label. Stone backgrounds come from theme tokens (t.accent,t.info, …), so the foreground should too —t.accentFgkeeps contrast correct if the palette changes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mobile/src/components/StackerGame.tsx` at line 440, Update the stoneLabel style in StackerGame to use the theme’s t.accentFg token instead of the hardcoded "`#fff`" color, preserving the existing typography styles.mobile/src/components/EmptyState.tsx (1)
84-108: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAndroid
Alert.alertis limited to three buttons — this already hits that limit.The secret-game picker has two choices plus
Cancel; if hidden mini-games are meant to grow, use a custom picker instead of adding moreAlert.alertoptions.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mobile/src/components/EmptyState.tsx` around lines 84 - 108, Replace the Android Alert.alert picker in openEggMenu with a custom picker component or modal that supports more than three options, while preserving the existing game choices, Cancel behavior, and setGame callbacks. Keep the iOS ActionSheetIOS flow unchanged.mobile/src/components/AiSettingsForm.tsx (1)
598-620: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winModel rows lack
accessibilityRole/accessibilityLabel, unlike other interactive elements in this file.The model list
Pressablerows don't declare accessibility role/label/selected-state, while other interactive elements in this same component (e.g. the "Tools & Services"navRowPressable) do. For a searchable list of selectable options, this is an easy, high-value a11y improvement consistent with the file's own convention.♿ Suggested accessibility props
<Pressable key={id} style={[styles.modelRow, active && styles.modelRowActive]} onPress={() => setModel(id)} + accessibilityRole="button" + accessibilityState={{ selected: active }} + accessibilityLabel={id} >🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mobile/src/components/AiSettingsForm.tsx` around lines 598 - 620, Add accessibility metadata to the model-row Pressable elements in the filteredModels map: declare their button role, provide a descriptive label based on each model id, and expose the active selection state via the appropriate accessibility prop. Follow the existing accessibility conventions in AiSettingsForm without changing the selection behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@mobile/app/`(tabs)/chat/index.tsx:
- Around line 297-316: Move the pinned EmptyState block from before
KeyboardGestureArea to immediately after its closing tag so it paints above the
absolute-fill scroll view and the “Set up AI” Pressable receives taps. Update
the accompanying comment to say pointerEvents="box-none", matching EmptyState’s
pinned behavior, while preserving the existing messages.length and configured
conditions.
In `@mobile/app/settings/tools.tsx`:
- Around line 355-358: Update the component containing mcpToolsById to track a
cache revision state, incrementing it after each refreshServerTools path at the
referenced refresh points (192, 237, and 269). Add that revision to the useMemo
dependency array so the map recomputes whenever refreshed MCP tool definitions
are cached.
In `@mobile/changelogs/v0.1.3.md`:
- Around line 79-81: Move the three bullets describing Apple Intelligence schema
normalization, Tools & Services caching/timeout, and Android API-key entry out
of the accessibility section in mobile/changelogs/v0.1.3.md and place them under
the changelog’s Fixes or Under the hood section. Keep only the
accessibility-related item under the current heading, preserving the existing
bullet text.
- Line 23: Insert a line break between the “Search & filter the community
catalog” bullet and the “Services can bundle several tools” bullet in the
changelog, preserving both bullet entries and their existing text.
In `@mobile/src/components/ai-settings/styles.ts`:
- Around line 176-178: Update creditsSub in the styles definition to use the
accessible textSecondary color instead of textTertiary, matching the treatment
of readable content such as keyNoteText and compatHint while leaving its
typography unchanged.
In `@mobile/src/components/CalendarView.tsx`:
- Around line 319-367: The bottom-band ScrollView must remain reachable and
support drag autoscroll when its content exceeds bottomBandHeight. Update the
ScrollView around UnscheduledTray to use the DragController
integration—ctrl.scrollRef, scrollHandler, onScrollLayout, and
onScrollContentSizeChange—and avoid disabling scrolling solely through
ctrl.scrollLocked during dragging; preserve normal selected-day and tray
rendering while allowing the active drag to scroll this area.
In `@mobile/src/components/StackerGame.tsx`:
- Around line 254-294: The game loop effect currently resets the game whenever W
or H changes because its local started flag is recreated. Update the effect
around step and reset to depend only on visible, and use the mutable gameState
dimensions already synchronized elsewhere rather than capturing W/H; preserve
initialization on visibility changes without wiping the tower or score during
dimension updates.
- Around line 308-311: Update the StackerGame positioning logic around stoneTop,
tower rendering, the moving stone, and particle y coordinates to introduce a
camera offset that keeps the tower top at a stable screen position as
tower.length grows. Add this offset to every rendered top/y calculation,
including the moving stone, and remove or correct firstVisible culling so
on-screen low-index stones remain visible; render all stones or cull based on
their adjusted screen position within fieldTop and fieldBottom.
- Around line 261-294: Update the animation loop in the step function so
publish() is called only when the game state changes, such as movement or
particle updates; avoid publishing while paused, stopped, or otherwise
unchanged, while preserving requestAnimationFrame scheduling and cleanup.
In `@src/components/settings/endpoint-components.tsx`:
- Around line 114-133: The credits lookup writes lack stale-response protection.
In src/components/settings/endpoint-components.tsx lines 114-133, update
fetchKeyInfo to capture and validate the current request generation (using
fetchGenRef/isCurrent or an equivalent identity check) before both setKeyInfo
calls. In mobile/src/components/AiSettingsForm.tsx lines 289-311, guard the
getKeyInfo promise callback with a request generation or active-provider
identity check before setKeyInfo, preventing previous requests from overwriting
current or cleared state.
- Around line 205-210: Update formatCredits in
src/components/settings/endpoint-components.tsx (lines 205-210) and the
duplicated helper in mobile/src/components/AiSettingsForm.tsx (lines 59-64) to
place the minus sign before the dollar symbol for negative amounts, while
preserving the existing positive formatting and precision rules.
---
Nitpick comments:
In `@mobile/src/components/AiSettingsForm.tsx`:
- Around line 598-620: Add accessibility metadata to the model-row Pressable
elements in the filteredModels map: declare their button role, provide a
descriptive label based on each model id, and expose the active selection state
via the appropriate accessibility prop. Follow the existing accessibility
conventions in AiSettingsForm without changing the selection behavior.
In `@mobile/src/components/EmptyState.tsx`:
- Around line 84-108: Replace the Android Alert.alert picker in openEggMenu with
a custom picker component or modal that supports more than three options, while
preserving the existing game choices, Cancel behavior, and setGame callbacks.
Keep the iOS ActionSheetIOS flow unchanged.
In `@mobile/src/components/StackerGame.tsx`:
- Line 440: Update the stoneLabel style in StackerGame to use the theme’s
t.accentFg token instead of the hardcoded "`#fff`" color, preserving the existing
typography styles.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 116cd86b-e117-4957-b217-2648f5314918
📒 Files selected for processing (27)
changelogs/v2.5.10.mdelectron/ipc/ai-handlers.tselectron/preload.tsmobile/app/(tabs)/chat/index.tsxmobile/app/(tabs)/search/index.tsxmobile/app/settings/tools.tsxmobile/app/sync.tsxmobile/changelogs/v0.1.3.mdmobile/src/chat/providers/openai.tsmobile/src/chat/registry.tsmobile/src/components/AiSettingsForm.tsxmobile/src/components/CalendarView.tsxmobile/src/components/DraggableBoard.tsxmobile/src/components/EmptyState.tsxmobile/src/components/KnowledgeGraphWebView.tsxmobile/src/components/StackerGame.tsxmobile/src/components/WebViewRenderer.tsxmobile/src/components/ai-settings/styles.tsmobile/src/components/calendar/DayCell.tsxmobile/src/components/calendar/UnscheduledTray.tsxmobile/src/components/calendar/styles.tsmobile/src/components/toolbar-icons.tsmobile/src/sync/sync.tsmobile/src/theme.tssrc/components/notes/note-editor.tsxsrc/components/settings/ProviderManager.tsxsrc/components/settings/endpoint-components.tsx
💤 Files with no reviewable changes (1)
- mobile/src/components/calendar/DayCell.tsx
| - **Apple Intelligence works with more tools**: Some installed services (and any tool with an optional/nullable field in its schema) made Apple's on-device / Private Cloud Compute models refuse the whole request with an "unsupported schema type: null" error. Cairn now normalises those schemas to the types Apple accepts before sending them, so those tools work on Apple Foundation Models too. | ||
| - **Faster Tools & Services screen**: When a community catalog is already cached, the screen now shows it instantly and refreshes in the background, instead of blocking on a full-screen spinner. The catalog fetch also has a 20-second timeout so a hung network falls back to the cache rather than spinning forever. | ||
| - **Android API-key entry**: Installing a key-based service on Android now opens an in-app key prompt (Android has no native text-input alert), so key-based connectors can be added there too — iOS keeps its native prompt. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
These three bullets aren't accessibility items.
Apple Intelligence schema normalisation, the Tools & Services caching/timeout, and the Android key prompt belong under "Fixes" (or "Under the hood"), not "Accessibility" — only line 78 fits that heading.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@mobile/changelogs/v0.1.3.md` around lines 79 - 81, Move the three bullets
describing Apple Intelligence schema normalization, Tools & Services
caching/timeout, and Android API-key entry out of the accessibility section in
mobile/changelogs/v0.1.3.md and place them under the changelog’s Fixes or Under
the hood section. Keep only the accessibility-related item under the current
heading, preserving the existing bullet text.
…11y polish - chat: paint pinned empty state above the scroll view so "Set up AI" is tappable - tools: recompute cached MCP tool-def map after each refreshServerTools - credits: guard fetchKeyInfo/getKeyInfo with a generation so stale responses can't overwrite current/cleared state (desktop + mobile); minus sign before $ - stacker: stop resetting on dimension changes, add a camera offset so the growing tower stays on screen, publish only on real state changes, theme the stone label + game-over close text - ai-settings: readable credits subtext; a11y on model rows - changelog: fix bullet line break + move non-a11y items out of Accessibility
What does this PR do?
Bundles the
ddutchie/creditsline of work plus two follow-up fixes:getSnapshot should be cachedinfinite render loop.Type of change
Screenshots / recording
Checklist
npm run type-check:allpasses (mobiletsc --noEmitclean for the mobile changes)npm run lintpasses (eslint clean on changed mobile files)npm testpassesnpm run test:e2epasses (run before merging UI changes or cutting a release)text-[Npx]pixel font classeshandle()and returnIpcResult<T>— n/a to the new mobile work; the credits IPC (electron/ipc/ai-handlers.ts) added earlier on the branchDatabaseconstruction outside the two bootstrap sites--external:compile-flag changesNotes for reviewer
npm test,test:e2e, fulltype-check:allacross renderer + electron) I could not run in this environment — please run them before merge.pointerEvents="box-none", confirm scroll / pull-to-refresh / tapping the native search bar still work over the empty state, and that the Chat "Set up AI" button + the 5-tap egg both fire.SPEED_RAMP/MAX_SPEED) and thePERFECT_EPSalignment tolerance are tuned by eye — adjust if it feels too easy/hard.ddutchie/creditsand includes several prior commits; the newest two commits are the empty-state/egg and the note-banner/Settings fixes.Summary by CodeRabbit