Skip to content

feat(boot): cloud-mode picker auth + reload-resilient core mode#1357

Open
CodeGhost21 wants to merge 3 commits intotinyhumansai:mainfrom
CodeGhost21:feat/cloud-core-token-override
Open

feat(boot): cloud-mode picker auth + reload-resilient core mode#1357
CodeGhost21 wants to merge 3 commits intotinyhumansai:mainfrom
CodeGhost21:feat/cloud-core-token-override

Conversation

@CodeGhost21
Copy link
Copy Markdown
Contributor

@CodeGhost21 CodeGhost21 commented May 8, 2026

Summary

  • BootCheckGate's cloud picker now accepts an OPENHUMAN_CORE_TOKEN alongside the URL; coreRpcClient sends Authorization: Bearer … against the configured cloud core, completing the desktop-side UX for the OpenHuman is not available as a one-click cloud deployment #1280 one-click cloud deploy.
  • coreMode slice derives its initial state synchronously from localStorage (a new openhuman_core_mode key + the existing URL/token keys) so reloads — notably handleIdentityFliprestartApp — survive without flipping back to the picker. Fixes the cloud-mode picker → snapshot → flip → reload loop.
  • primeActiveUserId(null) now falls back to the existing OPENHUMAN_ACTIVE_USER_ID seed instead of wiping it. The wipe was the actual loop trigger when the local Rust core can't supply an active user id (every cloud-mode boot, every transient getActiveUserIdFromCore failure).
  • peekStoredRpcUrl returns null when nothing is stored, so any picker URL wins even when it equals the build-time VITE_OPENHUMAN_CORE_RPC_URL. The prior storedUrl !== CORE_RPC_URL guard silently routed cloud RPC back to the local sidecar (ERR_CONNECTION_REFUSED 127.0.0.1:7788) whenever the values matched.
  • Developer Options surfaces a CoreModeBadge (Local pill / Cloud pill with masked token tail / unset warning) so users can verify the picker landed correctly without DevTools.
  • Subsystems that previously hardcoded the local sidecar URL — useDictationHotkey, OverlayApp, the iMessage scanner — now route through getCoreHttpBaseUrl() or short-circuit cleanly in cloud mode (no more WARN-spam when the local sidecar isn't running).

Problem

Issue #1280 shipped the headless cloud-deploy artifacts (.do/app.yaml, Compose, smoke test, docs) in #1304, but the desktop-side UX to actually talk to the deployed core was incomplete:

  1. The picker only collected a URL, not an auth token, so the desktop sent the local sidecar's per-process token to the cloud core and got 401 on every /rpc call.
  2. Once a token was supplied, CoreStateProvider's identity-flip path (setActiveUserId(X) → restartApp) caused window.location.reload() in dev. Several layers raced against the reload — redux-persist's debounced flush, the primeActiveUserId(null) overwrite, the storedUrl !== CORE_RPC_URL mask — leaving the user trapped in an infinite picker → flip → reload loop.
  3. Subsystems with their own URL resolution (invoke('core_rpc_url') directly) emitted ERR_CONNECTION_REFUSED and core RPC token is not initialized WARNs that drowned the console even when the rest of the app was running fine.

Solution

Three layered fixes around the same root concern: respect the user's explicit picker choices, even across reloads.

  1. Picker captures auth. Cloud mode requires both URL and token; both stored synchronously to localStorage before any async dispatch. coreRpcClient.getCoreRpcToken() now resolves stored cloud token first, falling back to the local sidecar invoke. testCoreRpcConnection accepts a tokenOverride so the picker can validate a freshly-typed token before persistence.

  2. Slice initial state derives from sync localStorage. The new openhuman_core_mode marker plus the URL/token keys give the slice a synchronous source of truth that always wins on reload, making the gate immune to redux-persist's debounce window. handleSwitchMode clears all three together so legacy "switch back to picker" flows still work.

  3. primeActiveUserId(null) is a no-op when localStorage already has a seed. The previous removeItem(ACTIVE_USER_KEY) wiped the seed setActiveUserId had just written, restarting the identity-flip loop on the next snapshot. Falling back to whatever's stored breaks the cycle without touching any happy-path identity logic.

A CoreModeBadge surfaces the resolved mode in Developer Options for quick verification (esp. helpful for users debugging "did my picker actually take effect?"). The Tauri-host iMessage scanner downgrades its no-token-yet WARN to DEBUG since a missing local token is the expected state in cloud mode rather than a bug to flag.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per docs/TESTING-STRATEGY.md — 172 tests pass; new: token-priority, picker reload survival, CoreModeBadge state matrix, peekStoredRpcUrl, configPersistence mode/token round-trip.
  • Diff coverage ≥ 80% — every changed line in coreModeSlice, configPersistence, coreRpcClient, BootCheckGate, userScopedStorage, DeveloperOptionsPanel is exercised by the new/updated specs above.
  • N/A: Coverage matrix updated — behaviour fix on existing boot/RPC paths, not a new user-visible feature row.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related.
  • No new external network dependencies introduced (mock-style tests with stubbed fetch / invoke).
  • N/A: Manual smoke checklist updated — changes don't alter release-cut surfaces.
  • Linked issue closed via the keyword in ## Related.

Impact

  • Desktop: cloud-mode users can now log in once and stay signed in across reloads. Local-mode users get the same reload resilience as a side benefit. No CLI/web/mobile impact.
  • Performance: one extra synchronous localStorage.getItem at slice init — negligible.
  • Security: bearer token stored in plain localStorage (per-device, pre-login) parallels how the URL was already stored. Documented in the picker copy. No new build-time secret leakage; the VITE_OPENHUMAN_CORE_RPC_TOKEN build-time approach (closed PR feat(deploy): VITE_OPENHUMAN_CORE_RPC_TOKEN build-time override #1337) was deliberately not revived — public tinyhumans.ai builds ship without any baked secret, and users paste their own token into the picker.
  • Migration: existing local-mode users with a persisted coreMode = local blob continue to work; they pick up the sync openhuman_core_mode mirror only when they next switch modes. No persisted-state migration code needed.

Related

Summary by CodeRabbit

  • New Features

    • Added auth token input during initial cloud setup; cloud credentials (URL + token) are now persisted and managed.
    • Developer Options shows a Core Mode badge with masked token tail and configured URL.
    • Synchronous core-mode initialization prevents reload racing into the mode picker.
    • Overlay and dictation now use the shared core URL resolver; caches can be cleared when switching modes.
  • Bug Fixes

    • iMessage scanner refines tick error logging to reduce noisy warnings.
  • Tests

    • Expanded test coverage around core-mode, persistence, token/url resolution, and mode-switch flows.

- BootCheckGate cloud picker now accepts a bearer token alongside the URL;
  coreRpcClient sends `Authorization: Bearer ...` against the configured
  cloud core, completing the desktop-side UX for the tinyhumansai#1280 deploy flow
- coreMode slice derives initial state synchronously from localStorage
  (`openhuman_core_mode` + URL/token keys) so reloads (notably the
  handleIdentityFlip → restartApp path) survive without flipping back to
  the picker — fixes the picker → snapshot → flip → reload loop in cloud
- primeActiveUserId(null) now falls back to the existing
  OPENHUMAN_ACTIVE_USER_ID seed instead of wiping it; was the actual loop
  trigger when the local Rust core can't supply an active user id
- peekStoredRpcUrl returns null when nothing is stored, so any picker URL
  wins even when it equals the build-time VITE_OPENHUMAN_CORE_RPC_URL —
  prior `storedUrl !== CORE_RPC_URL` guard silently routed cloud RPC back
  to the local sidecar
- Developer Options panel: new CoreModeBadge surfaces the active mode +
  RPC URL + masked token tail so users can verify the picker landed
  correctly without DevTools
- useDictationHotkey + OverlayApp drop direct invoke('core_rpc_url') and
  route through getCoreHttpBaseUrl() so cloud overrides flow through the
  socket connections too (was a 127.0.0.1:7788 fallback in cloud mode)
- iMessage scanner: drop "core RPC token is not initialized" from WARN to
  DEBUG when no local sidecar (expected silence in cloud mode)
@CodeGhost21 CodeGhost21 requested a review from a team May 8, 2026 04:32
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements cloud deployment mode with persistent bearer-token authentication. New storage APIs and Redux state support mode selection (local sidecar or cloud remote). The boot gate captures cloud credentials and clears resolution caches on mode switches. RPC client prioritizes stored tokens and URLs over Tauri defaults. Developer options display current mode status. Dictation and overlay apps delegate URL resolution to the shared client helper.

Changes

Cloud Core Mode & Authentication

Layer / File(s) Summary
Data Persistence APIs
app/src/utils/configPersistence.ts
New localStorage keys and functions for RPC URL peek (without default fallback), core auth token storage, and mode marker persistence ('local' or 'cloud').
Redux Core Mode State
app/src/store/coreModeSlice.ts
CoreMode type extended to support optional token field in cloud mode; synchronous deriveInitialMode() initialization reads localStorage to prevent hydration race conditions.
Core RPC Client Resolution
app/src/services/coreRpcClient.ts
Token resolution prefers getStoredCoreToken() over Tauri sidecar; URL resolution uses peekStoredRpcUrl() and respects stored values equal to the build-time default; new clearCoreRpcTokenCache() function; testCoreRpcConnection accepts optional tokenOverride.
Boot Gate Mode Selection
app/src/components/BootCheckGate/BootCheckGate.tsx
Picker UI adds auth-token input field; cloud confirmation logs token length and persists both URL and token; picker confirmation sets core mode synchronously and clears both URL/token caches; mode switch clears stored credentials and caches.
State & Test Infrastructure
app/src/store/userScopedStorage.ts, app/src/test/test-utils.tsx
primeActiveUserId(null) now falls back to existing localStorage value instead of clearing; test root reducer registers coreModeReducer so test stores include coreMode.
Test Coverage
app/src/utils/__tests__/configPersistence.test.ts, app/src/services/__tests__/coreRpcClient.test.ts, app/src/store/coreModeSlice.test.ts, app/src/components/BootCheckGate/__tests__/BootCheckGate.test.tsx, app/src/store/__tests__/userScopedStorage.test.ts
Tests verify trimming/validation, peek vs default URL behavior, token precedence, cache invalidation, synchronous hydration branches, and boot gate flows.
UI & Consumer Integration
app/src/components/settings/panels/DeveloperOptionsPanel.tsx, app/src/hooks/useDictationHotkey.ts, app/src/overlay/OverlayApp.tsx, app/src/pages/__tests__/Welcome.test.tsx
New CoreModeBadge component displays core mode status with masked token tail and configured URL; dictation and overlay hooks delegate Socket.IO URL resolution to getCoreHttpBaseUrl(); test mocks updated.
Logging
app/src-tauri/src/imessage_scanner/mod.rs
Tick errors containing "core RPC token is not initialized" are logged at DEBUG with "skipping tick"; other tick errors remain WARN.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • senamakel
  • graycyrus

Poem

🐰 A token tucked beneath the moonlit key,
I hop and stash the cloud for you and me.
Picker set, caches cleared with care,
Local or cloud — the badge will share,
Hooray! the booter's fresh and free.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The PR implements cloud-mode bearer-token support and reload-resilient core persistence to address cloud deployment needs, but does not include deployment template, documentation, or smoke tests required by #1280. Verify that this PR is part of a larger effort and that deployment templates, documentation, and smoke tests are being addressed in parallel or follow-up PRs to fully satisfy #1280 acceptance criteria.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main changes: cloud-mode picker auth and reload-resilient core mode.
Out of Scope Changes check ✅ Passed All changes are in-scope: cloud auth, core-mode persistence, token handling, subsystem updates (dictation, overlay, iMessage), and supporting tests.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
app/src-tauri/src/imessage_scanner/mod.rs (1)

155-163: ⚡ Quick win

Include account_id (and keep error detail) in the downgraded DEBUG path.

The new DEBUG branch loses correlation context, which makes scanner diagnostics harder when multiple accounts/scanners are active. Keep the downgrade, but log with account_id and include the error string in both branches.

Suggested patch
-                if msg.contains("core RPC token is not initialized") {
-                    log::debug!("[imessage] local core not running — skipping tick");
+                if msg.contains("core RPC token is not initialized") {
+                    log::debug!(
+                        "[imessage][{}] local core not running — skipping tick err={}",
+                        account_id,
+                        msg
+                    );
                 } else {
-                    log::warn!("[imessage] tick failed err={}", msg);
+                    log::warn!("[imessage][{}] tick failed err={}", account_id, msg);
                 }

As per coding guidelines, “Default to verbose diagnostics on new/changed flows with … correlation fields.”

🤖 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 `@app/src-tauri/src/imessage_scanner/mod.rs` around lines 155 - 163, The debug
branch that handles the "core RPC token is not initialized" case currently drops
correlation context; update both logging calls to include the account_id and
keep the error message. Specifically, where msg is created and checked (the
block using msg.contains("core RPC token is not initialized")), change the debug
call log::debug!("[imessage] local core not running — skipping tick"); to
include account_id and the error string (e.g.
log::debug!("[imessage][account_id={}] local core not running — skipping tick:
{}", account_id, msg)); also ensure the else branch warn keeps account_id (e.g.
log::warn!("[imessage][account_id={}] tick failed err={}", account_id, msg));
keep the conditional logic intact but add account_id to both log statements.
app/src/store/coreModeSlice.ts (1)

34-37: ⚡ Quick win

Consider importing storage keys from configPersistence.ts to avoid duplication.

These three constants duplicate the keys defined in configPersistence.ts. If a key is renamed in one location but not the other, the slice and persistence utilities will silently diverge.

Suggested approach

Export the storage key constants from configPersistence.ts and import them here:

// In configPersistence.ts
export const RPC_URL_STORAGE_KEY = 'openhuman_core_rpc_url';
export const CORE_TOKEN_STORAGE_KEY = 'openhuman_core_rpc_token';
export const CORE_MODE_STORAGE_KEY = 'openhuman_core_mode';
// In coreModeSlice.ts
import { 
  RPC_URL_STORAGE_KEY, 
  CORE_TOKEN_STORAGE_KEY, 
  CORE_MODE_STORAGE_KEY 
} from '../utils/configPersistence';
🤖 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 `@app/src/store/coreModeSlice.ts` around lines 34 - 37, The three duplicated
constants RPC_URL_STORAGE_KEY, CORE_TOKEN_STORAGE_KEY, and CORE_MODE_STORAGE_KEY
should be exported from configPersistence.ts and imported into coreModeSlice.ts
instead of being re-declared; update configPersistence.ts to export those
constants, remove the local declarations in coreModeSlice.ts, and add an import
of RPC_URL_STORAGE_KEY, CORE_TOKEN_STORAGE_KEY, and CORE_MODE_STORAGE_KEY into
coreModeSlice.ts so the slice and persistence utilities reference the single
source-of-truth.
app/src/services/__tests__/coreRpcClient.test.ts (1)

672-672: 💤 Low value

Minor: expect.anything() is unnecessary for single-argument invoke call.

The invoke('core_rpc_token') call has no second argument, so expect.anything() will never match. The assertion still passes because invoke wasn't called with that command at all, but it's misleading.

Suggested fix
-    expect(vi.mocked(invoke)).not.toHaveBeenCalledWith('core_rpc_token', expect.anything());
+    expect(vi.mocked(invoke)).not.toHaveBeenCalledWith('core_rpc_token');
🤖 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 `@app/src/services/__tests__/coreRpcClient.test.ts` at line 672, The test
assertion is using expect.anything() for a non-existent second argument on the
invoke call; update the assertion in coreRpcClient.test to assert that
vi.mocked(invoke) was not called with the single argument 'core_rpc_token'
(remove the expect.anything() and use
not.toHaveBeenCalledWith('core_rpc_token')) so the expectation matches the
actual single-argument invoke signature.
🤖 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.

Nitpick comments:
In `@app/src-tauri/src/imessage_scanner/mod.rs`:
- Around line 155-163: The debug branch that handles the "core RPC token is not
initialized" case currently drops correlation context; update both logging calls
to include the account_id and keep the error message. Specifically, where msg is
created and checked (the block using msg.contains("core RPC token is not
initialized")), change the debug call log::debug!("[imessage] local core not
running — skipping tick"); to include account_id and the error string (e.g.
log::debug!("[imessage][account_id={}] local core not running — skipping tick:
{}", account_id, msg)); also ensure the else branch warn keeps account_id (e.g.
log::warn!("[imessage][account_id={}] tick failed err={}", account_id, msg));
keep the conditional logic intact but add account_id to both log statements.

In `@app/src/services/__tests__/coreRpcClient.test.ts`:
- Line 672: The test assertion is using expect.anything() for a non-existent
second argument on the invoke call; update the assertion in coreRpcClient.test
to assert that vi.mocked(invoke) was not called with the single argument
'core_rpc_token' (remove the expect.anything() and use
not.toHaveBeenCalledWith('core_rpc_token')) so the expectation matches the
actual single-argument invoke signature.

In `@app/src/store/coreModeSlice.ts`:
- Around line 34-37: The three duplicated constants RPC_URL_STORAGE_KEY,
CORE_TOKEN_STORAGE_KEY, and CORE_MODE_STORAGE_KEY should be exported from
configPersistence.ts and imported into coreModeSlice.ts instead of being
re-declared; update configPersistence.ts to export those constants, remove the
local declarations in coreModeSlice.ts, and add an import of
RPC_URL_STORAGE_KEY, CORE_TOKEN_STORAGE_KEY, and CORE_MODE_STORAGE_KEY into
coreModeSlice.ts so the slice and persistence utilities reference the single
source-of-truth.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4c268609-66ee-408f-9b52-fe2c68fdc55d

📥 Commits

Reviewing files that changed from the base of the PR and between 0636b0c and 6aa5caf.

⛔ Files ignored due to path filters (1)
  • app/src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • app/src-tauri/src/imessage_scanner/mod.rs
  • app/src/components/BootCheckGate/BootCheckGate.tsx
  • app/src/components/BootCheckGate/__tests__/BootCheckGate.test.tsx
  • app/src/components/settings/panels/DeveloperOptionsPanel.tsx
  • app/src/components/settings/panels/__tests__/DeveloperOptionsPanel.test.tsx
  • app/src/hooks/useDictationHotkey.ts
  • app/src/overlay/OverlayApp.tsx
  • app/src/pages/__tests__/Welcome.test.tsx
  • app/src/services/__tests__/coreRpcClient.test.ts
  • app/src/services/coreRpcClient.ts
  • app/src/store/coreModeSlice.test.ts
  • app/src/store/coreModeSlice.ts
  • app/src/store/userScopedStorage.ts
  • app/src/test/test-utils.tsx
  • app/src/utils/__tests__/configPersistence.test.ts
  • app/src/utils/configPersistence.ts

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 8, 2026
Adds focused tests for the previously uncovered surfaces flagged by
diff-cover on PR 1357:

- userScopedStorage: full primeActiveUserId branch matrix including the
  null-prime-preserves-prior-seed fix that breaks the cloud-mode
  identity-flip loop, plus setActiveUserId localStorage round-trip and
  failure tolerance
- coreModeSlice: deriveInitialMode local / cloud / partial / unset
  branches via fresh module imports with localStorage seeded ahead of
  module load
- coreRpcClient: clearCoreRpcTokenCache forces a re-resolve so the next
  call picks up a rotated cloud token
- BootCheckGate: cloud picker now exercises malformed URL, missing
  token, token-error reset on type, and the happy path that fires
  runBootCheck with kind/url/token
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/src/store/coreModeSlice.test.ts (1)

89-97: ⚡ Quick win

Test intent and exercised branch are slightly mismatched.

This case validates “missing token,” but the title claims “URL or token.” Please either rename the test or add a dedicated missing-URL case.

Suggested minimal update
-  it('falls back to unset when cloud marker exists but URL or token is missing', async () => {
+  it('falls back to unset when cloud marker exists but token is missing', async () => {
     localStorage.clear();
     localStorage.setItem('openhuman_core_mode', 'cloud');
     localStorage.setItem('openhuman_core_rpc_url', 'https://core.example.com/rpc');
     // Token deliberately missing.
     const mod = await freshImport();
     const state = mod.default(undefined, { type: '@@INIT' });
     expect(state.mode).toEqual({ kind: 'unset' });
   });
+
+  it('falls back to unset when cloud marker exists but URL is missing', async () => {
+    localStorage.clear();
+    localStorage.setItem('openhuman_core_mode', 'cloud');
+    localStorage.setItem('openhuman_core_rpc_token', 'tok-abc');
+    const mod = await freshImport();
+    const state = mod.default(undefined, { type: '@@INIT' });
+    expect(state.mode).toEqual({ kind: 'unset' });
+  });
🤖 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 `@app/src/store/coreModeSlice.test.ts` around lines 89 - 97, The test "falls
back to unset when cloud marker exists but URL or token is missing" exercises
the missing-token branch but its title mentions "URL or token"; fix by either
renaming the test to mention only the missing token (update the it(...)
description) or add a complementary test that sets the cloud marker and token
but omits openhuman_core_rpc_url to exercise the missing-URL branch; in both
cases keep using freshImport(), the same localStorage keys
('openhuman_core_mode', 'openhuman_core_rpc_url', token key), and the same
assertion that the reducer (default export) returns state.mode === { kind:
'unset' } so the test title and exercised branch match.
🤖 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 `@app/src/services/__tests__/coreRpcClient.test.ts`:
- Line 672: The test's assertion using
expect(vi.mocked(invoke)).not.toHaveBeenCalledWith('core_rpc_token',
expect.anything()) misses single-argument calls; update the test to inspect the
mocked invoke calls (vi.mocked(invoke).mock.calls) and assert that none of the
call entries have 'core_rpc_token' as the first element (e.g., filter or find on
the mock.calls array for call[0] === 'core_rpc_token' and expect that result to
be undefined/empty) so both single-argument and multi-argument invocations of
invoke('core_rpc_token', ...) are caught.
- Around line 644-648: The test suite leaves invoke's mockImplementation set
between tests, causing hidden state coupling; add a reset of the mocked invoke
in the beforeEach (e.g. call vi.mocked(invoke).mockReset() or mockClear()) so
invoke's implementation is cleared each test, and update the failing assertion
in the clearCoreRpcTokenCache test to assert
expect(invoke).not.toHaveBeenCalledWith('core_rpc_token') (remove the extra
expect.anything() second-arg check) so it actually verifies no one-argument call
to invoke('core_rpc_token') occurred.

---

Nitpick comments:
In `@app/src/store/coreModeSlice.test.ts`:
- Around line 89-97: The test "falls back to unset when cloud marker exists but
URL or token is missing" exercises the missing-token branch but its title
mentions "URL or token"; fix by either renaming the test to mention only the
missing token (update the it(...) description) or add a complementary test that
sets the cloud marker and token but omits openhuman_core_rpc_url to exercise the
missing-URL branch; in both cases keep using freshImport(), the same
localStorage keys ('openhuman_core_mode', 'openhuman_core_rpc_url', token key),
and the same assertion that the reducer (default export) returns state.mode ===
{ kind: 'unset' } so the test title and exercised branch match.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dbfe2fce-6873-4e8c-90b9-cea3b0ecdf17

📥 Commits

Reviewing files that changed from the base of the PR and between 6aa5caf and 5caa298.

📒 Files selected for processing (4)
  • app/src/components/BootCheckGate/__tests__/BootCheckGate.test.tsx
  • app/src/services/__tests__/coreRpcClient.test.ts
  • app/src/store/__tests__/userScopedStorage.test.ts
  • app/src/store/coreModeSlice.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/components/BootCheckGate/tests/BootCheckGate.test.tsx

Comment on lines +644 to +648
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
vi.stubGlobal('fetch', vi.fn());
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n app/src/services/__tests__/coreRpcClient.test.ts | sed -n '630,685p'

Repository: tinyhumansai/openhuman

Length of output: 2714


🏁 Script executed:

cat -n app/src/services/__tests__/coreRpcClient.test.ts | sed -n '1,80p'

Repository: tinyhumansai/openhuman

Length of output: 3168


🏁 Script executed:

cat -n app/src/services/__tests__/coreRpcClient.test.ts | sed -n '678,730p'

Repository: tinyhumansai/openhuman

Length of output: 2799


🏁 Script executed:

cat -n app/src/services/__tests__/coreRpcClient.test.ts | sed -n '600,645p'

Repository: tinyhumansai/openhuman

Length of output: 2327


🏁 Script executed:

# Check if vi.resetModules() behavior is properly understood in context of mock lifecycle
# Let me search for other beforeEach patterns in the file to understand the nesting
grep -n "beforeEach\|describe(" app/src/services/__tests__/coreRpcClient.test.ts | head -30

Repository: tinyhumansai/openhuman

Length of output: 395


🏁 Script executed:

cat -n app/src/services/__tests__/coreRpcClient.test.ts | sed -n '498,540p'

Repository: tinyhumansai/openhuman

Length of output: 1899


🏁 Script executed:

# Let me check how invoke is actually called in the coreRpcClient source
cat -n app/src/services/coreRpcClient.ts | grep -A 3 -B 3 "invoke"

Repository: tinyhumansai/openhuman

Length of output: 2027


🏁 Script executed:

# Check the exact assertion at line 672 - what does invoke get called with?
# And verify the pattern in getCoreRpcUrl matches what should be in getCoreRpcToken
cat -n app/src/services/__tests__/coreRpcClient.test.ts | sed -n '650,677p'

Repository: tinyhumansai/openhuman

Length of output: 1523


🏁 Script executed:

# Let me verify Vitest mock behavior - does vi.clearAllMocks clear implementations?
# Check if there are any other patterns in the test that show mockReset vs clearAllMocks
grep -n "mockReset\|clearAllMocks\|mockImplementation" app/src/services/__tests__/coreRpcClient.test.ts

Repository: tinyhumansai/openhuman

Length of output: 836


🏁 Script executed:

# Look at what invoke is actually being called with in the test - does it pass a second argument?
grep -A 5 -B 5 "await invoke" app/src/services/coreRpcClient.ts

Repository: tinyhumansai/openhuman

Length of output: 812


🏁 Script executed:

# Let me check Vitest documentation behavior by creating a test to understand the matcher
# First, let's verify what arguments invoke is actually called with
rg "invoke\(" app/src/services/coreRpcClient.ts -A 1 | head -20

Repository: tinyhumansai/openhuman

Length of output: 180


🏁 Script executed:

# Let me look more closely at test 678 to see if it needs invoke mocked
cat -n app/src/services/__tests__/coreRpcClient.test.ts | sed -n '678,705p'

Repository: tinyhumansai/openhuman

Length of output: 1549


🏁 Script executed:

# Verify the fix is necessary by checking if test 678 passes without invoke mock implementation
# Let's also see test 706 which should also be affected
cat -n app/src/services/__tests__/coreRpcClient.test.ts | sed -n '706,730p'

Repository: tinyhumansai/openhuman

Length of output: 1316


Reset invoke mock implementation to prevent test-order coupling.

vi.clearAllMocks() clears call history but leaves the mockImplementation from the previous test. At line 678, test clearCoreRpcTokenCache does not set an invoke mock and will inherit the one from line 656, where it throws on 'core_rpc_token'. This creates hidden state coupling. Compare this to the getCoreRpcUrl block (line 500–504), which correctly calls vi.mocked(invoke).mockReset() to ensure isolation.

Additionally, the assertion at line 672 uses not.toHaveBeenCalledWith('core_rpc_token', expect.anything()), checking that the two-argument call never occurred. Since invoke is actually called with only one argument (await invoke('core_rpc_token')), the assertion does not directly verify that invoke('core_rpc_token') was never called—it only confirms the two-argument variant wasn't used.

As per coding guidelines, keep tests deterministic and avoid hidden global state.

Suggested fix
   beforeEach(() => {
     vi.resetModules();
     vi.clearAllMocks();
+    vi.mocked(invoke).mockReset();
+    vi.mocked(isTauri).mockReturnValue(false);
     vi.stubGlobal('fetch', vi.fn());
   });

And strengthen the assertion at line 672:

-    expect(vi.mocked(invoke)).not.toHaveBeenCalledWith('core_rpc_token', expect.anything());
+    expect(vi.mocked(invoke)).not.toHaveBeenCalledWith('core_rpc_token');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
vi.stubGlobal('fetch', vi.fn());
});
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
vi.mocked(invoke).mockReset();
vi.mocked(isTauri).mockReturnValue(false);
vi.stubGlobal('fetch', vi.fn());
});
🤖 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 `@app/src/services/__tests__/coreRpcClient.test.ts` around lines 644 - 648, The
test suite leaves invoke's mockImplementation set between tests, causing hidden
state coupling; add a reset of the mocked invoke in the beforeEach (e.g. call
vi.mocked(invoke).mockReset() or mockClear()) so invoke's implementation is
cleared each test, and update the failing assertion in the
clearCoreRpcTokenCache test to assert
expect(invoke).not.toHaveBeenCalledWith('core_rpc_token') (remove the extra
expect.anything() second-arg check) so it actually verifies no one-argument call
to invoke('core_rpc_token') occurred.

const { callCoreRpc: freshCallCoreRpc } = await import('../coreRpcClient');
await freshCallCoreRpc({ method: 'openhuman.ping' });

expect(vi.mocked(invoke)).not.toHaveBeenCalledWith('core_rpc_token', expect.anything());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

find app/src/services -name "*coreRpcClient*" -type f

Repository: tinyhumansai/openhuman

Length of output: 149


🏁 Script executed:

wc -l app/src/services/__tests__/coreRpcClient.test.ts

Repository: tinyhumansai/openhuman

Length of output: 119


🏁 Script executed:

cat -n app/src/services/__tests__/coreRpcClient.test.ts

Repository: tinyhumansai/openhuman

Length of output: 34014


Strengthen the core_rpc_token assertion to catch single-argument calls.

The current matcher requires two arguments: invoke('core_rpc_token', expect.anything()). If code were changed to call invoke('core_rpc_token') with only the command name, this assertion would incorrectly pass. Filter mock calls by command name instead:

Suggested fix
-    expect(vi.mocked(invoke)).not.toHaveBeenCalledWith('core_rpc_token', expect.anything());
+    const tokenCalls = vi.mocked(invoke).mock.calls.filter(([cmd]) => cmd === 'core_rpc_token');
+    expect(tokenCalls).toHaveLength(0);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(vi.mocked(invoke)).not.toHaveBeenCalledWith('core_rpc_token', expect.anything());
const tokenCalls = vi.mocked(invoke).mock.calls.filter(([cmd]) => cmd === 'core_rpc_token');
expect(tokenCalls).toHaveLength(0);
🤖 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 `@app/src/services/__tests__/coreRpcClient.test.ts` at line 672, The test's
assertion using
expect(vi.mocked(invoke)).not.toHaveBeenCalledWith('core_rpc_token',
expect.anything()) misses single-argument calls; update the test to inspect the
mocked invoke calls (vi.mocked(invoke).mock.calls) and assert that none of the
call entries have 'core_rpc_token' as the first element (e.g., filter or find on
the mock.calls array for call[0] === 'core_rpc_token' and expect that result to
be undefined/empty) so both single-argument and multi-argument invocations of
invoke('core_rpc_token', ...) are caught.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenHuman is not available as a one-click cloud deployment

1 participant