> = {
retrieving: "searching",
reasoning: "solving",
writing: "composing",
creating: "shaping",
};
export function AgentActivity({ phase }: { phase: AgentPhase }) {
const state = ORB_BY_PHASE[phase];
if (!state) return null;
return ;
}
```
Remove the orb on `done`, `error`, cancellation, or idle. Show the appropriate result, retry, or error UI instead of leaving the last activity animation running.
## Announce Status Once
The component defaults to `role="img"` with a per-state label such as “Searching…”. When visible text describes the same state, make the text the single announcement source:
```tsx
export function LiveAgentStatus() {
return (
Reviewing the repository…
);
}
```
Use `aria-live="polite"` for ordinary phase changes. Avoid rapid label churn. Do not add another hidden live region when `role="status"` already owns the announcement.
## Theme
Use one of:
```tsx
```
- `auto` first checks an ancestor `data-theme="dark|light"` attribute or `dark` / `light` class.
- If no ancestor theme exists, `auto` follows `prefers-color-scheme`.
- Theme changes update live.
- `dark` means light dots intended for a dark background.
- `light` means dark dots intended for a light background.
The canvas is transparent. Verify contrast against the actual surface rather than the page root alone.
## Speed and Pause
```tsx
```
`speed` multiplies the baked speed of the selected state and size. Start at `1`; use roughly `0.75–1.25` for subtle product tuning. Extreme values can make the hand-tuned motion feel frantic or stalled.
`paused` freezes the current frame while retaining the visual status. Remove the component when the activity has actually ended.
## Next.js and Client Rendering
The component uses React effects, canvas, `requestAnimationFrame`, media queries, and observers. Keep the package import behind a client boundary in the Next.js App Router:
```tsx
"use client";
import { ThinkingOrb } from "thinking-orbs";
export function ThinkingStatus() {
return ;
}
```
The library is SSR-safe because it paints only on the client after resolving the theme. A client boundary is still required where the framework enforces server and client component separation.
## Built-In Runtime Behavior
- Draws with plain Canvas 2D arcs; no WebGL or SVG filters.
- Caps device pixel ratio at `2`.
- Uses one `requestAnimationFrame` loop per visible instance.
- Uses the shared `performance.now()` clock so multiple orbs stay in phase.
- Pauses when the canvas scrolls offscreen through `IntersectionObserver`.
- Pauses when the browser tab is hidden.
- Renders one deterministic static frame under `prefers-reduced-motion: reduce`.
- Continues following live theme changes in reduced-motion mode.
Do not rebuild these behaviors in a wrapper. Add product state management and layout around the component, not a second animation loop.
## Power-User Canvas API
Prefer `` for product UI. The package also exports its resolved presets and raw frame painters for a custom canvas outside React:
```ts
import { MODE_DRAWS, resolvePreset } from "thinking-orbs";
const { mode, speed, opts } = resolvePreset("searching", 64);
const drawFrame = MODE_DRAWS[mode];
drawFrame(
context,
64,
(performance.now() / 1000) * speed,
true, // true draws light ink for a dark surface
opts,
);
```
`STATE_TO_MODE` exposes the internal mapping:
- `working` → `orbits`
- `searching` → `globe`
- `solving` → `rubik`
- `listening` → `wave`
- `composing` → `ribbon`
- `shaping` → `morph`
Use the raw API only when another renderer owns the canvas lifecycle. It provides a frame painter, not component behavior. Reimplement DPR sizing, clearing, animation scheduling, pausing, theme resolution, reduced motion, visibility handling, cleanup, and accessibility when bypassing ``.
## Verification
Run the project's typecheck, tests, production build, and `git diff --check`. Then verify in a real browser:
1. Trigger every product phase and confirm the mapped orb state is truthful.
2. Confirm `20` and `64` pixel instances are crisp without CSS scaling.
3. Test dark, light, and live theme switching.
4. Test reduced motion and confirm the orb becomes a static representative frame.
5. Scroll the orb offscreen and return; confirm it resumes without visible breakage.
6. Hide and restore the tab; confirm animation resumes.
7. Inspect accessibility: announce the status exactly once and use a task-specific label.
8. Confirm the orb disappears on success, error, cancellation, and idle.
9. Confirm no console errors, hydration warnings, or layout shifts occur.
## Common Pitfalls
- **Type error on size:** use exactly `20` or `64`; do not pass arbitrary dimensions.
- **Wrong contrast:** remember `dark` targets dark backgrounds and therefore draws light ink.
- **Duplicate screen-reader output:** hide the canvas when adjacent `role="status"` text already announces the task.
- **Misleading state:** do not show `searching` during generation or `composing` during microphone capture.
- **Permanent loading UI:** remove the orb when work ends and render the actual terminal state.
- **Hydration or server-component error:** move the import into a client component.
- **Brand color requested:** the public API is monochrome; choose another loader or make an intentional library fork instead of relying on unsupported styling.
## Handoff
Report the mapped product phases, chosen size, theme mode, accessible label strategy, reduced-motion behavior, and build/browser verification. Distinguish local implementation from a deployed release.