Skip to content

feat(chat): merge Human and Chat pages into unified conversation surface#2227

Closed
graycyrus wants to merge 12 commits into
tinyhumansai:mainfrom
graycyrus:feat/merge-human-chat-v2
Closed

feat(chat): merge Human and Chat pages into unified conversation surface#2227
graycyrus wants to merge 12 commits into
tinyhumansai:mainfrom
graycyrus:feat/merge-human-chat-v2

Conversation

@graycyrus
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus commented May 19, 2026

Summary

  • Add a voice mode toggle (mic button) in the chat composer that switches to cloud MicComposer — same transcription engine as the old /human page
  • Voice mode shows a centered mascot (180px) as the hero, mic button below, and "Switch to text" / "Speak replies" controls in a footer row
  • Persist voice/text mode in Redux (voiceModeActive in mascotSlice) so the user's last choice survives navigation
  • Migrate speakReplies from localStorage to Redux with one-time backward-compat migration
  • Remove /human route (redirect to /chat) and Human tab from bottom nav (7→6 tabs)
  • Device selector only shows when >1 mic available, positioned below mic button

Test plan

  • Typecheck passes
  • Lint passes (0 errors, pre-existing warnings only)
  • Format passes
  • i18n coverage passes
  • Unit tests pass (268 files, 2611 tests, 0 failures)
  • Manual: mic toggle in composer switches to voice mode with mascot
  • Manual: voice mode uses cloud transcription (same as old /human)
  • Manual: voice/text mode persists across navigation
  • Manual: /human redirects to /chat
  • Manual: bottom tab bar shows 6 tabs (no Human)

Supersedes #2199 (closed due to force-push blocking CI triggers on fork PRs)

Closes #1520

Summary by CodeRabbit

  • New Features

    • Voice mode toggle, "Speak replies" option, and mascot voice UI integrated into chat (voice button in composer)
  • Navigation & UI Updates

    • Removed human route/tab; /human now redirects to /chat
    • Microphone selector hidden when only one device; composer layout and voice controls improved
  • Localization

    • Added voice-related translations across multiple languages
  • Persistence

    • Mascot voice preferences persist with migration from legacy storage
  • Tests

    • Expanded tests covering voice UI and mascot behavior

Review Change Stack

graycyrus added 6 commits May 19, 2026 13:01
…ace (tinyhumansai#1520)

Move the mascot + voice interaction from a standalone /human route into a
selectable mode within the /chat (Accounts) page. The mascot appears as a
rail button alongside Agent and connected providers, eliminating tab-bar
clutter and unifying thread management across voice and text modes.

- Add MascotChatPane component (mascot stage + Conversations sidebar)
- Add mascot entry to Accounts rail with MASCOT_ACCOUNT_ID sentinel
- Migrate speakReplies from localStorage to Redux (mascotSlice)
- Remove /human route (redirect to /chat) and Human tab from bottom nav
- Exclude mascot mode from fullscreen in accountsFullscreen.ts
- Add unit tests for MascotChatPane and speakReplies slice

Closes tinyhumansai#1520
The i18n coverage CI check requires keys in en.ts to also exist in the
chunked locale files. Adds translated accounts.human to all 11 chunk-3
files.
…tsFullscreen

Cover the mascot selection branch in Accounts.tsx (isMascotSelected,
selectMascot, MascotChatPane render) and the MASCOT_ACCOUNT_ID export +
fullscreen exclusion in accountsFullscreen.ts to satisfy the ≥80%
diff-cover gate.
…inyhumansai#1520)

Replace the separate mascot rail button approach with a mic toggle in the
Conversations composer. Clicking it switches to MicComposer (cloud STT) —
the same component the old /human page used — showing a centered mascot
(180px), mic button, and speak-replies toggle. The device selector is
handled by the system, keeping the UI clean.

- Revert Accounts.tsx and accountsFullscreen.ts to pre-tinyhumansai#1520 state
- Delete MascotChatPane (no longer needed)
- Add composerOverride state to toggle between text and mic-cloud mode
- Center mascot as hero focal point in voice mode
- Wire speakReplies from Redux into voice mode controls

Closes tinyhumansai#1520
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR consolidates the /human mascot/voice route into /chat by adding mascot voice-mode state to Redux (persisted), removing the /human tab/route, integrating YellowMascot and voice controls into Conversations, refining MicComposer, adding i18n keys, updating tests, and documenting persistence/migration behavior.

Changes

Mascot Mode in Chat

Layer / File(s) Summary
Redux mascot state extension & persistence
app/src/store/mascotSlice.ts, app/src/store/index.ts, app/src/store/__tests__/mascotSlice.test.ts
MascotState adds speakReplies and voiceModeActive with defaults; new setSpeakReplies and setVoiceModeActive reducers and selectors; REHYDRATE restores persisted values, falls back to legacy localStorage human.speakReplies (converting "0"/"1" to booleans and deleting the key), and redux-persist whitelist expanded to include the new fields. Tests cover defaults, transitions, REHYDRATE, and migration.
Route consolidation & navigation cleanup
app/src/AppRoutes.tsx, app/src/components/BottomTabBar.tsx, app/test/e2e/specs/navigation.spec.ts
Removes /human route (redirects to /chat), removes human tab from bottom navigation, and updates e2e navigation ROUTES to include /chat instead of /human.
Conversations page mascot integration & voice controls
app/src/pages/Conversations.tsx, app/src/pages/__tests__/Conversations.render.test.tsx
Imports YellowMascot and useHumanMascot; wires selectSpeakReplies, selectVoiceModeActive, and selectMascotColor; derives composerOverride to switch to mic-cloud; renders mascot + MicComposer in mic-cloud mode; adds "Voice mode" button in text composer to activate override; adds "Speak replies" checkbox bound to speakReplies; refactors voice composer controls; render tests stub mascot and assert voice-mode flows.
Microphone device selector refinement
app/src/features/human/MicComposer.tsx, app/src/features/human/MicComposer.test.tsx
Increases outer gap spacing; device <select> now renders only when devices.length > 1 and is disabled only while recording/transcribing; updates select styling and layout classes; tests updated to hide selector when only one device is present.
Multilingual support for voice mode controls
app/src/lib/i18n/chunks/*, app/src/lib/i18n/en.ts
Adds chat.speakReplies and chat.voiceMode keys across language chunk files and the main English map; minor whitespace adjustments in chunk files.
Project documentation update
.claude/memory.md
New "Mascot Redux & Persistence" section documenting pnpm worktree notes, sentinel account exclusion, persist whitelist changes, REHYDRATE requirements and defaults, and the localStorage→Redux migration pattern for one-time values.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A rabbit hopped into the code,

"Chat and human share the road."
Redux keeps the mascot's voice,
One route now gives the user choice,
Hooray — small hops, a united node.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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 title accurately captures the main objective: merging the Human and Chat pages into a unified conversation surface, which is the primary change reflected throughout the diff.
Linked Issues check ✅ Passed The PR substantially implements the requirements from #1520: voice mode toggle in chat composer, mascot/voice as a selectable mode, persistence of speakReplies via Redux with localStorage migration, removal of /human route and Human tab, and device selector conditional rendering.
Out of Scope Changes check ✅ Passed Changes are scoped to the stated objectives. I18n updates (chat.speakReplies, chat.voiceMode) support the new UI. MicComposer selector logic and test updates align with device detection requirements. Layout changes in Conversations.tsx implement the unified surface.

✏️ 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 ready for review May 19, 2026 15:07
@graycyrus graycyrus requested a review from a team May 19, 2026 15:07
@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label May 19, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
.claude/memory.md (1)

187-187: ⚡ Quick win

Move this entry to the "Build & Tooling Gotchas" section.

The worktree native dependencies issue is a general pnpm/tooling concern, not specific to mascot functionality. It would be more discoverable and appropriately categorized in the existing "Build & Tooling Gotchas" section (line 147), alongside similar environment and build issues.

📝 Suggested reorganization

Remove from line 187 and add to the "Build & Tooling Gotchas" section:

 ## Build & Tooling Gotchas
 
 - **`pnpm typecheck` script was renamed** — Check `app/package.json` for the current name; as of issue `#830` work, use `pnpm workspace openhuman-app compile` for tsc checks.
 - **PR `#745` (command palette) merged without its deps** — `@radix-ui/react-dialog`, `cmdk`, and `@testing-library/user-event` are missing from `package.json`. Install them if tsc fails after syncing main.
 - **Pre-push hooks fail on upstream lint warnings** — ESLint warns on `setState` in effects and unused `eslint-disable` directives inherited from upstream. Use `--no-verify` only when the lint errors are pre-existing upstream issues, not new code.
++ **Worktree native deps issue** — pnpm worktrees may lack platform-specific native bindings (e.g. `@rolldown/binding-darwin-arm64`). Workaround: run tests from the main repo directory (shares git objects), or `rm -rf node_modules && pnpm install` in the worktree.

Then remove the redundant line from the "Mascot Redux & Persistence" section:

 ## Mascot Redux & Persistence
 
-- **Worktree native deps issue** — pnpm worktrees may lack platform-specific native bindings (e.g. `@rolldown/binding-darwin-arm64`). Workaround: run tests from the main repo directory (shares git objects), or `rm -rf node_modules && pnpm install` in the worktree.
 - **`accountsFullscreen.ts` sentinel exclusion** — Any new sentinel account ID (like `MASCOT_ACCOUNT_ID`) must be excluded from fullscreen mode in `accountsFullscreen.ts`, same as `AGENT_ACCOUNT_ID`. Otherwise the bottom tab bar hides during those views.
🤖 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 @.claude/memory.md at line 187, Move the "Worktree native deps issue" bullet
out of the "Mascot Redux & Persistence" section and add it to the "Build &
Tooling Gotchas" section: remove the existing line "- **Worktree native deps
issue** — pnpm worktrees may lack platform-specific native bindings (e.g.
`@rolldown/binding-darwin-arm64`). Workaround: run tests from the main repo
directory (shares git objects), or `rm -rf node_modules && pnpm install` in the
worktree." from where it currently appears and insert the same bullet under the
"Build & Tooling Gotchas" heading so it sits with other build/tooling notes;
ensure no duplicate remains in "Mascot Redux & Persistence" and keep the
original formatting and wording when moving.
app/src/store/__tests__/mascotSlice.test.ts (1)

149-210: ⚡ Quick win

Add parity tests for voiceModeActive persistence behavior.

You added thorough speakReplies tests, but the newly persisted voiceModeActive path still lacks default/reducer/reset/REHYDRATE coverage. Adding those cases here would close the regression gap for mode restoration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/store/__tests__/mascotSlice.test.ts` around lines 149 - 210, Add a
parallel test block that mirrors the speakReplies suite but for voiceModeActive:
use reducer, setVoiceModeActive, selectVoiceModeActive and resetUserScopedState
to assert the default value, toggling false/true via setVoiceModeActive,
resetUserScopedState resets to default, and a REHYDRATE block (use the REHYDRATE
action helper) that restores persisted voiceModeActive true/false, defaults when
missing, and migrates legacy localStorage keys ('human.voiceModeActive' =
'0'/'1') to false/true while removing that localStorage key after migration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.claude/memory.md:
- Line 187: Move the "Worktree native deps issue" bullet out of the "Mascot
Redux & Persistence" section and add it to the "Build & Tooling Gotchas"
section: remove the existing line "- **Worktree native deps issue** — pnpm
worktrees may lack platform-specific native bindings (e.g.
`@rolldown/binding-darwin-arm64`). Workaround: run tests from the main repo
directory (shares git objects), or `rm -rf node_modules && pnpm install` in the
worktree." from where it currently appears and insert the same bullet under the
"Build & Tooling Gotchas" heading so it sits with other build/tooling notes;
ensure no duplicate remains in "Mascot Redux & Persistence" and keep the
original formatting and wording when moving.

In `@app/src/store/__tests__/mascotSlice.test.ts`:
- Around line 149-210: Add a parallel test block that mirrors the speakReplies
suite but for voiceModeActive: use reducer, setVoiceModeActive,
selectVoiceModeActive and resetUserScopedState to assert the default value,
toggling false/true via setVoiceModeActive, resetUserScopedState resets to
default, and a REHYDRATE block (use the REHYDRATE action helper) that restores
persisted voiceModeActive true/false, defaults when missing, and migrates legacy
localStorage keys ('human.voiceModeActive' = '0'/'1') to false/true while
removing that localStorage key after migration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a10947f9-a924-4a59-a21b-86c3387da12d

📥 Commits

Reviewing files that changed from the base of the PR and between 1f98614 and 77822f6.

📒 Files selected for processing (33)
  • .claude/memory.md
  • app/src/AppRoutes.tsx
  • app/src/components/BottomTabBar.tsx
  • app/src/features/human/MicComposer.tsx
  • app/src/lib/i18n/chunks/ar-1.ts
  • app/src/lib/i18n/chunks/ar-2.ts
  • app/src/lib/i18n/chunks/bn-1.ts
  • app/src/lib/i18n/chunks/bn-2.ts
  • app/src/lib/i18n/chunks/en-1.ts
  • app/src/lib/i18n/chunks/en-2.ts
  • app/src/lib/i18n/chunks/es-1.ts
  • app/src/lib/i18n/chunks/es-2.ts
  • app/src/lib/i18n/chunks/fr-1.ts
  • app/src/lib/i18n/chunks/fr-2.ts
  • app/src/lib/i18n/chunks/hi-1.ts
  • app/src/lib/i18n/chunks/hi-2.ts
  • app/src/lib/i18n/chunks/id-1.ts
  • app/src/lib/i18n/chunks/id-2.ts
  • app/src/lib/i18n/chunks/it-1.ts
  • app/src/lib/i18n/chunks/it-2.ts
  • app/src/lib/i18n/chunks/pt-1.ts
  • app/src/lib/i18n/chunks/pt-2.ts
  • app/src/lib/i18n/chunks/ru-1.ts
  • app/src/lib/i18n/chunks/ru-2.ts
  • app/src/lib/i18n/chunks/zh-CN-1.ts
  • app/src/lib/i18n/chunks/zh-CN-2.ts
  • app/src/lib/i18n/en.ts
  • app/src/pages/Conversations.tsx
  • app/src/pages/__tests__/Conversations.render.test.tsx
  • app/src/store/__tests__/mascotSlice.test.ts
  • app/src/store/index.ts
  • app/src/store/mascotSlice.ts
  • app/test/e2e/specs/navigation.spec.ts
💤 Files with no reviewable changes (2)
  • app/test/e2e/specs/navigation.spec.ts
  • app/src/components/BottomTabBar.tsx

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 19, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/src/pages/__tests__/Conversations.render.test.tsx (1)

932-985: ⚡ Quick win

Consider organizing voice mode tests in a separate describe block.

The three voice mode tests are well-structured and provide good coverage. However, they're currently mixed into the "smoke render (#1123 welcome-lock removal)" suite, while the tests address #1520 (voice mode integration). Following the existing pattern (e.g., line 992 has a separate describe block for worker thread tests), consider extracting these into their own suite:

+describe('Conversations — voice mode toggle (`#1520`)', () => {
+  beforeEach(() => {
+    vi.clearAllMocks();
+    mockGetThreads.mockResolvedValue({ threads: [], count: 0 });
+    mockGetThreadMessages.mockResolvedValue({ messages: [], count: 0 });
+  });
+
   it('renders a voice mode button in the text composer', async () => {
     ...
   });
   ...
+});

This improves test organization and makes it easier to locate tests by feature area.

🤖 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/pages/__tests__/Conversations.render.test.tsx` around lines 932 -
985, These three tests ("renders a voice mode button in the text composer",
"clicking voice mode button shows the voice controls and mascot", "clicking
Switch to text returns to text composer") should be moved out of the existing
smoke suite into their own describe block (e.g., describe('voice mode
integration', ...)) to mirror the existing pattern; wrap the three it(...) cases
together, keeping their internal calls to renderConversations, emptyThreadState,
socketState, act, fireEvent, and waitFor unchanged, and update any localized
beforeEach/afterEach setup if needed so the new describe block has the same test
fixtures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/src/pages/__tests__/Conversations.render.test.tsx`:
- Around line 932-985: These three tests ("renders a voice mode button in the
text composer", "clicking voice mode button shows the voice controls and
mascot", "clicking Switch to text returns to text composer") should be moved out
of the existing smoke suite into their own describe block (e.g., describe('voice
mode integration', ...)) to mirror the existing pattern; wrap the three it(...)
cases together, keeping their internal calls to renderConversations,
emptyThreadState, socketState, act, fireEvent, and waitFor unchanged, and update
any localized beforeEach/afterEach setup if needed so the new describe block has
the same test fixtures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 608e1250-7ca4-4262-a545-03e583327f79

📥 Commits

Reviewing files that changed from the base of the PR and between 77822f6 and c4718c0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • app/src/lib/i18n/chunks/ar-2.ts
  • app/src/lib/i18n/chunks/bn-2.ts
  • app/src/lib/i18n/chunks/en-2.ts
  • app/src/lib/i18n/chunks/es-2.ts
  • app/src/lib/i18n/chunks/fr-2.ts
  • app/src/lib/i18n/chunks/hi-2.ts
  • app/src/lib/i18n/chunks/id-2.ts
  • app/src/lib/i18n/chunks/it-2.ts
  • app/src/lib/i18n/chunks/pt-2.ts
  • app/src/lib/i18n/chunks/ru-2.ts
  • app/src/lib/i18n/chunks/zh-CN-2.ts
  • app/src/lib/i18n/en.ts
  • app/src/pages/Conversations.tsx
  • app/src/pages/__tests__/Conversations.render.test.tsx
✅ Files skipped from review due to trivial changes (11)
  • app/src/lib/i18n/chunks/es-2.ts
  • app/src/lib/i18n/chunks/it-2.ts
  • app/src/lib/i18n/chunks/zh-CN-2.ts
  • app/src/lib/i18n/chunks/hi-2.ts
  • app/src/lib/i18n/chunks/en-2.ts
  • app/src/lib/i18n/chunks/bn-2.ts
  • app/src/lib/i18n/chunks/id-2.ts
  • app/src/lib/i18n/chunks/ru-2.ts
  • app/src/lib/i18n/chunks/pt-2.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/chunks/fr-2.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/pages/Conversations.tsx

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 19, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/src/features/human/MicComposer.test.tsx (1)

368-382: ⚡ Quick win

Use waitFor from @testing-library/react and combine assertions to match established pattern.

The test now uses vi.waitFor (line 380), which is inconsistent with the rest of the file where waitFor from @testing-library/react is used throughout (lines 118, 120, 133, etc.). More importantly, splitting the mock verification and DOM assertion into separate steps creates a potential race condition—the component may not have finished updating state by the time line 381 executes.

The test at lines 360–365 demonstrates the correct pattern: wrap both the mock check and DOM query inside a single waitFor callback to ensure they're evaluated together.

♻️ Align with the established testing pattern
   render(<MicComposer disabled={false} onSubmit={vi.fn()} showDeviceSelector />);

-  await vi.waitFor(() => expect(enumerateDevicesMock).toHaveBeenCalled());
-  expect(screen.queryByRole('combobox', { name: /microphone device/i })).not.toBeInTheDocument();
+  await waitFor(() => {
+    expect(enumerateDevicesMock).toHaveBeenCalled();
+    expect(
+      screen.queryByRole('combobox', { name: /microphone device/i })
+    ).not.toBeInTheDocument();
+  });
 });
🤖 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/features/human/MicComposer.test.tsx` around lines 368 - 382, The test
uses vi.waitFor and splits the mock verification and DOM assertion, risking a
race; replace vi.waitFor with the waitFor imported from `@testing-library/react`
and wrap both checks together: inside a single waitFor call assert that
enumerateDevicesMock has been called and that screen.queryByRole('combobox', {
name: /microphone device/i }) is not in the document, keeping the
render(<MicComposer ... />) and the enumerateDevicesMock definition as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/src/features/human/MicComposer.test.tsx`:
- Around line 368-382: The test uses vi.waitFor and splits the mock verification
and DOM assertion, risking a race; replace vi.waitFor with the waitFor imported
from `@testing-library/react` and wrap both checks together: inside a single
waitFor call assert that enumerateDevicesMock has been called and that
screen.queryByRole('combobox', { name: /microphone device/i }) is not in the
document, keeping the render(<MicComposer ... />) and the enumerateDevicesMock
definition as-is.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e9346209-228a-4043-b540-49287cf3af08

📥 Commits

Reviewing files that changed from the base of the PR and between c4718c0 and 5f5b6dc.

📒 Files selected for processing (1)
  • app/src/features/human/MicComposer.test.tsx

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 19, 2026
@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 19, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
app/src/pages/__tests__/Conversations.render.test.tsx (3)

1218-1224: ⚡ Quick win

Prefer the simpler store-capture pattern used elsewhere in this file.

The IIFE wrapping renderConversations adds cognitive overhead compared to the pattern used throughout this file (e.g., lines 260–269, 430–440):

let store: ReturnType<typeof buildStore> | undefined;
await act(async () => {
  store = await renderConversations({ thread: emptyThreadState });
});
// ... later: store!.getState()

Consider refactoring for consistency.

♻️ Suggested refactor
- const store = await (async () => {
-   let s: ReturnType<typeof buildStore>;
-   await act(async () => {
-     s = await renderConversations({ thread: emptyThreadState });
-   });
-   return s!;
- })();
+ let store: ReturnType<typeof buildStore> | undefined;
+ await act(async () => {
+   store = await renderConversations({ thread: emptyThreadState });
+ });

  const micBtn = screen.getByRole('button', { name: /voice mode/i });
  await act(async () => {
    fireEvent.click(micBtn);
  });

- expect(store.getState().mascot.voiceModeActive).toBe(true);
+ expect(store!.getState().mascot.voiceModeActive).toBe(true);
🤖 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/pages/__tests__/Conversations.render.test.tsx` around lines 1218 -
1224, Replace the IIFE used to capture the store with the simpler pattern used
elsewhere: declare let store: ReturnType<typeof buildStore> | undefined; then
call await act(async () => { store = await renderConversations({ thread:
emptyThreadState }); }); and later use store! where needed. Target the block
that currently awaits an immediately-invoked async function around
renderConversations and replace it to use the top-level store variable, keeping
the same use of act, renderConversations, buildStore, and emptyThreadState.

1236-1252: ⚡ Quick win

Same IIFE pattern; apply the same refactor.

The IIFE pattern here should be simplified to match the existing pattern (see comment on lines 1218–1224).

♻️ Suggested refactor
- const store = await (async () => {
-   let s: ReturnType<typeof buildStore>;
-   await act(async () => {
-     s = await renderConversations({
-       thread: emptyThreadState,
-       mascot: {
-         color: 'yellow',
-         speakReplies: true,
-         voiceModeActive: true,
-         voiceId: null,
-         voiceGender: 'female',
-         voiceUseLocaleDefault: false,
-         selectedMascotId: null,
-       },
-     });
-   });
-   return s!;
- })();
+ let store: ReturnType<typeof buildStore> | undefined;
+ await act(async () => {
+   store = await renderConversations({
+     thread: emptyThreadState,
+     mascot: {
+       color: 'yellow',
+       speakReplies: true,
+       voiceModeActive: true,
+       voiceId: null,
+       voiceGender: 'female',
+       voiceUseLocaleDefault: false,
+       selectedMascotId: null,
+     },
+   });
+ });

  const backBtn = screen.getByTitle(/switch to text/i);
  await act(async () => {
    fireEvent.click(backBtn);
  });

- expect(store.getState().mascot.voiceModeActive).toBe(false);
+ expect(store!.getState().mascot.voiceModeActive).toBe(false);
🤖 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/pages/__tests__/Conversations.render.test.tsx` around lines 1236 -
1252, Replace the unnecessary immediately-invoked async function with the same
simpler pattern used earlier: declare s: ReturnType<typeof buildStore> before
the act call, then call await act(async () => { s = await renderConversations({
thread: emptyThreadState, mascot: { ... } }); }); and finally return s!; update
references to buildStore, renderConversations, emptyThreadState and act
accordingly so the IIFE is removed and the code matches the pattern at lines
~1218–1224.

932-940: 💤 Low value

Minor: duplicate test coverage.

This test duplicates lines 1184–1191 (both assert the same "Voice mode" button renders). Consider removing this test since the dedicated voice-mode describe block already provides comprehensive coverage.

🤖 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/pages/__tests__/Conversations.render.test.tsx` around lines 932 -
940, The test "renders a voice mode button in the text composer" duplicates
existing coverage (see the voice-mode describe block) and should be removed to
avoid redundant tests; delete or skip the it block named 'renders a voice mode
button in the text composer' that calls renderConversations({ thread:
emptyThreadState, socket: socketState('connected') }) and asserts
screen.getByRole('button', { name: 'Voice mode' }) so the dedicated voice-mode
tests remain the single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/src/pages/__tests__/Conversations.render.test.tsx`:
- Around line 1218-1224: Replace the IIFE used to capture the store with the
simpler pattern used elsewhere: declare let store: ReturnType<typeof buildStore>
| undefined; then call await act(async () => { store = await
renderConversations({ thread: emptyThreadState }); }); and later use store!
where needed. Target the block that currently awaits an immediately-invoked
async function around renderConversations and replace it to use the top-level
store variable, keeping the same use of act, renderConversations, buildStore,
and emptyThreadState.
- Around line 1236-1252: Replace the unnecessary immediately-invoked async
function with the same simpler pattern used earlier: declare s:
ReturnType<typeof buildStore> before the act call, then call await act(async ()
=> { s = await renderConversations({ thread: emptyThreadState, mascot: { ... }
}); }); and finally return s!; update references to buildStore,
renderConversations, emptyThreadState and act accordingly so the IIFE is removed
and the code matches the pattern at lines ~1218–1224.
- Around line 932-940: The test "renders a voice mode button in the text
composer" duplicates existing coverage (see the voice-mode describe block) and
should be removed to avoid redundant tests; delete or skip the it block named
'renders a voice mode button in the text composer' that calls
renderConversations({ thread: emptyThreadState, socket: socketState('connected')
}) and asserts screen.getByRole('button', { name: 'Voice mode' }) so the
dedicated voice-mode tests remain the single source of truth.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a6a58608-49ed-43e8-a220-046dc853ffed

📥 Commits

Reviewing files that changed from the base of the PR and between 5f5b6dc and 191c7e0.

📒 Files selected for processing (1)
  • app/src/pages/__tests__/Conversations.render.test.tsx

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 19, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 19, 2026
@graycyrus graycyrus requested a review from senamakel May 19, 2026 19:44
@graycyrus graycyrus force-pushed the feat/merge-human-chat-v2 branch 2 times, most recently from 9181530 to 28b5c2e Compare May 20, 2026 13:32
…ansai#1520)

Change DefaultRedirect to navigate to /human instead of /home after
login + onboarding, making the mascot + voice page the first thing
users see.

Closes tinyhumansai#1520
@graycyrus graycyrus force-pushed the feat/merge-human-chat-v2 branch from 28b5c2e to 752c64e Compare May 20, 2026 13:35
@graycyrus graycyrus marked this pull request as draft May 20, 2026 13:37
@senamakel
Copy link
Copy Markdown
Member

i'd request we not do this. this would lose a lot of users.

@senamakel senamakel closed this May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Merge Human and Chat pages into a unified conversation surface

2 participants