# HM gallery interaction probes

Auto-generated by `tools/gallery_probes.py --write-catalog`.
Do not hand-edit — update `PROBES` in `tools/gallery_probes.py`.

Run:

```bash
python packages/hatchi-maxchi/tools/gallery_probes.py --discover
python packages/hatchi-maxchi/tools/gallery_probes.py --run
python scripts/hm_gallery_probes.py --run   # monorepo entrypoint
```

| id | stem | intent | severity | fix_surface | claim |
|----|------|--------|----------|-------------|-------|
| `menubar.exclusive_open` | `menubar` | exclusive | high | controller | Opening a second menubar item closes the previously open item (exclusive open — File then Edit leaves only Edit open) |
| `navigation_menu.exclusive_open` | `navigation-menu` | exclusive | high | controller | Opening a second navigation-menu panel closes the previously open panel (exclusive open — Product then Resources leaves only Resources open) |
| `accordion.exclusive_open` | `accordion` | exclusive | medium | partial | Accordion items sharing the HTML name= attribute stay exclusive (open second trigger leaves only that panel open — zero JS) |
| `tree.multi_open` | `tree` | multi_open | medium | partial | Tree nodes stay multi-open by design — expanding Platform then Design systems leaves both open (native details forest, no exclusive controller) |
| `menubar.dismiss_outside` | `menubar` | exclusive | high | controller | Clicking outside the menubar closes the open panel (app chrome must not leave File/Edit stuck open) |
| `navigation_menu.dismiss_outside` | `navigation-menu` | exclusive | high | controller | Clicking outside the navigation menu closes the open panel (mega panels must not stick after leaving the nav) |
| `popover.dismiss_outside` | `popover` | exclusive | high | controller | Clicking outside an open popover closes it (details-light-dismiss spatial dismiss — free panels must not stick) |
| `menu.dismiss_outside` | `menu` | exclusive | high | controller | Clicking outside an open action menu closes it (details-light-dismiss spatial dismiss — local action menus must not stick) |
| `menubar.escape_dismiss` | `menubar` | exclusive | high | controller | Pressing Escape closes the open menubar panel (keyboard dismiss must match outside-click — File must not stick) |
| `dialog.escape_closes` | `dialog` | exclusive | high | controller | Pressing Escape closes an open modal dialog (native <dialog> cancel — modal must not trap Escape) |
| `drawer.escape_closes` | `drawer` | exclusive | high | controller | Pressing Escape closes an open drawer (drawer is a side <dialog> — Escape must dismiss filters/record panels) |
| `command.escape_closes` | `command` | exclusive | high | controller | Pressing Escape closes an open command palette (dz-command owns Esc — type=search must not trap the first Escape) |
| `menu.escape_dismiss` | `menu` | exclusive | high | controller | Pressing Escape closes an open action menu (details-light-dismiss keyboard path — Actions must not stick) |
| `tooltip.hover_shows_hint` | `tooltip` | hover | medium | css | Hovering the tooltip host reveals the CSS ::after hint (data-tooltip rest state is closed; tip must appear on hover) |
| `tooltip.force_open_shows_hint` | `tooltip` | hover | medium | css | data-tooltip-open / data-dz-tooltip-open forces the CSS ::after hint visible without hover (capture / docs still path) |
| `popover.escape_dismiss` | `popover` | exclusive | high | controller | Pressing Escape closes an open popover (details-light-dismiss keyboard path — free panels must not stick) |
| `hover_card.escape_closes` | `hover-card` | hover | high | controller | Pressing Escape closes a click-opened hover-card (touch path uses data-open — Escape must clear it so previews do not stick) |
| `hover_card.dismiss_outside` | `hover-card` | hover | high | controller | Clicking outside a click-opened hover-card closes it (spatial dismiss for touch/explicit open — previews must not stick) |
| `tabs.exclusive_select` | `tabs` | exclusive | high | controller | Selecting a second tab reveals only its panel and moves aria-current (exclusive select — Overview then Activity leaves only Activity visible) |
| `switch.toggles_checked` | `switch` | exclusive | medium | partial | Clicking a switch track toggles the checkbox checked state (settings must flip Email notifications off then on again) |
| `controls.checkbox_toggles` | `controls` | exclusive | medium | css | Clicking a designed checkbox flips native checked state both ways (selection controls must not be label-only paint) |
| `controls.switch_toggles` | `controls` | exclusive | medium | css | Clicking the controls-page switch checkbox flips checked both ways (primitive switch class shares semantics with dedicated switch page) |
| `slider.updates_readout` | `slider` | exclusive | high | controller | Changing the range input updates the group's live value readout (settings must show the new number without a page reload) |
| `toggle.pressed_flips` | `toggle` | exclusive | high | controller | Clicking a toolbar toggle flips aria-pressed both ways (Bold/Italic mode must stay live without a page reload) |
| `toggle_group.radio_exclusive` | `toggle-group` | exclusive | high | css | Selecting a second toggle-group segment checks only that radio (List then Board leaves only Board checked — native exclusive group) |
| `carousel.advance_next` | `carousel` | exclusive | high | controller | Clicking Next advances the clamp carousel from slide 1 to 2 (data-carousel-index, active slide, and live status must update) |
| `combobox.enhance_and_select` | `combobox` | exclusive | high | controller | Native select[data-combobox] enhances on pointerdown; picking High writes select value=high, shows High in the overlay input, and closes the listbox (data-open cleared) |
| `wizard.forward_after_valid` | `wizard` | exclusive | high | controller | After filling the required field on step 0, clicking the step-1 stepper advances data-step to 1, shows stage 1, and marks step 1 current (forward is validity-gated one step at a time) |
| `toast.dismiss_and_client_fire` | `toast` | exclusive | high | controller | Dismissing a stack toast removes it after leave motion; dispatching showToast appends a new toast with the detail title (stack host must honor dismiss + client fire — not a static markup demo) |
| `tags.seed_add_and_remove` | `tags` | exclusive | high | controller | Seeded comma value enhances into chips; Enter adds a new chip and rewrites the native comma-joined value; Remove × drops a chip (chips UI must own submit contract — not a static demo) |
| `search_select.open_typeahead_select_hold` | `search-select` | exclusive | high | controller | Focus opens the typeahead panel; typing filters mock rows; selecting a row shows confirm and holds the panel past blur grace; after confirm-hold the panel auto-dismisses (not a static markup demo) |
| `money.sync_minor_and_blur_normalize` | `money` | exclusive | high | controller | Typing a major amount rewrites the hidden minor carrier; blur normalizes display to scale decimals; empty blur clears the carrier (money submit contract is integer minor — not a static demo) |
| `app_shell.sidebar_toggle` | `app-shell` | exclusive | high | controller | Hamburger sidebar toggle flips data-sidebar open↔closed and aria-expanded on the toggle (live shell chrome — not a static iframe demo) |
| `confirm.intercept_and_accept` | `confirm` | exclusive | high | controller | Clicking hx-delete[hx-confirm] opens the designed alert-dialog with the confirm text; accepting closes the dialog and issues the request (MOCK_HTMX toast) — not a silent window.confirm fallback |

## Loop (autonomous improve)

1. `--discover` → uncovered multi-details stems (must declare intent)
2. Author `Probe` with `intent=exclusive|multi_open` + fix_surface
3. `--run` → FAIL drains via `improve/strategies/gallery_probes.md`
4. Human observation → `--validate-observation '{…}'`
5. CI pin optional: `tests/test_behaviour.py` scenario for ship-grade contracts

### Intent

- **exclusive** — menubar / nav / accordion: only one panel open
- **multi_open** — tree forests: expanding siblings must *not* close peers
