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