---
title: Instant new session (Ctrl+T fast path)
---

# Instant new session

## What it is

### What it does

Pressing **Ctrl+T** (or **N**) to start a new Claude Code session in the current project now feels instant — the empty session panel appears and the composer textarea is focused in the same frame you press the key, with no spinner, no "Starting…" flicker, and no round-trip to the main process before the panel paints.

It works by quietly preparing one real, empty session ahead of time. While you're working in a project, Omniscio keeps a single genuine blank session ready in the background — a real database row with a real CLI process, not a fake placeholder — and keeps its panel pre-rendered off-screen. When you hit Ctrl+T, Omniscio just reveals that already-prepared panel. The work that used to happen _after_ you pressed the key (create the row, spawn the process, mount the panel, attach the textarea) has already happened _before_ you pressed it.

If no prepared blank is available for any reason — you've already started typing in it, the kill switch is set, the project can't be pre-warmed, or the background prep hasn't finished yet — Ctrl+T silently falls through to the normal launch path. You get exactly the behavior you had before: a brief spawn, then the panel. Nothing breaks; it's just not as fast that one time.

## Where to find it

There is nothing to open — press **Ctrl+T** (or **N**) in the current project and the new session is simply there, text box focused. It is on by default and there is no setting to enable it.

## How it behaves

### What the user sees

- **Ctrl+T / N is immediate.** The new blank session is active and the cursor is in the composer before you've finished lifting your finger off the key. You can start typing your first message with zero perceptible delay.
- **No duplicate empty sessions.** Mashing Ctrl+T five times in a row does not create five empty sessions. The first press reveals the prepared blank; the rest land on that same session (the background prep is idempotent — it reuses an existing blank instead of minting a new one).
- **Your typed text is never lost.** If you've started typing a message into the blank session and then press Ctrl+T again, Omniscio will _not_ swap you onto a different blank or reset your draft. The fast path deliberately refuses any blank that has draft text in it, so a half-written message is safe. (This is a direct defense against a historical bug — see "The two regressions it avoids" below.)
- **The session is real from the first frame.** There's no moment where a fake/temporary session shows up and then gets replaced by "the real one." What you switch into on Ctrl+T is the real session, with a real ID, that was created in the background — so there's no flicker, no re-mount, and no risk of your first keystrokes landing on a placeholder that's about to be thrown away.
- **It only ever opens a session Omniscio prepared for you.** A session that something else created — an agent spawning work through the CLI, a bug-intake auto-spawn, a wave of audit fixes — is never a candidate, even while it is still empty and waiting for its first message. That distinction is not cosmetic: such a session can sit empty for a while, because its prompt waits behind worktree setup, so "looks empty at this moment" is not the same as "yours to start".
- **It's scoped to the project you're in.** The prepared blank belongs to the active project. Switch to a different project and Omniscio prepares a blank for _that_ project (after a short debounce), so Ctrl+T is fast in whichever project you're currently looking at.

### How pre-warmed sessions look in the sidebar

A pre-warmed blank looks like **any other new session** — it gets the same auto-numbered **"Session N"** name (e.g. "Session 42") a session gets when you create one yourself, and it isn't badged or specially marked. It simply sits ready at the top of the project's list. The fast path recognizes it **structurally** (an empty `ready` session), never by its name, so the ordinary numbered name costs it nothing, and its sidebar status dot is a plain solid dot like every other session's.

Once you press Ctrl+T and start typing, your first message triggers the usual automatic title generation, so "Session N" is replaced by a real title describing your conversation (exactly like any other new session). If you use numbered names instead of AI titles, the blank simply keeps its "Session N" — already the correct final name, nothing to replace. The recognizer that lets the auto-titler rename a placeholder blank lives in [session-placeholder-name.ts](../../src/shared/session-placeholder-name.ts) — see [session-placeholder-name-contract.md](../../.claude/memory/contracts/session-placeholder-name-contract.md).

> Blanks were briefly (2026-06-11 → 2026-07-01) labeled **"Session Ready"** with a pulsing dot; that name stranded permanently whenever an AI title never landed (always, in numbered-name mode), so it was reverted to the plain numbered default. The "Session Ready" recognizer is kept only so any blank still saved with the old name gets re-titled.

### Turning it off

The pre-warm is on by default. If you'd rather not have Omniscio create sessions in advance — for example, if you're running close to the session cap and want tighter control — you can turn it off in **Settings → Performance → "Pre-load new sessions."** When off, Ctrl+T works exactly as it did before this feature: a normal session spawn (~700 ms), no background preparation, no pre-created blank waiting in the sidebar.

There's also a developer-level environment variable kill switch (`AMC_DISABLE_INSTANT_NEW_SESSION=1`) that force-disables both the background pre-warm and the Ctrl+T fast path regardless of the setting. The setting and the kill switch are independent gates — either one being off disables the feature.

### The two regressions it avoids

This feature exists in the shadow of two specific bugs from Omniscio's history. Both were the reason a naive "make Ctrl+T instant" attempt was dangerous, and the design is shaped around never reintroducing them.

1. **The phantom session.** An earlier fast-new-session attempt showed a _fake_ session immediately (a temporary client-side placeholder) and then swapped in the real session once the backend caught up. The user's words: _"It would only be a fake thing at the start, and then the real thing would appear later on."_ That swap caused flicker, lost focus, and a window where keystrokes could land on the soon-to-be-discarded placeholder. **This design never uses a placeholder.** The blank that Ctrl+T reveals is a genuine `sessions` row, created in the database by the background pre-warm before you ever press the key. There is nothing to swap — the "fast" session and the "real" session are the same object.

2. **The typed-text overwrite.** A second historical bug _"sometimes it would overwrite the user's message that they'd already started typing."_ If you'd begun composing a message and then triggered a new-session action, the draft could get clobbered. **This design refuses to touch any blank with a draft.** The "is there a typed draft?" check lives in the renderer's store layer (where drafts actually live), and a blank that has any draft text is excluded from the fast path — so Ctrl+T can never land you on, or reset, a session you're mid-way through typing into.

### What didn't change

- **The "+" button.** Clicking the **+** icon in the Sessions panel header still runs the normal `launchSession()` path. The instant fast path is wired to the keyboard action only. (The + button still benefits from the backend's existing `findBlankSession()` reuse, so it also won't create duplicate blanks — that behavior predates this feature.)
- **Blank-session reuse on the slow path.** The backend's `findBlankSession()` dedupe (don't create a second empty session when one already exists) is unchanged and still runs on the `SESSION_LAUNCH` path. The pre-warm uses the _same_ helper, so the two paths agree on what counts as a reusable blank.
- **openclaw / codex / gemini / antigravity / opencode.** Those projects are never pre-warmed and always take the normal launch path on Ctrl+T. **DeepSeek and Kimi projects ARE pre-warmed** — they run the Claude binary, which is what the gate actually tests.
- **Mobile.** Mobile gets the instant reveal too — and as of 2026-06-20 the _panel_ is pre-built there as well. The background **pre-warm** (the blank `ready` row) runs on mobile, and all three mobile new-session controls — the footer **New Session** button, the inbox-group **+**, and the breadcrumb **+** — route through ONE shared action (`startNewSessionMobile` in [launch-and-reveal-session.ts](../../src/renderer/src/features/dashboard/launch-and-reveal-session.ts)) and so hit the fast path (`tryInstantNewSession`) exactly like desktop Ctrl+T (skipping it on mobile was a bug fixed 2026-06-17; the breadcrumb **+** was the LAST to be unified, 2026-06-23 — it previously called `launchSession` directly and only navigated on a non-null return, so a tunnel-timeout left the tap dead, no navigate and no toast). What used to stay desktop-only was the _panel_ pre-mount: a phone has no multi-panel keep-alive pool, so the new-session screen was rebuilt from scratch on every tap (the textarea + chat subtree assembled live inside the slide — the "fast-path activates instantly but the screen still rebuilds" gap). That's now closed by a **mobile warm host**: Omniscio keeps ONE new-session panel mounted heavy in the background (the single-panel mobile counterpart of the desktop P3 render-warm), so tapping New Session _reveals_ a ready composer instead of constructing one. It's flash-free because an empty blank's box is one line tall (a browser can't measure a hidden textarea, so pre-mounting pre-pays the expensive subtree build, not the measurement). So the warm panel is ready even for the most common gesture — open a project and immediately tap **New Session**. As of 2026-06-27 the blank is **reserved and inserted optimistically** the instant you enter a project: the renderer picks the new session's id locally (`safeRandomUUID`) and shows the real blank **synchronously** (a transient "New Session" label until the backend row lands), then confirms it with the backend underneath using that same id — so the warm host has a blank to reveal on the **first tap regardless of network**. That closes the last quick-tap gap: previously the blank waited on a ~quarter-second `SESSION_ENSURE_BLANK` round-trip over the phone tunnel, so a fast tap inside that window fell to the "Starting session…" spinner (the "still not instant on a quick tap" report). It mints no extra sessions (the same one-per-project blank, just shown before the round-trip finishes) and is gated to spawnable Claude projects under the session cap. The backend creates the row with the **same reserved id** (no id-swap — the phantom-session class the postmortems forbid); if the create ever fails it rolls back cleanly (you're never stranded on a removed row), and if another device already minted a blank it adopts that one. Non-Claude / at-cap / rare transient-failure cases still fall back to navigating immediately and revealing the real composer the instant the session activates (the `SESSION_LAUNCHED` push beats the worktree build), keeping you in that session even if the launch request later times out. Gated by the same "Pre-load new sessions" setting; kill switch `AMC_DISABLE_MOBILE_BLANK_PREWARM=1`. See [keep-alive-pool-contract.md](../../.claude/memory/contracts/keep-alive-pool-contract.md) ("Mobile surface — the new-session PANEL is pre-mounted hidden" + the mobile miss path) and [start-a-new-session.md](start-a-new-session.md).
- **Virtual projects and the Inbox.** Ask Omniscio, the Gmail/SMS virtual hosts, and the Inbox can't host a spawnable session, so they're excluded from the pre-warm trigger.

## For agents

### How it works

Three coordinated pieces, plus a kill switch:

### 1. The background pre-warm (idempotent, debounced)

When you switch to a real (non-virtual, non-Inbox) project on desktop, the Dashboard fires a debounced (500 ms) request to _ensure a blank exists_ for that project. This is fire-and-forget — the UI doesn't wait on it.

The request is the `SESSION_ENSURE_BLANK` IPC channel, handled in [session-handlers.ts](../../src/main/ipc/session-handlers.ts). Its contract is _"make sure a blank session exists for this project, but do not activate anything."_ It runs inside the same `withProjectLaunchLock` chokepoint that the normal `SESSION_LAUNCH` no-prompt path uses, so calling it N times yields exactly one blank (idempotent): it calls `findBlankSession()`, returns the existing blank if there is one, and only mints a fresh one (via `createSessionWithPrompt({ initialPrompt: undefined, background: true })`) when none exists. The `background: true` flag is important — it keeps your _current_ active session and project unchanged when the new-session push lands, so the pre-warm never yanks your focus.

**Reviving a settled blank.** A pre-warmed blank settles to `ended` when Omniscio is quit and restarted (or its idle CLI dies). When the pre-warm or the Ctrl+T fallback reuses such a blank, it **revives** it to `ready` via `reviveBlankToReady` — _not_ `updateSessionStatus(id, 'ready')`, which silently no-ops a settled row (its terminal-state guard only lets an `ended` row exit via `'starting'`). That no-op was a real bug: Ctrl+T landed the user on a gray, closed "Session Ready" panel ("Continue the conversation…", absent from Live Sessions) instead of a fresh one. A `ready` blank has no live process (the CLI cold-spawns on the first message), so reviving to `ready` is exactly equivalent to a fresh blank. See [session-status-transition-contract.md](../../.claude/memory/contracts/session-status-transition-contract.md).

**Startup cleanup keeps only a usable blank.** The once-per-startup sweep `purgeStaleBlankSessions` keeps only the newest **`ready`** blank per project and soft-deletes stale `ended`/`error` blanks (even a project's only one) — so closed "Session Ready" rows don't pile up across restarts; the pre-warm mints a fresh one when you next open the project. It only ever touches genuine user blanks (`source = 'ui'`/legacy NULL, not council), so a momentarily-empty programmatic session is never swept.

Pre-warm applies to every provider that runs the Claude binary — plain Claude, the Anthropic-compatible vendors (deepseek, kimi, glm, minimax, meta) and the proxy-backed engines. The gate is `usesClaudeBinary(effectiveProvider)`. If the project's effective provider is one of the bespoke-manager engines (openclaw, codex, gemini, antigravity, opencode), the handler bails with a "not pre-warmable" result and Ctrl+T just uses the normal path — those have heavier readiness checks and their own session managers, so eagerly spawning one for a session you might never open isn't worth it.

The renderer side of this lives in `ensureBlankSession()` in [session-store.ts](../../src/renderer/src/stores/session-store.ts). It debounces per-project (so rapid project switches collapse into one IPC), short-circuits when a blank already exists in renderer state, and — on success — seeds the conversation cache with an empty message array for the new blank. On **mobile** the Dashboard passes `{ eager: true }`, which mints on the next tick instead of after the 500 ms debounce (so the mobile warm panel is ready before a quick New Session tap); it keeps the 500 ms debounce on desktop, and because the trailing timer already mints exactly one blank per distinct project entered, eager only moves that same mint earlier — never an extra one. As of 2026-06-27 the mobile eager path goes one step further for a spawnable Claude project under the session cap: `optimisticallyPrewarmBlank` **reserves the id** (`safeRandomUUID`) and inserts the real `ready` blank row (a transient "New Session" label that the backend row replaces on confirm) + seeds its cache **synchronously**, then fires `SESSION_ENSURE_BLANK` with that `reservedId` (now accepted by the schema → `createSessionWithPrompt` → `createSession`, which uses `reservedId ?? randomUUID()`). When the mint resolves it **confirms** (same id, replace in place — no swap), **reconciles** (the backend reused a different existing blank — adopt it, move the active selection if the user already tapped), or **rolls back** (the rare mint failure — drop the row, restore the prior session). This makes the reveal network-independent — see the "Mobile optimistic pre-warm" section + invariants I16–I19 of the contract. That seed is what makes the blank immediately eligible for the fast path without waiting for a separate history fetch (a true blank has zero messages by definition, so an empty array is the correct, complete history).

**Desktop committed "+ New" MINTS its own placeholder — it is never reconciled away (the anti-takeover guarantee).** The desktop MISS path (`tryOptimisticNewSession`) uses the same optimistic-blank machinery, but the user has EXPLICITLY started a session and is already active on the reserved placeholder — so it passes `dedicated: true` on `SESSION_ENSURE_BLANK`, and the handler MINTS that exact id instead of reusing a pre-existing blank. That means the mint can only **confirm** (same id) — it can never **reconcile** the user off the placeholder they are typing into onto a shared blank that another flow then claims. Without it, under heavy multi-agent load a slow pre-warm dropped "+ New" to this path, the handler reused whatever blank existed, and the reconcile-swap made the user's new session "turn into" an auto-spawned agent session (the report that motivated the fix). The mobile SPECULATIVE pre-warm above OMITS `dedicated`, keeping its dedup reconcile (the user isn't committed to that id yet). See invariant **I19** in the contract + [background-spawn-blank-takeover-postmortem.md](../../.claude/memory/postmortems/background-spawn-blank-takeover-postmortem.md) (recurrence 2).

### 2. The pre-rendered panel (keep-alive pool + render-warm)

A session being _created_ in the background isn't enough — its panel also needs to be already mounted and "heavy" (fully rendered, textarea attached) so that revealing it is a pure CSS show, not a mount. Omniscio's **keep-alive pool** is the mechanism that keeps certain off-screen session panels mounted. The instant-new-session blank joins that pool through two carve-outs:

- **It's pooled even though its status is `ready`.** Normally the keep-alive pool only holds the active session, `needs_you` sessions, and `running`/`starting` sessions; a plain `ready` session would be excluded and mount only on click. The pool adds a dedicated single-element priority class (called **P3**) for the active-project blank so that this one `ready` row stays mounted. See `selectKeepAlivePool()` in [keep-alive-pool.ts](../../src/renderer/src/features/dashboard/keep-alive-pool.ts).
- **Its panel is rendered heavy even before its history is cached.** The background render-warm pump (the same machinery that pre-mounts `needs_you` panels so switching into them is instant) normally waits until a session's history is in the cache before rendering it heavy — rendering before the data lands would paint a _wrong_ empty list. The blank is exempt from that wait, because a blank genuinely has no messages: "rendered before the cache loads" produces the correct empty list, not a wrong one. See `selectWarmHeavyIds()` in the same file.

Both carve-outs are locked by named invariants (**I14** = the blank is an additional render-warm class exempt from the cache gate; **I15** = the blank is pooled despite its `ready` status via the P3 class) in [keep-alive-pool.test.ts](../../tests/unit/features/dashboard/keep-alive-pool.test.ts). The full behavior contract is [keep-alive-pool-contract.md](../../.claude/memory/contracts/keep-alive-pool-contract.md).

### 3. The Ctrl+T fast path (synchronous, zero IPC)

The keyboard handler for the `newSession` action lives in [useKeyboardShortcuts.ts](../../src/renderer/src/hooks/useKeyboardShortcuts.ts). Before falling into the normal `launchSession()` flow, it calls `tryInstantNewSession(projectId)` (defined in [session-store.ts](../../src/renderer/src/stores/session-store.ts)):

- On a **hit**, it synchronously activates the prepared blank — bumps it to the head of the session list, sets it as the active session/project, bumps the focus token so the textarea auto-focuses, and returns the session. The keyboard handler then focuses the composer textarea and stops. **No IPC crosses the preload bridge** — every mutation is a local Zustand `setState`. That zero-IPC property is what makes it feel instant, and it's pinned by a lint test.
- On a **miss** (no eligible blank, draft present, kill switch set, openclaw project, etc.), it returns `null` and the handler falls through to the existing `launchSession()` path — identical toasts (session-limit, binary-missing), identical error handling, identical behavior to before this feature existed. It's strictly additive: the slow path is never made slower or different.

The "is this blank eligible?" decision is `selectExistingBlankForProject()` in [session-store.ts](../../src/renderer/src/stores/session-store.ts). It layers the slice-dependent gates that the pure pool predicate can't see:

- **the BACKEND has vetted this session id as a reusable blank** — the gate everything else cannot replace. Only an id the main process answered with (`SESSION_ENSURE_BLANK`'s mint or its reuse), or a reserved id the renderer just minted for this purpose, is ever adoptable, and that confirmation is dropped the moment the session gains a message, leaves the idle statuses, is sent into, or is archived. The renderer's own view is not proof: output for a session you are not looking at is deliberately withheld, so its cached history can look empty for a session that has turns, and a launch push deliberately reports `ready` for a row the database holds as `starting`. See [blank-confirmation.ts](../../src/renderer/src/stores/blank-confirmation.ts);
- the session passes `isWarmableActiveProjectBlank` (active project, `ready`/`starting` status, no scheduled response, not a recipe/pipeline row, a user-creatable `source`, and no deferred opening waiting to fire);
- **it has no typed draft** (the typed-text-overwrite defense — drafts live in a Zustand slice, checked here);
- its history is _known_ to be cached (an `undefined` cache entry means "we don't know yet," which is treated as not-a-blank — never guess);
- the cached history has **zero non-system messages** (a real blank).

The backend's own reuse query ([`findBlankSession`](../../src/main/db/queries-sessions/lifecycle.ts)) carries the matching rules for the slow path — a user-creatable `source`, an **idle** status (`ready`/`ended`/`error`), no deferred launch stash, no draft, no queued send, no playbook lane, and zero non-system messages. Status matters there because every session row is INSERTed `starting` and a launch with a prompt keeps that status, with no persisted message, for the whole worktree-setup phase — so "empty right now" is not the same as "a blank".

If multiple candidates qualify, it picks the most recently active one.

### The kill switch (developer reference)

There are two ways to disable this feature:

1. **User setting**: `preloadSessionsEnabled` in Settings → Performance → "Pre-load new sessions" (see "Turning it off" above). This is the user-facing control.
2. **Environment variable**: `AMC_DISABLE_INSTANT_NEW_SESSION=1` — force-disables regardless of the setting.

With either gate off, both halves shut off together: the Dashboard stops firing the background pre-warm, and `tryInstantNewSession()` always returns `null` so every Ctrl+T takes the legacy `launchSession()` path. There is no half-on state — both gates cover both halves, by design, so you can't end up pre-warming blanks that the fast path then ignores (or vice versa).

The env flag is read once and bridged from the main process to the renderer by the preload (`window.__amcDisableInstantNewSession`), exactly like the `needs_you` render-warm kill switch (`AMC_DISABLE_NEEDS_YOU_WARM`). The renderer reads both gates via `isInstantNewSessionEnabled(settingValue?)` in [instant-new-session-flag.ts](../../src/renderer/src/lib/instant-new-session-flag.ts). The setting `false` disables immediately; a missing/undefined setting defaults to ON. For the env kill switch, only a literal boolean `true` disables — a missing bridge, an empty string, or a malformed value all keep the fast path **on**, so the feature fails safe toward "enabled" rather than silently disabling on a quirk.

### Where it lives in code

| What                                                                                                         | Where                                                                                                                                                                                                                                                                                                                       |
| ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Settings toggle (`preloadSessionsEnabled`)                                                                   | [src/shared/types/settings/chat-ui-settings.ts](../../src/shared/types/settings/chat-ui-settings.ts) (type + default), [PerformanceSettings-definitions.tsx](../../src/renderer/src/features/settings/sections/performance/PerformanceSettings-definitions.tsx) (UI)                                                                             |
| Two-gate reader (`isInstantNewSessionEnabled(settingValue?)`)                                                | [src/renderer/src/lib/instant-new-session-flag.ts](../../src/renderer/src/lib/instant-new-session-flag.ts)                                                                                                                                                                                                                  |
| Preload bridge (`window.__amcDisableInstantNewSession`)                                                      | [src/preload/index.ts](../../src/preload/index.ts)                                                                                                                                                                                                                                                                          |
| Backend idempotent ensure-blank IPC handler                                                                  | `IPC.SESSION_ENSURE_BLANK` in [src/main/ipc/session-handlers.ts](../../src/main/ipc/session-handlers.ts) — mints a numbered "Session N" blank (`name: undefined` → counter default)                                                                                                                                         |
| Sidebar status dot                                                                                           | [SidebarSessionRow.tsx](../../src/renderer/src/features/dashboard/SidebarSessionRow.tsx) — solid `getStatusBgColor(status)`; the pre-warm-blank pulse was removed ([sidebar-status-dot-solid-contract.md](../../.claude/memory/contracts/sidebar-status-dot-solid-contract.md))                                             |
| Placeholder-name recognition (auto-titler + 60s recovery sweep)                                              | [session-placeholder-name.ts](../../src/shared/session-placeholder-name.ts) — dep-light `SESSION_NAME_PATTERN` / `PRELOAD_BLANK_SESSION_NAME`; contract [session-placeholder-name-contract.md](../../.claude/memory/contracts/session-placeholder-name-contract.md)                                                         |
| IPC channel + Zod schema                                                                                     | [src/shared/ipc-channels/session.ts](../../src/shared/ipc-channels/session.ts), [src/shared/ipc-schemas/session.ts](../../src/shared/ipc-schemas/session.ts)                                                                                                                                                              |
| Renderer debounced pre-warm (`ensureBlankSession`)                                                           | [src/renderer/src/stores/session-store.ts](../../src/renderer/src/stores/session-store.ts)                                                                                                                                                                                                                                  |
| Mobile optimistic pre-warm (`optimisticallyPrewarmBlank` + confirm/reconcile/rollback; backend `reservedId`) | [session-store.ts](../../src/renderer/src/stores/session-store.ts) + [lifecycle.ts](../../src/main/db/queries-sessions/lifecycle.ts) / [session-create.ts](../../src/main/services/session/session-create.ts) / [launch.ts](../../src/main/ipc/session/launch.ts) / [sessions.ts](../../src/shared/ipc-schemas/session.ts) |
| Synchronous Ctrl+T fast path (`tryInstantNewSession`)                                                        | [src/renderer/src/stores/session-store.ts](../../src/renderer/src/stores/session-store.ts)                                                                                                                                                                                                                                  |
| Eligibility gate (`selectExistingBlankForProject`)                                                           | [src/renderer/src/stores/session-store.ts](../../src/renderer/src/stores/session-store.ts)                                                                                                                                                                                                                                  |
| Pool + render-warm carve-outs (`isWarmableActiveProjectBlank`, `selectKeepAlivePool`, `selectWarmHeavyIds`)  | [src/renderer/src/features/dashboard/keep-alive-pool.ts](../../src/renderer/src/features/dashboard/keep-alive-pool.ts)                                                                                                                                                                                                      |
| Dashboard wiring (pre-warm trigger, `activeProjectBlankId`)                                                  | [src/renderer/src/features/dashboard/Dashboard.tsx](../../src/renderer/src/features/dashboard/Dashboard.tsx)                                                                                                                                                                                                                |
| Render-warm pump (`renderWarmIds`, synchronous warm + idle prefetch)                                         | [src/renderer/src/features/dashboard/useRenderWarmPump.ts](../../src/renderer/src/features/dashboard/useRenderWarmPump.ts)                                                                                                                                                                                                  |
| Ctrl+T keyboard handler (`case 'newSession'`)                                                                | [src/renderer/src/hooks/useKeyboardShortcuts.ts](../../src/renderer/src/hooks/useKeyboardShortcuts.ts)                                                                                                                                                                                                                      |
| Invariants I14 / I15 (pool + render-warm)                                                                    | [tests/unit/features/dashboard/keep-alive-pool.test.ts](../../tests/unit/features/dashboard/keep-alive-pool.test.ts)                                                                                                                                                                                                        |
| Zero-IPC + wiring lint                                                                                       | [tests/unit/lint/instant-new-session-wiring.test.ts](../../tests/unit/lint/instant-new-session-wiring.test.ts)                                                                                                                                                                                                              |
| Kill-switch default-on behavior                                                                              | [tests/unit/lib/instant-new-session-flag.test.ts](../../tests/unit/lib/instant-new-session-flag.test.ts)                                                                                                                                                                                                                    |
| Full behavior contract (I1–I15, P0–P3, change checklist)                                                     | [.claude/memory/contracts/keep-alive-pool-contract.md](../../.claude/memory/contracts/keep-alive-pool-contract.md)                                                                                                                                                                                                          |

## Related

- [start-a-new-session.md](start-a-new-session.md) — the full new-session flow, the + button, launch targets, and what happens when there's no account or the folder is missing.
- [keyboard-shortcuts.md](keyboard-shortcuts.md) — Ctrl+T / N and every other binding; the new-session action is rebindable like the rest.
- [keep-alive sessions / lazy content load](lazy-content-load.md) — the broader "make switching sessions instant" effort this builds on; the keep-alive pool and render-warm pump are shared machinery.
