* Add live AI checks to system health
Give super admins a dedicated AI status view with bounded liveness probes for LLMs, vector stores, pgvector, and embedding endpoints. Record sanitized failures in both the system debug log and Rails logger, and document the recommended local configuration.\n\nCloses #3145
* Fix AI health CI checks
* Address AI health review feedback
* Correct Ollama model preload guidance
* Distinguish OpenAI-compatible providers
* Make Ollama startup readiness explicit
* Recognize Cloudflare AI endpoints
* fix(design-system): DS::Tabs a11y — WAI-ARIA tab pattern + keyboard nav
Closes#1745. DS::Tabs rendered as a bare `<nav>` + `<button>` list
with no role wiring. AT users would hear "navigation, button,
button, button" instead of the tab semantics. Keyboard users got
no arrow-key nav between tabs.
Five fixes:
1. **Role scaffolding.** `<nav>` → `role="tablist"`,
`aria-orientation="horizontal"`. Each tab `<button>` →
`role="tab"`, `aria-selected`, `aria-controls="panel-#{id}"`.
Each panel `<div>` → `role="tabpanel"`, `id="panel-#{tab_id}"`,
`aria-labelledby="#{tab_id}"`, `tabindex="0"` (so the panel
itself is reachable via keyboard for in-panel content nav).
2. **Roving tabindex.** Active tab is `tabindex="0"`, inactive are
`tabindex="-1"`. ArrowLeft/Right cycles focus across the tablist
without leaving the widget; Tab jumps past the whole widget.
Stimulus controller updates both `aria-selected` and `tabindex`
on tab switch.
3. **Manual activation.** Per WAI-ARIA APG "Tabs with Manual
Activation" — arrow keys MOVE focus, Enter/Space ACTIVATES the
focused tab. Avoids accidental tab swaps when the user is just
navigating. Important here because several tab contents trigger
Turbo fetches (transactions index, account sidebar, budgets).
4. **Home/End shortcuts.** Home jumps focus to the first tab, End
to the last. WAI-ARIA APG-standard.
5. **Raw palette → token.** Replace `bg-white theme-dark:bg-gray-700`
on the active button with the existing `tab-item-active` utility
(defined in `_generated.css` from `design/tokens/sure.tokens.json`).
Single class, dual-mode. Also gate the transition behind
`motion-safe:` so reduced-motion users get an instant snap.
API unchanged — the slot signatures (`btns(id:, label:)`,
`panels(tab_id:)`) take the same args. Caller-provided `id:` is
still the public identifier; `panel-#{id}` is internal naming for
the `aria-controls`/`aria-labelledby` pair.
* fix(review): scope DS::Tabs DOM ids to component instance
Per CodeRabbit review on #1847: raw `panel-#{tab_id}` and `id: tab_id`
on buttons collide when multiple DS::Tabs widgets on the same page
share generic tab ids (e.g., "all", "overview", "transactions"),
breaking aria-controls / aria-labelledby associations.
Scope ids via per-instance `dom_prefix` ("tabs-#{object_id}") and
share the same prefix between DS::Tabs and DS::Tabs::Nav so button
ids and panel labelledby/controls stay consistent.
* fix(review): use <div> host for role=tablist in DS::Tabs::Nav
Codex P2 follow-up on #1847: \`<nav>\` has a fixed landmark role per
ARIA-in-HTML and may not be repurposed as a tablist. The current
\`tag.nav class: ..., role: \"tablist\"\` produces invalid markup —
some AT implementations ignore the role override, in which case the
child \`role=\"tab\"\` buttons end up without a valid tablist parent
and the keyboard / AT contract this PR is meant to add silently
regresses.
Swap the container for a neutral \`tag.div\`. Tab semantics (\`role\`,
\`aria-orientation\`, keyboard nav, manual-activation pattern) are
unchanged.