# DOX — packages/client-utils/src

Files in this directory. One row per source file.

| File | Purpose |
|------|---------|
| `ActionList.tsx` | `ui:action-list` primitive. Exports `ActionList({ actions })` — horizontal row of MDI-icon + label buttons. `ExtendedActionItem` carries `dataAction` (pluginId/sessionId/action/payload) dispatched via `sendPluginAction`, or classical `onClick`. `IconByKey` reads `@mdi/js` eagerly (statically imported across the shell — synchronous flat property lookup, no dynamic import) and looks up path by export-name key. See change: shrink-client-index-chunk. |
| `agent-card-utils.ts` | Shared helpers for agent card rendering. Exports `formatTokens`, `formatDuration`, `AgentCardStatus` type, `StatusIconDef`, `statusIconDefs` (pending/running/complete/error/blocked/stopped/background → MDI icon + color), `getStatusIcon`. Used by `FlowAgentCard` and `AgentToolRenderer`. |
| `AgentCardShell.tsx` | Exports `AgentCardShell` — reusable card container with status-colored border, header row (status icon + name + optional `headerRight`), optional `stats` line, clickable/selected states. `getStatusIcon` drives icon + border color. Consumers pass tool-specific content as children. |
| `Confirm.tsx` | Confirm preset over Dialog (size sm). Props `{open,onClose,title,message,body?,intent?,confirmLabel?,cancelLabel?,onConfirm,testId?}`. Action → onConfirm only (no auto-close); cancel/Esc/overlay → onClose. Replaces legacy ConfirmDialog. See change: unify-dialog-system. |
| `Dialog.tsx` | Unified modal shell. Props `{open,onClose,title?,icon?,size?,testId?,ariaLabel?,children}`. Owns DialogPortal, overlay `bg-black/60`, Esc via shared escape-stack (`useEscapeDismiss(open,onClose)` — onClose fires on Escape only when topmost registered layer; overlay opened above consumes it first), click-outside, focus trap (useFocusTrap), `role="dialog"`/`aria-modal`/`aria-labelledby`, `z-[60]`, size sm/md/lg → max-w-sm/md/lg, `max-h-[80vh]`. Renders standard ✕ close button (absolute top-right, testId `<testId>-close`) on every dialog incl. flush/headerless. Static subcomponents `Dialog.Footer`/`Dialog.Cancel`/`Dialog.Action` (intent primary\|danger\|neutral). See change: unify-dialog-system. |
| `DialogPortal.tsx` | Exports `DialogPortal` — `createPortal` to `document.body`. Hides body scroll (`overflow: hidden`) on mount, restores prior value on unmount. |
| `LayerPortal.tsx` | Exports `LayerPortal` — `createPortal` to `document.body` WITHOUT locking body scroll (unlike `DialogPortal`). The overlay-layering "portal-or-perish" primitive: escapes ancestor stacking contexts (`transform`/`will-change`/`opacity`/`isolate`/`z-*`) so a portaled overlay can't UNDERLAP a sibling (e.g. a `SessionCard`, which sets `isolate`). Portaled surface positions itself `fixed` from its trigger rect + a `z-*` layer token. Use for menus/popovers/dropdowns; `DialogPortal` for modals. See spec overlay-layering. See change: add-overlay-layering-system. |
| `EmptyState.tsx` | EmptyState primitive. Props `{title, body?, icon?, action?, secondaryAction?}`. Single `action` prop enforces one-primary-CTA. Renders NN/g empty-state pattern: value-framed heading, optional body, ≤1 primary CTA, ≤1 secondary escape-hatch. data-empty-state markers. See change: extend-client-utils-state-feedback-primitives. |
| `escape-stack.ts` | Shared escape-dismiss stack. Single module-stable `document`-bubble `keydown` listener + LIFO registry; Escape invokes `onEscape` of topmost registered layer only, then `preventDefault`+`stopImmediatePropagation`; guards `e.repeat`+`e.defaultPrevented`. Attach-once/never-detach (early-returns on empty). Exports `useEscapeDismiss(active,onEscape)` (`useId`-stable id, ref-backed onEscape, remove-by-id), `registerEscapeLayer`/`unregisterEscapeLayer`, `__resetEscapeStack` (test-only, prod no-op). See change: fix-stacked-escape-closes-layers. |
| `focusRing.ts` | Exports `focusRing` = "focus-ring" className string. Points at `.focus-ring` utility in packages/client/src/index.css (:focus-visible, 2px outline, 2px offset, var(--focus-ring) color). Replaces ad-hoc focus:outline-none + 1px border. WCAG 2.2 §2.4.7/§2.4.11. See change: extend-client-utils-state-feedback-primitives. |
| `Popover.tsx` | `ui:popover` primitive. Exports `Popover`, `PopoverProps`, `computePopoverPosition` (pure). Anchors floating panel to `anchorEl`'s rect; flips above on bottom overflow, shifts left on right overflow; recomputes on resize/scroll. Dismisses on outside mousedown or Escape. Body portal, not modal/scroll-lock/tooltip. |
| `SearchableSelectDialog.tsx` | Exports `SearchableSelectDialog`, `SelectOption`. Filterable list inside `Dialog` (size `sm`). Arrow/Enter/Esc keyboard nav, scrolls selected into view, optional badge. Used for single-select choices. |
| `Skeleton.tsx` | Skeleton primitive for content-layout loads. Props `{variant=text\|card\|bubble\|row, count?}`. Honors prefers-reduced-motion via useMediaQuery (static, no animate-pulse). aria-hidden. data-skeleton / data-skeleton-item / data-static markers. See change: extend-client-utils-state-feedback-primitives. |
| `StatusPill.tsx` | `ui:status-pill` primitive. Exports `StatusPill({ state, text, icon?, tooltip? })` — state-tinted pill. `STATE_STYLE_DARK`/`STATE_STYLE_LIGHT` palettes keyed by `UiStatusPillState` (running/success/info/warn/error/muted). `useIsLightTheme` observes `data-theme` attr. `IconByKey` reads `@mdi/js` eagerly (statically imported across the shell — synchronous flat lookup, no dynamic import). See change: shrink-client-index-chunk. |
| `statusPresentation.ts` | `statusPresentation(kind)` maps StatusKind (done\|current\|todo\|error) to `{glyph, tokenVar, label}`. Glyph = mandatory non-hue channel (✓ done, ▸ current, ○ todo, ✕ error). tokenVar references semantic --status-* / --text-muted token. `statusAriaLabel(name, kind)` returns "Name, label". WCAG 2.2 §1.4.1. Consumed by composer ArtifactChip + board BoardStatePill. See change: extend-client-utils-state-feedback-primitives. |
| `useFocusTrap.ts` | `useFocusTrap(ref,open)` hook. Stores prev activeElement, focuses first focusable child on open, traps Tab/Shift+Tab, restores focus on close. See change: unify-dialog-system. |
| `useMediaQuery.ts` | Reactive CSS media-query hook. Returns match boolean. Guards missing window.matchMedia (returns false). See change: extend-client-utils-state-feedback-primitives. |
| `useMobile.tsx` | Exports `MobileProvider` + `useMobile`. `MobileContext` provides boolean: viewport `max-width: 767px` OR `max-height: 599px` (catches landscape phones). Backed by `useMediaQuery`. |
| `useZoomPan.ts` | Exports `useZoomPan`, `ZoomPanState`, `UseZoomPanOptions`. State `{ scale, translateX, translateY }`. Wheel zoom centered on cursor, pointer drag pan (deferred capture past `DRAG_THRESHOLD` so clicks reach children), pinch zoom, double-click reset, `zoomIn`/`zoomOut`/`reset` controls. Clamp `minScale`/`maxScale`. Used by `MermaidBlock`, `FlowGraph`. |
| `ZoomControls.tsx` | Exports `ZoomControls({ onZoomIn, onZoomOut, onReset, scale })` — top-right overlay buttons (zoom in/out/reset) using MDI icons, percent readout when `scale !== 1`. Shared by `MermaidBlock` and `FlowGraph`. Stops pointer propagation. |
