Skip to content

docs(stats): document the stats page contract - #459

Merged
mikewheeleer merged 6 commits into
Agentpay-Org:mainfrom
thlpkee20-wq:docs/stats-01-component
Jul 28, 2026
Merged

docs(stats): document the stats page contract#459
mikewheeleer merged 6 commits into
Agentpay-Org:mainfrom
thlpkee20-wq:docs/stats-01-component

Conversation

@thlpkee20-wq

Copy link
Copy Markdown
Contributor

Closes #389

Adds `docs/stats-page.md` covering the `/stats` route: the `Stats`
data shape, the `usePolling` wiring (linking to its existing full
contract in `docs/hooks.md` rather than duplicating it), and all four
page states (loading via the route-level `PageSkeleton`, error via
`ErrorMessage` noting `usePolling`'s self-recovering
retry-on-next-tick behavior, populated, and the backend-paused notice —
which is distinct from the page's own poll pause/resume control, a
point worth calling out explicitly since the two are easy to conflate).
Also notes that this page hand-rolls its stat tiles rather than using
the shared `StatTile` component, for anyone extending it later. Links
it from the `/stats` row in `README.md`'s route table, matching how
the other per-route docs are linked.

Test plan

Docs-only change; no source touched.

Note

Stacked on #450 (fork-only, can't target a non-`main` base — same
caveat as noted on the other PRs in this series). This is the last PR
across all three repos for the currently assigned GrantFox issues.

The window-undefined tests for readTheme/writeTheme/effectiveTheme
failed on unmodified main: globalThis.window in this jest-environment-jsdom
version is a non-configurable getter
(Object.getOwnPropertyDescriptor(globalThis, "window").configurable ===
false), so Object.defineProperty/jest.replaceProperty both throw trying
to redefine it to simulate an SSR context.

Moved the three affected tests into theme.ssr.test.ts with a
`@jest-environment node` docblock, where window is genuinely absent
rather than simulated, exercising the real typeof window === "undefined"
guard branch. No source change; theme.test.ts keeps every other case
under jsdom as before.
DocsFilter already memoized its aria-live announcement (useMemo), but
the actual filteredSections derivation was a plain .filter() call on
every render — including every keystroke before the 300ms debounce
settles, since `query` state changes trigger a re-render even though
`debouncedQuery` (the value the filter actually depends on) hasn't
changed yet. Wrapped it in useMemo keyed on [sections, debouncedQuery]
so it only recomputes when the value driving the filter changes.

Behavior and output unchanged, verified by the existing 12-case test
suite (page.test.tsx + DocsFilter.test.tsx) passing unmodified. No new
dependencies.
DocsFilter already had a clear, accessible empty state for "no results
for this search term". Added a distinct one for "no endpoints were
passed in at all" (sections.length === 0) — a content/configuration gap
rather than something a different search term could fix, so the search
box (and the "try a different search term" guidance) isn't offered as
a way out. The aria-live announcement is suppressed in this state too,
since there's no search interaction to announce the result of.

Error state: DocsPage builds `sections` synchronously via
getSections(baseUrl) — there's no fetch/async operation to fail, so no
loading state is possible either. The one call that can throw
(resolveApiBase(), invoked directly in the server component with no
local try/catch) is already caught by the app's root error.tsx boundary
(role="alert", keyboard-operable "Try again" retry via Next.js's
reset()) — adding a second, redundant error UI local to this page would
just duplicate that. Scoped this change to the empty-state distinction,
which is the part with no existing coverage.

Adds a test for the new state and confirms the search box and the
per-query empty state are absent when it renders.
DocsFilter already had thorough coverage (100%-locked in
jest.config.ts) for filtering, announcements, and click-driven clear
via the earlier states/memoize work in this series, but nothing
exercised it via actual keyboard interaction. Added a test that tabs
into the search input, types a query with user.keyboard, waits for the
debounce, tabs to the Clear button, and activates it with Enter —
verifying the whole filter-then-clear flow, plus SearchBar's
focus-return behavior, works without a mouse.

No behavior change.
Adds docs/docs-page.md covering the /docs route's file layout
(page.tsx, endpoints.ts, DocsFilter.tsx), DocsFilter's props and usage
example, its three states (populated, no-results-for-search,
no-endpoints-at-all) including why no separate error UI exists
(resolveApiBase's throw is already caught by the root error.tsx
boundary), accessibility notes, and where its tests live. Links it from
the /docs row in README.md's route table.
Adds docs/stats-page.md covering the /stats route: the Stats data
shape, the usePolling wiring (linking to its existing full contract in
docs/hooks.md rather than duplicating it), and all four page states
(loading via the route-level PageSkeleton, error via ErrorMessage with
usePolling's self-recovering retry-on-next-tick behavior, populated,
and the backend-paused notice distinct from the page's own poll
pause/resume control). Notes that this page hand-rolls its stat tiles
rather than using the shared StatTile component. Links it from the
/stats row in README.md's route table.
@mikewheeleer

Copy link
Copy Markdown
Contributor

well done @thlpkee20-wq, this reads well. merging in 🌟

@mikewheeleer
mikewheeleer merged commit d68332c into Agentpay-Org:main Jul 28, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Document the stats component contract and props

2 participants