4.0 KiB
4.0 KiB
Tasks: enterprise-react-infinite-scroll
Change ID: enterprise-react-infinite-scroll
Package: @suaveplan/react-infinite-scroll
Target path: react/ui/react-infinite-scroll
Prerequisite packages on Verdaccio: @suaveplan/error, @suaveplan/react-translate
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin each) - 0.2 If any worktrees exist under
.claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - 0.3 Confirm the package set in
MANIFEST.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Scaffold
- Create directory tree per proposal layout.
- Write
package.jsonat0.1.0with"sideEffects": false, exports map. - Dependencies:
@suaveplan/error workspace:^,@suaveplan/react-translate workspace:^,zod catalog:forms,@opentelemetry/api catalog:observability,tslib catalog:build. - DevDependencies:
intersection-observerpolyfill for tests (or use@suaveplan/testing-domwhich includes it). - Write
tsconfig.json,vitest.config.ts(includeintersection-observerinsetupFiles), scripts.
2. Error codes and types
- Define
InfiniteScrollErrorextendingSuaveplanError; code:INFINITE_SCROLL_LOAD_FAILED. - Define
UseInfiniteScrollOptions:onLoadMore,threshold,rootRef,rootMargin,debounceMs,direction,isEnd,isLoading. - Write
UseInfiniteScrollOptionsSchemaZod schema.
3. Hook — useInfiniteScroll
- Returns
{ ref: RefCallback, isLoading, isEnd }. - Uses real
IntersectionObserver(no mocks);rootRefpassed asrootoption. direction: 'down'places sentinel after last item;direction: 'up'places it before first.- Debounces
onLoadMorebydebounceMs(default 100ms) to prevent rapid calls. - Wraps
onLoadMorecall in a try/catch; on throw wraps inInfiniteScrollError(INFINITE_SCROLL_LOAD_FAILED). - Emits OTel
infinite_scroll.loadspan withinfinite_scroll.directionattribute. - Write
use-infinite-scroll.test.tsx(real DOM with polyfill): sentinel enters viewport →onLoadMorecalled;isEnd=truestops future calls; debounce reduces calls;rootRefrestricts observer root.{ timeout: 10000 }. - Write
use-infinite-scroll.stories.tsx: playground (toggle isEnd/isLoading). - Write
use-infinite-scroll.md(≥200 words).
4. Component — InfiniteScroll
- Renders children + sentinel + loading spinner (from
@suaveplan/react-translate"Loading...") + end message. - Forwards
rootRefprop to hook; acceptsdirection. loadingComponentprop to override default spinner;endComponentto override end message.- Write
infinite-scroll.test.tsx: renders items, loading state shows spinner, end state shows message, custom components used when provided.{ timeout: 5000 }. - Write
infinite-scroll.stories.tsx: default, reverse-scroll (chat), nested-container, playground. - Write
infinite-scroll.md(≥200 words).
5. E2E
e2e/infinite-scroll.spec.ts— Playwright:- Scroll to bottom of list, verify
onLoadMorefired (intercept via page evaluate counter). - Set
isEnd=true, scroll to bottom, verify no further loads. - Reverse-scroll: scroll to top of chat list, verify load triggered.
- Scroll to bottom of list, verify
6. Verify and publish
bunx turbo lint typecheck test --filter=@suaveplan/react-infinite-scroll— green.bunx turbo test:coverage --filter=@suaveplan/react-infinite-scroll— 100%.bun scripts/publish.ts --package @suaveplan/react-infinite-scroll.- Archive change folder.