# Accessibility Focus Management

Mechanica now includes a reusable focus-management layer for modal UI, command surfaces, and future inspector drawers. The goal is to keep keyboard and assistive-technology behaviour predictable as the shell grows more complex.

## Added primitives

- `src/utils/focusManagement.ts`
  - Finds focusable descendants in browser tab order.
  - Skips hidden, `inert`, `aria-hidden`, disabled, `aria-disabled`, and negative-tabindex elements by default.
  - Provides helpers for moving focus, restoring focus, and wrapping `Tab` / `Shift+Tab` inside a contained region.
- `src/hooks/useFocusTrap.ts`
  - React hook for modal surfaces.
  - Supports initial focus targets, Escape handling, focus restoration, and `preventScroll`.
- `src/components/ui/Dialog.tsx`
  - Accessible dialog primitive with `role="dialog"`, `aria-modal`, labelled title, optional description, focus trap, overlay dismissal, Escape dismissal, and body scroll locking.

## Dialog usage

```tsx
import { useRef, useState } from "react";

import { Button } from "../components/ui/Button";
import { Dialog } from "../components/ui/Dialog";

export function ExampleDialog() {
  const [open, setOpen] = useState(false);
  const primaryActionRef = useRef<HTMLButtonElement>(null);

  return (
    <>
      <Button onClick={() => setOpen(true)}>Open technical note</Button>

      <Dialog
        open={open}
        title="Planetary gearset note"
        description="Engineering reference details for the selected drivetrain component."
        initialFocusRef={primaryActionRef}
        onOpenChange={setOpen}
        footer={<Button ref={primaryActionRef}>Continue</Button>}
      >
        <p>
          Use dialogs for focused decisions or compact reference panels where the user should not
          continue interacting with the catalogue behind the overlay.
        </p>
      </Dialog>
    </>
  );
}
```

## Command palette integration pattern

The command palette should keep focus inside the palette while open and return focus to the invoking
button or shortcut origin when closed.

```tsx
const panelRef = useRef<HTMLDivElement>(null);

useFocusTrap(panelRef, {
  enabled: open,
  initialFocus: "[data-command-input]",
  onEscapeKey: () => setOpen(false),
});
```

Prefer `aria-activedescendant` for the command results list when focus remains on the input, or a
roving `tabIndex` pattern when each result is directly focusable. Avoid placing positive tabindex
values in application code; the utility supports them because third-party and legacy markup may
contain them, but normal DOM order is easier to reason about.

## Manual QA checklist

For every modal, palette, and future drawer:

1. Open the surface with keyboard only.
2. Confirm focus moves to the intended first control or to the panel fallback.
3. Press `Tab` through every control and verify focus wraps at the end.
4. Press `Shift+Tab` from the first control and verify focus wraps to the last control.
5. Press `Escape` and verify the surface closes only when Escape dismissal is enabled.
6. Confirm focus returns to the opener after close.
7. With a screen reader, confirm the announced name matches the visible title and the description is
   read when present.
8. Confirm background page scrolling is locked while a dialog is open and restored after close.

## Automated coverage

`tests/focusManagement.test.ts` covers the most failure-prone focus behaviours:

- hidden and disabled filtering;
- positive, zero, implicit, and negative tabindex ordering;
- programmatic container fallback focus;
- first/last focus movement; and
- forward/backward focus wrapping for trapped regions.
