# Popover Positioning Engine

## Objective

Place anchored panels where the user expects them and never off the edge of the screen.

A single positioning layer that places any anchored element relative to its trigger and keeps it inside the viewport.

## Before You Begin

This feature is being added to an application that already exists and already
works. Do not scaffold a new project, and do not assume a blank slate.

Inspect the codebase first and establish:

- The existing application structure and where code of this kind already lives.
- The framework and version in use.
- The existing design system — colours, spacing, typography, and component conventions.
- Existing UI components you can reuse instead of writing new ones.
- The existing database structure, if this feature needs to persist anything.
- The existing authentication and authorization system, if this feature is user-scoped.
- Dependencies already installed, so you don't add a library that duplicates one.
- The existing test setup and conventions.

Only start writing code once you understand the above. If the application
already implements part of this feature, extend it rather than replacing it.

## Implementation Instructions

1. Find everything the app anchors to a trigger — menus, tooltips, date pickers, select lists, autocomplete panels, colour pickers — and move them all onto one positioning layer rather than leaving each with its own offset arithmetic.
2. Express placement as a preferred side and alignment, then let the engine flip to the opposite side, slide along the anchor, or reduce the panel's available height when the preferred placement does not fit.
3. Recompute position when the anchor moves for any reason: page scroll, scroll inside a container, window resize, content changing size, or the anchor itself being repositioned.
4. Measure against the real rendered geometry rather than assumed offsets, so panels stay correct under browser zoom, page-level scaling, and ancestors with transforms applied.
5. Do not position by absolute coordinates measured once at open. The first frame will be right and every subsequent scroll will leave the panel stranded away from its trigger.

## UI and UX Requirements

Match the application's existing design system exactly. Reuse its components,
spacing, and typography. This feature should look like it was always there.

## Responsive Requirements

Works on mobile, tablet, and desktop. Touch targets are large enough to hit on a
phone, and nothing overflows horizontally at 320px.

## Accessibility Requirements

- Fully keyboard navigable.
- Correct semantic elements and ARIA roles.
- Visible focus states.
- Meets WCAG AA contrast.
- Dynamic changes are announced to screen readers.
- Respects prefers-reduced-motion.

## Edge Cases

- When the preferred side has insufficient room, the engine must flip to the opposite side, shift along the cross axis to stay in view, or constrain the panel's height and let it scroll internally — and it must pick one deterministically rather than oscillating between two placements.
- An anchor inside a scrolling container moves independently of the page. Track that container's scrolling too, and hide or detach the panel when the anchor scrolls out of sight rather than leaving it floating over unrelated content.
- Browser zoom, transformed ancestors, and fractional device pixel ratios all break naive coordinate arithmetic and leave the panel a few pixels adrift or badly misplaced. Measure real geometry at position time.
- A pointer arrow must be repositioned along with the panel after any flip or shift, and clamped so it still touches the anchor and never overhangs the panel's own rounded corner.
- Two anchored panels open at once need a defined stacking order, so a menu opened from inside a dialog renders above it rather than behind.
- A panel wider than the viewport on a small screen must not simply be pushed inward until it covers its own trigger; below a defined width, fall back to a full-width sheet.
- Position must be settled before the panel becomes visible, or the user sees it appear in the wrong place and slide into position.

## Testing

Exercise the feature end to end in the running application. Cover every edge case
above, then run the existing test suite and confirm nothing regressed.

## Acceptance Criteria

- [ ] Every anchored surface in the app is positioned by the same engine.
- [ ] No anchored panel is ever clipped by or extends beyond the viewport edge.
- [ ] Panels follow their anchor through page scroll, container scroll, and window resize.
- [ ] Placement is stable and does not flicker between two sides at a boundary size.
- [ ] Arrows remain aligned to the anchor after a flip or shift.
- [ ] Positioning is correct under browser zoom and inside transformed ancestors.
- [ ] A panel is fully positioned before it is painted, with no visible jump.
- [ ] The feature matches the existing design system.
- [ ] No existing functionality is broken.

## Adaptation Rules

- Match the existing design system. Do not introduce a new colour palette,
  spacing scale, or component library.
- Reuse existing components and utilities wherever they fit.
- Follow the naming, file layout, and code style already present.
- Do not upgrade, replace, or remove existing dependencies to make this
  feature fit. Adapt the feature to the app, not the app to the feature.
- Do not break existing functionality. If a change is genuinely required in
  existing code, make the smallest one that works and say so.
- If something in these instructions conflicts with how the application is
  built, follow the application and explain the deviation.

## Final Verification

Before you report the work as done:

1. Re-read the acceptance criteria above and check each one against what you
   actually built.
2. Run the application and exercise the feature end to end.
3. Run the existing test suite and confirm you have broken nothing.
4. Check the feature on mobile, tablet, and desktop widths.
5. Check keyboard navigation and focus handling.
6. Summarize what changed: files added, files modified, and anything you
   deliberately did differently because of how this application is built.

If any acceptance criterion is unmet, fix it before reporting completion.
