Skip to content

fix(oauth): make loopback redirect actually work, plus settings cleanup#2550

Merged
senamakel merged 11 commits into
tinyhumansai:mainfrom
senamakel:fix/oauth-loopback-dev-redirect
May 23, 2026
Merged

fix(oauth): make loopback redirect actually work, plus settings cleanup#2550
senamakel merged 11 commits into
tinyhumansai:mainfrom
senamakel:fix/oauth-loopback-dev-redirect

Conversation

@senamakel
Copy link
Copy Markdown
Member

@senamakel senamakel commented May 23, 2026

Summary

  • Fix the desktop loopback OAuth flow end-to-end: it never delivered a callback before because of a snake_case/camelCase mismatch silently failing JS, plus several other race conditions.
  • Move Log Out + Clear App Data out of the Settings home into the Account page footer.
  • Combine Notifications preferences and Notification Routing under one Settings entry with two tabs.
  • Move the homepage theme toggle into the main home card's header row.
  • Delete the unused Connections panel (MetaMask / Binance / Notion / Google placeholders) and its route entirely.

Problem

The loopback OAuth path landed in #2511 but was effectively dead on arrival:

  • StartResult serialized as redirect_uri / state while the TS wrapper read result.redirectUri. The undefined value tripped a silent TypeError in appendState, so every successful Rust-side bind was immediately abandoned in JS. Net effect: every OAuth click silently fell back to the openhuman:// deep link.
  • In dev (pnpm dev:app), the OAuth button always set responseType=json even when a loopback was active. The backend then returned JSON in the browser tab instead of redirecting, so the loopback listener never fired.
  • The start_loopback_oauth_listener / stop_loopback_oauth_listener Tauri commands weren't in the allow-core-process capability allow-list, so every invoke returned "Command not found".
  • Two rapid clicks failed the second bind with EADDRINUSE: cancel was just a oneshot signal, not awaited.
  • The JS-side listen() handler from a previous click stayed registered after Rust cancelled its listener, so the next callback fanned out to multiple stale handlers.

The Settings home was also getting noisy — destructive actions mixed with navigation rows, and notification preferences vs routing lived in two unrelated entries. The Connections panel exposed Web3 / wallet placeholders that aren't part of the product anymore.

Solution

  • Tag StartResult with #[serde(rename_all = \"camelCase\")].
  • Only set responseType=json when there is no loopback handle (web build or bind failure).
  • Add start_loopback_oauth_listener / stop_loopback_oauth_listener to allow-core-process.toml.
  • Use TcpSocket with SO_REUSEADDR, hold a JoinHandle for the active listener, and await it before re-binding so the OS has actually released the fixed loopback port.
  • Fall back to an OS-assigned ephemeral port when the requested port is taken by an unrelated process — the backend's redirectUri whitelist restricts host but not port.
  • Track and unsubscribe the previous listen() handle before installing a new one.
  • Extract Log Out / Clear App Data + the confirmation modal into LogoutAndClearActions and render it as the footer of the Account page via a new footer prop on SettingsSectionPage.
  • New NotificationsTabbedPanel owns the header + tab strip and embeds both existing panels (each gained an embedded flag to suppress its own chrome). Legacy /settings/notification-routing redirects to /settings/notifications#routing.
  • Delete ConnectionsPanel.tsx + its test; remove the account-section entry, route, navigation hook type, and Developer Options entry. Repoint the post-onboarding oauth wizard deep link to /settings/composio-routing. walletApi stays — it's still used by the recovery-phrase flow.

The Settings → Account page now renders Recovery Phrase / Team / Privacy / Migration followed by Clear App Data + Log out. The home menu is purely navigational.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • N/A: only the diff-cover gate enforced by CI applies here. Local snapshot below.
  • N/A: no matrix-tracked feature added/removed/renamed (loopback OAuth flow already had a row; Notifications/Account changes are UX-only).
  • N/A: no matrix row added.
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • N/A: changes don't touch release-cut manual smoke surfaces — OAuth path already in the smoke list; this fixes its actual behavior without changing the surface.
  • N/A: no linked issue.

Impact

  • Desktop only (Windows / macOS / Linux). Loopback OAuth flow now actually delivers the callback into the app instead of silently falling back to openhuman://.
  • Backend dependency: tinyhumansai/backend#836 (merged) which honors the redirectUri query param.
  • Settings UX: home menu loses 2 rows (destructive actions moved to Account); Developer Options loses the Notification Routing entry (now a tab inside Notifications); /settings/connections is gone.

Pre-push hook note: the pre-push hook auto-applied formatter fixes (Prettier import-order + cargo fmt). Committed as chore: apply auto-fixes. No --no-verify was used.

Related

  • Closes:
  • Follow-up PR(s)/TODOs: cover the new loopback re-bind path with a focused cargo test (currently exercised manually + via existing loopback_oauth unit tests).

AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/oauth-loopback-dev-redirect
  • Commit SHA: 8b0cc3d

Validation Run

  • `pnpm --filter openhuman-app format:check` (pre-push hook applied auto-fixes, then passed)
  • `pnpm typecheck`
  • Focused tests: `pnpm test src/components/settings/tests/SettingsHome.test.tsx src/components/settings/tests/LogoutAndClearActions.test.tsx src/components/settings/hooks/tests/useSettingsNavigation.test.tsx src/components/settings/panels/NotificationsPanel.test.tsx src/components/settings/panels/NotificationRoutingPanel.test.tsx src/pages/tests/Home.test.tsx` — all green (47 passing).
  • Rust fmt/check (if changed): `pnpm rust:check` clean.
  • Tauri fmt/check (if changed): `pnpm rust:check` clean.

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: desktop OAuth now completes via the loopback path instead of falling back to `openhuman://` every time. Settings → Notifications becomes a tabbed page; destructive actions move to the Account page; Connections is gone.
  • User-visible effect: OAuth logins on macOS/Linux/Windows actually return the user to the app; cleaner Settings hierarchy.

Parity Contract

  • Legacy behavior preserved: yes — `/settings/notification-routing` redirects to the new tab, loopback path still falls back to deep-link on bind failure, and the openhuman:// handler is unchanged.
  • Guard/fallback/dispatch parity checks: loopback callback URL is still rewritten to `openhuman://auth?…` and dispatched through `handleDeepLinkUrls` so token consume + CoreStateProvider commit logic is identical.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this PR
  • Resolution: N/A

Summary by CodeRabbit

  • New Features

    • Added OAuth loopback listener support for improved authentication callback handling.
    • Notification settings now organized with tabbed interface for Preferences and Routing.
  • UI Changes

    • Moved "Log Out" and "Clear App Data" actions to Account settings page.
    • Removed Connections settings page.
    • Repositioned theme toggle in Home page header.
  • Tests

    • Added tests for logout and clear data functionality.
    • Updated settings tests for new structure.
  • Refactor

    • Restructured notification settings routing.
    • Enhanced settings panel footer extensibility.

Review Change Stack

senamakel added 8 commits May 23, 2026 02:39
In dev (pnpm dev:app), the OAuth button was setting both
`responseType=json` and `redirectUri=<loopback>` on the backend login URL.
`responseType=json` makes the backend return JSON in the browser tab
instead of redirecting, so the loopback listener never receives the
callback and the app stays stuck on the loading state even though
OAuth itself succeeds.

Only set `responseType=json` when there is no loopback handle (web
build or bind failure), preserving the pre-loopback dev workaround as
a fallback.
`start_loopback_oauth_listener` / `stop_loopback_oauth_listener` were
registered in the invoke handler (tinyhumansai#2511) but never added to the
`allow-core-process` capability allow-list, so every Tauri invoke was
rejected with "Command not found" and the loopback path silently fell
back to the deep-link redirect on every login click.

Adds both commands to the allow-list so the loopback flow can actually
fire on desktop.
Five issues that together prevented the loopback OAuth path from ever
completing end-to-end in dev:

- StartResult serialized as `redirect_uri`/`state` (snake_case) but TS
  read `result.redirectUri`. The undefined value tripped a silent
  TypeError in appendState, so every "successful" listener bind was
  immediately abandoned in JS. Tag with `#[serde(rename_all = "camelCase")]`.
- Two clicks in quick succession failed the second bind with EADDRINUSE:
  `cancel_active_listener` only signalled the previous task; the socket
  was still owned. Switch to TcpSocket with SO_REUSEADDR + await the
  previous task's JoinHandle before re-binding.
- Fall back to an OS-assigned ephemeral port if the requested port is
  taken by a stale/unrelated process. The backend `redirectUri` whitelist
  restricts host but not port, so this is safe.
- JS-side `listen()` handler from a previous click stayed registered
  after Rust cancelled its listener, so the next callback fanned out to
  every stale handler. Track and unsubscribe before installing a new one.
- (Sibling commit) Loopback commands were missing from the
  `allow-core-process` capability allow-list; every invoke returned
  "Command not found".
The sun/moon toggle used to sit above the main card in its own row.
Move it into the card's header row alongside the version label so the
card owns its own controls. The version stays visually centered via a
width-matched spacer on the left.
…ntry

Notification routing (previously buried in Developer Options) is now a
second tab inside the main Settings → Notifications page, alongside the
existing per-channel/DnD preferences. The two panels remain independent
components but accept an `embedded` flag so the new
`NotificationsTabbedPanel` wrapper owns the SettingsHeader and tab strip.

The legacy `/settings/notification-routing` path redirects to
`/settings/notifications#routing` so existing deep links keep working,
and the entry has been removed from Developer Options.
…ions

- Extract Log Out / Clear App Data + the confirmation modal out of
  Settings home into a new LogoutAndClearActions component and render
  it as the footer of the Settings → Account page. SettingsSectionPage
  grows an optional `footer` slot. The home menu is now purely
  navigational; destructive actions live with the rest of the
  account-scoped controls.
- Delete the Connections panel entirely (MetaMask / Binance / Notion /
  Google placeholders that are no longer part of the product), along
  with its test and account-section entry. The deep route
  `/settings/connections` is gone; references in the navigation hook,
  Developer Options, and the post-onboarding deep link are removed or
  repointed to `/settings/composio-routing`. `walletApi` stays — it's
  still used by the recovery-phrase flow.
- Move the Clear-App-Data flow tests to the new component, and add a
  guard on SettingsHome that the destructive entries no longer appear
  on the home screen.
@senamakel senamakel requested a review from a team May 23, 2026 16:44
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 2026

Warning

Review limit reached

@senamakel, we couldn't start this review because you've used your available PR reviews for now.

Your plan currently allows 2 reviews/hour. Refill in 1 minute and 51 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 57919eef-bb31-41bd-ab40-d8cc107ca94f

📥 Commits

Reviewing files that changed from the base of the PR and between 82261a8 and bb42e73.

📒 Files selected for processing (3)
  • app/src/components/settings/LogoutAndClearActions.tsx
  • app/src/components/settings/SettingsHome.tsx
  • app/src/components/settings/__tests__/LogoutAndClearActions.test.tsx
📝 Walkthrough

Walkthrough

This PR enhances loopback OAuth callback reliability via Rust task lifecycle tracking and socket reuse, while simultaneously reorganizing the Settings page: moving destructive account actions to a dedicated footer component, consolidating notifications into a hash-driven tabbed interface, and removing the deprecated Connections panel.

Changes

Loopback OAuth lifecycle and reliability

Layer / File(s) Summary
Loopback OAuth task lifecycle and socket binding (Rust)
app/src-tauri/src/loopback_oauth.rs
ActiveListener now tracks async join handles to await prior listener completion before rebinding. New bind_loopback helper uses TcpSocket with SO_REUSEADDR for improved port reuse. start_loopback_oauth_listener implements ephemeral port fallback when the requested port is unavailable.
Loopback OAuth Tauri permissions configuration
app/src-tauri/capabilities/default.json, app/src-tauri/permissions/allow-loopback-oauth.toml
Added allow-loopback-oauth permission granting access to start_loopback_oauth_listener and stop_loopback_oauth_listener commands for RFC 8252 loopback OAuth callback handling.
Loopback OAuth JavaScript listener lifecycle management
app/src/utils/loopbackOauthListener.ts, app/src/components/oauth/OAuthProviderButton.tsx
Module-level activeUnlisten tracks and cancels prior JS listeners before starting new OAuth flows, preventing stale callback duplication. Dev-mode JSON response type now conditional on loopback unavailability.

Settings page reorganization and account management

Layer / File(s) Summary
LogoutAndClearActions component with modal and error handling
app/src/components/settings/LogoutAndClearActions.tsx, app/src/components/settings/__tests__/LogoutAndClearActions.test.tsx
New component renders logout and "clear all app data" actions with confirmation modal, loading state, and localized error messages. Derives userId from snapshot and calls clearAllAppData with proper error fallback. Test suite validates UI, modal flow, and error-message handling.
SettingsSectionPage footer prop support
app/src/components/settings/SettingsSectionPage.tsx
Adds optional footer?: ReactNode prop enabling injection of content (e.g., LogoutAndClearActions) below settings menu items.
Remove destructive actions from Settings Home page
app/src/components/settings/SettingsHome.tsx, app/src/components/settings/__tests__/SettingsHome.test.tsx
Removes logout/clear handlers, modal state, and JSX from Settings Home. Simplifies menu to derive only from settingsSections. Tests updated to assert actions no longer appear on home, with comments indicating they moved to Account.
Remove Connections panel and update routing
app/src/components/settings/panels/ConnectionsPanel.tsx, app/src/components/settings/panels/__tests__/ConnectionsPanel.test.tsx, app/src/components/settings/hooks/useSettingsNavigation.ts, app/src/pages/onboarding/customWizardSteps.ts
Deletes ConnectionsPanel component and tests. Removes 'connections' route from SettingsRoute union and its URL resolution and breadcrumb handling. Updates onboarding wizard oauth link from /settings/connections to /settings/composio-routing.
Notifications tabbed UI with hash-driven navigation
app/src/components/settings/panels/NotificationsTabbedPanel.tsx, app/src/components/settings/panels/NotificationsPanel.tsx, app/src/components/settings/panels/NotificationRoutingPanel.tsx, app/src/components/settings/panels/DeveloperOptionsPanel.tsx
Creates NotificationsTabbedPanel with preferences/routing tabs selected via URL hash (#routing). Makes both panel components embeddable via optional embedded prop that hides headers. Removes notification-routing from developer menu and redirects legacy route to /settings/notifications#routing.
Translation keys for notification tabs
app/src/lib/i18n/chunks/*-1.ts, app/src/lib/i18n/en.ts
Adds settings.notifications.tabs.preferences and settings.notifications.tabs.routing keys across 13 language chunks (ar, bn, de, en, es, fr, hi, id, it, ko, pt, ru, zh-CN) and main en file.
Update Settings page routing and UI composition
app/src/pages/Settings.tsx
Wires LogoutAndClearActions footer into account settings, switches /settings/notifications to NotificationsTabbedPanel, and redirects /settings/notification-routing to /settings/notifications#routing. Removes all connections-related imports and routes.
Home page theme toggle layout repositioning
app/src/pages/Home.tsx
Moves theme toggle from standalone top-right block into main card header row with left spacer for centered version text.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • tinyhumansai/openhuman#2511: Directly modifies the same loopback OAuth listener teardown lifecycle in loopback_oauth.rs and OAuth button flow in OAuthProviderButton.tsx.
  • tinyhumansai/openhuman#2247: Modifies OAuthProviderButton.tsx to add loopback OAuth preflight validation and related redirect logic.
  • tinyhumansai/openhuman#1816: Changes clearAllAppData behavior to wipe only active-user storage, directly connected to the new LogoutAndClearActions component that calls this utility.

Suggested reviewers

  • M3gA-Mind
  • sanil-23

Poem

🐰 Through loopback paths we hop with care,
Join handles tracked, no lingering snares,
Settings shuffled to their proper place,
Tabbed notifications, a cleaner space,
Clear and logout, now from Account they hare!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 'fix(oauth): make loopback redirect actually work, plus settings cleanup' clearly and concisely describes the main objectives of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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

@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. working A PR that is being worked on by the team. bug labels May 23, 2026
@sanil-23
Copy link
Copy Markdown
Contributor

test

Added the two new tab labels (`settings.notifications.tabs.preferences`
and `.routing`) to every locale's `*-1.ts` chunk so the i18n coverage
gate stops failing on `missing[head]`. Values are English placeholders
for non-English locales — they'll show up as untranslated (which is a
non-blocking warning) until human translations land.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":401,"request":{"method":"PATCH","url":"https://api.github.com/repos/tinyhumansai/openhuman/issues/comments/4525978533","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n<details>\n<summary>📝 Walkthrough</summary>\n\n## Walkthrough\n\nThis PR enhances loopback OAuth callback reliability and reorganizes the Settings pages. Rust-side improvements include task join-handle tracking, SO_REUSEADDR socket binding, and ephemeral port fallback. A new LogoutAndClearActions component moves destructive account actions from Settings Home to the Account page footer. The notifications interface is consolidated into a single tabbed panel with hash-based routing, and the deprecated Connections panel is removed entirely.\n\n## Changes\n\n**Settings Reorganization & Loopback OAuth Reliability**\n\n| Layer / File(s) | Summary |\n|---|---|\n| **Loopback OAuth lifecycle and socket binding (Rust)** <br> `app/src-tauri/src/loopback_oauth.rs` | Tracks spawned task join handles, binds with `TcpSocket` and `SO_REUSEADDR`, falls back to ephemeral port on failure, and serializes `StartResult` with camelCase JSON naming (`redirectUri`). |\n| **Loopback OAuth Tauri permissions** <br> `app/src-tauri/permissions/allow-core-process.toml` | Adds RFC 8252 loopback OAuth listener start/stop permissions with explanatory comments. |\n| **Loopback OAuth listener subscription management (TypeScript)** <br> `app/src/utils/loopbackOauthListener.ts`, `app/src/components/oauth/OAuthProviderButton.tsx` | Tracks module-level active unlisten handler, unsubscribes prior listeners before new ones start, and conditions dev-mode `responseType=json` only when loopback is unavailable. |\n| **Destructive account actions component and footer wiring** <br> `app/src/components/settings/LogoutAndClearActions.tsx`, `app/src/components/settings/SettingsSectionPage.tsx`, `app/src/pages/Settings.tsx`, `app/src/components/settings/__tests__/LogoutAndClearActions.test.tsx` | New `LogoutAndClearActions` component with logout and \"clear app data\" confirmation modal; `SettingsSectionPage` accepts optional `footer` prop; Account page injects actions footer. |\n| **Remove destructive actions from Settings Home** <br> `app/src/components/settings/SettingsHome.tsx`, `app/src/components/settings/__tests__/SettingsHome.test.tsx` | Removes logout/clear-data UI, state, modal, and handlers from home screen; tests assert actions no longer appear there. |\n| **Remove connections panel and route** <br> `app/src/components/settings/panels/ConnectionsPanel.tsx` (deleted), `app/src/components/settings/hooks/useSettingsNavigation.ts`, `app/src/pages/Settings.tsx`, `app/src/pages/onboarding/customWizardSteps.ts` | Deletes ConnectionsPanel component and test, removes 'connections' route literal and URL resolution, updates onboarding route from `/settings/connections` to `/settings/composio-routing`. |\n| **Tabbed notifications settings with hash-based routing** <br> `app/src/components/settings/panels/NotificationsTabbedPanel.tsx`, `app/src/components/settings/panels/NotificationsPanel.tsx`, `app/src/components/settings/panels/NotificationRoutingPanel.tsx`, `app/src/components/settings/panels/DeveloperOptionsPanel.tsx`, `app/src/pages/Settings.tsx`, `app/src/lib/i18n/en.ts` | Creates tabbed interface with \"preferences\" and \"routing\" tabs driven by URL hash; both panels now accept optional `embedded` prop to hide headers when nested; removes notification-routing from dev options and redirects legacy route to `#routing` anchor. |\n| **Supporting updates** <br> `app/src/pages/Home.tsx` | Repositions Home page theme toggle from standalone right-aligned block into card header row alongside centered version label. |\n\n## Estimated code review effort\n\n🎯 3 (Moderate) | ⏱️ ~20 minutes\n\n## Possibly related PRs\n\n- [tinyhumansai/openhuman#2511](https://github.com/tinyhumansai/openhuman/pull/2511): Implements the foundational loopback OAuth flow that this PR refines with lifecycle management and socket binding improvements.\n- [tinyhumansai/openhuman#2247](https://github.com/tinyhumansai/openhuman/pull/2247): Modifies OAuth launch path in `OAuthProviderButton.tsx` with preflight logic overlapping this PR's loopback conditional adjustments.\n- [tinyhumansai/openhuman#1885](https://github.com/tinyhumansai/openhuman/pull/1885): Also refactors `SettingsHome.tsx` destructive action handling, making this PR's removal of logout/clear-data from home a direct continuation.\n\n## Suggested labels\n\n`bug`, `feature`, `rust-core`, `working`\n\n## Suggested reviewers\n\n- graycyrus\n- M3gA-Mind\n\n## Poem\n\n> A rabbit hops through settings so grand, 🐰\n> Moving logout actions to Account's command,\n> OAuth callbacks now bind with care,\n> Notifications tabbed in hash-routed air,\n> Connections panel fades, the way gleams bright! ✨\n\n</details>\n\n<!-- walkthrough_end -->\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 5</summary>\n\n<details>\n<summary>✅ Passed checks (5 passed)</summary>\n\n|         Check name         | Status   | Explanation                                                                                                                                                                                                                           |\n| :------------------------: | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n|      Description Check     | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled.                                                                                                                                                                           |\n|         Title check        | ✅ Passed | The PR title clearly and concisely summarizes the main changes: fixing the loopback OAuth redirect mechanism and reorganizing Settings screens, matching the substantial changes across oauth implementation and settings components. |\n|     Docstring Coverage     | ✅ Passed | Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.                                                                                                                                                  |\n|     Linked Issues check    | ✅ Passed | Check skipped because no linked issues were found for this pull request.                                                                                                                                                              |\n| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request.                                                                                                                                                              |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n<!-- This is an auto-generated comment: all tool run failures by coderabbit.ai -->\n\n> [!WARNING]\n> 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.\n> \n> <details>\n> <summary>🔧 ESLint</summary>\n> \n> > If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.\n> \n> ESLint skipped: no ESLint configuration detected in root package.json. To enable, add `eslint` to devDependencies.\n> \n> \n> \n> </details>\n\n<!-- end of auto-generated comment: all tool run failures by coderabbit.ai -->\n<!-- tips_start -->\n\n---\n\n\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":1,"signal":{}}},"response":{"url":"https://api.github.com/repos/tinyhumansai/openhuman/issues/comments/4525978533","status":401,"headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset","connection":"close","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Sat, 23 May 2026 16:52:06 GMT","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"github.com","strict-transport-security":"max-age=31536000; includeSubdomains; preload","vary":"Accept-Encoding, Accept, X-Requested-With","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-media-type":"github.v3; format=json","x-github-request-id":"8055:2B31A7:B936E8E:2C9F6ADB:6A11DB36","x-xss-protection":"0"},"data":{"message":"Bad credentials","documentation_url":"https://docs.github.com/rest","status":"401"}}}

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
app/src/utils/loopbackOauthListener.ts (2)

90-123: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make cancel() tear down the JS side immediately.

Line 90 only invokes the Rust stop command. If awaitCallback() is already pending, its timeout and listen() subscription stay alive until callback delivery or the 5-minute timer fires, so a canceled flow can still reject later and keep a stale handler registered. Share the same cleanup path between callback, timeout, and cancel so cancel() unlistens, clears activeUnlisten, cancels the timer, and rejects the pending promise right away.

🤖 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/utils/loopbackOauthListener.ts` around lines 90 - 123, The cancel()
path currently only calls stop() and doesn't perform the JS-side cleanup used by
awaitCallback(), so a pending awaitCallback() stays subscribed and its timer can
later reject; modify awaitCallback() to expose a shared teardown function that
clears the timeout (timer), calls the current unlisten (and sets activeUnlisten
= null), and rejects the pending promise, and then update cancel() to call that
teardown before/alongside invoking('stop_loopback_oauth_listener') so cancel
immediately unlistens, clears activeUnlisten, clears the timer, and rejects the
pending awaitCallback() promise; reference awaitCallback(), cancel(), stop(),
activeUnlisten, unlisten, timer, listen, CALLBACK_EVENT to locate spots to wire
the shared cleanup.

52-118: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add namespaced diagnostics around listener replacement and cleanup.

This file adds new stateful cleanup paths (activeUnlisten replacement, callback consume, timeout) but still has no debug() tracing for the entry/exit and branch transitions that would explain duplicate-callback regressions. Please instrument these paths with a named logger and avoid logging the callback URL or state value.

As per coding guidelines: "Debug logging in the app should be namespaced using debug() from a named logger instance and include dev-only detail" and "All changes lacking diagnosis logging are incomplete."

🤖 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/utils/loopbackOauthListener.ts` around lines 52 - 118, Add a
namespaced debug logger and sprinkle debug() calls through
startLoopbackOauthListener and its inner helpers (notably the activeUnlisten
replacement block, the result handling after
invoke('start_loopback_oauth_listener'), awaitCallback promise branches, the
timeout handler, the listen() resolution, and stop()). Log entry/exit for
startLoopbackOauthListener and awaitCallback, and branch events like
"listener-created", "listener-replaced", "callback-consumed",
"listener-timeout", and "listener-stopped" using the logger; do NOT log the
callback URL or the raw state value (log presence or an opaque marker only). Use
the same logger instance name across these sites so logs are namespaced and
filterable.
app/src-tauri/src/loopback_oauth.rs (1)

177-236: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Serialize loopback listener startup across concurrent invokes.

Line 186 cancels only the listener currently stored in ACTIVE_LISTENER, but the cancel → bind → spawn → install sequence is still open to races. Two near-simultaneous start_loopback_oauth_listener calls can both observe None, bind different sockets (requested port + ephemeral fallback), and then compete to install themselves. The loser can still emit loopback-oauth-callback into the latest frontend subscription, completing the wrong OAuth round-trip. Guard the whole start path behind a single async mutex/state machine so only one start can be in flight at a time.

🤖 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/loopback_oauth.rs` around lines 177 - 236, Concurrent
invocations of start_loopback_oauth_listener can race between
take_active_listener, bind_loopback, spawn and install_active_listener; protect
the entire startup sequence by introducing a global async mutex (e.g.,
START_STARTUP_MUTEX: tokio::sync::Mutex or tauri::async_runtime::Mutex) and
await a lock at the top of start_loopback_oauth_listener before calling
take_active_listener, bind_loopback, spawning the task and
install_active_listener, then release the lock after install_active_listener
completes; ensure the critical section covers the cancel→bind→spawn→install path
so only one start is in-flight and references unique symbols
start_loopback_oauth_listener, take_active_listener, bind_loopback,
install_active_listener and NEXT_LISTENER_ID in the change.
🧹 Nitpick comments (3)
app/src/components/settings/SettingsHome.tsx (1)

1-1: ⚡ Quick win

Use import type for ReactNode.

Line 1 should be a type-only import to match repo TS conventions and avoid unnecessary value-import semantics.

-import { ReactNode } from 'react';
+import type { ReactNode } from 'react';

As per coding guidelines: "Use static import type for TypeScript type imports instead of import to enable tree-shaking and faster type checking."

🤖 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/components/settings/SettingsHome.tsx` at line 1, Change the value
import to a type-only import for ReactNode: replace the current import of
ReactNode with a TypeScript type import (use the "import type" form) at the top
of the file so that ReactNode is imported as a type-only symbol; update the
import statement that references ReactNode in SettingsHome.tsx to use "import
type { ReactNode } from 'react'".
app/src/components/settings/LogoutAndClearActions.tsx (1)

76-77: ⚡ Quick win

Add dialog semantics to the confirmation modal.

The modal should expose dialog semantics (role="dialog", aria-modal, and label wiring) so assistive tech treats it as a blocking confirmation flow.

Also applies to: 94-96

🤖 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/components/settings/LogoutAndClearActions.tsx` around lines 76 - 77,
The confirmation modal in LogoutAndClearActions lacks dialog semantics; update
the modal wrapper divs to include role="dialog" and aria-modal="true", add an
aria-labelledby that points to the modal title element and an aria-describedby
that points to the explanatory text/confirmation message, and ensure the title
and message elements have unique ids so assistive tech can reference them; apply
the same changes to the second confirmation modal instance in this component so
both modals are properly labelled and announced.
app/src/components/settings/__tests__/LogoutAndClearActions.test.tsx (1)

11-16: ⚡ Quick win

Prefer shared test helpers over a bespoke render/store harness.

This setup is valid, but it duplicates test plumbing that should come from app/src/test/ helpers to keep settings tests consistent.

As per coding guidelines: "Use existing test helpers from app/src/test/ (test-utils.tsx, shared mock backend) before adding new harness code in Vitest tests."

Also applies to: 32-40

🤖 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/components/settings/__tests__/LogoutAndClearActions.test.tsx` around
lines 11 - 16, The test introduces a bespoke store helper (makeTestStore)
duplicating shared test plumbing; replace its usage in
LogoutAndClearActions.test.tsx with the project's shared test helpers (the
test-utils.tsx render/store harness and shared mock backend) instead of calling
configureStore directly with localeReducer and preloadedState; locate and remove
makeTestStore and update tests to use the common renderWithProviders /
getTestStore helper from the shared test utilities so settings tests share the
same store/mocks as other tests.
🤖 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-tauri/permissions/allow-core-process.toml`:
- Around line 129-137: Remove the loopback OAuth entries from the broad
allow-core-process group and place them into a new, narrowly-scoped permission
TOML (e.g., create a new file that declares "start_loopback_oauth_listener" and
"stop_loopback_oauth_listener" as its permitted commands). Update any capability
grants that currently rely on allow-core-process so the OAuth surface (the
OAuth/login component) is the only one granted the new permission TOML, and
ensure existing consumers that should not get OAuth control keep referencing
allow-core-process unchanged. Verify no other modules still depend on
start_loopback_oauth_listener/stop_loopback_oauth_listener from
allow-core-process and adjust their capability declarations to reference the new
permission.

In `@app/src/components/settings/LogoutAndClearActions.tsx`:
- Around line 24-25: In LogoutAndClearActions (e.g., inside the logout handler
where console.warn('[Account] Rust logout failed:', err) and
setError(t('clearData.failedLogout')) are used), replace the raw console.warn
with a namespaced debug logger (create/import a debug instance such as
debug('app:logout') or similar) and log only a sanitized message or minimal
reason (no full err object or stack) while still calling
setError(t('clearData.failedLogout')); additionally, add proper dialog semantics
to the modal container element by adding role="dialog", aria-modal="true", and
aria-labelledby pointing to the modal title element's id (ensure the title
element has that id) so accessibility attributes are present.

In `@app/src/components/settings/panels/NotificationsTabbedPanel.tsx`:
- Around line 28-38: Replace the effect-driven local state with a derived value:
remove useState and the useEffect that syncs tab with location.hash, and instead
declare tab directly as const tab: TabId = hashToTab(location.hash); update
selectTab (and any callers) to stop calling setTab and only call
navigate(`${location.pathname}${TAB_HASH[next]}`, { replace: true }); retain
hashToTab, selectTab, TAB_HASH, navigate, and the TabId typing so the UI is
driven solely from location.hash.

---

Outside diff comments:
In `@app/src-tauri/src/loopback_oauth.rs`:
- Around line 177-236: Concurrent invocations of start_loopback_oauth_listener
can race between take_active_listener, bind_loopback, spawn and
install_active_listener; protect the entire startup sequence by introducing a
global async mutex (e.g., START_STARTUP_MUTEX: tokio::sync::Mutex or
tauri::async_runtime::Mutex) and await a lock at the top of
start_loopback_oauth_listener before calling take_active_listener,
bind_loopback, spawning the task and install_active_listener, then release the
lock after install_active_listener completes; ensure the critical section covers
the cancel→bind→spawn→install path so only one start is in-flight and references
unique symbols start_loopback_oauth_listener, take_active_listener,
bind_loopback, install_active_listener and NEXT_LISTENER_ID in the change.

In `@app/src/utils/loopbackOauthListener.ts`:
- Around line 90-123: The cancel() path currently only calls stop() and doesn't
perform the JS-side cleanup used by awaitCallback(), so a pending
awaitCallback() stays subscribed and its timer can later reject; modify
awaitCallback() to expose a shared teardown function that clears the timeout
(timer), calls the current unlisten (and sets activeUnlisten = null), and
rejects the pending promise, and then update cancel() to call that teardown
before/alongside invoking('stop_loopback_oauth_listener') so cancel immediately
unlistens, clears activeUnlisten, clears the timer, and rejects the pending
awaitCallback() promise; reference awaitCallback(), cancel(), stop(),
activeUnlisten, unlisten, timer, listen, CALLBACK_EVENT to locate spots to wire
the shared cleanup.
- Around line 52-118: Add a namespaced debug logger and sprinkle debug() calls
through startLoopbackOauthListener and its inner helpers (notably the
activeUnlisten replacement block, the result handling after
invoke('start_loopback_oauth_listener'), awaitCallback promise branches, the
timeout handler, the listen() resolution, and stop()). Log entry/exit for
startLoopbackOauthListener and awaitCallback, and branch events like
"listener-created", "listener-replaced", "callback-consumed",
"listener-timeout", and "listener-stopped" using the logger; do NOT log the
callback URL or the raw state value (log presence or an opaque marker only). Use
the same logger instance name across these sites so logs are namespaced and
filterable.

---

Nitpick comments:
In `@app/src/components/settings/__tests__/LogoutAndClearActions.test.tsx`:
- Around line 11-16: The test introduces a bespoke store helper (makeTestStore)
duplicating shared test plumbing; replace its usage in
LogoutAndClearActions.test.tsx with the project's shared test helpers (the
test-utils.tsx render/store harness and shared mock backend) instead of calling
configureStore directly with localeReducer and preloadedState; locate and remove
makeTestStore and update tests to use the common renderWithProviders /
getTestStore helper from the shared test utilities so settings tests share the
same store/mocks as other tests.

In `@app/src/components/settings/LogoutAndClearActions.tsx`:
- Around line 76-77: The confirmation modal in LogoutAndClearActions lacks
dialog semantics; update the modal wrapper divs to include role="dialog" and
aria-modal="true", add an aria-labelledby that points to the modal title element
and an aria-describedby that points to the explanatory text/confirmation
message, and ensure the title and message elements have unique ids so assistive
tech can reference them; apply the same changes to the second confirmation modal
instance in this component so both modals are properly labelled and announced.

In `@app/src/components/settings/SettingsHome.tsx`:
- Line 1: Change the value import to a type-only import for ReactNode: replace
the current import of ReactNode with a TypeScript type import (use the "import
type" form) at the top of the file so that ReactNode is imported as a type-only
symbol; update the import statement that references ReactNode in
SettingsHome.tsx to use "import type { ReactNode } from 'react'".
🪄 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: bd1ecda2-871c-4307-8e93-0aa596a251e8

📥 Commits

Reviewing files that changed from the base of the PR and between 2a5d821 and 8b0cc3d.

📒 Files selected for processing (20)
  • app/src-tauri/permissions/allow-core-process.toml
  • app/src-tauri/src/loopback_oauth.rs
  • app/src/components/oauth/OAuthProviderButton.tsx
  • app/src/components/settings/LogoutAndClearActions.tsx
  • app/src/components/settings/SettingsHome.tsx
  • app/src/components/settings/SettingsSectionPage.tsx
  • app/src/components/settings/__tests__/LogoutAndClearActions.test.tsx
  • app/src/components/settings/__tests__/SettingsHome.test.tsx
  • app/src/components/settings/hooks/useSettingsNavigation.ts
  • app/src/components/settings/panels/ConnectionsPanel.tsx
  • app/src/components/settings/panels/DeveloperOptionsPanel.tsx
  • app/src/components/settings/panels/NotificationRoutingPanel.tsx
  • app/src/components/settings/panels/NotificationsPanel.tsx
  • app/src/components/settings/panels/NotificationsTabbedPanel.tsx
  • app/src/components/settings/panels/__tests__/ConnectionsPanel.test.tsx
  • app/src/lib/i18n/en.ts
  • app/src/pages/Home.tsx
  • app/src/pages/Settings.tsx
  • app/src/pages/onboarding/customWizardSteps.ts
  • app/src/utils/loopbackOauthListener.ts
💤 Files with no reviewable changes (3)
  • app/src/components/settings/panels/ConnectionsPanel.tsx
  • app/src/components/settings/panels/tests/ConnectionsPanel.test.tsx
  • app/src/components/settings/hooks/useSettingsNavigation.ts

Comment thread app/src-tauri/permissions/allow-core-process.toml Outdated
Comment thread app/src/components/settings/LogoutAndClearActions.tsx Outdated
Comment thread app/src/components/settings/panels/NotificationsTabbedPanel.tsx Outdated
- Split loopback OAuth into its own `allow-loopback-oauth` permission TOML
  so consumers of the broad `allow-core-process` capability don't inherit
  control of the OAuth listener. Add the new permission to the default
  desktop capability and drop the entries from `allow-core-process`.
- LogoutAndClearActions: replace raw console.warn with a namespaced
  `debug('settings:account:warn')` logger, sanitize the error before
  logging (message only, no stack / raw payload), and tag the confirm
  modal with `role="dialog"`, `aria-modal`, and `aria-labelledby`.
- NotificationsTabbedPanel: drop the useState + useEffect mirror and
  derive `tab` directly from `location.hash`. The router is already the
  source of truth — the local state was just churn.
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/components/settings/__tests__/SettingsHome.test.tsx (1)

59-73: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Prefer app/src/test/ helpers over local test harness setup.

This custom render/store wrapper should be replaced with the shared test utilities used across the repo to keep test setup consistent.

As per coding guidelines: “Use existing test helpers from app/src/test/ (test-utils.tsx, shared mock backend) before adding new harness code in Vitest tests.”

🤖 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/components/settings/__tests__/SettingsHome.test.tsx` around lines 59
- 73, Replace the local renderSettingsHome harness with the repo's shared test
helpers: remove the custom makeTestStore/Provider/MemoryRouter/I18nProvider
wrapping in renderSettingsHome and instead call the shared render helper (e.g.,
renderWithProviders or renderWithI18n) that accepts a locale option and handles
store/router/i18n setup; update the test imports to use that helper and adjust
any calls to renderSettingsHome to pass locale/withI18n through the shared
helper API, then delete the now-unused renderSettingsHome function and its
related local test-only setup.
🧹 Nitpick comments (1)
app/src/utils/loopbackOauthListener.ts (1)

1-4: 💤 Low value

Consider separating type-only imports.

The UnlistenFn type is imported inline with the listen value. As per coding guidelines, prefer a separate import type statement for type-only imports to enable tree-shaking and faster type checking.

♻️ Suggested fix
 import { invoke } from '`@tauri-apps/api/core`';
-import { listen, type UnlistenFn } from '`@tauri-apps/api/event`';
+import { listen } from '`@tauri-apps/api/event`';
+import type { UnlistenFn } from '`@tauri-apps/api/event`';
🤖 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/utils/loopbackOauthListener.ts` around lines 1 - 4, The import mixes
a value import (listen) with a type-only import (UnlistenFn); change the
statement to import listen as a regular import and move UnlistenFn to a separate
`import type { UnlistenFn } from '`@tauri-apps/api/event`'` so the type is erased
at runtime and enables better tree-shaking/type-checking; update the imports
where `listen` and `UnlistenFn` are referenced (e.g., in
loopbackOauthListener.ts) accordingly.
🤖 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/components/settings/__tests__/LogoutAndClearActions.test.tsx`:
- Around line 11-16: Replace the bespoke store/render harness (the makeTestStore
function and any local render wrapper used in this test file) with the shared
test helpers from app/src/test/, e.g., import and use the test-utils exports
such as renderWithProviders or render (and the shared mock backend helper)
instead of calling makeTestStore or configuring a local store; update the test
setup in LogoutAndClearActions.test.tsx to remove makeTestStore and use the
shared helpers for preloaded locale state (set current: 'en') and mock backend
wiring used by other suites (also apply the same replacement for the logic
around lines 32–40).

In `@app/src/components/settings/LogoutAndClearActions.tsx`:
- Around line 24-33: handleLogout currently calls setError(...) on failure but
that error state is only visible inside the clear-data modal which is not shown
on logout failure; update handleLogout (and the similar block at 125-129) to
both set the error and ensure the clear-data modal is displayed (e.g., call the
existing state setter like setShowClearDataModal(true) or invoke the app's
global notification/toast function) so users see the failure; reference
handleLogout, clearSession, setError and the modal visibility setter (or global
toast API) when making the change.

In `@app/src/components/settings/SettingsHome.tsx`:
- Line 1: Change the import of ReactNode in SettingsHome.tsx to a type-only
import: replace the regular import that references ReactNode with an import type
{ ReactNode } so the symbol ReactNode is imported purely as a TypeScript type
(ensuring tree-shaking and faster type checking).

---

Outside diff comments:
In `@app/src/components/settings/__tests__/SettingsHome.test.tsx`:
- Around line 59-73: Replace the local renderSettingsHome harness with the
repo's shared test helpers: remove the custom
makeTestStore/Provider/MemoryRouter/I18nProvider wrapping in renderSettingsHome
and instead call the shared render helper (e.g., renderWithProviders or
renderWithI18n) that accepts a locale option and handles store/router/i18n
setup; update the test imports to use that helper and adjust any calls to
renderSettingsHome to pass locale/withI18n through the shared helper API, then
delete the now-unused renderSettingsHome function and its related local
test-only setup.

---

Nitpick comments:
In `@app/src/utils/loopbackOauthListener.ts`:
- Around line 1-4: The import mixes a value import (listen) with a type-only
import (UnlistenFn); change the statement to import listen as a regular import
and move UnlistenFn to a separate `import type { UnlistenFn } from
'`@tauri-apps/api/event`'` so the type is erased at runtime and enables better
tree-shaking/type-checking; update the imports where `listen` and `UnlistenFn`
are referenced (e.g., in loopbackOauthListener.ts) accordingly.
🪄 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: 9d1b9be6-465d-4b5c-8061-4b795fd72cfa

📥 Commits

Reviewing files that changed from the base of the PR and between 2a5d821 and 82261a8.

📒 Files selected for processing (34)
  • app/src-tauri/capabilities/default.json
  • app/src-tauri/permissions/allow-loopback-oauth.toml
  • app/src-tauri/src/loopback_oauth.rs
  • app/src/components/oauth/OAuthProviderButton.tsx
  • app/src/components/settings/LogoutAndClearActions.tsx
  • app/src/components/settings/SettingsHome.tsx
  • app/src/components/settings/SettingsSectionPage.tsx
  • app/src/components/settings/__tests__/LogoutAndClearActions.test.tsx
  • app/src/components/settings/__tests__/SettingsHome.test.tsx
  • app/src/components/settings/hooks/useSettingsNavigation.ts
  • app/src/components/settings/panels/ConnectionsPanel.tsx
  • app/src/components/settings/panels/DeveloperOptionsPanel.tsx
  • app/src/components/settings/panels/NotificationRoutingPanel.tsx
  • app/src/components/settings/panels/NotificationsPanel.tsx
  • app/src/components/settings/panels/NotificationsTabbedPanel.tsx
  • app/src/components/settings/panels/__tests__/ConnectionsPanel.test.tsx
  • app/src/lib/i18n/chunks/ar-1.ts
  • app/src/lib/i18n/chunks/bn-1.ts
  • app/src/lib/i18n/chunks/de-1.ts
  • app/src/lib/i18n/chunks/en-1.ts
  • app/src/lib/i18n/chunks/es-1.ts
  • app/src/lib/i18n/chunks/fr-1.ts
  • app/src/lib/i18n/chunks/hi-1.ts
  • app/src/lib/i18n/chunks/id-1.ts
  • app/src/lib/i18n/chunks/it-1.ts
  • app/src/lib/i18n/chunks/ko-1.ts
  • app/src/lib/i18n/chunks/pt-1.ts
  • app/src/lib/i18n/chunks/ru-1.ts
  • app/src/lib/i18n/chunks/zh-CN-1.ts
  • app/src/lib/i18n/en.ts
  • app/src/pages/Home.tsx
  • app/src/pages/Settings.tsx
  • app/src/pages/onboarding/customWizardSteps.ts
  • app/src/utils/loopbackOauthListener.ts
💤 Files with no reviewable changes (3)
  • app/src/components/settings/panels/ConnectionsPanel.tsx
  • app/src/components/settings/panels/tests/ConnectionsPanel.test.tsx
  • app/src/components/settings/hooks/useSettingsNavigation.ts

Comment thread app/src/components/settings/__tests__/LogoutAndClearActions.test.tsx Outdated
Comment thread app/src/components/settings/LogoutAndClearActions.tsx
Comment thread app/src/components/settings/SettingsHome.tsx Outdated
Round-2 CodeRabbit fixes on PR tinyhumansai#2550:

- Render an inline `role="alert"` banner below the Log out row when
  `handleLogout` fails. Previously `setError` was only displayed inside
  the clear-data modal, so a failed logout was silent unless the user
  happened to open that modal afterwards.
- Add a failure-path test covering the inline error path.
- Migrate LogoutAndClearActions tests to the shared
  `renderWithProviders` helper from `app/src/test/test-utils.tsx`
  instead of a bespoke `makeTestStore`/wrapper, matching project
  guidelines.
- SettingsHome: use `import type { ReactNode }` since the symbol is
  only used as a type.
@senamakel senamakel merged commit 4c6007b into tinyhumansai:main May 23, 2026
23 of 24 checks passed
senamakel added a commit to senamakel/openhuman that referenced this pull request May 24, 2026
Post PR tinyhumansai#2550 real OAuth flows hit a loopback HTTP server (RFC 8252)
and the openhuman:// deep link is only the fallback. The E2E auth
bypass was still firing openhuman://auth?token=... directly through
window.__simulateDeepLink, the legacy path. Switch it to loopback so
every spec exercises the same Rust HTTP server + state-nonce check +
Tauri event emit that ships to users.

- app/src/utils/loopbackOauthListener.ts: expose
  startLoopbackOauthListener on window.__startLoopbackOauthListener
  when the E2E build flag (VITE_OPENHUMAN_E2E_RESTART_APP_AS_RELOAD)
  is set. No prod-bundle leak.

- app/test/e2e/helpers/loopback-auth-helpers.ts: new
  triggerAuthLoopbackBypass(userId). WebView starts the production
  loopback listener and wires its awaitCallback() to __simulateDeepLink
  the same way OAuthProviderButton does. Node then fetches the loopback
  URL with the bypass JWT + matching state nonce appended; the Rust
  listener accepts, validates, and emits loopback-oauth-callback. The
  WebView listener rewrites the URL to openhuman://auth?... and routes
  it through the existing handleDeepLinkUrls pipeline.

- app/test/e2e/helpers/reset-app.ts: use the new helper in both the
  primary and retry auth bootstrap calls. resetApp's external contract
  is unchanged; specs see the same authenticated state at the end.

Non-auth deep-link helpers stay for mega-flow's oauth-success
integration callbacks, which still fire through the openhuman://
fallback path.
senamakel added a commit to senamakel/openhuman that referenced this pull request May 24, 2026
…ucture

PR tinyhumansai#2550 (oauth loopback + settings cleanup) moved several settings
surfaces. Tests were still hitting the old routes/text and producing
13 Linux full-suite failures across foundation / chat / commerce
shards. Fix the four most-affected:

- shared-flows.ts logoutViaSettings(): logout + clear-data buttons
  moved out of /settings (main page) and into the Account section
  (LogoutAndClearActions footer on /settings/account). Navigate
  straight there. Unblocks auth-access-control,
  logout-relogin-onboarding, runtime-picker-login, onboarding-modes
  (all of which failed with 'Could not find logout button').

- settings-data-management.spec.ts: same root cause — Clear App Data
  lives at /settings/account now, not /settings.

- settings-account-preferences.spec.ts: /settings/connections (the
  Web3 Wallet status card) was deleted in PR tinyhumansai#2550. Drop the
  navigation + waitForText. The openhuman.wallet_status RPC
  assertion above is the canonical signal that the recovery-phrase
  flow wired through.

- settings-advanced-config.spec.ts: 'Notification Routing' was
  removed as a top-level Developer Options entry (now a tab on the
  Notifications panel). Drop the waitForText. The persistence test
  navigates to /settings/notifications and clicks the Routing tab
  instead of the legacy /settings/notification-routing path
  (HashRouter doesn't carry through the redirect cleanly for
  navigateViaHash's hash-match check).
senamakel added a commit to senamakel/openhuman that referenced this pull request May 24, 2026
Same root cause as the other settings fixes — PR tinyhumansai#2550 moved Log out
out of the main /settings page into the Account section. This spec
has its own inline logout sequence (doesn't use logoutViaSettings)
so it needed the route update separately. 9 of 9 tests passing
locally after the change.
senamakel added a commit to senamakel/openhuman that referenced this pull request May 24, 2026
- screen-intelligence: 'Permissions' sub-section is conditionally
  rendered only when status.platform_supported is true (macOS).
  ScreenIntelligencePanel:121 gates it out on Linux/Windows so the
  assertion has always been impossible to satisfy in CI. Keep the
  'Screen Awareness' title check (renders everywhere) and drop the
  Permissions one — the second test in this spec already exercises
  the debug panel where permissions UI is reachable for diagnostics.

- navigation-settings-panels N2.2: /settings/connections route was
  deleted in PR tinyhumansai#2550 (ConnectionsPanel removed). Mark the case
  .skip with a pointer to the PR so test numbering N2.1..N2.9 still
  reads consistently against the spec list.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants