Skip to content

fix(channels): show channel selector error state#2170

Closed
sjh9714 wants to merge 1 commit into
tinyhumansai:mainfrom
sjh9714:codex/GH-2141-channel-selector-error-status
Closed

fix(channels): show channel selector error state#2170
sjh9714 wants to merge 1 commit into
tinyhumansai:mainfrom
sjh9714:codex/GH-2141-channel-selector-error-status

Conversation

@sjh9714
Copy link
Copy Markdown
Contributor

@sjh9714 sjh9714 commented May 19, 2026

Summary

Fixes the channel selector status aggregation so channel-level errors are surfaced instead of falling through to Disconnected.

Fixes #2141

What changed

  • Added regression coverage for a channel with an auth mode in error state.
  • Updated ChannelSelector status priority to check error before falling back to disconnected.
  • Preserved the existing connected and connecting priority.

Why

The channel detail panels can store failed auth attempts as error, but the selector only checked for connected and connecting. That made failed Telegram or Discord setup look inactive instead of broken in the top-level channel selector.

Files changed

  • app/src/components/channels/ChannelSelector.tsx
  • app/src/components/channels/__tests__/ChannelSelector.test.tsx

Validation

  • RED: pnpm --dir app exec vitest run src/components/channels/__tests__/ChannelSelector.test.tsx --config test/vitest.config.ts failed before the implementation because the Telegram tab rendered Disconnected instead of Error.
  • GREEN: pnpm --dir app exec vitest run src/components/channels/__tests__/ChannelSelector.test.tsx --config test/vitest.config.ts passed after the fix: 4 tests.
  • node scripts/codex-pr-preflight.mjs --lightweight passed.
  • PATH="$HOME/.cargo/bin:$PATH" pnpm --filter openhuman-app format:check passed.
  • pnpm typecheck passed.
  • pnpm lint passed with existing warnings.
  • pnpm --dir app run lint:commands-tokens passed.
  • PATH="$HOME/.cargo/bin:$PATH" pnpm rust:check passed with existing warnings after initializing the repository submodules.
  • git diff --check passed.
  • pnpm pr:checklist /tmp/openhuman-2141-pr.md passed locally before PR creation.

AI Authored PR Metadata

Linear Issue

Commit & Branch

  • Branch: codex/GH-2141-channel-selector-error-status
  • Commit SHA: 4226f225e213c528c582f65769021df93b1dfd90

Validation Run

  • Format check: PATH="$HOME/.cargo/bin:$PATH" pnpm --filter openhuman-app format:check
  • Typecheck: pnpm typecheck
  • Lint: pnpm lint
  • Focused test: pnpm --dir app exec vitest run src/components/channels/__tests__/ChannelSelector.test.tsx --config test/vitest.config.ts
  • Rust check: PATH="$HOME/.cargo/bin:$PATH" pnpm rust:check
  • Command token lint: pnpm --dir app run lint:commands-tokens

Validation Blocked

  • N/A - no validation command remained blocked. Local shell needed $HOME/.cargo/bin in PATH for Rust commands and needed git submodule update --init --recursive app/src-tauri/vendor/tauri-cef app/src-tauri/vendor/tauri-plugin-notification before pnpm rust:check. The local Node version produced the repo's engine warning (wanted >=24.0.0, current v22.22.0) but did not block the listed commands.

Behavior Changes

Duplicate / Superseded PR Handling

  • Duplicate PRs: none found by searching 2141 in:body,title ChannelSelector error Disconnected.

AI assistance disclosure

I used OpenAI Codex to inspect the issue and code path, draft the regression test and fix, then reviewed the final diff and ran the listed validation commands locally.

Summary by CodeRabbit

  • Bug Fixes

    • Channel status now correctly displays "Error" state when a channel's authentication is in an error state, improving visibility of authentication issues.
  • Tests

    • Added test case verifying proper error status display for channels with authentication failures.

Review Change Stack

@sjh9714 sjh9714 requested a review from a team May 19, 2026 03:26
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

📝 Walkthrough

Walkthrough

This PR updates ChannelSelector to surface channel connection error states in the status indicator. The per-channel bestStatus resolution now prioritizes 'error' before falling back to 'disconnected', and a test case verifies the error state displays correctly when an auth mode is in an error state.

Changes

Channel Status Error Fallback

Layer / File(s) Summary
Error status fallback and test coverage
app/src/components/channels/ChannelSelector.tsx, app/src/components/channels/__tests__/ChannelSelector.test.tsx
ChannelSelector's bestStatus logic adds an 'error' fallback in the priority chain before 'disconnected'. Test imports include within helper and a new test case verifies that channels with an auth mode in error state display the "Error" indicator in the channel tab.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A little hop toward clarity!
When channels fail, now errors show their face,
No more masked as disconnected space.
Tests confirm the badge shines bright,
Error states now surface into sight. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: implementing error state display in the ChannelSelector component.
Linked Issues check ✅ Passed All acceptance criteria from issue #2141 are met: error status is prioritized over disconnected, status priority is correct, regression test added, and behavior addresses both the problem and solution specified.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #2141: the ChannelSelector status aggregation fix and corresponding test coverage, with no unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

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

Walkthrough

Clean, minimal fix for #2141 — adds error to the channel status priority chain in ChannelSelector.tsx (between connecting and the disconnected fallback) with a well-structured regression test. The one-line implementation change matches exactly what the issue described and the test covers the key scenario.

Change summary

File Change Description
ChannelSelector.tsx Bug fix Added error status check before disconnected fallback in status aggregation
ChannelSelector.test.tsx Test Added regression test seeding Telegram bot_token in error state, verifying Error renders in selector

Notes

  • Issue #2141 acceptance criteria asks for cross-channel verification (both Telegram and Discord). The test only covers Telegram, though the code path is channel-agnostic (Object.values(channelModes) iterates all auth modes regardless of channel type), so a separate Discord test would exercise the same branch. Not blocking — just noting for completeness.
  • Nice TDD workflow documented in the PR description. Solid work.

@sjh9714
Copy link
Copy Markdown
Contributor Author

sjh9714 commented May 19, 2026

Thanks for the review. Since the selector aggregation is channel-agnostic and CI is green, is there anything else you'd like me to add before this can be merged? Happy to add a Discord-specific regression case if you'd prefer.

@sjh9714
Copy link
Copy Markdown
Contributor Author

sjh9714 commented May 20, 2026

Closing this as superseded. Issue #2141 was resolved by #2169, which has been merged, and the latest main already includes the channel selector error-priority change plus regression coverage. Thanks for the review here, and sorry for the extra noise.

@sjh9714 sjh9714 closed this May 20, 2026
senamakel added a commit that referenced this pull request May 20, 2026
…2128)

## Summary

- Centralises OAuth deep-link → channel-badge transitions behind a new
  `useOAuthConnectionListener` hook so every channel panel handles both
  `oauth:success` and `oauth:error` consistently.
- Adds a `clearOtherPendingForChannel` reducer so starting a connect flow on
  one auth mode drops any sibling auth mode that's still mid-`connecting` on
  the same channel.
- Wires `DiscordConfig` and `TelegramConfig` onto the shared hook; future
  channels with an OAuth auth mode inherit correct pending-state transitions
  automatically.
- Covers the new reducer (4 cases) and hook (8 cases) with Vitest.

## Problem

OAuth badges on the channel connection panels could get pinned at
`Connecting` indefinitely (issue #2128):

- `DiscordConfig` had a per-component `oauth:success` listener but no
  `oauth:error` listener — failed OAuth attempts never transitioned the badge
  out of `connecting`.
- `TelegramConfig` had neither — completed *and* failed OAuth attempts left
  the badge pinned.
- Both panels set `connecting` on the chosen auth mode but never cancelled
  any sibling auth mode that was already pending. Triggering a second OAuth
  method on Discord (`OAuth Sign-in` then `Login with OpenHuman`, or the
  reverse) left both methods badged `Connecting` simultaneously.

This is the exact repro from the issue. The same shape was visible across
GitHub/GitLab style multi-method panels because the underlying state model
(`channelConnections`, keyed by `(channel, authMode)`) had no notion of
mutual exclusion.

## Solution

**Shared listener hook** —
[`app/src/hooks/useOAuthConnectionListener.ts`](app/src/hooks/useOAuthConnectionListener.ts)
subscribes to both `oauth:success` and `oauth:error` window events
(dispatched from `utils/desktopDeepLinkListener.ts`), filters by `toolkit` /
`provider` case-insensitively, and dispatches the matching slice action.
Per-channel panels mount it once with `{ channel, authMode }`; cleanup on
unmount is deterministic. New channels with an OAuth auth mode inherit the
behaviour without copying any logic.

**Pending-state cancellation reducer** — `clearOtherPendingForChannel({ channel,
exceptAuthMode })` in `channelConnectionsSlice.ts` walks the auth-mode map
for one channel and transitions every `connecting` row (except the
exception) to `disconnected` with `lastError: undefined`. Cancelled rows go
to `disconnected` rather than `error` so the UI doesn't surface a misleading
failure — the user explicitly switched methods, they didn't experience an
error.

**Per-panel wiring** — `DiscordConfig` and `TelegramConfig` each:

1. Mount `useOAuthConnectionListener({ channel: <name>, authMode: 'oauth' })`
   at the top of the component (replacing the bespoke effect on Discord;
   net-new on Telegram).
2. Dispatch `clearOtherPendingForChannel` at the start of `handleConnect`
   *before* setting their own auth mode to `connecting`.

**Tradeoffs**

- The cancellation transition is `disconnected`, not a new `cancelled` state.
  Adding a dedicated state would expand the `ChannelConnectionStatus` union
  across many call sites for marginal UX value.
- The deep-link CustomEvent payload (`{ integrationId, toolkit }` for
  success, `{ provider, errorCode, message }` for error) is unchanged, so
  no symmetric change in the Tauri-side handler is needed.

## Submission Checklist

- [x] Tests added or updated (happy path + at least one failure / edge case) per [Testing Strategy](../gitbooks/developing/testing-strategy.md#failure-path-requirement) — 12 new Vitest cases (4 reducer + 8 hook) covering success, error, mismatched channel, mismatched provider, missing error message, custom capabilities, unsubscribe on unmount, and three sibling-cancellation shapes.
- [x] **Diff coverage ≥ 80%** — frontend-only change; `pnpm test:coverage` locally over the new files reaches 100% on changed lines (every branch in the hook + reducer is exercised by the suite).
- [x] Coverage matrix updated — `N/A: behaviour-only fix on existing surfaces (channel connection pending state)`.
- [x] All affected feature IDs from the matrix are listed in the PR description under `## Related` — `N/A: no feature ID changes`.
- [x] No new external network dependencies introduced — purely in-app state plumbing.
- [x] Manual smoke checklist updated if this touches release-cut surfaces — `N/A: no release-cut surface touched (channels panel is part of the always-shipped settings UX)`.
- [x] Linked issue closed via `Closes #NNN` in the `## Related` section — see below.

## Impact

- **Desktop only** — no mobile/web/CLI impact. The deep-link event source
  (`desktopDeepLinkListener.ts`) is Tauri-gated; the hook is a no-op outside
  Tauri because no deep-link events fire.
- **No persistence shape change** — `channelConnections` slice schema
  (`SCHEMA_VERSION = 1`) is unchanged. The new reducer only mutates existing
  rows; no migration needed.
- **No security implications** — the listener filters strictly by channel
  identifier and never reads tokens. Existing `[DeepLink][oauth:*]` logs
  remain the canonical diagnostic surface; the hook adds its own
  `channels:oauth-listener` debug namespace per the project's
  verbose-diagnostics rule.

## Related

- Closes: #2128
- Follow-up PR(s)/TODOs: none

## Provider coverage

The issue body mentions Discord, GitHub, and GitLab. The Channels page in this codebase only exposes three multi-method channel-config panels today: `DiscordConfig.tsx`, `TelegramConfig.tsx`, and `WebChannelConfig.tsx` (the last is not OAuth-driven). There is no `GitHubConfig.tsx` / `GitLabConfig.tsx` — verified via `find app/src -name "*Config.tsx"`.

GitHub OAuth does appear elsewhere in the app, but on different state slices that this PR's `channelConnections`-bound hook does not (and should not) touch:

| Surface | File(s) | State path | This PR applies? |
|---|---|---|---|
| App-level sign-in | `BootCheckGate.tsx`, OAuth callback | `deepLinkAuth` slice | No — different slice. App-level OAuth's hot-instance issue is the family fixed by #2228 / #2229. |
| Skill OAuth install | `InstallSkillDialog.tsx`, `services/api/skillsApi.ts` | skills-domain state | No — different surface. |
| Composio integration | `components/composio/TriggerToggles.tsx`, `composio/providerConfigs.tsx` | Composio integration state | No — different surface. |
| **Channel config** (this PR) | `DiscordConfig.tsx`, `TelegramConfig.tsx` | `channelConnections` slice | **Yes — wired.** |

So this PR's `useOAuthConnectionListener` covers every multi-method OAuth panel that actually exists on the Channels surface. The shared hook is also the right shape for any future `GitHubConfig.tsx` / `GitLabConfig.tsx` channel panels — wiring them in becomes a one-line `useOAuthConnectionListener({ channelId, capabilities, ... })` import.

If the stale-`Connecting` symptom also surfaces in the app-level / skills / Composio OAuth flows, those are separate fixes against different state slices and out of scope for this PR — I'm happy to file follow-up issues if any are observed.

---

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

### Linear Issue
- Key: N/A
- URL: N/A

### Commit & Branch
- Branch: `fix/2128-oauth-badge-pending-state`
- Commit SHA: `2d93f7c0`

### Validation Run
- [x] `pnpm --filter openhuman-app format:check` — `All matched files use Prettier code style!` on the 6 changed files
- [x] `pnpm typecheck` — clean (`tsc --noEmit`)
- [x] Focused tests: `pnpm --filter openhuman-app exec vitest run --config test/vitest.config.ts src/store/__tests__/channelConnectionsSlice.test.ts src/hooks/__tests__/useOAuthConnectionListener.test.tsx src/components/channels/__tests__/DiscordConfig.test.tsx src/components/channels/__tests__/TelegramConfig.test.tsx` → 4 files, 27 tests pass
- [x] Rust fmt/check (if changed): `N/A: no Rust changes`
- [x] Tauri fmt/check (if changed): `N/A: no Tauri shell changes`

### Validation Blocked
- `command:` `git push` pre-push hook (`app:lint:commands-tokens`)
- `error:` `lint:commands-tokens requires ripgrep` — `rg` not installed on the dev environment
- `impact:` zero — the check greps a directory I did not modify (`src/components/commands/`). Pushed with `--no-verify` per the CLAUDE.md guidance for environment-related hook failures unrelated to the diff. Maintainers can re-run on CI to validate.

### Behavior Changes
- Intended behavior change: OAuth badges on channel panels transition out of `connecting` when the OAuth flow completes *or* fails, and starting a new method cancels the previous method's `connecting` row.
- User-visible effect: the reported bug (multiple methods stuck on `Connecting` simultaneously, Telegram OAuth never clearing) goes away. No new UI elements; only badge state transitions are affected.

### Parity Contract
- Legacy behavior preserved: existing `connected` and `error` transitions are unchanged; `disconnectChannelConnection`, `upsertChannelConnection`, `setChannelConnectionStatus` are all untouched. The Discord `oauth:success` path still produces the same final state (`status: 'connected'`, `capabilities: ['read', 'write']`); the inline effect was just refactored behind the shared hook.
- Guard/fallback/dispatch parity checks: hook only reacts when the event's `toolkit` (success) or `provider` (error) field matches the subscribed channel — siblings on other channels, and mismatched dispatches, are no-ops.

### Duplicate / Superseded PR Handling
- Duplicate PR(s): none found. #2170 cross-references #2128 in passing but its title and body close #2141 (channel selector error-status aggregation, a different surface).
- Canonical PR: this one.
- Resolution: N/A.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Reusable OAuth connection listener to handle OAuth success/error deep-link flows for Discord and Telegram.
  * New action to clear other pending/connecting auth methods for a channel.

* **Bug Fixes**
  * Prevents multiple auth methods from remaining "connecting"; switching stops in-flight polling and clears sibling pending modes.
  * OAuth errors now record meaningful messages and listeners unsubscribe on unmount.

* **Tests**
  * Added tests covering the OAuth listener and pending-clearing reducer behaviors.

<!-- review_stack_entry_start -->

[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2256?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: sanil-23 <sanil@alphahuman.xyz>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
mtkik pushed a commit to mtkik/openhuman-meet that referenced this pull request May 21, 2026
…inyhumansai#2128)

## Summary

- Centralises OAuth deep-link → channel-badge transitions behind a new
  `useOAuthConnectionListener` hook so every channel panel handles both
  `oauth:success` and `oauth:error` consistently.
- Adds a `clearOtherPendingForChannel` reducer so starting a connect flow on
  one auth mode drops any sibling auth mode that's still mid-`connecting` on
  the same channel.
- Wires `DiscordConfig` and `TelegramConfig` onto the shared hook; future
  channels with an OAuth auth mode inherit correct pending-state transitions
  automatically.
- Covers the new reducer (4 cases) and hook (8 cases) with Vitest.

## Problem

OAuth badges on the channel connection panels could get pinned at
`Connecting` indefinitely (issue tinyhumansai#2128):

- `DiscordConfig` had a per-component `oauth:success` listener but no
  `oauth:error` listener — failed OAuth attempts never transitioned the badge
  out of `connecting`.
- `TelegramConfig` had neither — completed *and* failed OAuth attempts left
  the badge pinned.
- Both panels set `connecting` on the chosen auth mode but never cancelled
  any sibling auth mode that was already pending. Triggering a second OAuth
  method on Discord (`OAuth Sign-in` then `Login with OpenHuman`, or the
  reverse) left both methods badged `Connecting` simultaneously.

This is the exact repro from the issue. The same shape was visible across
GitHub/GitLab style multi-method panels because the underlying state model
(`channelConnections`, keyed by `(channel, authMode)`) had no notion of
mutual exclusion.

## Solution

**Shared listener hook** —
[`app/src/hooks/useOAuthConnectionListener.ts`](app/src/hooks/useOAuthConnectionListener.ts)
subscribes to both `oauth:success` and `oauth:error` window events
(dispatched from `utils/desktopDeepLinkListener.ts`), filters by `toolkit` /
`provider` case-insensitively, and dispatches the matching slice action.
Per-channel panels mount it once with `{ channel, authMode }`; cleanup on
unmount is deterministic. New channels with an OAuth auth mode inherit the
behaviour without copying any logic.

**Pending-state cancellation reducer** — `clearOtherPendingForChannel({ channel,
exceptAuthMode })` in `channelConnectionsSlice.ts` walks the auth-mode map
for one channel and transitions every `connecting` row (except the
exception) to `disconnected` with `lastError: undefined`. Cancelled rows go
to `disconnected` rather than `error` so the UI doesn't surface a misleading
failure — the user explicitly switched methods, they didn't experience an
error.

**Per-panel wiring** — `DiscordConfig` and `TelegramConfig` each:

1. Mount `useOAuthConnectionListener({ channel: <name>, authMode: 'oauth' })`
   at the top of the component (replacing the bespoke effect on Discord;
   net-new on Telegram).
2. Dispatch `clearOtherPendingForChannel` at the start of `handleConnect`
   *before* setting their own auth mode to `connecting`.

**Tradeoffs**

- The cancellation transition is `disconnected`, not a new `cancelled` state.
  Adding a dedicated state would expand the `ChannelConnectionStatus` union
  across many call sites for marginal UX value.
- The deep-link CustomEvent payload (`{ integrationId, toolkit }` for
  success, `{ provider, errorCode, message }` for error) is unchanged, so
  no symmetric change in the Tauri-side handler is needed.

## Submission Checklist

- [x] Tests added or updated (happy path + at least one failure / edge case) per [Testing Strategy](../gitbooks/developing/testing-strategy.md#failure-path-requirement) — 12 new Vitest cases (4 reducer + 8 hook) covering success, error, mismatched channel, mismatched provider, missing error message, custom capabilities, unsubscribe on unmount, and three sibling-cancellation shapes.
- [x] **Diff coverage ≥ 80%** — frontend-only change; `pnpm test:coverage` locally over the new files reaches 100% on changed lines (every branch in the hook + reducer is exercised by the suite).
- [x] Coverage matrix updated — `N/A: behaviour-only fix on existing surfaces (channel connection pending state)`.
- [x] All affected feature IDs from the matrix are listed in the PR description under `## Related` — `N/A: no feature ID changes`.
- [x] No new external network dependencies introduced — purely in-app state plumbing.
- [x] Manual smoke checklist updated if this touches release-cut surfaces — `N/A: no release-cut surface touched (channels panel is part of the always-shipped settings UX)`.
- [x] Linked issue closed via `Closes #NNN` in the `## Related` section — see below.

## Impact

- **Desktop only** — no mobile/web/CLI impact. The deep-link event source
  (`desktopDeepLinkListener.ts`) is Tauri-gated; the hook is a no-op outside
  Tauri because no deep-link events fire.
- **No persistence shape change** — `channelConnections` slice schema
  (`SCHEMA_VERSION = 1`) is unchanged. The new reducer only mutates existing
  rows; no migration needed.
- **No security implications** — the listener filters strictly by channel
  identifier and never reads tokens. Existing `[DeepLink][oauth:*]` logs
  remain the canonical diagnostic surface; the hook adds its own
  `channels:oauth-listener` debug namespace per the project's
  verbose-diagnostics rule.

## Related

- Closes: tinyhumansai#2128
- Follow-up PR(s)/TODOs: none

## Provider coverage

The issue body mentions Discord, GitHub, and GitLab. The Channels page in this codebase only exposes three multi-method channel-config panels today: `DiscordConfig.tsx`, `TelegramConfig.tsx`, and `WebChannelConfig.tsx` (the last is not OAuth-driven). There is no `GitHubConfig.tsx` / `GitLabConfig.tsx` — verified via `find app/src -name "*Config.tsx"`.

GitHub OAuth does appear elsewhere in the app, but on different state slices that this PR's `channelConnections`-bound hook does not (and should not) touch:

| Surface | File(s) | State path | This PR applies? |
|---|---|---|---|
| App-level sign-in | `BootCheckGate.tsx`, OAuth callback | `deepLinkAuth` slice | No — different slice. App-level OAuth's hot-instance issue is the family fixed by tinyhumansai#2228 / tinyhumansai#2229. |
| Skill OAuth install | `InstallSkillDialog.tsx`, `services/api/skillsApi.ts` | skills-domain state | No — different surface. |
| Composio integration | `components/composio/TriggerToggles.tsx`, `composio/providerConfigs.tsx` | Composio integration state | No — different surface. |
| **Channel config** (this PR) | `DiscordConfig.tsx`, `TelegramConfig.tsx` | `channelConnections` slice | **Yes — wired.** |

So this PR's `useOAuthConnectionListener` covers every multi-method OAuth panel that actually exists on the Channels surface. The shared hook is also the right shape for any future `GitHubConfig.tsx` / `GitLabConfig.tsx` channel panels — wiring them in becomes a one-line `useOAuthConnectionListener({ channelId, capabilities, ... })` import.

If the stale-`Connecting` symptom also surfaces in the app-level / skills / Composio OAuth flows, those are separate fixes against different state slices and out of scope for this PR — I'm happy to file follow-up issues if any are observed.

---

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

### Linear Issue
- Key: N/A
- URL: N/A

### Commit & Branch
- Branch: `fix/2128-oauth-badge-pending-state`
- Commit SHA: `2d93f7c0`

### Validation Run
- [x] `pnpm --filter openhuman-app format:check` — `All matched files use Prettier code style!` on the 6 changed files
- [x] `pnpm typecheck` — clean (`tsc --noEmit`)
- [x] Focused tests: `pnpm --filter openhuman-app exec vitest run --config test/vitest.config.ts src/store/__tests__/channelConnectionsSlice.test.ts src/hooks/__tests__/useOAuthConnectionListener.test.tsx src/components/channels/__tests__/DiscordConfig.test.tsx src/components/channels/__tests__/TelegramConfig.test.tsx` → 4 files, 27 tests pass
- [x] Rust fmt/check (if changed): `N/A: no Rust changes`
- [x] Tauri fmt/check (if changed): `N/A: no Tauri shell changes`

### Validation Blocked
- `command:` `git push` pre-push hook (`app:lint:commands-tokens`)
- `error:` `lint:commands-tokens requires ripgrep` — `rg` not installed on the dev environment
- `impact:` zero — the check greps a directory I did not modify (`src/components/commands/`). Pushed with `--no-verify` per the CLAUDE.md guidance for environment-related hook failures unrelated to the diff. Maintainers can re-run on CI to validate.

### Behavior Changes
- Intended behavior change: OAuth badges on channel panels transition out of `connecting` when the OAuth flow completes *or* fails, and starting a new method cancels the previous method's `connecting` row.
- User-visible effect: the reported bug (multiple methods stuck on `Connecting` simultaneously, Telegram OAuth never clearing) goes away. No new UI elements; only badge state transitions are affected.

### Parity Contract
- Legacy behavior preserved: existing `connected` and `error` transitions are unchanged; `disconnectChannelConnection`, `upsertChannelConnection`, `setChannelConnectionStatus` are all untouched. The Discord `oauth:success` path still produces the same final state (`status: 'connected'`, `capabilities: ['read', 'write']`); the inline effect was just refactored behind the shared hook.
- Guard/fallback/dispatch parity checks: hook only reacts when the event's `toolkit` (success) or `provider` (error) field matches the subscribed channel — siblings on other channels, and mismatched dispatches, are no-ops.

### Duplicate / Superseded PR Handling
- Duplicate PR(s): none found. tinyhumansai#2170 cross-references tinyhumansai#2128 in passing but its title and body close tinyhumansai#2141 (channel selector error-status aggregation, a different surface).
- Canonical PR: this one.
- Resolution: N/A.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Reusable OAuth connection listener to handle OAuth success/error deep-link flows for Discord and Telegram.
  * New action to clear other pending/connecting auth methods for a channel.

* **Bug Fixes**
  * Prevents multiple auth methods from remaining "connecting"; switching stops in-flight polling and clears sibling pending modes.
  * OAuth errors now record meaningful messages and listeners unsubscribe on unmount.

* **Tests**
  * Added tests covering the OAuth listener and pending-clearing reducer behaviors.

<!-- review_stack_entry_start -->

[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2256?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: sanil-23 <sanil@alphahuman.xyz>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
CodeGhost21 pushed a commit to CodeGhost21/openhuman that referenced this pull request May 22, 2026
…inyhumansai#2128)

## Summary

- Centralises OAuth deep-link → channel-badge transitions behind a new
  `useOAuthConnectionListener` hook so every channel panel handles both
  `oauth:success` and `oauth:error` consistently.
- Adds a `clearOtherPendingForChannel` reducer so starting a connect flow on
  one auth mode drops any sibling auth mode that's still mid-`connecting` on
  the same channel.
- Wires `DiscordConfig` and `TelegramConfig` onto the shared hook; future
  channels with an OAuth auth mode inherit correct pending-state transitions
  automatically.
- Covers the new reducer (4 cases) and hook (8 cases) with Vitest.

## Problem

OAuth badges on the channel connection panels could get pinned at
`Connecting` indefinitely (issue tinyhumansai#2128):

- `DiscordConfig` had a per-component `oauth:success` listener but no
  `oauth:error` listener — failed OAuth attempts never transitioned the badge
  out of `connecting`.
- `TelegramConfig` had neither — completed *and* failed OAuth attempts left
  the badge pinned.
- Both panels set `connecting` on the chosen auth mode but never cancelled
  any sibling auth mode that was already pending. Triggering a second OAuth
  method on Discord (`OAuth Sign-in` then `Login with OpenHuman`, or the
  reverse) left both methods badged `Connecting` simultaneously.

This is the exact repro from the issue. The same shape was visible across
GitHub/GitLab style multi-method panels because the underlying state model
(`channelConnections`, keyed by `(channel, authMode)`) had no notion of
mutual exclusion.

## Solution

**Shared listener hook** —
[`app/src/hooks/useOAuthConnectionListener.ts`](app/src/hooks/useOAuthConnectionListener.ts)
subscribes to both `oauth:success` and `oauth:error` window events
(dispatched from `utils/desktopDeepLinkListener.ts`), filters by `toolkit` /
`provider` case-insensitively, and dispatches the matching slice action.
Per-channel panels mount it once with `{ channel, authMode }`; cleanup on
unmount is deterministic. New channels with an OAuth auth mode inherit the
behaviour without copying any logic.

**Pending-state cancellation reducer** — `clearOtherPendingForChannel({ channel,
exceptAuthMode })` in `channelConnectionsSlice.ts` walks the auth-mode map
for one channel and transitions every `connecting` row (except the
exception) to `disconnected` with `lastError: undefined`. Cancelled rows go
to `disconnected` rather than `error` so the UI doesn't surface a misleading
failure — the user explicitly switched methods, they didn't experience an
error.

**Per-panel wiring** — `DiscordConfig` and `TelegramConfig` each:

1. Mount `useOAuthConnectionListener({ channel: <name>, authMode: 'oauth' })`
   at the top of the component (replacing the bespoke effect on Discord;
   net-new on Telegram).
2. Dispatch `clearOtherPendingForChannel` at the start of `handleConnect`
   *before* setting their own auth mode to `connecting`.

**Tradeoffs**

- The cancellation transition is `disconnected`, not a new `cancelled` state.
  Adding a dedicated state would expand the `ChannelConnectionStatus` union
  across many call sites for marginal UX value.
- The deep-link CustomEvent payload (`{ integrationId, toolkit }` for
  success, `{ provider, errorCode, message }` for error) is unchanged, so
  no symmetric change in the Tauri-side handler is needed.

## Submission Checklist

- [x] Tests added or updated (happy path + at least one failure / edge case) per [Testing Strategy](../gitbooks/developing/testing-strategy.md#failure-path-requirement) — 12 new Vitest cases (4 reducer + 8 hook) covering success, error, mismatched channel, mismatched provider, missing error message, custom capabilities, unsubscribe on unmount, and three sibling-cancellation shapes.
- [x] **Diff coverage ≥ 80%** — frontend-only change; `pnpm test:coverage` locally over the new files reaches 100% on changed lines (every branch in the hook + reducer is exercised by the suite).
- [x] Coverage matrix updated — `N/A: behaviour-only fix on existing surfaces (channel connection pending state)`.
- [x] All affected feature IDs from the matrix are listed in the PR description under `## Related` — `N/A: no feature ID changes`.
- [x] No new external network dependencies introduced — purely in-app state plumbing.
- [x] Manual smoke checklist updated if this touches release-cut surfaces — `N/A: no release-cut surface touched (channels panel is part of the always-shipped settings UX)`.
- [x] Linked issue closed via `Closes #NNN` in the `## Related` section — see below.

## Impact

- **Desktop only** — no mobile/web/CLI impact. The deep-link event source
  (`desktopDeepLinkListener.ts`) is Tauri-gated; the hook is a no-op outside
  Tauri because no deep-link events fire.
- **No persistence shape change** — `channelConnections` slice schema
  (`SCHEMA_VERSION = 1`) is unchanged. The new reducer only mutates existing
  rows; no migration needed.
- **No security implications** — the listener filters strictly by channel
  identifier and never reads tokens. Existing `[DeepLink][oauth:*]` logs
  remain the canonical diagnostic surface; the hook adds its own
  `channels:oauth-listener` debug namespace per the project's
  verbose-diagnostics rule.

## Related

- Closes: tinyhumansai#2128
- Follow-up PR(s)/TODOs: none

## Provider coverage

The issue body mentions Discord, GitHub, and GitLab. The Channels page in this codebase only exposes three multi-method channel-config panels today: `DiscordConfig.tsx`, `TelegramConfig.tsx`, and `WebChannelConfig.tsx` (the last is not OAuth-driven). There is no `GitHubConfig.tsx` / `GitLabConfig.tsx` — verified via `find app/src -name "*Config.tsx"`.

GitHub OAuth does appear elsewhere in the app, but on different state slices that this PR's `channelConnections`-bound hook does not (and should not) touch:

| Surface | File(s) | State path | This PR applies? |
|---|---|---|---|
| App-level sign-in | `BootCheckGate.tsx`, OAuth callback | `deepLinkAuth` slice | No — different slice. App-level OAuth's hot-instance issue is the family fixed by tinyhumansai#2228 / tinyhumansai#2229. |
| Skill OAuth install | `InstallSkillDialog.tsx`, `services/api/skillsApi.ts` | skills-domain state | No — different surface. |
| Composio integration | `components/composio/TriggerToggles.tsx`, `composio/providerConfigs.tsx` | Composio integration state | No — different surface. |
| **Channel config** (this PR) | `DiscordConfig.tsx`, `TelegramConfig.tsx` | `channelConnections` slice | **Yes — wired.** |

So this PR's `useOAuthConnectionListener` covers every multi-method OAuth panel that actually exists on the Channels surface. The shared hook is also the right shape for any future `GitHubConfig.tsx` / `GitLabConfig.tsx` channel panels — wiring them in becomes a one-line `useOAuthConnectionListener({ channelId, capabilities, ... })` import.

If the stale-`Connecting` symptom also surfaces in the app-level / skills / Composio OAuth flows, those are separate fixes against different state slices and out of scope for this PR — I'm happy to file follow-up issues if any are observed.

---

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

### Linear Issue
- Key: N/A
- URL: N/A

### Commit & Branch
- Branch: `fix/2128-oauth-badge-pending-state`
- Commit SHA: `2d93f7c0`

### Validation Run
- [x] `pnpm --filter openhuman-app format:check` — `All matched files use Prettier code style!` on the 6 changed files
- [x] `pnpm typecheck` — clean (`tsc --noEmit`)
- [x] Focused tests: `pnpm --filter openhuman-app exec vitest run --config test/vitest.config.ts src/store/__tests__/channelConnectionsSlice.test.ts src/hooks/__tests__/useOAuthConnectionListener.test.tsx src/components/channels/__tests__/DiscordConfig.test.tsx src/components/channels/__tests__/TelegramConfig.test.tsx` → 4 files, 27 tests pass
- [x] Rust fmt/check (if changed): `N/A: no Rust changes`
- [x] Tauri fmt/check (if changed): `N/A: no Tauri shell changes`

### Validation Blocked
- `command:` `git push` pre-push hook (`app:lint:commands-tokens`)
- `error:` `lint:commands-tokens requires ripgrep` — `rg` not installed on the dev environment
- `impact:` zero — the check greps a directory I did not modify (`src/components/commands/`). Pushed with `--no-verify` per the CLAUDE.md guidance for environment-related hook failures unrelated to the diff. Maintainers can re-run on CI to validate.

### Behavior Changes
- Intended behavior change: OAuth badges on channel panels transition out of `connecting` when the OAuth flow completes *or* fails, and starting a new method cancels the previous method's `connecting` row.
- User-visible effect: the reported bug (multiple methods stuck on `Connecting` simultaneously, Telegram OAuth never clearing) goes away. No new UI elements; only badge state transitions are affected.

### Parity Contract
- Legacy behavior preserved: existing `connected` and `error` transitions are unchanged; `disconnectChannelConnection`, `upsertChannelConnection`, `setChannelConnectionStatus` are all untouched. The Discord `oauth:success` path still produces the same final state (`status: 'connected'`, `capabilities: ['read', 'write']`); the inline effect was just refactored behind the shared hook.
- Guard/fallback/dispatch parity checks: hook only reacts when the event's `toolkit` (success) or `provider` (error) field matches the subscribed channel — siblings on other channels, and mismatched dispatches, are no-ops.

### Duplicate / Superseded PR Handling
- Duplicate PR(s): none found. tinyhumansai#2170 cross-references tinyhumansai#2128 in passing but its title and body close tinyhumansai#2141 (channel selector error-status aggregation, a different surface).
- Canonical PR: this one.
- Resolution: N/A.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Reusable OAuth connection listener to handle OAuth success/error deep-link flows for Discord and Telegram.
  * New action to clear other pending/connecting auth methods for a channel.

* **Bug Fixes**
  * Prevents multiple auth methods from remaining "connecting"; switching stops in-flight polling and clears sibling pending modes.
  * OAuth errors now record meaningful messages and listeners unsubscribe on unmount.

* **Tests**
  * Added tests covering the OAuth listener and pending-clearing reducer behaviors.

<!-- review_stack_entry_start -->

[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2256?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: sanil-23 <sanil@alphahuman.xyz>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
…inyhumansai#2128)

## Summary

- Centralises OAuth deep-link → channel-badge transitions behind a new
  `useOAuthConnectionListener` hook so every channel panel handles both
  `oauth:success` and `oauth:error` consistently.
- Adds a `clearOtherPendingForChannel` reducer so starting a connect flow on
  one auth mode drops any sibling auth mode that's still mid-`connecting` on
  the same channel.
- Wires `DiscordConfig` and `TelegramConfig` onto the shared hook; future
  channels with an OAuth auth mode inherit correct pending-state transitions
  automatically.
- Covers the new reducer (4 cases) and hook (8 cases) with Vitest.

## Problem

OAuth badges on the channel connection panels could get pinned at
`Connecting` indefinitely (issue tinyhumansai#2128):

- `DiscordConfig` had a per-component `oauth:success` listener but no
  `oauth:error` listener — failed OAuth attempts never transitioned the badge
  out of `connecting`.
- `TelegramConfig` had neither — completed *and* failed OAuth attempts left
  the badge pinned.
- Both panels set `connecting` on the chosen auth mode but never cancelled
  any sibling auth mode that was already pending. Triggering a second OAuth
  method on Discord (`OAuth Sign-in` then `Login with OpenHuman`, or the
  reverse) left both methods badged `Connecting` simultaneously.

This is the exact repro from the issue. The same shape was visible across
GitHub/GitLab style multi-method panels because the underlying state model
(`channelConnections`, keyed by `(channel, authMode)`) had no notion of
mutual exclusion.

## Solution

**Shared listener hook** —
[`app/src/hooks/useOAuthConnectionListener.ts`](app/src/hooks/useOAuthConnectionListener.ts)
subscribes to both `oauth:success` and `oauth:error` window events
(dispatched from `utils/desktopDeepLinkListener.ts`), filters by `toolkit` /
`provider` case-insensitively, and dispatches the matching slice action.
Per-channel panels mount it once with `{ channel, authMode }`; cleanup on
unmount is deterministic. New channels with an OAuth auth mode inherit the
behaviour without copying any logic.

**Pending-state cancellation reducer** — `clearOtherPendingForChannel({ channel,
exceptAuthMode })` in `channelConnectionsSlice.ts` walks the auth-mode map
for one channel and transitions every `connecting` row (except the
exception) to `disconnected` with `lastError: undefined`. Cancelled rows go
to `disconnected` rather than `error` so the UI doesn't surface a misleading
failure — the user explicitly switched methods, they didn't experience an
error.

**Per-panel wiring** — `DiscordConfig` and `TelegramConfig` each:

1. Mount `useOAuthConnectionListener({ channel: <name>, authMode: 'oauth' })`
   at the top of the component (replacing the bespoke effect on Discord;
   net-new on Telegram).
2. Dispatch `clearOtherPendingForChannel` at the start of `handleConnect`
   *before* setting their own auth mode to `connecting`.

**Tradeoffs**

- The cancellation transition is `disconnected`, not a new `cancelled` state.
  Adding a dedicated state would expand the `ChannelConnectionStatus` union
  across many call sites for marginal UX value.
- The deep-link CustomEvent payload (`{ integrationId, toolkit }` for
  success, `{ provider, errorCode, message }` for error) is unchanged, so
  no symmetric change in the Tauri-side handler is needed.

## Submission Checklist

- [x] Tests added or updated (happy path + at least one failure / edge case) per [Testing Strategy](../gitbooks/developing/testing-strategy.md#failure-path-requirement) — 12 new Vitest cases (4 reducer + 8 hook) covering success, error, mismatched channel, mismatched provider, missing error message, custom capabilities, unsubscribe on unmount, and three sibling-cancellation shapes.
- [x] **Diff coverage ≥ 80%** — frontend-only change; `pnpm test:coverage` locally over the new files reaches 100% on changed lines (every branch in the hook + reducer is exercised by the suite).
- [x] Coverage matrix updated — `N/A: behaviour-only fix on existing surfaces (channel connection pending state)`.
- [x] All affected feature IDs from the matrix are listed in the PR description under `## Related` — `N/A: no feature ID changes`.
- [x] No new external network dependencies introduced — purely in-app state plumbing.
- [x] Manual smoke checklist updated if this touches release-cut surfaces — `N/A: no release-cut surface touched (channels panel is part of the always-shipped settings UX)`.
- [x] Linked issue closed via `Closes #NNN` in the `## Related` section — see below.

## Impact

- **Desktop only** — no mobile/web/CLI impact. The deep-link event source
  (`desktopDeepLinkListener.ts`) is Tauri-gated; the hook is a no-op outside
  Tauri because no deep-link events fire.
- **No persistence shape change** — `channelConnections` slice schema
  (`SCHEMA_VERSION = 1`) is unchanged. The new reducer only mutates existing
  rows; no migration needed.
- **No security implications** — the listener filters strictly by channel
  identifier and never reads tokens. Existing `[DeepLink][oauth:*]` logs
  remain the canonical diagnostic surface; the hook adds its own
  `channels:oauth-listener` debug namespace per the project's
  verbose-diagnostics rule.

## Related

- Closes: tinyhumansai#2128
- Follow-up PR(s)/TODOs: none

## Provider coverage

The issue body mentions Discord, GitHub, and GitLab. The Channels page in this codebase only exposes three multi-method channel-config panels today: `DiscordConfig.tsx`, `TelegramConfig.tsx`, and `WebChannelConfig.tsx` (the last is not OAuth-driven). There is no `GitHubConfig.tsx` / `GitLabConfig.tsx` — verified via `find app/src -name "*Config.tsx"`.

GitHub OAuth does appear elsewhere in the app, but on different state slices that this PR's `channelConnections`-bound hook does not (and should not) touch:

| Surface | File(s) | State path | This PR applies? |
|---|---|---|---|
| App-level sign-in | `BootCheckGate.tsx`, OAuth callback | `deepLinkAuth` slice | No — different slice. App-level OAuth's hot-instance issue is the family fixed by tinyhumansai#2228 / tinyhumansai#2229. |
| Skill OAuth install | `InstallSkillDialog.tsx`, `services/api/skillsApi.ts` | skills-domain state | No — different surface. |
| Composio integration | `components/composio/TriggerToggles.tsx`, `composio/providerConfigs.tsx` | Composio integration state | No — different surface. |
| **Channel config** (this PR) | `DiscordConfig.tsx`, `TelegramConfig.tsx` | `channelConnections` slice | **Yes — wired.** |

So this PR's `useOAuthConnectionListener` covers every multi-method OAuth panel that actually exists on the Channels surface. The shared hook is also the right shape for any future `GitHubConfig.tsx` / `GitLabConfig.tsx` channel panels — wiring them in becomes a one-line `useOAuthConnectionListener({ channelId, capabilities, ... })` import.

If the stale-`Connecting` symptom also surfaces in the app-level / skills / Composio OAuth flows, those are separate fixes against different state slices and out of scope for this PR — I'm happy to file follow-up issues if any are observed.

---

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

### Linear Issue
- Key: N/A
- URL: N/A

### Commit & Branch
- Branch: `fix/2128-oauth-badge-pending-state`
- Commit SHA: `2d93f7c0`

### Validation Run
- [x] `pnpm --filter openhuman-app format:check` — `All matched files use Prettier code style!` on the 6 changed files
- [x] `pnpm typecheck` — clean (`tsc --noEmit`)
- [x] Focused tests: `pnpm --filter openhuman-app exec vitest run --config test/vitest.config.ts src/store/__tests__/channelConnectionsSlice.test.ts src/hooks/__tests__/useOAuthConnectionListener.test.tsx src/components/channels/__tests__/DiscordConfig.test.tsx src/components/channels/__tests__/TelegramConfig.test.tsx` → 4 files, 27 tests pass
- [x] Rust fmt/check (if changed): `N/A: no Rust changes`
- [x] Tauri fmt/check (if changed): `N/A: no Tauri shell changes`

### Validation Blocked
- `command:` `git push` pre-push hook (`app:lint:commands-tokens`)
- `error:` `lint:commands-tokens requires ripgrep` — `rg` not installed on the dev environment
- `impact:` zero — the check greps a directory I did not modify (`src/components/commands/`). Pushed with `--no-verify` per the CLAUDE.md guidance for environment-related hook failures unrelated to the diff. Maintainers can re-run on CI to validate.

### Behavior Changes
- Intended behavior change: OAuth badges on channel panels transition out of `connecting` when the OAuth flow completes *or* fails, and starting a new method cancels the previous method's `connecting` row.
- User-visible effect: the reported bug (multiple methods stuck on `Connecting` simultaneously, Telegram OAuth never clearing) goes away. No new UI elements; only badge state transitions are affected.

### Parity Contract
- Legacy behavior preserved: existing `connected` and `error` transitions are unchanged; `disconnectChannelConnection`, `upsertChannelConnection`, `setChannelConnectionStatus` are all untouched. The Discord `oauth:success` path still produces the same final state (`status: 'connected'`, `capabilities: ['read', 'write']`); the inline effect was just refactored behind the shared hook.
- Guard/fallback/dispatch parity checks: hook only reacts when the event's `toolkit` (success) or `provider` (error) field matches the subscribed channel — siblings on other channels, and mismatched dispatches, are no-ops.

### Duplicate / Superseded PR Handling
- Duplicate PR(s): none found. tinyhumansai#2170 cross-references tinyhumansai#2128 in passing but its title and body close tinyhumansai#2141 (channel selector error-status aggregation, a different surface).
- Canonical PR: this one.
- Resolution: N/A.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Reusable OAuth connection listener to handle OAuth success/error deep-link flows for Discord and Telegram.
  * New action to clear other pending/connecting auth methods for a channel.

* **Bug Fixes**
  * Prevents multiple auth methods from remaining "connecting"; switching stops in-flight polling and clears sibling pending modes.
  * OAuth errors now record meaningful messages and listeners unsubscribe on unmount.

* **Tests**
  * Added tests covering the OAuth listener and pending-clearing reducer behaviors.

<!-- review_stack_entry_start -->

[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2256?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: sanil-23 <sanil@alphahuman.xyz>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
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.

Channel selector hides channel error state and falls back to Disconnected

2 participants