Conversation
Adds Sleepy Mode: after a configurable stretch with no input, the window is covered by a clock, the date, and a live count of what the fleet is doing. Any key, click, or scroll wakes it. - Settings → Agents: Never / 5 / 15 / 30 min, plus "Start Sleepy Mode" now. - Also startable from the Keep-computer-awake status bar menu. - The pet rises above the scene so the resting screen keeps an animated read on agent state. Display sleep is deliberately untouched — "Keep computer awake" already owns that, and Sleepy Mode composes with it instead of asserting its own blocker.
A tooltip left open by the pointer (z-[90]) painted over the resting screen, so the scene moves to z-[95] — above tooltips and menus, below onboarding and the blocking SSH/link dialogs. The pet follows at z-[96]. The E2E drives the real path: status bar menu → Start Sleepy Mode → scene covers the window → a keypress wakes it.
A flat fill read as a blank window rather than a resting screen. The scene now sits on a radial wash mixed from the existing tokens, so it gains depth without introducing a colour. The direction flips per theme: light mode can't go brighter than the canvas, so it vignettes the edges; dark mode lights a pale pool behind the clock.
Orca already ships Claudino and animates it off live agent state, but only as a draggable corner overlay. The resting screen now shows the same pet centred above the clock, so the scene reads the fleet at a glance: it runs while agents work, waits when one is blocked, and idles when the night is quiet. The rendering half of PetOverlay (sprite sheet stepping, auto-detected frames, the bob keyframes, the document-visibility gate) moves to PetSprite so both surfaces share one implementation instead of a copy. PetOverlay keeps all of its positioning, drag, and persistence behaviour. The scene's pet is not gated on the experimentalPet flag — that flag owns the draggable overlay, not the artwork — but an explicit "Hide pet" still hides it.
`pnpm lint` regenerates en-runtime-required.json from the keys the renderer reaches at boot; the new Sleepy Mode search keywords were missing from it. Also lets the E2E capture a before shot for the PR.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe pull request adds Sleepy Mode with configurable idle delays, explicit activation from Settings and the status bar, transient activation state, and a full-window resting overlay. The overlay checks OS idle time, displays a fleet summary, and consumes wake input. The changes also add localization, theme styling, automated tests, and an end-to-end test. Pet sprite rendering and animation logic move into a shared Priority: ➖ Normal Severity of issue fixed: Medium Merge Risk: 🔵 Low · up to Sleepy Mode currently consumes wheel input, but its tests would not catch removal of the non-passive listener option; a later regression could scroll the workspace on wake. Add the focused regression check before merging. The current behavior is correct, so the remaining risk is bounded. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 1aa98862-a938-4d96-b173-3ed1c5fa5861
📒 Files selected for processing (24)
src/renderer/src/app-shell/AppRootSurfaces.tsxsrc/renderer/src/app-shell/app-root-surface-settings.tssrc/renderer/src/assets/main.csssrc/renderer/src/components/pet/PetOverlay.tsxsrc/renderer/src/components/pet/PetSprite.tsxsrc/renderer/src/components/settings/AgentsPane.tsxsrc/renderer/src/components/settings/SleepyModeSetting.tsxsrc/renderer/src/components/settings/sleepy-mode-copy.tssrc/renderer/src/components/sleepy-mode/SleepyModeOverlay.test.tsxsrc/renderer/src/components/sleepy-mode/SleepyModeOverlay.tsxsrc/renderer/src/components/sleepy-mode/sleepy-mode-fleet-summary.test.tssrc/renderer/src/components/sleepy-mode/sleepy-mode-fleet-summary.tssrc/renderer/src/components/sleepy-mode/use-sleepy-mode-idle-trigger.tssrc/renderer/src/components/status-bar/CaffeinateStatusSegment.localization.test.tsxsrc/renderer/src/components/status-bar/CaffeinateStatusSegment.tsxsrc/renderer/src/i18n/en-runtime-required.jsonsrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/store/slices/ui/ui-slice-contract-preferences.tssrc/renderer/src/store/slices/ui/ui-slice-surface-actions.tssrc/shared/default-global-settings.tssrc/shared/global-settings-types.tssrc/shared/sleepy-mode-settings.test.tssrc/shared/sleepy-mode-settings.tstests/e2e/sleepy-mode.spec.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
There was a problem hiding this comment.
Important
The idle countdown does not reset while you type or scroll in a terminal or editor — xterm/Monaco consume those events before they reach the bubble-phase listeners — so the scene can cover the window mid-session. Details inline.
Reviewed changes
SleepyModeOverlayscene + idle trigger — a new full-window resting screen (clock, date, live fleet line, pet) that auto-starts aftersleepyModeIdleMinutesof renderer input and wakes on the first key/pointerdown/wheel.PetSpriteextraction — sprite/canvas rendering, the bob keyframes,usePetUrl, and the document-visibility gate move out ofPetOverlayinto a shared component; the corner overlay keeps its positioning/drag/persistence and unchanged DOM. The blob cache retain is refcounted, so the secondusePetUrlcaller is safe.- Settings + status-bar entry points — optional
sleepyModeIdleMinutes(Never/5/15/30) with a Start button and a status-bar menu item, both behind the existing paired-web-client guards. - Fleet summary —
summarizeSleepyModeFleetcounts fresh explicit agent statuses and ignores stale rows andmonitoringturns. - Transient store flag —
sleepyModeActiveis never persisted, so a restart cannot resume into the scene. - Tests — idle-delay normalisation, fleet-summary freshness/monitoring, overlay timing/wake, and a status-bar E2E.
ℹ️ Nitpicks
SleepyModeOverlayformats both clock and date withIntl.DateTimeFormat(undefined, …), so the weekday/month names follow the OS locale instead of the selected UI language. The app already resolves this throughgetIntlLocale()(NativeChatMessageTimestamp,StatsPane,weekday-names); routing at least the date through it would keep the scene in the user's chosen language. (OS-locale 12/24-hour time may still be the right call.)
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
Two interaction defects from review. The wake listener dismissed the scene but let the same event through, so the key that woke Orca also typed into the terminal or editor behind it. It is now consumed before the scene closes. The idle countdown listened for renderer events, which miss the two places work actually happens: xterm and Monaco stop propagation on what they handle, and browser panes are `<webview>`s in their own process that emit nothing in the host document. Typing in a terminal or driving a browser pane therefore did not reset the countdown and the scene could cover the window mid-use. It now samples `powerMonitor.getSystemIdleTime()` through a new `agentAwake:getSystemIdleSeconds` channel, which is the same clock the OS screensaver uses and covers every surface. An unknown idle time — a platform that cannot measure it, or a paired web client — never starts the scene.
|
Thanks — both functional findings were real and are fixed in 78364c3. Replies are on the individual threads; the short version:
On the docstring coverage check (27.27% against an 80% threshold): leaving this one as is, deliberately. This repo's What the diff does carry is a short Happy to revisit if a maintainer would rather have the coverage number. |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 81c5e2af-bd72-42e5-956e-f111c907de73
📒 Files selected for processing (9)
src/main/ipc/settings.tssrc/main/system-idle-seconds.test.tssrc/main/system-idle-seconds.tssrc/preload/api/agent-awake-bridge.tssrc/preload/api/agent-status-api.tssrc/renderer/src/components/sleepy-mode/SleepyModeOverlay.test.tsxsrc/renderer/src/components/sleepy-mode/SleepyModeOverlay.tsxsrc/renderer/src/components/sleepy-mode/use-sleepy-mode-idle-trigger.tssrc/renderer/src/web/preload-api/web-settings-api.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/renderer/src/components/sleepy-mode/SleepyModeOverlay.tsx
Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.
Dispatching on `window` meant the event never travelled through the workspace, so the "never reached the workspace" listener could not have fired either way and that half of the test proved nothing. It now dispatches at an element in the document, and the test fails if the consumption is removed.
There was a problem hiding this comment.
ℹ️ No critical issues — one note on platform coverage plus a small inline nit.
Reviewed changes
The delta since the prior pullfrog review (a1e1f7a → 78364c3) moves the idle decision off renderer events entirely, which addresses the earlier capture-phase finding.
- Idle trigger now reads the OS input clock —
useSleepyModeIdleTriggerpollswindow.api.agentAwake.getSystemIdleSeconds()everymin(15s, delayMs)instead of registeringkeydown/pointerdown/pointermove/wheel/touchstartlisteners, so terminal/editorstopPropagationand out-of-process webviews can no longer let the scene cover an active session. - Wake input is consumed — the scene's
keydown/pointerdown/wheelcapture handler now callspreventDefault()andstopImmediatePropagation()(withpassive: false), so the dismissing key no longer leaks into the terminal or editor underneath. - New main-process surface —
readSystemIdleSeconds()wrapspowerMonitor.getSystemIdleTime()(non-finite/negative/throw →null), exposed over a newagentAwake:getSystemIdleSecondsIPC channel and preload bridge, withnullstubbed for paired web clients. - Tests updated — the overlay timing tests now drive the fake OS clock (including "unknown never starts" and "low OS clock keeps waiting"), plus a new test that the wake key is swallowed;
readSystemIdleSecondshas its own unit test.
ℹ️ Platform-sensitive OS API added, but the description still says "renderer-only"
The delta adds a main-process powerMonitor.getSystemIdleTime() read and a new IPC channel, which is exactly the platform-dependent piece the change previously avoided. The PR body's "Mechanism"/"Why" sections and the review bullet still state the feature is renderer-only with "no new IPC channel or main-process state," and the "not exercised on Linux/Windows" note leans on that. The description should be updated so a reviewer evaluates the real surface.
Technical details
# Description and platform-coverage framing are stale after the OS-clock switch
## Affected sites
- `src/main/system-idle-seconds.ts:14` — new main-process read of `powerMonitor.getSystemIdleTime()`.
- `src/main/ipc/settings.ts:82` — new `agentAwake:getSystemIdleSeconds` IPC channel.
- `src/preload/api/agent-awake-bridge.ts:7` — new preload bridge method.
- PR body "Mechanism"/"Why" and the "no new IPC channel or main-process state" bullet — now contradicted by the above.
## Required outcome
- The PR description reflects the new IPC/main-process surface.
- The accepted behavior on platforms where the idle clock cannot measure input (e.g. Wayland) is stated, since the scene then silently never auto-starts. The code degrades safely (throw/NaN → `null`), so this is a framing/decision note, not a code defect.
## Open questions for the human
- On platforms where `getSystemIdleTime()` reports a constant rather than throwing, is "Sleepy Mode silently disabled" the intended fallback?DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
| * process and terminals/editors stop propagation, so renderer listeners miss real activity. | ||
| * Null (Wayland, or a throwing monitor) must read as "unknown", never as "idle". | ||
| */ | ||
| export function readSystemIdleSeconds(monitor: IdleMonitor = powerMonitor): number | null { |
There was a problem hiding this comment.
readSystemIdleSeconds repeats the Number.isFinite(idle) && idle >= 0 guard already in readDesktopAwayState (src/main/notifications/desktop-away-state.ts:15). Defer-able, but extracting a shared raw-seconds reader would keep the "unknown vs idle" rule in one place if more callers appear.
Only conflict was en.json, where both sides appended sibling keys under auto.components: upstream's native-chat resume copy and Sleepy Mode's. Kept both, then regenerated en-runtime-required.json against the merged catalog.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.
🟡 Minor · Add wake-path coverage for pointer and scroll input. · SleepyModeOverlay.test.tsx:142-159
src/renderer/src/components/sleepy-mode/SleepyModeOverlay.test.tsx:142-159
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAdd wake-path coverage for pointer and scroll input.
The changed unit test and E2E test exercise only
keydown.SleepyModeOverlayalso wakes onpointerdownandwheel, so a regression in either listener can pass these tests. Add assertions for pointer/click and scroll wake behavior at this test boundary.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 48ebf113-c50f-48c6-b82c-ebbb861b33c4
📒 Files selected for processing (5)
src/renderer/src/assets/main.csssrc/renderer/src/i18n/en-runtime-required.jsonsrc/renderer/src/i18n/locales/en.jsonsrc/shared/default-global-settings.tssrc/shared/global-settings-types.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/renderer/src/i18n/locales/en.json
- src/renderer/src/i18n/en-runtime-required.json
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.
Only keydown was asserted, so a regression in either of the other two wake listeners passed. The case is now table-driven over keydown, pointerdown and wheel: each must dismiss the scene, be cancelled, and never reach a listener on the element it was dispatched at. Removing either listener fails its row.
|
Fair — only The case is now table-driven over Checked the rows are load-bearing rather than assuming it: deleting the One limitation worth stating rather than implying otherwise: this runs in happy-dom, which does not model Chromium's passive-by-default rule for |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/renderer/src/components/sleepy-mode/SleepyModeOverlay.test.tsx (1)
142-172: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winProtect the omitted
passive: falseregression.The happy-dom test does not model Chromium’s passive default for
wheellisteners onwindow. If the option is removed, the test can still pass because happy-dom treats an omitted option as non-passive. Chromium can then ignorepreventDefault(), so the wheel default action may scroll the workspace. The E2E test covers only keyboard wake.Suggested fix
+ it('registers the wheel wake listener as non-passive', () => { + setState({ sleepyModeActive: true }) + const addEventListener = vi.spyOn(window, 'addEventListener') + + try { + render(<SleepyModeOverlay />) + + const wheelRegistration = addEventListener.mock.calls.find( + ([eventName]) => eventName === 'wheel' + ) + expect(wheelRegistration?.[2]).toEqual({ capture: true, passive: false }) + } finally { + addEventListener.mockRestore() + } + })
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 48d531e3-ddaa-4776-bb9b-4355eb397024
📒 Files selected for processing (1)
src/renderer/src/components/sleepy-mode/SleepyModeOverlay.test.tsx
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

ELI5
If you walk away from Orca while your agents keep working, the screen keeps showing everything they are doing — prompts, diffs, terminal output — to whoever walks past. This adds a quiet resting screen: after a few minutes with no typing or clicking, Orca covers its own window with a clock, the date, a one-line summary of what the fleet is up to, and the pet. Touch anything and you are straight back where you were.
What Changed
Before. Leaving the machine leaves the workspace on display. With "Keep computer awake" set to Agent, that is deliberate — the display is held awake for as long as agents run, so the content stays lit. Coming back, nothing summarises what happened; you scan tabs to find out whether anything is still running or waiting on you.
After. A new Sleepy Mode:
Never(default) /5/15/30minutes of no input before the resting screen appears, plus a Start Sleepy Mode button for right now.2 working · 1 waiting, orNo agents working), and the pet animating off the same agent state the corner overlay reads — running while agents work, waiting when one is blocked, idle when the night is quiet.keydown,pointerdownorwheeldismisses it. Listeners are registered in the capture phase, because xterm and Monaco consume plenty of events before they reachwindow.Mechanism. Almost all of it is renderer-side; the one exception is reading the OS input clock:
powerMonitor.getSystemIdleTime()over a newagentAwake:getSystemIdleSecondschannel rather than listening for renderer events: terminals and editors stop propagation, and browser panes are<webview>s in their own process, so DOM listeners miss real work and would cover the window mid-use. An unmeasurable clock reads as unknown and never starts the scene.SleepyModeOverlayowns its own idle polling and rendersnulluntil it fires, so it is mounted only when auto-start is configured or the scene is up, and it is behind a lazy chunk.sleepyModeActive) — never persisted, so a crash or restart can never come back asleep.agentStatusByPaneKeystore with the same freshness andmonitoringrules the pet uses, so a stale row cannot read as live work.PetOverlay(sprite-sheet stepping, auto-detected frames, bob keyframes, the document-visibility gate) moved intoPetSpriteso the scene and the corner overlay share one implementation instead of a copy.PetOverlaykeeps all of its positioning, drag and persistence behaviour; its DOM is unchanged.z-[95]: above tooltips (z-[90]) and menus (z-[70]), below onboarding (z-[100]) and the blocking SSH/link dialogs. An earlier revision sat atz-50and a tooltip left open by the pointer painted straight through it.Why
Two smaller alternatives were considered and rejected:
Why not the OS screensaver? It is fleet-blind, so it solves privacy but not "what happened while I was away". More importantly it is suppressed by exactly the awake assertion Orca asks for on the user's behalf, so for the users who most need this — the ones running agents unattended — it never appears.
Why does Sleepy Mode not assert its own power blocker? cmux's equivalent bundles a keep-awake toggle into the screensaver. Orca already has
AgentAwakeServiceand a user-facing three-state setting for exactly that decision. A second, hidden assertion would fight it and would quietly hold a display awake that the user asked to sleep. Sleepy Mode therefore changes what is on the screen and nothing about power: it composes with whatever "Keep computer awake" is set to. It does read one thing frompowerMonitor— the system idle time, to decide when to appear (see the review thread onuse-sleepy-mode-idle-trigger.ts) — but it asserts nothing and holds nothing.Why no colour themes? cmux ships several glow palettes.
docs/STYLEGUIDE.mdis explicit that Orca is monochrome and quiet with colour reserved for state, so the scene gets depth from a radial wash mixed out of the existing--foreground/--backgroundtokens instead of a new palette. The direction flips per theme, because light mode cannot go brighter than its canvas: it vignettes the edges, while dark mode lights a pale pool behind the clock. No new colour values.Why is the pet not gated on
experimentalPet? That flag owns the draggable corner overlay, not the artwork, and the assets are already bundled. An explicit "Hide pet" is a preference about the pet itself, so the scene honours that and hides it. Happy to gate it on the flag instead if you would rather keep every pet surface behind it.Linked Issue
Fixes #21774
Visual Proof
Before — the workspace as it is left, still on display:
After — the resting screen (light mode):

Testing
Reviewer steps:
pnpm dev5 min, then leave the machine alone for five minutes.Platforms actually tested: macOS 15 (arm64), light mode, local workspace. Not exercised on Linux, Windows, or an SSH host — though the change is renderer-only and touches no platform API, no path handling, and no keyboard accelerator; time and date rendering goes through
Intl.DateTimeFormatwith the OS locale, so 12/24-hour follows the system. The settings row and the status-bar entry are both inside the existingisPairedWebClientWindow()guards, so paired web clients are unchanged.Automated:
src/shared/sleepy-mode-settings.test.ts— idle-delay normalisation, including hostile persisted values.sleepy-mode-fleet-summary.test.ts— stale rows andmonitoringturns must not count as work.SleepyModeOverlay.test.tsx— fires at the configured delay and not before, never fires when auto-start is off, restarts the countdown on input, renders the live fleet, wakes on a keypress.tests/e2e/sleepy-mode.spec.ts— drives the real path: status-bar menu → scene covers the window → keypress wakes it.Local check status:
pnpm lint,pnpm typecheck,pnpm buildandpnpm run check:code-quality:changedare green.pnpm testreports 85,238 passing with 20 files failing, none of them touched by this branch: thecross-version-wiresuites need tags and history this shallow clone does not have, and the rest are load-sensitive timeouts (renderer-node-builtin-boundary,windows-transient-lock-removal,codex-session-index-heal-state) plus real-CLI / SSH / WSL / native-build suites that need tooling absent on this machine. Each of the load-sensitive ones passes when run on its own. CI will be the real verdict.AI Disclosure
Written with Claude Code (Claude Opus 5). The author set the direction and made the design calls recorded above — an ambient token-derived background rather than cmux's colour themes, and the pet on the scene — and is reviewing this draft before it leaves draft state. The implementation, tests and screenshots were produced by the agent; the screenshots and the E2E come from real runs of the built app, not from a model description.
Review
Agent code-review summary:
process.platform, no path handling, nometaKey/accelerator, no native module.Intl.DateTimeFormatwith the OS locale gives 12/24-hour and month/weekday names per platform locale.AgentStatusEntry.stateand nothing agent-specific. No git-provider surface touched.Neverselected the overlay is not mounted at all, so no listeners and no timers. When auto-start is on, fivepassivelisteners and one timeout; the clock interval only ticks while the scene is visible; sprite animation is already gated ondocument.visibilityState. The scene is a lazy chunk, so its module and the pet assets are not fetched for users who never enable it.prefers-reduced-motionstops both the pulse and the pet bob;pnpm run check:code-quality:changed(including the design-system gate) reports 0 findings.sleepyModeIdleMinutes?), defaulted off and normalised on read, so older and newer profiles both load. No wire, schema, or storage format changed.Residual risk the author flags for the reviewer: Linux and Windows are unverified by hand, and the z-index choice (
z-[95]) is a judgement call about which surfaces should outrank a resting screen.Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
No migration, no release-version change. The only shared-surface edits are one optional
GlobalSettingsfield and thePetSpriteextraction described above.Checklist
N/Awith reasonpnpm lint,pnpm typecheck, andpnpm buildpass locally;pnpm testpasses apart from 20 pre-existing environment failures described under Testing