Focus mode
Two independent features share this name. The headline one batches Omniscio's noisy inbox alerts into a single interruption when a focus window ends; the older one is a view-only sidebar collapse. What each does, and what they deliberately leave alone.
What it is
Two distinct features share the name "Focus mode" in Omniscio. They are independent and can be used together. The batch-alerts feature (this page's primary subject) is the headline; the older session-list filter (Shift+F) is a separate, view-only sidebar collapse described at the bottom.
Focus Mode (batch alerts) is a backend gate that batches noisy inbox notifications. Without Focus Mode on, every Needs-You session, SMS, daily digest, cron approval, automation approval, recipe approval, and CLI Pending row fires its own toast/sound/badge as soon as it arrives — that's interruption-per-item. With Focus Mode on, those alerts queue up and only fire as a single batch alert when a configurable rule trips (count threshold, time threshold, or both).
The rule shape is a (count, minutes, operator) triple, scoped per project (with a 'global' system default). Examples:
count=4, minutes=null, operator=OR→ fire when 4 items have queued (no time floor).count=null, minutes=30, operator=OR→ fire when the oldest queued item has waited 30 minutes.count=4, minutes=30, operator=OR→ fire when EITHER condition trips first.count=4, minutes=30, operator=AND→ fire only when BOTH are met (4+ items AND oldest is 30+ min old).
While Focus Mode is on, the amber Needs You dot in the sidebar and inbox is also suppressed for sessions that are not whitelisted, so the sidebar visually quiets down. Sessions in pierce states (error, auth_error, api_error) always bypass the gate and fire individually — Focus Mode never silences a real failure. An auto-recovering session — a rate_limited one, or a suspended one being auto-resumed after an app restart (while your auto-resume setting is on) — is the opposite case: it's suppressed on every surface (Focus Mode or not) because it resolves itself without you — see session-stuck-in-needs-you.md.
Where to find it
How to use it
Turn it on / off
There is a bell-icon pill in the top toolbar. It is a pinnable toolbar item (pinned by default) — like every other toolbar icon you can right-click it to unpin, move it into the "…" overflow menu, or reorder it, so its exact position is whatever you've set it to rather than a fixed slot:
- Off: outline
Bellicon. Tooltip "Focus Mode (off)". - Armed: filled
BellMinusicon in the accent color. Tooltip "Focus Mode — armed, alerts hidden". - Tripped: filled
BellRingicon in amber. Tooltip "Focus Mode — items released until inbox clears".
Click the pill to toggle. The same toggle is mirrored at Settings → Notifications → Focus Mode (block 1), and voice commands can flip it too.
On a phone there is no pill: the tools menu (grid icon ▦ on the mobile tab bar) has a Focus Mode row. Because nothing on the phone shows the state once the menu closes, the row confirms with a short "Focus Mode on" / "Focus Mode off" message, or "Could not change Focus Mode" when the change did not go through.
When Focus Mode flips on, Omniscio stamps a per-project "starting line" (an ISO timestamp called startedAt) and seeds a per-project last-batch cookie for every project that already has queued items. Pre-existing items do not count toward the next batch — only items that arrive AFTER you turned Focus Mode on contribute to the count and oldest-age computation. This is so flipping the toggle on doesn't immediately fire a batch for everything that was already sitting in your inbox.
Edit the rule from the header (popover)
The bell pill has a small chevron next to it (ChevronDown icon, aria-label="Focus Mode settings"). Two gestures open a FocusModePopover anchored under the bell, so you can edit the rule without leaving the dashboard:
- Long-press the bell (touch surfaces) — 500 ms hold opens the popover; the synthetic click that follows touchend is suppressed so Focus Mode doesn't toggle on release.
- Click the chevron — left-click. The chevron rotates 180° while the popover is open (
aria-expanded={popoverOpen}).
Left-click on the bell itself still toggles Focus Mode on/off — preserved unchanged. Right-click is intentionally NOT a popover gesture. Because the bell is now a pinnable toolbar item, right-clicking it opens Omniscio's toolbar item-config menu (pin / unpin / move to the "…" overflow / reorder) — the same context menu every toolbar icon shares, and the only way to unpin Focus Mode. Use the chevron or a long-press to reach the rule popover.
The popover contains the same controls as Settings → Notifications → Focus Mode in compact form:
- Master enable toggle — same as the bell pill / Settings.
- Hard-kill switch ("Enable Focus Mode feature") — when off, hides the bell pill and short-circuits the service. Same as Settings.
- Global rule editor — count threshold + minutes threshold + AND/OR operator, debounce-saving 500 ms after the last change. Same validation rules as Settings.
- Footer link — "Open full settings →" deep-links to Settings → Notifications → Focus Mode for the whitelist + per-project rules.
The popover is React.lazy()-loaded in every caller so its weight stays out of the toolbar's entry chunk.
Edit the rule
Settings → Notifications → Focus Mode has four blocks:
Master enable toggle — same as the toolbar pill.
Hard-kill switch ("Enable Focus Mode feature") — when this is off, the entire Focus Mode subsystem no-ops: the bell pill is hidden from the toolbar, alerts always fire individually, and the service skips evaluation. Use this if you want to permanently disable the feature without deleting your rule. Default is on. When off, the Settings page also hides blocks 3 + 4 below (the rule editor + whitelist — config for a disabled feature); only the two toggles remain, so config stays invisible until the feature is on.
Global rule editor — three controls:
- Count threshold (sessions) — number 1–1000, or blank for "no count threshold".
- Time threshold (minutes) — number 1–1440, or blank for "no time threshold".
- Operator — segmented
AND/ORradio.ANDrequires both thresholds to be set;ORrequires at least one.
Edits debounce-save 500 ms after the last change. Invalid combinations (OR with both thresholds blank, AND with either blank) display a red error and the save is held until you correct it.
Whitelisted sessions — list of sessions you've explicitly opted to surface even while Focus Mode is on. Whitelisted sessions:
- Continue to fire alerts individually (their decision is
fire-immediately/whitelist). - Continue to show the amber Needs-You dot in the sidebar/inbox.
Each row has an inline remove (×) button that takes the session off the whitelist (via
removeSessionFromWhitelist); the removal is reversible — re-add by pinning the session through Focus Mode again. Adding entries via "Pin a session through Focus Mode" is wired in the backend but the in-app entry point is shipping in a follow-up.- Continue to fire alerts individually (their decision is
What the batch alert looks like
When the rule trips, Omniscio emits a single toast in the Attention alerts category that reads 4 items ready (count) (or (time) / (count+time) depending on which threshold fired). The batch toast bypasses the global Focus-Mode toast filter via a one-shot flag, so it surfaces even though regular attention toasts are being silenced. Click the toast to jump into the inbox and triage.
How it behaves
What it does NOT do
- It does not pause sessions. Sessions keep running, output keeps streaming. Focus Mode only changes when notifications fire.
- It does not affect Settings → Notifications → Silence until. That global silence still wins; Focus Mode is layered on top.
- It does not block pierce-state alerts. Errors, auth errors, API errors, and rate-limited sessions always fire immediately — Focus Mode never silences a real failure.
- It is not the session-list filter. Pressing Shift+F toggles the older view-only session-list filter (described below; bare F stopped doing this on 2026-09-28); the bell pill is a separate, persistent backend gate.
- No per-project rule editor in v1. You can edit the global rule. Per-project overrides are stored in the schema (
focus_mode_rulesper-row) but the UI to author them is not in v1; everything routes to the global default.
Inbox-hide overlay (v1.5)
v1.5 layers a visible "armed" state on top of the v1 batch-alerts gate. The original v1 changed only when notifications fired — the sidebar still showed amber dots and the unified inbox still rendered every queued item, so an interruption-prone user could still be visually pulled in. v1.5 adds a sidebar-and-inbox-wide "alerts hidden" treatment that stays visually quiet until the rule trips, then lights up to amber and releases everything until the inbox empties.
State machine
The toolbar pill, sidebar, project rows, and unified inbox all read from one store flag pair: enabled (you turned Focus Mode on) and revealed (the rule has tripped at least once and the inbox has not yet drained). The combination produces three visible states:
| State | enabled |
revealed |
What the user sees |
|---|---|---|---|
| A — off | false |
(n/a) | Everything renders normally. No banner, no overlay, dots and badges visible. The pill is a plain ghost Bell icon. |
| B — armed | true |
false |
Toolbar tinted accent. Pill filled accent (BellMinus icon, tooltip "Focus Mode — armed, alerts hidden"). Sidebar banner reads "Focus Mode — N waiting" in accent. Unified inbox is replaced by a "Focus Mode — alerts hidden / Click and hold to peek" overlay. Sidebar dots and project badges are suppressed. |
| C — tripped | true |
true |
Toolbar tinted amber. Pill filled amber (BellRing icon, tooltip "Focus Mode — items released until inbox clears"). Banner colours flip to amber and read about released items. The inbox overlay is unmounted, so the unified inbox renders normally. Sidebar dots and project badges return. |
On-state visibility layers
When Focus Mode is on (states B and C), three coordinated tints make the state legible at a glance:
- Toolbar tint — a subtle background tint behind the toolbar (
bg-accent/10-ish in armed,bg-amber-500/10in tripped). Same tone as the pill, just much lighter. - Pill bg-fill — the FocusModeToggle pill fills with
bg-accentwhile armed andbg-amber-500while tripped. The icon flips between three lucide bells:Bell(off, ghost),BellMinus(armed, alerts batched silently),BellRing(tripped, batch released). - Sidebar banner — FocusModeStatusBanner sits at the top of the sessions sidebar with the same accent-then-amber colour story. Armed copy is "Focus Mode — N waiting"; tripped copy describes the released-items state.
Accent for "armed and quiet" matches Omniscio's neutral-active colour. Amber for "tripped" matches the Needs You colour Omniscio uses everywhere else for "look at this now" — so when Focus Mode releases items, the entire stripe (toolbar, pill, banner) shifts from quiet to needs-you in lockstep.
Inbox overlay and click-and-hold peek
In state B, the unified inbox section is replaced by FocusModeInboxOverlay — a vertically-centered EmptyState (wrapped in h-full flex items-center justify-center) with a BellMinus icon in the accent colour and an accent-coloured "Focus Mode — alerts hidden" title, plus the description "Click and hold to peek". The queued items still exist in the store and still render behind the overlay; they're just covered by the overlay's surface.
Pressing and holding (mouse down, finger down) on the overlay temporarily swaps the EmptyState branch out (CSS hidden, removing it from layout) and the held branch wraps the queued items in a block container, letting the user peek at what's behind it. Releasing the press returns the overlay. Peek is CSS-only — revealed does NOT flip during the press, so peeking does not start the "released until empty" cycle. The user can sneak a look without committing the inbox to its loud state.
In state C the overlay is not mounted at all. The user has already been interrupted by the batch-alert toast at this point, so hiding the inbox would be working against them — items are visible normally until the inbox drains.
Re-arm cycle
The system returns to armed automatically once the inbox empties:
- Focus Mode is on, items queue, the rule trips.
revealedbecomestrue(set by the renderer when it receives theFOCUS_BATCH_FIREDpush fromfocusModeService.evaluate()'sfire-batchdecision — see the "How it works (technical)" section above) → state C. Items are visible. - The user archives, snoozes, or handles each item.
- When the unified inbox attention count reaches zero, useFocusModeRevealReset resets
revealedtofalse. State C → state B. - The toolbar tint, pill, and banner shift accent → amber → accent without a click. The overlay remounts.
The reset is a consumer hook reading useInboxAttentionCount(). It lives in the renderer hook layer rather than the store so focus-mode-store stays a pure CRUD producer with no cross-store reads — a producer-versus-consumer split Omniscio enforces across all approval and inbox stores.
Sidebar suppression
While armed (state B), seven sidebar / chrome surfaces drop their alerting cues:
- Session row dots — SidebarSessionRow and UnifiedInboxRow receive
focusSuppressDot={true}and stop rendering the amber Needs-You dot. - Project count badges — ProjectListItem receives
suppressAlerts={true}and hides its attention / running / Gmail-unread / channel-unread count badges. - Projects sidebar Inbox row badge — the amber pill rendering the unified-inbox attention count next to the Inbox row in ProjectsSidebar is gated on
!focusArmed. The row'saria-labelalso drops the, N needing attentionfragment when armed so screen-reader users see the same quiet treatment as visual users. The Inbox label, icon, and active-highlight all stay in place — only the count pill disappears. - Title-bar attention count — the bold accent number to the right of the Omniscio favicon in CustomTitlebar, plus its trailing
|separator, are gated on!focusArmed. The favicon-button itself remains clickable (so the "Go to inbox" affordance is still discoverable); only the numeric count and divider hide. This number is the UNIFIED inbox count — the SAME value the Projects-sidebar Inbox badge and the Mobile tab badge show, computed once in App (useInboxAttentionCount) and threaded toCustomTitlebaras a prop, so the header can never disagree with the inbox. (2026-08-02: previously the title-bar computed its OWN sessions-only count viacountSessionsNeedingAttention, so it under-counted whenever a non-session inbox item existed — the "header 60 vs inbox 62" bug; see inbox-count-list-parity-contract.md.) - Sessions sidebar Inbox column header count — the
(N)count in the "Inbox (N)" column header inside SessionsSidebar is dropped while armed (the header reads justInbox). Filter and search action buttons on the right stay visible; only the parenthesised count disappears. - Mobile tab bar attention badge — the amber count pill on the Inbox tab in MobileTabBar is gated on
!focusArmed && attentionCount > 0. The tab itself stays clickable so users can still navigate to the inbox; only the alerting count badge hides. - Mobile session + project list rows — on the phone, MobileSessionRow receives
focusSuppressDot={true}(hides its amber Needs-You dot) and MobileProjectRow receivessuppressAlerts={true}(hides its attention / running / Gmail-unread / SMS-unread count badges) — the mobile mirror of the desktopSidebarSessionRow/ProjectListItemsuppression above.MobileSessionsList/MobileProjectsListderivefocusArmed = enabled && !revealedand pass it down. Wired 2026-07-06 — previously desktop-only, so an armed phone still showed dots + badges.
The active-highlight nav state stays in all cases — selected projects, selected sessions, and the inbox row's selection background still show their highlight; only the alerting cues disappear. Whitelisted sessions are a v1 concept and remain unaffected here: their pierce-decision still fires individual alerts, separate from the inbox-hide overlay.
In state C (tripped), focusSuppressDot, suppressAlerts, and the two !focusArmed gates above flip back to false — dots, badges, and counts return so the user can see what's waiting while the inbox is in "released" mode.
Main-panel mirror
While armed AND the unified inbox is the active view, the main panel ALSO suppresses the active session — replacing it with a Focus-Mode-specific empty state instead of mounting SessionPanel with the previously-selected session's chat content. Without this mirror the sidebar correctly hides its inbox list (via FocusModeInboxOverlay) but the main panel keeps showing a needs-you session's transcript, defeating the whole "hide the alerts" intent.
The gate lives in stores/session-navigation.ts as isActiveSessionInboxBlocked({ isInboxSelected, focusArmed, activeSession, visibilityState }). It returns true (blank the main panel) when either focus is armed or the session is filtered out of the canonical inbox right now (snoozed / grace-period / overlay-pending — the existing 2026-05-02 orphan-session rule). Dashboard.tsx calls the helper to derive displayedActiveSession / displayedActiveSessionId, which shadow the raw activeSession everywhere downstream.
When the main panel blanks AND focusArmed is true, the inbox empty-state branch in Dashboard.tsx renders a "Focus Mode" empty state rather than the green "All clear" success state — a BellMinus icon in the accent colour, an accent-coloured "Focus Mode" title, and the description "Alerts hidden until inbox clears". Both empty-state sites in Dashboard (mobile and desktop) are gated on focusArmed and centered with flex items-center justify-center h-full so the visual matches the sidebar overlay's vertical centering. When focusArmed is false, the existing green "All clear" state still wins.
In state C (tripped), focusArmed is false, so the main panel returns to normal even before the user clicks an item.
Project views and other non-inbox surfaces are unaffected — focus armed only blanks the inbox main pane, never a project's session list. Verified by tests/unit/stores/inbox-visibility-predicate.test.ts "isActiveSessionInboxBlocked — contract".
Hard-kill behaviour (unchanged)
The focusModeFeatureEnabled setting still acts as a hard-kill switch. When off, every state-machine path short-circuits: the toolbar pill renders nothing, the banner does not mount, the inbox overlay never appears, and useFocusModeRevealReset is a no-op. v1.5 layers on top of v1's existing kill switch — there is no second flag to remember.
Where it lives in code
- Store contract — src/renderer/src/stores/focus-mode-store.ts (
enabled,revealed, plus actions; pure CRUD) - Reset hook — src/renderer/src/hooks/useFocusModeRevealReset.ts (consumer-side cross-store read)
- Toolbar pill — src/renderer/src/features/toolbar/FocusModeToggle.tsx
- Toolbar registry entry — the
focus-modeid in src/renderer/src/features/toolbar/toolbar-items.ts (visibleWhen: focusModeFeatureEnabled !== false), rendered through thecase 'focus-mode'branch of ToolbarPinnedItem.tsx. It is no longer a hardcoded chip in AppToolbar.tsx; existing users get it spliced into their pinned list by a one-time migration (burst-focus-pin-migration.ts, gated byburstFocusPinMigrationCompleted). - Header popover — src/renderer/src/features/toolbar/FocusModePopover.tsx (anchored under bell, opens on long-press / chevron — right-click is reserved for the toolbar pin/unpin menu)
- Rule editor (shared) — src/renderer/src/features/toolbar/FocusModeRuleEditor.tsx (count + minutes + AND/OR; consumed by both the popover and the FocusModeSettings panel)
- Long-press hook — src/renderer/src/hooks/useLongPress.ts (500 ms timer; suppresses synthetic click on touchend)
- Sidebar banner — src/renderer/src/features/dashboard/FocusModeStatusBanner.tsx
- Inbox overlay — src/renderer/src/features/dashboard/FocusModeInboxOverlay.tsx
- Sidebar wiring — src/renderer/src/features/dashboard/SessionsSidebar.tsx and src/renderer/src/features/dashboard/ProjectsSidebar.tsx
- Title-bar count suppression — src/renderer/src/components/ui/CustomTitlebar.tsx (reads
useFocusModeStoredirectly with primitive selectors) - Suppression-prop consumers — ProjectListItem.tsx, SidebarSessionRow.tsx, UnifiedInboxRow.tsx
- Main-panel mirror gate —
isActiveSessionInboxBlockedcalled from Dashboard.tsx
See also
- docs/plans/2026-05-07-focus-mode-inbox-hide-design.md — full design doc for v1.5: state-machine rationale, peek-without-reveal decision, accent/amber colour split, sidebar-suppression scope.
Pomodoro integration (auto-enable during focus blocks)
A pomodoro run can auto-flip Focus Mode on while you're in a focus block and
off during breaks — so notifications batch while you're heads-down and
release the moment a break starts. Controlled at
Settings → Notifications → Alarms → Auto-Enable Focus Mode During Focus Blocks
(pomodoroAutoEnableFocusModeDefault, default on) with a per-preset
tri-state override (null inherit / true always / false never).
Manual-toggle protection: if you click the bell icon during a managed run, Pomodoro stops touching Focus Mode for the rest of that run — your click is a deliberate statement and shouldn't get overridden on the next phase change. The next run starts fresh.
The wiring goes both ways via an in-process change-listener (not a push):
every focus-mode state change carries a source field
(FocusModeChangeSource = 'manual' | 'pomodoro' | 'startup' | 'system' in
src/main/services/focus/focus-mode-service.ts).
The bell-icon click chokepoint
(src/main/ipc/focus-mode-handlers.ts)
toggles with the default source: 'manual', and the Pomodoro service —
subscribed to focusModeService's change-listener — clears
pomodoroOwnsFocusMode whenever it sees a change with source !== 'pomodoro'.
Programmatic enable/disable from the Pomodoro service itself carries
source: 'pomodoro', so the service never accidentally locks itself out. Full
per-phase / pause / crash behavior:
pomodoro.md § Focus Mode integration.
Related
Focus mode (part 2) — the continuation of this page. A separate, older feature also called "Focus mode" is the Shift+F sidebar filter:
Press Shift+F (with the dashboard focused — a chord fires even while typing, but the bare letter this used to be did not) and the hub's session list hides every session that isn't waiting on you: running, paused and interrupted sessions leave the list, while Needs You, pinned sessions and the Snoozed / Scheduled / Saved / Archived shelves stay. A pop-up confirms it: "Focus mode on: running sessions are hidden from the session list".
It is never silent. While it's on, a banner is pinned at the top of the sessions sidebar — above the scrolling list, so it stays on screen at every scroll position — reading "Focus mode is hiding sessions that don't need you." with a Show all button that turns the filter off, and the Live Sessions heading still counts the running sessions it is hiding (so it matches the running count on the hub's row in the left rail). The button's tooltip shows the current key.
Press Shift+F again, or click Show all, to restore the full list.
Why the chord and the banner. Until 2026-09-28 this was a bare F, with the only readout a muted line INSIDE the list. A user pressed F by accident, read the pop-up as the toolbar bell's alert batching, and worked for days beside a hub whose list read "Live Sessions (0)" while its badge said 1. A bare letter could empty a whole hub; the filter has no control anywhere that turns it back ON, so the one thing that says a hub is filtered must not scroll away.
Ctrl+Shift+Fwas not available (Team Chat's message search) and the rest of the Ctrl+Shift band belongs to OS-wide hotkeys, so the deliberate modifier is Shift.It's view-only — does not pause, archive, or batch anything. Hidden sessions still run, still receive output, still fire notifications (the new batch-alerts feature above is what gates notifications).
Not persisted across app restarts. Resets to off when Omniscio starts. Lives in renderer state as the session store's
focusModeFilter.Only the main hub sidebar applies it — the session lists inside integrations (KMS, SMS, Mind Maps, …) always show their running sessions.
The keybinding id is
toggleFocusModein core.keybindings.ts (default['Shift+F'], categoryglobal,passthroughInputs: false, label "Focus mode (session list)"). Rebind via Settings → Keyboard Shortcuts. The one-handed layout preset deliberately does NOT touch it — it clears nothing about the filter, so the chord survives that layout.Voice commands can also flip it via the session store's
toggleFocusModeFilter(), and so can the CLI'sPOST /ui/openwith targetfocus-filter.
This older feature and the new batch-alerts feature are independent — toggling one does not affect the other. The new toolbar bell pill controls only the batch-alerts feature, and its tooltip shows no key; Shift+F controls only the sidebar filter.
Note: this is also unrelated to the CLI control server's /focus HTTP endpoint, which simply brings the Omniscio window to the front. That endpoint is described in tray-and-window.md.
- keyboard-shortcuts.md — Shift+F is in the global category; rebind via Settings → Keyboard Shortcuts
- notifications-and-silence.md — silence notifications globally instead of (or in addition to) batching them
- inbox-overview.md — the inbox surfaces all the items that flow through Focus Mode's evaluation
- tray-and-window.md — the unrelated
/focusCLI endpoint that focuses the window - pomodoro.md — the paired feature that drives the auto-enable hookup
Last verified 2026-10-02