Guidance for Claude Code when working in this repository.
@marimo-team/react-finder is a headless React file-explorer library
(TanStack-Table-style): pluggable FileSystemAdapters, a framework-free store,
and unstyled components built on react-aria-components (RAC). See README.md
for the public API.
The toolchain is Vite+ (vp); vite.config.ts is the single config file for
the demo build, the library build, tests, oxlint, oxfmt, the task cache and the
pre-commit staged rules.
pnpm dev— demo gallery (Vite, http://localhost:5173/demo/)pnpm test— vitest viavp test(src/**/*.test.tsrun in node;*.test.tsxopt into jsdom). Tests import fromvite-plus/test, not fromvitest(enforced byno-restricted-imports).pnpm typecheck— TypeScript 7tscforsrc/(tests included) anddemo/pnpm lint— oxlint (pnpm lint:fixto autofix — review the diff, fixers can change semantics)pnpm fmt— oxfmt (pnpm fmt:checkin CI)pnpm check—vp check(format + lint + type-check) pluspnpm typecheckpnpm build—vp pack(tsdown) →dist/(tests excluded);pnpm check:packagevalidates the result with publint + arethetypeswrongvpr <script>— cached run of any script (run.cache.scriptsis on)
The oxlint config in vite.config.ts is strict and type-aware: every category
except nursery is an error, with a documented list of rules turned off. Do not
add biome-ignore comments; use // oxlint-disable-next-line <rule> -- reason
only for genuine false positives. Use top-level import type (never inline
type specifiers): verbatimModuleSyntax is on. src/index.ts must never
import adapters/s3 (no-restricted-imports enforces the subpath boundary).
.vscode/extensions.json recommends oxc.oxc-vscode so the editor reports the
same diagnostics.
src/core/— framework-free:types.ts(adapter contract,FileItem),errors.ts,path.ts,naming.ts,selectors.ts, andstore/(zustand vanilla store:cache.tsdirectory cache with abort/sequencing,navigation.tshistory,operations.tsthe singlerunOperationfunnel,misc.tsthe rest).src/adapters/—memory/(VirtualFS+MemoryAdapter, the conformance reference),fileSystemAccess/,s3/(subpath export only; never import it fromsrc/index.ts),testing/(adapter contract suite, controlled adapter).src/components/— thin RAC wrappers.FinderRootowns the store, contexts, shortcuts and context-menu delegation;collections/FinderItemrendersGridListItem/Row/TreeItemdepending on the enclosing collection.src/hooks/—useFinder/useFinderStoreare public;internal/is not.src/actions/,src/keyboard/,src/dnd/— pure, unit-tested logic.demo/— Vite gallery, styled with Tailwind (CDN) viadata-*variants.
- Headless. No styles in
src/. Expose state asdata-*attributes (RAC already emitsdata-selected,data-focused,data-dragging, …; we adddata-kind,data-path,data-editing,data-cut,data-loading, …). Consumers style withdata-[selected]:…(attribute presence). - Use RAC components, not hand-rolled ARIA. Selection, keyboard navigation, typeahead, drag and drop, menus, popovers and virtualization come from RAC.
- Logic outside React. Anything testable without a DOM lives in
src/core,src/actions,src/keyboard,src/dnd. Components are glue. - Paths are identity.
FileItem.pathis the key everywhere (RAC keys are the path too — seekeyItems). Adapters return normalized paths. - Capabilities are structural. An adapter enables a feature by implementing
the method; the store checks
capabilitiesbefore running an operation. - The store is the only writer of the cache. Mutations go through
runOperation; listings go throughloadDirectory. Never call the adapter from a component. - Selectors returning arrays must be referentially stable (see the caches in
selectors.ts), oruseFinderwill re-render forever. - Consumers own the item element. Collections render
children(item) => <Finder.Item item={item}>…</Finder.Item>. - Items must stay cheap. react-aria renders every item (virtualized or not)
to build its collection, so
Finder.ItemreadsItemStateContextpublished once per collection instead of subscribing to the store. Options handed touseDragAndDropare memoized: react-aria keys its hooks on object identity.
- Store tests use
createControlledAdapter()to resolve adapter calls by hand (races, aborts, partial failures). - Every adapter should pass
describeAdapterContract(). - Component tests use Testing Library + user-event under jsdom
(
// @vitest-environment jsdom).src/test/setup.tspolyfillsResizeObserver,scrollIntoView,matchMedia.