Skip to content

feat(app): desktop companion UI — Tauri bridge, overlay, settings (#1909)#2246

Merged
senamakel merged 6 commits into
tinyhumansai:mainfrom
yuvrxj-afk:feat/desktop-companion-ui
May 19, 2026
Merged

feat(app): desktop companion UI — Tauri bridge, overlay, settings (#1909)#2246
senamakel merged 6 commits into
tinyhumansai:mainfrom
yuvrxj-afk:feat/desktop-companion-ui

Conversation

@yuvrxj-afk
Copy link
Copy Markdown
Contributor

@yuvrxj-afk yuvrxj-afk commented May 19, 2026

Summary

Wires the UI/bridge layer for the desktop companion domain landed in #2025.

  • Tauri hotkey commands: register_companion_hotkey, unregister_companion_hotkey, companion_activate — same managed-state + rollback pattern as dictation_hotkeys. No JS injection.
  • Socket.IO bridge: subscribes to desktop_companion::bus::subscribe_state_changed() and emits companion:state_changed over the web channel.
  • Redux companionSlice (non-persisted) + socketService listener dispatching on inbound events.
  • OverlayApp extended with 'companion' mode rendering idle / listening / thinking / speaking / pointing / error. Label-only CompanionPointer (full desktop-absolute pointing requires a separate transparent window — deferred).
  • Settings → Features → Companion panel: start/stop, status + remaining-TTL badge, hotkey registration, activation/screen-capture/app-context toggles.
  • Pulsing indicator on the bottom-tab settings icon while a session is active.

Problem

The core domain in #2025 has no entry point into the desktop product surface — no hotkey, no Socket.IO state into the React app, no settings affordance, no overlay rendering. This PR closes those gaps so the #1909 acceptance criteria around desktop entry, state visibility, and frontend tests are actually met.

Solution

Stacked on #2025 (squash-merged as 504d10e9 on main). A single fixup commit reconciles with main post-rebase: crate::AppRuntime instead of stale tauri::Wry, ESM-pattern Vitest mock for coreRpcClient, deduplicated companionSlice imports.

Reuses the RPC and event surface from #2025openhuman.companion_start_session / companion_stop_session / companion_status / companion_config_get / companion_config_set, and the CompanionSessionStarted / CompanionStateChanged / CompanionSessionEnded events.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) — companionSlice.test.ts (15 cases), CompanionPanel.test.tsx (6 cases incl. "shows error when start session fails")
  • Diff coverage ≥ 80% — Vitest covers the slice and panel; Tauri command paths follow the established dictation_hotkeys pattern
  • Coverage matrix updated — N/A: companion entries already added in feat(core): desktop companion domain — session, pipeline, pointing (#1909) #2025
  • All affected feature IDs from the matrix are listed in the PR description under ## Related
  • No new external network dependencies introduced
  • Manual smoke checklist updated — N/A: gated behind a settings panel, not a release-cut surface
  • Linked issue referenced under ## Related

Impact

  • Desktop only. No mobile/web/CLI surface.
  • New Tauri commands and one new Socket.IO event (companion:state_changed + underscore alias).
  • macOS-first per the platform scope doc landed in feat(core): desktop companion domain — session, pipeline, pointing (#1909) #2025. Hotkey is cross-platform; overlay rendering is cross-platform; absolute-positioned pointer is deferred.
  • No DB migrations, no breaking RPC changes.

Related

Refs #1909 — completes the UI/bridge half deferred from #2025.

PS: #1909 shows as closed because GitHub's Closes keyword in #2025's body auto-closed it on merge — the feature itself is only complete once this PR ships.

Test plan

  • cargo check (core lib + Tauri shell) — clean
  • pnpm typecheck / lint / format:check — clean (0 lint errors)
  • pnpm debug unit companionSlice — 15/15
  • pnpm debug unit CompanionPanel — 6/6

Summary by CodeRabbit

  • New Features

    • Desktop Companion: start/stop sessions, register/unregister a desktop hotkey, activate companion, TTL and capture/app-context toggles, live status in Settings, and a pulsing active indicator on the Settings tab.
    • Real-time companion updates and a pointer overlay that highlights labeled on-screen targets.
  • Tests

    • Added unit and UI tests covering companion UI, state transitions, start/stop flows, polling, payload parsing, and selectors.
  • Documentation

    • Added companion translation strings and an accessibility aria label.

Review Change Stack

…inyhumansai#1909)

Socket.IO + Tauri hotkey bridge, Redux companion slice, overlay
companion mode, settings panel with session controls, status badge.
- companion_commands: use crate::AppRuntime (tauri::Cef, not stale Wry)
- CompanionPanel.test: replace require() with ESM import + mock cast
- socketService: merge duplicate companionSlice imports
@yuvrxj-afk yuvrxj-afk requested a review from a team May 19, 2026 20:34
@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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: abae7dcb-5a78-4dab-a2ad-6fe2bb929bc7

📥 Commits

Reviewing files that changed from the base of the PR and between fb316f4 and 1db26e0.

📒 Files selected for processing (14)
  • app/src/lib/i18n/chunks/ar-4.ts
  • app/src/lib/i18n/chunks/bn-4.ts
  • app/src/lib/i18n/chunks/en-4.ts
  • app/src/lib/i18n/chunks/es-4.ts
  • app/src/lib/i18n/chunks/fr-4.ts
  • app/src/lib/i18n/chunks/hi-4.ts
  • app/src/lib/i18n/chunks/id-4.ts
  • app/src/lib/i18n/chunks/it-4.ts
  • app/src/lib/i18n/chunks/pt-4.ts
  • app/src/lib/i18n/chunks/ru-4.ts
  • app/src/lib/i18n/chunks/zh-CN-4.ts
  • app/src/lib/i18n/en.ts
  • app/src/overlay/OverlayApp.tsx
  • app/src/overlay/__tests__/companionStateLabel.test.ts
✅ Files skipped from review due to trivial changes (3)
  • app/src/lib/i18n/chunks/ru-4.ts
  • app/src/lib/i18n/chunks/en-4.ts
  • app/src/lib/i18n/chunks/id-4.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/lib/i18n/chunks/es-4.ts
  • app/src/lib/i18n/en.ts
  • app/src/overlay/OverlayApp.tsx

📝 Walkthrough

Walkthrough

Adds a desktop companion subsystem: Tauri hotkey commands and shared hotkey state, Redux companion slice and selectors with tests, Socket.IO bridging, Settings UI with session controls and tests, overlay visuals including a companion mode and pointer overlay, and i18n strings.

Changes

Desktop Companion Feature

Layer / File(s) Summary
Redux state management and TypeScript types
app/src/store/companionSlice.ts, app/src/store/companionSlice.test.ts, app/src/store/index.ts, app/src/test/test-utils.tsx
Defines companion protocol-aligned types and CompanionSliceState, implements reducers/actions/selectors, adds unit tests, and registers the slice in app and test stores.
Tauri hotkey command handlers
app/src-tauri/src/companion_commands.rs, app/src-tauri/src/lib.rs
Adds CompanionHotkeyState and Tauri commands register_companion_hotkey, unregister_companion_hotkey, companion_activate; implements registration/unregistration with rollback and wires state/commands into Tauri.
Socket.IO event bridging and synchronization
src/core/socketio.rs, app/src/services/socketService.ts
Adds a Tokio bridge broadcasting desktop companion state_changed events to Socket.IO; frontend validates/parses events and dispatches setCompanionState into Redux.
Companion settings panel and session controls
app/src/components/settings/panels/CompanionPanel.tsx, app/src/components/settings/panels/__tests__/CompanionPanel.test.tsx, app/src/pages/Settings.tsx
Implements CompanionPanel with status/config RPC calls, 3s polling, start/stop handlers, config rendering; adds tests and registers the Settings route and menu entry.
Visual indicators, overlays, pointer, and i18n
app/src/components/BottomTabBar.tsx, app/src/overlay/OverlayApp.tsx, app/src/overlay/CompanionPointer.tsx, app/src/lib/i18n/*, app/src/overlay/__tests__/*
Renders pulsing companion dot in BottomTabBar, adds companion overlay mode and handler in OverlayApp, introduces CompanionPointer overlay and tests, and adds companion i18n entries across locale chunks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • graycyrus

Poem

🐇 I hopped in with hotkeys and a nod,
signals creaked from backend sod,
Redux keeps the session bright,
overlays shimmer, dots take flight,
companion hums — the app feels glad.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding desktop companion UI with Tauri bridge, overlay, and settings integration.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

@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.

Actionable comments posted: 7

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

Inline comments:
In `@app/src-tauri/src/companion_commands.rs`:
- Around line 111-120: The guard.clear() is called before attempting OS
unregisters, causing in-memory state to be lost if any unregister fails; move
the mutation so the in-memory registry is only cleared/updated after successful
unregisters — e.g., iterate over a cloned list (the existing let old =
guard.clone();) and call app.global_shortcut().unregister(...) for each
shortcut, and only clear the original guard (or remove each shortcut from guard)
after all unregister calls succeed; adjust error handling so failures return
without mutating guard (referencing guard, old, and the for shortcut in old
loop).

In `@app/src/components/settings/panels/CompanionPanel.tsx`:
- Line 3: Replace direct callCoreRpc usage in CompanionPanel.tsx with the
in-process relay invocation; any calls that currently pass RPC names like
"openhuman.companion_status" or companion control RPCs should instead call the
Tauri/IPC relay via invoke('core_rpc_relay', { method: '<rpc_name>', params:
<params> }). Locate each occurrence of callCoreRpc in this file (the status
fetch and the companion control handlers) and swap the call to
invoke('core_rpc_relay', ...) preserving the same method name and parameters
structure, and ensure the calling code still handles the returned promise/result
the same way.

In `@app/src/overlay/CompanionPointer.tsx`:
- Around line 21-24: The effect currently calls setVisible(true) synchronously
which violates the react-hooks/set-state-in-effect rule; change it to schedule
the show update asynchronously (e.g., call setVisible(true) inside a short timer
or requestAnimationFrame) and manage timers in the same effect so they are
cleared on cleanup. In the CompanionPointer useEffect (references: targets,
setVisible, dismissMs) replace the direct setVisible(true) with an async
showTimer (e.g., window.setTimeout(() => setVisible(true), 0) or
requestAnimationFrame) and keep/clear the existing dismiss timer (and any
created timers) in the cleanup to avoid leaks.

In `@app/src/overlay/OverlayApp.tsx`:
- Around line 688-690: The ARIA label for the companion branch in OverlayApp
(the ternary that checks mode === 'companion') is hardcoded as "Companion
active"; replace that literal with a localized string using the existing i18n
function (t) — e.g., t('overlay.companionActive') — and add the matching
key/value to the localization resources so the companion branch uses the same
translation mechanism as the other branches.

In `@app/src/pages/Settings.tsx`:
- Around line 246-252: The new settings entry with id 'companion' currently
hardcodes English strings for title and description; update the Settings
component to use the i18n translation function (the same t(...) used by other
items) so both title and description call t with appropriate keys (e.g.,
t('settings.desktopCompanion.title') and
t('settings.desktopCompanion.description') and ensure keys are added to the
locale files), and keep the other properties (id, route, icon) unchanged so the
item integrates with the existing settings array.

In `@app/src/services/socketService.ts`:
- Around line 295-300: The companion event handler currently only checks for a
'state' key and may dispatch malformed data; create and use a strict type-guard
(e.g., isCompanionStateChangedEvent) that validates the shape and types of
CompanionStateChangedEvent (verify event is object, event.state is one of the
allowed values/enums, and event.sessionId is a non-empty string or other
expected type) inside the socket.on('companion:state_changed', ...) callback,
log an error via socketLog when validation fails, and only call
store.dispatch(setCompanionState(event)) when the guard returns true to prevent
invalid sessionId/state from being written to Redux.

In `@app/src/store/companionSlice.ts`:
- Around line 76-84: The reducer setCompanionState currently only assigns
state.lastError on error events, allowing stale errors to persist; update
setCompanionState (handling CompanionStateChangedEvent) to clear state.lastError
when event.state is not 'error' (e.g., set it to undefined or null) so that once
the companion recovers the UI no longer shows a previous error.
🪄 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: 45d91403-dce4-496b-a48d-6cec20572cde

📥 Commits

Reviewing files that changed from the base of the PR and between ba38e72 and b60cbef.

📒 Files selected for processing (14)
  • app/src-tauri/src/companion_commands.rs
  • app/src-tauri/src/lib.rs
  • app/src/components/BottomTabBar.tsx
  • app/src/components/settings/panels/CompanionPanel.tsx
  • app/src/components/settings/panels/__tests__/CompanionPanel.test.tsx
  • app/src/overlay/CompanionPointer.tsx
  • app/src/overlay/OverlayApp.tsx
  • app/src/pages/Settings.tsx
  • app/src/services/socketService.ts
  • app/src/store/companionSlice.test.ts
  • app/src/store/companionSlice.ts
  • app/src/store/index.ts
  • app/src/test/test-utils.tsx
  • src/core/socketio.rs

Comment thread app/src-tauri/src/companion_commands.rs
Comment thread app/src/components/settings/panels/CompanionPanel.tsx
Comment thread app/src/overlay/CompanionPointer.tsx Outdated
Comment thread app/src/overlay/OverlayApp.tsx
Comment thread app/src/pages/Settings.tsx
Comment thread app/src/services/socketService.ts
Comment thread app/src/store/companionSlice.ts
@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label May 19, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 19, 2026
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.

Actionable comments posted: 2

🤖 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/overlay/OverlayApp.tsx`:
- Around line 99-112: The companionStateLabel helper currently returns hardcoded
English strings; update it so labels are localized by either returning
translation keys or calling the i18n translator directly (use
companionStateLabel to call t(...) instead of returning raw strings, or have it
return keys like 'companion.state.listening' and translate at the call site).
Locate the function companionStateLabel and replace each hardcoded string (cases
'listening','thinking','speaking','pointing', default) with calls to the
translation function t(...) or their corresponding translation keys, and ensure
the file imports the translator (or the caller performs t(...) if you choose to
return keys). Keep the same wording/ellipsis in translations and preserve the
default behavior by mapping unknown states to a translated template (e.g., a key
that accepts the state as a variable).
- Around line 311-316: When handling the 'error' state in the OverlayApp (the
block that calls setMode('companion') and setBubble with id
`companion-error-...`), trim payload.message before deciding whether to use it;
i.e., compute a trimmed string from payload?.message and if that trimmed value
is non-empty use it inside the quoted text, otherwise fall back to the literal
"Error" so whitespace-only messages don't render as an empty quoted bubble.
🪄 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: c28e603c-10f4-4935-b336-2f27f00c87ce

📥 Commits

Reviewing files that changed from the base of the PR and between c87e8e9 and fb316f4.

📒 Files selected for processing (7)
  • app/src/components/__tests__/BottomTabBar.test.tsx
  • app/src/components/settings/panels/__tests__/CompanionPanel.test.tsx
  • app/src/overlay/OverlayApp.tsx
  • app/src/overlay/__tests__/CompanionPointer.test.tsx
  • app/src/overlay/__tests__/companionStateLabel.test.ts
  • app/src/services/__tests__/companionPayload.test.ts
  • app/src/services/socketService.ts
✅ Files skipped from review due to trivial changes (1)
  • app/src/overlay/tests/companionStateLabel.test.ts

Comment thread app/src/overlay/OverlayApp.tsx Outdated
Comment thread app/src/overlay/OverlayApp.tsx
@yuvrxj-afk
Copy link
Copy Markdown
Contributor Author

Hey Steve (@senamakel) — UI for the companion core from #2025.

Closing the loop on #1909 properly.

@senamakel senamakel merged commit e920522 into tinyhumansai:main May 19, 2026
29 checks passed
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.

2 participants