Skip to content

fix(onboarding): make Connect your apps toggles start auth and reflect real connection state#1226

Open
graycyrus wants to merge 4 commits intotinyhumansai:mainfrom
graycyrus:fix/connect-apps-toggle-auth
Open

fix(onboarding): make Connect your apps toggles start auth and reflect real connection state#1226
graycyrus wants to merge 4 commits intotinyhumansai:mainfrom
graycyrus:fix/connect-apps-toggle-auth

Conversation

@graycyrus
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus commented May 5, 2026

Summary

  • Toggle-on + Done navigates to /chat and activates the first newly-added account, mounting WebviewHost so the auth webview opens immediately (user authenticates in-webview)
  • Toggle state reflects real connection status via colored dot + label: Connected (green), Needs sign-in / Loading (amber), Error / Timed out (red), Closed (grey)
  • Skip for now still just closes the modal without navigating
  • DoneFooter now accepts an optional onDone prop for custom Done behavior

Acceptance criteria (from #1154)

  • Toggle-on starts connection/auth flow where required
  • Connected vs selected-but-not-authorized states are visually distinct
  • Chat runtime only treats fully connected apps as usable (already handled by Rust core's cookie-based detect_webview_logins())

Test plan

  • 8 new unit tests covering toggle ON/OFF, Done navigation, Skip behavior, and status indicator rendering
  • Existing notification tests still pass
  • pnpm typecheck — pass
  • pnpm lint — 0 errors
  • pnpm format:check — pass
  • pnpm build — pass

Closes #1154

Summary by CodeRabbit

  • New Features

    • Status indicators in accounts setup now show connection states (Connected/Needs sign-in) with visual markers.
    • Done flow closes the modal, navigates to chat, and activates the first newly-added account for immediate use.
    • Improved tracking and management of accounts added during the current setup session; provider rows reflect current status.
  • Bug Fixes

    • Provider toggles reliably add or remove accounts during setup.
  • Tests

    • Added comprehensive tests covering the accounts/setup flow, toggles, CTA behavior, and navigation.

…t real connection state

When the user toggles ON a provider and clicks Done, the modal now
navigates to /chat and activates the first new account so its WebviewHost
mounts and the auth webview opens immediately. Toggles also show live
status indicators (Connected / Needs sign-in / Error) instead of just
on/off.

Closes tinyhumansai#1154
@graycyrus graycyrus requested a review from a team May 5, 2026 13:26
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

The PR enhances the accounts/setup flow in OpenhumanLinkModal: toggling a provider creates/removes local webview accounts and shows lifecycle statuses; Done closes the modal, activates the first newly-added account, and navigates to /chat to start the auth/webview flow. Tests for the full flow are added.

Changes

Accounts Setup Integration and Navigation

Layer / File(s) Summary
Imports & Dependencies
app/src/components/OpenhumanLinkModal.tsx
Adds useNavigate from react-router-dom and setActiveAccount, AccountStatus imports to support navigation and account activation.
Status Display Helper
app/src/components/OpenhumanLinkModal.tsx (lines ~398–415)
Introduces statusDisplay(status: AccountStatus) to map account lifecycle states to user-facing labels and CSS dot classes.
Account Tracking & Done Logic
app/src/components/OpenhumanLinkModal.tsx (lines ~416–477)
AccountsSetupBody adds newlyAdded tracking, updates toggle behavior to create/remove accounts and maintain newlyAdded, and implements handleDone to close modal, call setActiveAccount for the first newly-added account, and navigate('/chat').
Provider Row Status Rendering
app/src/components/OpenhumanLinkModal.tsx (lines ~492–511)
Provider rows render a colored status dot and label when an account exists with a status; otherwise show provider description.
UI Copy & Footer Wiring
app/src/components/OpenhumanLinkModal.tsx (lines ~533–565)
Adds explanatory paragraph about toggles creating private webviews; DoneFooter accepts optional onDone?: () => void and uses onDone ?? close so accounts setup passes handleDone.
Tests / Integration
app/src/components/__tests__/OpenhumanLinkModal.accounts.test.tsx
New Vitest + React Testing Library suite: mocks Tauri/webview services and react-router, covers provider toggles (on/off), account creation/removal, Done/Skip navigation behavior, CTA label updates, and status label rendering for pre-existing accounts.

Sequence Diagram

sequenceDiagram
    participant User
    participant Modal as OpenhumanLinkModal
    participant Redux as Redux Store
    participant Router as React Router
    participant Auth as Webview/Auth Flow

    User->>Modal: Toggle provider ON
    Modal->>Redux: Dispatch create account (status: pending)
    Redux-->>Modal: Account present in store
    Modal->>Modal: Add id to newlyAdded

    User->>Modal: Toggle provider OFF
    Modal->>Redux: Dispatch remove account
    Redux-->>Modal: Account removed
    Modal->>Modal: Remove id from newlyAdded

    User->>Modal: Click "Done"
    Modal->>Redux: Dispatch setActiveAccount(first newlyAdded)
    Redux-->>Modal: activeAccountId updated
    Modal->>Router: navigate('/chat')
    Router->>Auth: Start webview/auth flow
    Modal->>Modal: Close modal

    User->>Modal: Click "Skip for now"
    Modal->>Modal: Close modal (no navigation)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • senamakel

Poem

🐰
Toggles twitch, a tiny seed is sown,
Dots of green and amber brightly shown,
Done hops onward to the chat-lit glade,
Webviews wake where private keys are made,
I nibble code and cheer the links you’ve grown.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.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 clearly describes the main objective: making toggles start auth and reflect real connection state, which aligns with the primary changes in OpenhumanLinkModal.tsx and its test suite.
Linked Issues check ✅ Passed The PR implements all acceptance criteria from #1154: toggles start auth flow, connected vs unauthorized states are visually distinct via statusDisplay helper, and chat runtime treats only fully connected apps as usable.
Out of Scope Changes check ✅ Passed All changes in OpenhumanLinkModal.tsx and its test suite directly support the linked objective of reflecting real connection state and starting auth flows; no unrelated modifications detected.

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


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

@graycyrus graycyrus marked this pull request as draft May 5, 2026 13:29
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.

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/OpenhumanLinkModal.tsx (1)

443-450: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

The row state is only looking at one account per provider.

This flow now claims to show the real connection state, but handleToggle and the new status UI both operate on just the first account returned for a provider. Since the store allows multiple accounts with the same provider, the row can show the wrong status and a single "Disconnect" click can remove one account while leaving the toggle effectively on because another same-provider account still exists. Consider deriving the row from all accounts for that provider and making disconnect semantics explicit.

Also applies to: 483-498

🤖 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/OpenhumanLinkModal.tsx` around lines 443 - 450, The row
currently checks only a single account via accountByProvider.get and
handleToggle removes just that one id; change the logic to derive row state from
all accounts for a provider (e.g., compute accountsForProvider =
accounts.filter(a => a.provider === providerId) and isConnected =
accountsForProvider.length > 0), update the UI to use isConnected instead of a
single account, and make disconnect semantics explicit in handleToggle (either
removeAll: iterate accountsForProvider and call purgeWebviewAccount +
dispatch(removeAccount) for each, or accept an accountId param to remove a
specific account). Replace usages of accountByProvider.get(providerId) in
handleToggle and the corresponding status UI with the aggregated
accountsForProvider logic and update newlyAdded handling to operate on each
removed id.
🧹 Nitpick comments (1)
app/src/components/OpenhumanLinkModal.tsx (1)

26-31: ⚡ Quick win

Split the type-only imports into an import type statement.

Account, AccountProvider, and AccountStatus are type-only here, so they should be imported via a separate import type line instead of a mixed value import with PROVIDERS.

As per coding guidelines, **/*.{ts,tsx}: Use import type for type-only imports in TypeScript.

🤖 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/OpenhumanLinkModal.tsx` around lines 26 - 31, The current
import mixes type-only symbols with a runtime value; split the types into an
explicit type-only import: import type { Account, AccountProvider, AccountStatus
} from '...'; and keep PROVIDERS in a separate value import: import { PROVIDERS
} from '...'; update the existing import that currently contains Account,
AccountProvider, AccountStatus, and PROVIDERS so that only PROVIDERS remains in
the runtime import and the three types are moved to an import type line to
satisfy the TypeScript guideline.
🤖 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.

Outside diff comments:
In `@app/src/components/OpenhumanLinkModal.tsx`:
- Around line 443-450: The row currently checks only a single account via
accountByProvider.get and handleToggle removes just that one id; change the
logic to derive row state from all accounts for a provider (e.g., compute
accountsForProvider = accounts.filter(a => a.provider === providerId) and
isConnected = accountsForProvider.length > 0), update the UI to use isConnected
instead of a single account, and make disconnect semantics explicit in
handleToggle (either removeAll: iterate accountsForProvider and call
purgeWebviewAccount + dispatch(removeAccount) for each, or accept an accountId
param to remove a specific account). Replace usages of
accountByProvider.get(providerId) in handleToggle and the corresponding status
UI with the aggregated accountsForProvider logic and update newlyAdded handling
to operate on each removed id.

---

Nitpick comments:
In `@app/src/components/OpenhumanLinkModal.tsx`:
- Around line 26-31: The current import mixes type-only symbols with a runtime
value; split the types into an explicit type-only import: import type { Account,
AccountProvider, AccountStatus } from '...'; and keep PROVIDERS in a separate
value import: import { PROVIDERS } from '...'; update the existing import that
currently contains Account, AccountProvider, AccountStatus, and PROVIDERS so
that only PROVIDERS remains in the runtime import and the three types are moved
to an import type line to satisfy the TypeScript guideline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 69926bea-67d1-4006-ae6b-fe91d6991fa1

📥 Commits

Reviewing files that changed from the base of the PR and between e945390 and d051a15.

📒 Files selected for processing (2)
  • app/src/components/OpenhumanLinkModal.tsx
  • app/src/components/__tests__/OpenhumanLinkModal.accounts.test.tsx

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 5, 2026
@graycyrus
Copy link
Copy Markdown
Contributor Author

CodeRabbit review responses

Nitpick (import type split): Fixed in 12849c3 — split type-only imports into a separate import type statement.

Major (single account per provider): This is by design and matches the pre-existing behavior. The accountByProvider map (unchanged from before this PR) intentionally maps each provider to its first account because this is the onboarding "Connect your apps" flow — a curated, one-account-per-provider setup experience. The addAccount reducer already sets activeAccountId ??= acct.id (first-wins), and the full settings UI (AddAccountModal + sidebar rail) is where multi-account management lives. Extending disconnect to purge all same-provider accounts would be over-scoped here and risks data loss if a user intentionally has multiple Telegram accounts configured elsewhere.

graycyrus added 2 commits May 6, 2026 13:23
Done button now shows "Continue with <app> sign-in" when a provider is
toggled on, making it clear what happens next.
@graycyrus graycyrus marked this pull request as ready for review May 6, 2026 09:20
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: 1

🧹 Nitpick comments (1)
app/src/components/__tests__/OpenhumanLinkModal.accounts.test.tsx (1)

152-164: 💤 Low value

Consider using the imported action creator for type safety.

Dispatching with a raw type string ('accounts/addAccount') works but bypasses TypeScript's payload type checking. Using the addAccount action creator would catch payload shape mismatches at compile time.

♻️ Suggested change
+import { addAccount } from '../../store/accountsSlice';
+
 it('shows status indicator for existing accounts with a status', () => {
   const store = createStore();
-  store.dispatch({
-    type: 'accounts/addAccount',
-    payload: {
-      id: 'test-acct-1',
-      provider: 'telegram',
-      label: 'Telegram',
-      createdAt: new Date().toISOString(),
-      status: 'open',
-    },
-  });
+  store.dispatch(
+    addAccount({
+      id: 'test-acct-1',
+      provider: 'telegram',
+      label: 'Telegram',
+      createdAt: new Date().toISOString(),
+      status: 'open',
+    })
+  );
🤖 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/__tests__/OpenhumanLinkModal.accounts.test.tsx` around
lines 152 - 164, Replace the raw action dispatch that uses the string type with
the typed action creator to get TypeScript payload checking: import and use the
addAccount action creator (e.g., call store.dispatch(addAccount({...}))) instead
of dispatching { type: 'accounts/addAccount', payload: ... } in the test that
sets up the account (the block using createStore and dispatch), keeping the same
payload fields but relying on addAccount for correct typing.
🤖 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/OpenhumanLinkModal.tsx`:
- Around line 398-414: The statusDisplay function can return undefined if
AccountStatus gains new values; add an exhaustive guard so it always returns the
expected {label,dotClass}. Update statusDisplay to include a default/fallback
branch that either throws a descriptive Error (e.g., `Unknown AccountStatus:
${status}`) or calls an assertUnreachable/never helper to force compile-time
exhaustiveness, ensuring the function always returns the correct shape for any
AccountStatus value.

---

Nitpick comments:
In `@app/src/components/__tests__/OpenhumanLinkModal.accounts.test.tsx`:
- Around line 152-164: Replace the raw action dispatch that uses the string type
with the typed action creator to get TypeScript payload checking: import and use
the addAccount action creator (e.g., call store.dispatch(addAccount({...})))
instead of dispatching { type: 'accounts/addAccount', payload: ... } in the test
that sets up the account (the block using createStore and dispatch), keeping the
same payload fields but relying on addAccount for correct typing.
🪄 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: bef564a9-28cc-4577-85a9-a28da64cb789

📥 Commits

Reviewing files that changed from the base of the PR and between d051a15 and 133cae0.

📒 Files selected for processing (2)
  • app/src/components/OpenhumanLinkModal.tsx
  • app/src/components/__tests__/OpenhumanLinkModal.accounts.test.tsx

Comment on lines +398 to +414
/** Status label + color for a given account lifecycle status. */
function statusDisplay(status: AccountStatus): { label: string; dotClass: string } {
switch (status) {
case 'open':
return { label: 'Connected', dotClass: 'bg-emerald-500' };
case 'loading':
return { label: 'Loading…', dotClass: 'bg-amber-400' };
case 'pending':
return { label: 'Needs sign-in', dotClass: 'bg-amber-400' };
case 'timeout':
return { label: 'Timed out', dotClass: 'bg-red-400' };
case 'error':
return { label: 'Error', dotClass: 'bg-red-400' };
case 'closed':
return { label: 'Closed', dotClass: 'bg-stone-300' };
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add exhaustive check for statusDisplay to prevent runtime errors.

The switch statement lacks a default case. If AccountStatus is extended with a new value and this function isn't updated, it will return undefined, causing the UI to fail when accessing .label or .dotClass.

🛡️ Proposed fix to ensure exhaustiveness
 function statusDisplay(status: AccountStatus): { label: string; dotClass: string } {
   switch (status) {
     case 'open':
       return { label: 'Connected', dotClass: 'bg-emerald-500' };
     case 'loading':
       return { label: 'Loading…', dotClass: 'bg-amber-400' };
     case 'pending':
       return { label: 'Needs sign-in', dotClass: 'bg-amber-400' };
     case 'timeout':
       return { label: 'Timed out', dotClass: 'bg-red-400' };
     case 'error':
       return { label: 'Error', dotClass: 'bg-red-400' };
     case 'closed':
       return { label: 'Closed', dotClass: 'bg-stone-300' };
+    default: {
+      const _exhaustive: never = status;
+      return { label: 'Unknown', dotClass: 'bg-stone-300' };
+    }
   }
 }
📝 Committable suggestion

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

Suggested change
/** Status label + color for a given account lifecycle status. */
function statusDisplay(status: AccountStatus): { label: string; dotClass: string } {
switch (status) {
case 'open':
return { label: 'Connected', dotClass: 'bg-emerald-500' };
case 'loading':
return { label: 'Loading…', dotClass: 'bg-amber-400' };
case 'pending':
return { label: 'Needs sign-in', dotClass: 'bg-amber-400' };
case 'timeout':
return { label: 'Timed out', dotClass: 'bg-red-400' };
case 'error':
return { label: 'Error', dotClass: 'bg-red-400' };
case 'closed':
return { label: 'Closed', dotClass: 'bg-stone-300' };
}
}
/** Status label + color for a given account lifecycle status. */
function statusDisplay(status: AccountStatus): { label: string; dotClass: string } {
switch (status) {
case 'open':
return { label: 'Connected', dotClass: 'bg-emerald-500' };
case 'loading':
return { label: 'Loading…', dotClass: 'bg-amber-400' };
case 'pending':
return { label: 'Needs sign-in', dotClass: 'bg-amber-400' };
case 'timeout':
return { label: 'Timed out', dotClass: 'bg-red-400' };
case 'error':
return { label: 'Error', dotClass: 'bg-red-400' };
case 'closed':
return { label: 'Closed', dotClass: 'bg-stone-300' };
default: {
const _exhaustive: never = status;
return { label: 'Unknown', dotClass: 'bg-stone-300' };
}
}
}
🤖 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/OpenhumanLinkModal.tsx` around lines 398 - 414, The
statusDisplay function can return undefined if AccountStatus gains new values;
add an exhaustive guard so it always returns the expected {label,dotClass}.
Update statusDisplay to include a default/fallback branch that either throws a
descriptive Error (e.g., `Unknown AccountStatus: ${status}`) or calls an
assertUnreachable/never helper to force compile-time exhaustiveness, ensuring
the function always returns the correct shape for any AccountStatus value.

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.

Make Connect your apps toggles start auth and reflect real connection state

1 participant