diff --git a/docs/designs/DECISIONS.md b/docs/designs/DECISIONS.md
index c7835331..2da06c93 100644
--- a/docs/designs/DECISIONS.md
+++ b/docs/designs/DECISIONS.md
@@ -303,6 +303,9 @@ check enforces the mechanical half. Full rationale:
| DL-232 | The Cmd/Ctrl+K command palette host anatomy is a bare fixed wrapper: a permanently-open Kobalte **Search** primitive (`open` pinned true; Control + Input + Listbox, with `Search.Portal`/`Search.Content` replaced by a plain div per Kobalte's published command-menu recipe, , `@kobalte/core` v0.13.13) inside a hand-positioned `position:fixed` div that IS `.cx-palette`, with its OWN `.cx-palette-backdrop`, Escape/outside-click dismiss, hand-wired focus grant (`ref` + `input.focus()` on mount) and focus-restore on close, plus per-row `onMouseDown`-preventDefault so a result click never blurs the pinned-open input — NO Portal/Content, floating-ui out of the test path. Search over Combobox because Search is built for caller-managed filtering (the palette's own fuzzy match) and publishes this pinned-open/no-portal recipe verbatim, where the same shape on bare Combobox was an unsupported extrapolation. The decider: a DOM-only test path with Kobalte reserved for the Search primitive's ARIA + keyboard traversal (DL-150's a11y-hard carve-out). A Search/Combobox hosted inside a Kobalte `Dialog` (scrim/trap/restore free) was rejected for nested-primitive + Portal/z-layer reconciliation cost against the shipped `.cx-palette` CSS and the window-level `Mod+K` toggle. | Active (Matt, 2026-08-23) | [command palette §Decisions](product/compass-command-palette/design.md#decisions) |
| DL-233 | The palette's navigation mode ships ALL six destination kinds, including issue AND pr. The issue provider rides the tracker's assigned-issue seam (`store.assignedIssues`); the pr provider requires a NEW store-level PR accessor — `prs: Accessor` as a `createMemo` over `issues()` via the existing pure `prRows()` (`board.ts:132-134`), mapping each `{issue, pr}` row to a `Destination{kind:"pr"}` that selects the owning issue and reveals the PR pane (`store.selectIssue` + `store.setActiveRightTab("pr")`) — which Matt explicitly accepted as added palette-impl scope (the store has no PR collection today; `IssueTab`'s `"pr"` is a detail-pane tab, not a collection). The fixture already carries PR shapes (`stub-data.ts:238`), so no new offline data source is needed. | Active (Matt, 2026-08-23) | [command palette §Decisions](product/compass-command-palette/design.md#decisions) |
| DL-234 | Point-of-use shortcut chips ship the seam plus the view-button now-win: the `ShortcutChip` primitive + `shortcutFor(id, platform)` + the `PrimaryAction` contract note + populated `board.*` shortcut fields (chips uniformly via `shortcutFor`, no hand-authored shortcut strings), AND `aria-keyshortcuts` + `title` populated via `shortcutFor` on the LeftSidebar `.bridge-link` view buttons (`LeftSidebar.tsx:437-482`: Bridge/Backlog/Done/Settings — the surfaces that actually invoke the D6-seeded `show*` paths), plus the Bridge topbar tab (`App.tsx:68-94`) for parity. Actual `.cx-menu`/hover-tooltip chip adoption is out of scope, deferred to follow-up issue RIG-2530 (it belongs to the first surface that mounts one). Seeds `view.settings/backlog/done` registrations stay under the shared commands-as-inventory rule (DL-229, owned by RIG-2529): the palette's seeds are `scope:"global"` view commands, so the palette is correct on the shared rule. | Active (Matt, 2026-08-23) | [command palette §Decisions](product/compass-command-palette/design.md#decisions) |
+| DL-245 | The coaching keyboard-discoverability layer (RIG-2530, refining DL-234's deferred `.cx-menu`/hover-tooltip adoption) is a reusable `CoachTip` component built on the installed **Kobalte v2-alpha `Tooltip`** primitive (`@kobalte/core@2.0.0-alpha.0`), not hand-rolled: a tooltip's hard parts (open/skip-delay timing, safe-area pointer travel, Escape dismiss, portal stacking, `aria-describedby` id plumbing, hover+focus unification) are exactly DL-150's a11y-hard scope, `.cx-tooltip` was authored naming Kobalte as the behavior owner (`tooltip.css:1-2`), and the alpha already ships via the palette's Search (`Palette.tsx:19`) so the version risk is on the books. It styles the existing `.cx-tooltip` box, reveals on hover AND focus (Kobalte default; a mouse-only coaching layer is self-defeating), and delays 400ms via `COACH_TIP_DELAY_MS` mirroring `--cx-tooltip-delay`. Hand-rolled on `.cx-tooltip` was rejected (re-derives ratified a11y-hard behavior beside Kobalte). | Active (Matt, 2026-08-23) | [coaching tooltips §A1](product/compass-coaching-tooltips/design.md#a1--kobalte-v2-alpha-tooltip-not-hand-rolled) |
+| DL-246 | `CoachTip` resolves its chord ONLY via `shortcutFor(id, platform)` (DL-234's single-derivation rule — never a hand-authored string) and renders it sequence-aware: a plus-chord (`Ctrl+B`) through `ShortcutChip` (which splits on `"+"`, `ShortcutChip.tsx:17`), a RIG-2484 sequence (`"G then B"`, no `"+"`) as plain text so it does not render one giant ``. Because it only reads `shortcutFor`'s return, it shows today's `Mod+B` now and `G then B` automatically once RIG-2484 (PR #544) lands — forward-compatible, never blocked on it. Teaching `ShortcutChip` to split on `" then "` is deferred to the RIG-2484 impl. A parallel tooltip-local chord formatter was rejected (forks the single derivation this net exists to keep). | Active (Matt, 2026-08-23) | [coaching tooltips §A3](product/compass-coaching-tooltips/design.md#a3--rig-2484-forward-compat-sequence-aware-chord-rendering) |
+| DL-247 | The `title=` adoption boundary: the coaching sweep converts ONLY the seven command-backed `title=` sites (LeftSidebar view buttons ×4, App.tsx Bridge tab + both sidebar toggles), removing the native `title` (no double-tooltip) and keeping `aria-keyshortcuts` (DL-234, the AT-authoritative chord); every other `title=` (command-less actionable controls, truncation/status text) stays native, per DL-229 (no command registered purely for discoverability). The two sidebar-toggle chords (`Mod+\`/`Mod+Shift+\`, `keymap.ts:116-117`) are dead today (declared in the keymap, registered nowhere — the buttons call `store.toggle*()` directly), so this record registers exactly `sidebar.toggleLeft`/`sidebar.toggleRight` beside their existing store behavior (DL-229-compliant) so the coached chords actually fire — Matt-ruled 2026-08-23, amending the record's "registers nothing new" constraint over shrinking the sweep to five live sites. | Active (Matt, 2026-08-23) | [coaching tooltips §A4](product/compass-coaching-tooltips/design.md#a4--adoption-boundary-the-title-classification) |
## Bridge spawn control
diff --git a/docs/designs/product/compass-coaching-tooltips/design.md b/docs/designs/product/compass-coaching-tooltips/design.md
new file mode 100644
index 00000000..4cf8fece
--- /dev/null
+++ b/docs/designs/product/compass-coaching-tooltips/design.md
@@ -0,0 +1,484 @@
+# Compass coaching keyboard-discoverability tooltips (RIG-2530)
+
+Status: Draft
+
+Parent: RIG-1661 (keyboard discoverability net). Refines DL-234 (point-of-use
+chips, RIG-2483), which explicitly deferred the `.cx-menu`/hover-tooltip
+adoption to this record. Forward-compatible with RIG-2484 (leader chords,
+`compass-leader-chords`, PR #544 — submitted, unmerged), never blocked on it.
+
+## Problem / Intent
+
+Compass has a full keyboard surface (dispatcher, registry, palette, `?`
+overlay) but the chrome teaches none of it: command-backed buttons carry only a
+raw native `title=` (slow ~1s browser delay, unstyled, no chip), so a mouse
+user never passively learns the keyboard path. Ship a real coaching Tooltip —
+label + resolved chord chip on hover **and** focus, in the already-shipped
+`.cx-tooltip` box — and adopt it across command-backed chrome: Linear's
+signature discoverability move. Scope is the tooltip layer only; the wider
+onboarding set (empty-state nudges, key-hint footer, first-run coach) is a
+Matt-ratified deferral (see Deferred).
+
+## Global Constraints
+
+- **SolidJS v2** (`apps/ui/package.json:26` — `"solid-js": "^2.0.0-rc.1"`;
+ `:19` — `"@solidjs/web": "^2.0.0-rc.1"`). v2 is a foundational rework: no v1
+ idioms (`produce`, `createResource`, ``, `batch`,
+ `solid-js/store`, `mergeProps`/`splitProps`). Component props are NEVER
+ destructured (severs reactivity — `skill://solid-skills` TTSR rule
+ `solid-no-destructured-props`). `ShortcutChip.tsx:14-17` is the in-tree v2
+ shape to match (non-destructured `props`, thunked derivation).
+- **Kobalte `2.0.0-alpha.0`** (`apps/ui/package.json:15` —
+ `"@kobalte/core": "2.0.0-alpha.0"`). Kobalte is scoped to a11y-hard behavior
+ only (DL-150, `docs/designs/product/DECISIONS.md:271`); all visuals via our
+ `.cx-*` classes. The v2-alpha Tooltip API used here is grounded from the
+ installed types (see Approach A1), never the 0.13 docs.
+- **Chord source of truth is the keymap.** Every displayed chord resolves via
+ `shortcutFor(id, platform)` and every AT-parseable chord via
+ `shortcutForAria(id, platform)` (`apps/ui/src/keyboard/keymap.ts:57-76`) —
+ "a registration never hand-authors a `shortcut` string" (`keymap.ts:53-55`,
+ DL-234's single-derivation rule). This record NEVER hand-authors a chord.
+- **Reuse `.cx-tooltip`** (`apps/ui/src/design/components/tooltip.css:6-19`)
+ and the `--cx-tooltip-delay: 400ms` token
+ (`apps/ui/src/design/tokens.css:227`). No new visual tokens.
+- **Command ids `noun.verbCamel`**; the registry contract stays as-is
+ (`commands.ts:113-121`). This record registers exactly TWO commands —
+ `sidebar.toggleLeft`/`sidebar.toggleRight` — beside their existing store
+ behavior (`store.ts:328/331`), because their chords are declared in the
+ keymap (`keymap.ts:116-117`) yet registered nowhere, so they are dead today
+ and coaching them would teach a lie (see A4/T2). This is DL-229-compliant:
+ the behavior already exists (`store.toggleLeft/toggleRight`), so the command
+ is registered beside a behavior, not purely for discoverability.
+- **Tests**: `cd apps/ui && bun test --conditions browser `; component
+ tests via `@solidjs/testing-library@1.0.0-beta.2`
+ (`apps/ui/package.json:36`). Red → green per `rule://red-green-testing`.
+- **Ledger**: the driver folds `DECISIONS.md` in the same PR; new rows are
+ DL-245..247 (PR #544's leader-chord block was renumbered to DL-248..252
+ after a concurrent merge, so it no longer overlaps). This record refines
+ DL-234; no row is superseded.
+- **Docs gate**: markdownlint (`.markdownlint.json`) — blank lines around
+ every fence, space-padded table delimiter rows, dash bullets.
+
+## Approach
+
+One reusable coaching Tooltip built on the Kobalte v2-alpha `Tooltip`
+primitive, styled by the existing `.cx-tooltip` box, whose content is the
+control's label plus its keymap-resolved chord; then a mechanical adoption
+sweep that converts the native `title=` on command-backed controls (removing
+the `title` so no double tooltip) and leaves every other `title=` native.
+
+### A1 — Kobalte v2-alpha Tooltip, not hand-rolled
+
+The `.cx-tooltip` CSS was authored FOR Kobalte from day one:
+
+> `tooltip.css:1-2`: "Tooltip — .cx-tooltip (D3, Kobalte). Elev-1 float, open
+> delay --cx-tooltip-delay (the delay is Kobalte's timing prop — this owns the
+> visual box)."
+
+and the component spec agrees (`apps/ui/src/design/components.md:458-460` —
+"**Class:** `.cx-tooltip` … open delay `--cx-tooltip-delay` (400ms) is
+Kobalte's timing prop"). A tooltip is squarely DL-150's "a11y-hard behavior"
+(hover+focus open, delay/skip-delay timing, Escape dismiss, safe-area
+pointer-travel, `aria-describedby` wiring) — the class of thing we adopt
+Kobalte for, unlike the ShortcutsOverlay's modal chrome which D5 ratified
+hand-rolled (`ShortcutsOverlay.tsx:6-7` — "Hand-rolled modal on the
+`.cx-dialog` convention … no @kobalte/core"). The palette already ships the
+same alpha (`Palette.tsx:19` — `import { Search } from
+"@kobalte/core/search";`), so the version risk is already on the books.
+
+The installed v2-alpha API (grounded from
+`apps/ui/node_modules/@kobalte/core/dist/tooltip/index.d.ts` re-exporting
+`dist/index/CDsbCLm32.d.ts`) is the familiar four-part anatomy, and it gives
+us every behavior the coaching layer needs out of the box:
+
+- `Tooltip` (Root) — `TooltipRootOptions` (`CDsbCLm32.d.ts:65-101`) carries
+ `openDelay?: number` (`:82-83`), `closeDelay`, `skipDelayDuration`, and
+ `triggerOnFocusOnly?: boolean` with the default we want: "By default, opens
+ for both focus and hover" (`:77-81`). Root doc: "A popup that displays
+ information related to an element when the element receives keyboard focus
+ or the mouse hovers over it" (`:103-106`).
+- `Tooltip.Trigger` — polymorphic, `declare function TooltipTrigger` (`:127`), and its render props include
+ `"aria-describedby": string | undefined` (`:120-122`) — the ARIA wiring is
+ automatic.
+- `Tooltip.Content` — render props carry `role: "tooltip"`
+ (`:48-50`); we put `class="cx-tooltip"` on it.
+- `Tooltip.Portal` — portals content to `body` (`:58-62`).
+
+So the WAI-ARIA bar (tooltip role + `aria-describedby` + focus reveal +
+Escape dismiss) is met by the primitive, and our component only owns content
+and styling. `openDelay` is passed as `400` — the number mirrored by the
+`--cx-tooltip-delay: 400ms` token (`tokens.css:227`), per the tooltip.css
+comment that the delay "is Kobalte's timing prop — this owns the visual box"
+(the token documents the value; Kobalte's number prop enforces it — see D5).
+
+### A2 — Component shape: composable trigger, encapsulated content
+
+Solid v2's reactive prop helpers are `omit`/`merge` (v1's `splitProps`/
+`mergeProps` are gone), so a monolithic ``
+wrapper that forwards a subset of props to the trigger is buildable
+v2-cleanly via `omit` (Kobalte's own Tooltip does exactly this internally).
+It is nonetheless the wrong shape. The three-part API instead mirrors
+Kobalte's own anatomy, keeps call sites authoring their own trigger element
+(same class/onClick/aria attributes they have today), and avoids a bespoke
+prop-forwarding contract — the component encapsulates only what is new:
+
+```tsx
+// apps/ui/src/components/CoachTip.tsx
+import { Tooltip } from "@kobalte/core/tooltip";
+
+/** Root with the house 400ms open delay; hover+focus reveal is Kobalte's
+ * default (triggerOnFocusOnly stays unset). */
+export const CoachTip: Component>;
+
+/** Re-export: the call site's existing