Skip to content

feat: Sleepy Mode — a resting screen for a fleet left running - #21775

Open
DannyHo15 wants to merge 9 commits into
stablyai:mainfrom
DannyHo15:feat/sleepy-mode
Open

DannyHo15 wants to merge 9 commits into
stablyai:mainfrom
DannyHo15:feat/sleepy-mode

Conversation

@DannyHo15

@DannyHo15 DannyHo15 commented Sep 20, 2026 •

Copy link
Copy Markdown

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:

  • Settings → Agents → Sleepy Mode: Never (default) / 5 / 15 / 30 minutes of no input before the resting screen appears, plus a Start Sleepy Mode button for right now.
  • Also startable from the Keep computer awake status-bar menu.
  • The scene shows the clock, the date, a live fleet line (2 working · 1 waiting, or No 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.
  • Any keydown, pointerdown or wheel dismisses it. Listeners are registered in the capture phase, because xterm and Monaco consume plenty of events before they reach window.

Mechanism. Almost all of it is renderer-side; the one exception is reading the OS input clock:

  • The trigger samples powerMonitor.getSystemIdleTime() over a new agentAwake:getSystemIdleSeconds channel 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.
  • SleepyModeOverlay owns its own idle polling and renders null until it fires, so it is mounted only when auto-start is configured or the scene is up, and it is behind a lazy chunk.
  • Scene state is one transient store flag (sleepyModeActive) — never persisted, so a crash or restart can never come back asleep.
  • The fleet line is derived from the existing agentStatusByPaneKey store with the same freshness and monitoring rules the pet uses, so a stale row cannot read as live work.
  • The rendering half of PetOverlay (sprite-sheet stepping, auto-detected frames, bob keyframes, the document-visibility gate) moved into PetSprite so the scene and the corner overlay share one implementation instead of a copy. PetOverlay keeps all of its positioning, drag and persistence behaviour; its DOM is unchanged.
  • The scene sits at z-[95]: above tooltips (z-[90]) and menus (z-[70]), below onboarding (z-[100]) and the blocking SSH/link dialogs. An earlier revision sat at z-50 and 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 AgentAwakeService and 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 from powerMonitor — the system idle time, to decide when to appear (see the review thread on use-sleepy-mode-idle-trigger.ts) — but it asserts nothing and holds nothing.

Why no colour themes? cmux ships several glow palettes. docs/STYLEGUIDE.md is 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/--background tokens 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:

1-before-workspace

After — the resting screen (light mode):
2-after-sleepy-mode

Testing

Reviewer steps:

  1. pnpm dev
  2. Status bar → ☕ Keep computer awake → Start Sleepy Mode. The scene covers the window; any key wakes it.
  3. Settings → Agents → Sleepy Mode → 5 min, then leave the machine alone for five minutes.
  4. With an agent running, start the scene again — the fleet line counts it and the pet switches to its running animation.

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.DateTimeFormat with the OS locale, so 12/24-hour follows the system. The settings row and the status-bar entry are both inside the existing isPairedWebClientWindow() 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 and monitoring turns 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 build and pnpm run check:code-quality:changed are green. pnpm test reports 85,238 passing with 20 files failing, none of them touched by this branch: the cross-version-wire suites 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.

  • I manually tested these changes locally — plus the E2E above, run against real Electron; both screenshots come from it
  • Automated tests added/updated, or explained why not below

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:

  • Cross-platform — renderer-only. No process.platform, no path handling, no metaKey/accelerator, no native module. Intl.DateTimeFormat with the OS locale gives 12/24-hour and month/weekday names per platform locale.
  • Remote / SSH / local — no execution-host interaction and no new IPC. The fleet line reads the existing renderer agent-status store, which already carries rows attributed to remote hosts, and applies only the established freshness rules; it makes no liveness claim of its own. Both entry points stay behind the existing paired-web-client guards.
  • Agents and integrations — provider-neutral: it consumes AgentStatusEntry.state and nothing agent-specific. No git-provider surface touched.
  • Performance — nothing runs while the feature is off: with Never selected the overlay is not mounted at all, so no listeners and no timers. When auto-start is on, five passive listeners and one timeout; the clock interval only ticks while the scene is visible; sprite animation is already gated on document.visibilityState. The scene is a lazy chunk, so its module and the pet assets are not fetched for users who never enable it.
  • UI quality — tokens only, no raw colour values; correct in light and dark; prefers-reduced-motion stops both the pulse and the pet bob; pnpm run check:code-quality:changed (including the design-system gate) reports 0 findings.
  • Security / privacy — the scene covers the window and its copy states plainly that it is a screen cover, not a lock; any key dismisses it. It stores nothing, reads no credentials, and adds no network or IPC surface. The active flag is deliberately transient so a restart can never resume into it.
  • Backwards compatibility — one new optional setting (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

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and 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 GlobalSettings field and the PetSprite extraction described above.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, and pnpm build pass locally; pnpm test passes apart from 20 pre-existing environment failures described under Testing

danny added 5 commits September 20, 2026 02:13
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.
@DannyHo15
DannyHo15 marked this pull request as ready for review September 20, 2026 09:40
@coderabbitai

coderabbitai Bot commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

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

The 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 PetSprite component.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🔵 Low · up to d871a

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)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 36 functions across 27 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: adding Sleepy Mode as a resting screen for an active fleet.
Description check ✅ Passed The description follows the required template and covers the user impact, implementation, rationale, linked issue, visual proof, testing, AI disclosure, review considerations, compatibility risks, and…
Linked Issues check ✅ Passed The pull request meets the coding requirements in issue #21774. It supports the Never, 5, 15, and 30 minute options and uses OS idle time through powerMonitor.getSystemIdleTime(). It provide…
Out of Scope Changes check ✅ Passed The changes stay within issue #21774. The OS idle-time bridge supports inactivity detection across applications. PetSprite extraction supports the required live pet display. Settings, status-bar int…
  • Fix all pre-merge checks with AI

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 1aa98862-a938-4d96-b173-3ed1c5fa5861

📥 Commits

Reviewing files that changed from the base of the PR and between 96c1dd8 and a1e1f7a.

📒 Files selected for processing (24)
  • src/renderer/src/app-shell/AppRootSurfaces.tsx
  • src/renderer/src/app-shell/app-root-surface-settings.ts
  • src/renderer/src/assets/main.css
  • src/renderer/src/components/pet/PetOverlay.tsx
  • src/renderer/src/components/pet/PetSprite.tsx
  • src/renderer/src/components/settings/AgentsPane.tsx
  • src/renderer/src/components/settings/SleepyModeSetting.tsx
  • src/renderer/src/components/settings/sleepy-mode-copy.ts
  • src/renderer/src/components/sleepy-mode/SleepyModeOverlay.test.tsx
  • src/renderer/src/components/sleepy-mode/SleepyModeOverlay.tsx
  • src/renderer/src/components/sleepy-mode/sleepy-mode-fleet-summary.test.ts
  • src/renderer/src/components/sleepy-mode/sleepy-mode-fleet-summary.ts
  • src/renderer/src/components/sleepy-mode/use-sleepy-mode-idle-trigger.ts
  • src/renderer/src/components/status-bar/CaffeinateStatusSegment.localization.test.tsx
  • src/renderer/src/components/status-bar/CaffeinateStatusSegment.tsx
  • src/renderer/src/i18n/en-runtime-required.json
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/store/slices/ui/ui-slice-contract-preferences.ts
  • src/renderer/src/store/slices/ui/ui-slice-surface-actions.ts
  • src/shared/default-global-settings.ts
  • src/shared/global-settings-types.ts
  • src/shared/sleepy-mode-settings.test.ts
  • src/shared/sleepy-mode-settings.ts
  • tests/e2e/sleepy-mode.spec.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment thread src/renderer/src/components/sleepy-mode/SleepyModeOverlay.tsx Outdated
Comment thread src/renderer/src/components/sleepy-mode/use-sleepy-mode-idle-trigger.ts Outdated

@pullfrog pullfrog Bot left a comment

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.

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

  • SleepyModeOverlay scene + idle trigger — a new full-window resting screen (clock, date, live fleet line, pet) that auto-starts after sleepyModeIdleMinutes of renderer input and wakes on the first key/pointerdown/wheel.
  • PetSprite extraction — sprite/canvas rendering, the bob keyframes, usePetUrl, and the document-visibility gate move out of PetOverlay into a shared component; the corner overlay keeps its positioning/drag/persistence and unchanged DOM. The blob cache retain is refcounted, so the second usePetUrl caller 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 — summarizeSleepyModeFleet counts fresh explicit agent statuses and ignores stale rows and monitoring turns.
  • Transient store flag — sleepyModeActive is 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

  • SleepyModeOverlay formats both clock and date with Intl.DateTimeFormat(undefined, …), so the weekday/month names follow the OS locale instead of the selected UI language. The app already resolves this through getIntlLocale() (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.)

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/renderer/src/components/sleepy-mode/use-sleepy-mode-idle-trigger.ts Outdated
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.
@DannyHo15

Copy link
Copy Markdown
Author

Thanks — both functional findings were real and are fixed in 78364c3. Replies are on the individual threads; the short version:

  • Wake event reaching the workspace — taken as suggested, plus passive: false so the wheel path can actually cancel.
  • Countdown missing real activity — taken, but not via capture: true. That fixes xterm and Monaco and still misses browser panes, which are <webview>s in their own process and emit nothing in the host document at any phase. The trigger now samples powerMonitor.getSystemIdleTime() instead, so every surface counts, and an unmeasurable clock reads as unknown rather than idle.

On the docstring coverage check (27.27% against an 80% threshold): leaving this one as is, deliberately. This repo's AGENTS.md asks for the opposite — "Concise/Brief Non-obvious Comments ONLY — DO NOT: be verbose, explain the obvious, walk through the code ('WHY not HOW'). BE CONCISE. 1 LINE if possible." Raising the ratio would mean adding prose to things like useClock, FleetLine and the sprite-frame components that restates their names, which is exactly what the house rule rejects.

What the diff does carry is a short Why: comment or doc block on every decision a reader cannot recover from the code: why the wake listeners run in the capture phase, why the trigger reads the OS clock instead of DOM events, why null must mean unknown, why the scene sits at z-[95], why the light and dark backgrounds run in opposite directions, and why the scene's pet is not behind the experimentalPet flag. A large share of the 33 functions counted here are also pre-existing PetOverlay internals that this PR only moved into PetSprite, unchanged.

Happy to revisit if a maintainer would rather have the coverage number.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 81c5e2af-bd72-42e5-956e-f111c907de73

📥 Commits

Reviewing files that changed from the base of the PR and between a1e1f7a and 78364c3.

📒 Files selected for processing (9)
  • src/main/ipc/settings.ts
  • src/main/system-idle-seconds.test.ts
  • src/main/system-idle-seconds.ts
  • src/preload/api/agent-awake-bridge.ts
  • src/preload/api/agent-status-api.ts
  • src/renderer/src/components/sleepy-mode/SleepyModeOverlay.test.tsx
  • src/renderer/src/components/sleepy-mode/SleepyModeOverlay.tsx
  • src/renderer/src/components/sleepy-mode/use-sleepy-mode-idle-trigger.ts
  • src/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.

Comment thread src/renderer/src/components/sleepy-mode/SleepyModeOverlay.test.tsx Outdated
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.

@pullfrog pullfrog Bot left a comment

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.

ℹ️ 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 — useSleepyModeIdleTrigger polls window.api.agentAwake.getSystemIdleSeconds() every min(15s, delayMs) instead of registering keydown/pointerdown/pointermove/wheel/touchstart listeners, so terminal/editor stopPropagation and out-of-process webviews can no longer let the scene cover an active session.
  • Wake input is consumed — the scene's keydown/pointerdown/wheel capture handler now calls preventDefault() and stopImmediatePropagation() (with passive: false), so the dismissing key no longer leaks into the terminal or editor underneath.
  • New main-process surface — readSystemIdleSeconds() wraps powerMonitor.getSystemIdleTime() (non-finite/negative/throw → null), exposed over a new agentAwake:getSystemIdleSeconds IPC channel and preload bridge, with null stubbed 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; readSystemIdleSeconds has 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?

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using 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 {

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.

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.

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 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 win

Add wake-path coverage for pointer and scroll input.

The changed unit test and E2E test exercise only keydown. SleepyModeOverlay also wakes on pointerdown and wheel, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 408bc83 and db33d96.

📒 Files selected for processing (5)
  • src/renderer/src/assets/main.css
  • src/renderer/src/i18n/en-runtime-required.json
  • src/renderer/src/i18n/locales/en.json
  • src/shared/default-global-settings.ts
  • src/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.
@DannyHo15

Copy link
Copy Markdown
Author

Fair — only keydown was asserted, so a regression in either of the other two listeners passed. Added in d871a3e.

The case is now table-driven over keydown, pointerdown and wheel, and each row asserts all three things: the scene is dismissed, the event comes back defaultPrevented, and a listener on the element it was dispatched at never sees it.

Checked the rows are load-bearing rather than assuming it: deleting the pointerdown registration fails the pointer row, deleting the wheel one fails the wheel row, and both pass again once restored.

One limitation worth stating rather than implying otherwise: this runs in happy-dom, which does not model Chromium's passive-by-default rule for window wheel listeners. So the wheel row proves the wake path is wired and consumed, but it is not what proves passive: false matters — only a real browser shows that, and the E2E covers the keyboard path there.

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
src/renderer/src/components/sleepy-mode/SleepyModeOverlay.test.tsx (1)

142-172: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Protect the omitted passive: false regression.

The happy-dom test does not model Chromium’s passive default for wheel listeners on window. If the option is removed, the test can still pass because happy-dom treats an omitted option as non-passive. Chromium can then ignore preventDefault(), 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

📥 Commits

Reviewing files that changed from the base of the PR and between db33d96 and d871a3e.

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

This branch has not been deployed

No deployments
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.

[Feature]: Sleepy Mode — a resting screen for a fleet left running

1 participant