Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copilot instructions

Primary agent docs live in [`AGENTS.md`](../AGENTS.md) and [`agent-docs/`](../agent-docs/). Read those first — they cover the public surface, footguns, and conventions in depth.

Path-scoped rules (tests, public surface, stories) are in [`.github/instructions/`](instructions/) and apply automatically when those files are in context.

## Quick orientation

- React component library (`@civicactions/cmsds-open-data-components`). Parcel-bundled, published to npm. Consumed by DKAN-based open-data catalog frontends.
- **Not** a runnable application — only entry is [`src/index.ts`](../src/index.ts), which re-exports the public surface.
- Peer deps: `react ^18.2`, `@cmsgov/design-system ^12.4.2`. Library bundles its own `react-router-dom` v6 and `@tanstack/react-query` v5.

## Load-bearing facts

- **Public surface is `src/index.ts`.** Adding, removing, or renaming exports is a breaking change. Update [`agent-docs/consumer-integration.md`](../agent-docs/consumer-integration.md) in the same PR.
- **Templates self-wrap with `withQueryProvider`.** Six exports do this: `Dataset`, `DatasetSearch`, `DatasetList`, `FilteredResource`, `DatasetListSubmenu`, `DatasetDataDictionaryTab`. Consumers do not need (and should not add) their own `QueryClientProvider` for these.
- **`useDatastore` uses `fetch`**, not axios. Mock `global.fetch` in tests, not axios.
- **MSW is Storybook-only.** Jest does not have MSW configured — don't reach for it in tests.
- **Cache keys are concatenated strings** (e.g. `"datastore" + id + paramsString`). `invalidateQueries(["datastore"])` matches nothing — invalidate by setter (`setConditions`) instead.
- **`customMetadataMapping` is shallow-spread.** `undefined` does NOT remove a default field — pass `() => []` to hide it.
- **Hard-coded routes:** `/datasets` and `/dataset/:id`. Consumers must match or override via `customMetadataMapping`.

## Common commands

| Command | Purpose |
|---|---|
| `npm run storybook` | Start Storybook on :6006 |
| `npm test` | Run Jest suite |
| `npm run build` | Parcel build to `dist/` |
| `npm run generate:inventory` | Regenerate `COMPONENTS_INVENTORY.md` |
| `npx generate-usage-report` | Audit which exports a consumer site uses |

## Pre-commit hook

`.husky/pre-commit` runs `npm run generate:inventory` and auto-stages `COMPONENTS_INVENTORY.md`. Don't bypass with `--no-verify` — it produces inventory drift.

## When in doubt

- Public-surface questions → [`agent-docs/consumer-integration.md`](../agent-docs/consumer-integration.md)
- Data fetching / React Query → [`agent-docs/data-flow.md`](../agent-docs/data-flow.md)
- DKAN HTTP contract → [`agent-docs/dkan-api.md`](../agent-docs/dkan-api.md)
- Tables, filters, sorting → [`agent-docs/data-table-system.md`](../agent-docs/data-table-system.md)
- Tests → [`agent-docs/testing.md`](../agent-docs/testing.md)
- Storybook + MSW → [`agent-docs/storybook-and-mocking.md`](../agent-docs/storybook-and-mocking.md)
- Releases → [`agent-docs/release-process.md`](../agent-docs/release-process.md)
- Accessibility → [`agent-docs/accessibility.md`](../agent-docs/accessibility.md)
26 changes: 26 additions & 0 deletions .github/instructions/public-surface.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
applyTo: "src/index.ts"
---

# Public surface

This file IS the public API of `@civicactions/cmsds-open-data-components`. Anything re-exported here is consumed by downstream sites.

## Rules

- **Adding, removing, or renaming an export is a breaking change.** Update [`agent-docs/consumer-integration.md`](../../agent-docs/consumer-integration.md) (the templates table, components list, hooks/utilities sections) in the same PR.
- **Renames** require keeping the old export as an alias for at least one minor version, OR a major version bump. Prior renames (`DataTable` ← `Datatable`, `DatasetTable` ← `DatasetTableTab`) kept aliases — follow that pattern.
- **New default-exported templates** that call query hooks must self-wrap with `withQueryProvider` at the default export, matching the existing six (`Dataset`, `DatasetSearch`, `DatasetList`, `FilteredResource`, `DatasetListSubmenu`, `DatasetDataDictionaryTab`).
- **Don't re-export internal utilities** unless a consumer explicitly needs them. Once exported, they're contractually stable.

## Verification before shipping

1. `npm run build` — confirm `dist/` regenerates cleanly.
2. `npx generate-usage-report` (in a consumer site) — confirm the change shows up.
3. Update the templates / components / utilities tables in [`agent-docs/consumer-integration.md`](../../agent-docs/consumer-integration.md).
4. Bump version per [`agent-docs/release-process.md`](../../agent-docs/release-process.md). Breaking changes = major bump.

## See also

- [`agent-docs/consumer-integration.md`](../../agent-docs/consumer-integration.md) — full export reference
- [`agent-docs/architecture.md`](../../agent-docs/architecture.md) — public-surface boundary rationale
37 changes: 37 additions & 0 deletions .github/instructions/storybook.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
applyTo: "**/*.stories.{ts,tsx,jsx}"
---

# Storybook conventions

## Mocking

- **MSW handlers** live in [`.storybook/mswHandlers.ts`](../../.storybook/mswHandlers.ts). Reuse before authoring new ones.
- The in-memory query engine `filterResultsByConditions` powers MSW responses for datastore queries. Use it for filter/search stories rather than hand-rolling response shapes.
- MSW is Storybook-only — Jest tests do **not** see these handlers.

## Decorators

- `FontAwesomeProToFree` rewrites Pro FA classes to Free equivalents at story render time. It runs in Storybook only — consumer sites don't get this decorator and may render some icons incorrectly if they ship Free FA.
- Stories that render data templates often need a `MemoryRouter` decorator (templates use `Link`/`useNavigate`/etc.).

## QueryClient

Templates that self-wrap with `withQueryProvider` (`Dataset`, `DatasetSearch`, `DatasetList`, `FilteredResource`, `DatasetListSubmenu`, `DatasetDataDictionaryTab`) bring their own client. Don't add a global `QueryClientProvider` decorator that wraps these — you'll end up with two clients.

For non-wrapping components/hooks, add a per-story `QueryClientProvider` decorator with a fresh client per story to prevent cache bleed.

## Common pitfalls

- **Perpetual loading after a mock change**: cached query result from a previous mock. Add a fresh `QueryClient` per story or call `queryClient.clear()` in a decorator.
- **Story imports a Pro FA icon directly**: works in Storybook (decorator rewrites), breaks for consumers. Use Free icons in source where possible.
- **MSW handler missing for a new endpoint**: returns the unhandled-request fallthrough; story shows error or empty state. Add the handler in `mswHandlers.ts`.

## Running

- `npm run storybook` — start dev on :6006
- `npm run build-storybook` — static build for deploys

## See also

- [`agent-docs/storybook-and-mocking.md`](../../agent-docs/storybook-and-mocking.md) — full reference
45 changes: 45 additions & 0 deletions .github/instructions/tests.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
applyTo: "**/*.test.{ts,tsx,js,jsx}"
---

# Test conventions

## Mocking HTTP

Match the mock to the transport:

| Service | Transport | Mock |
|---|---|---|
| `useDatastore` | `fetch` | `global.fetch = jest.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve(...) }))` — branch on URL/query in the implementation |
| `useMetastoreDataset` | `axios` | `jest.mock('axios')` |
| `useSearchAPI` | `axios` | `jest.mock('axios')` |

**Do NOT reach for MSW** — it is not configured for Jest. MSW is Storybook-only ([`.storybook/mswHandlers.ts`](../../.storybook/mswHandlers.ts)).

`jest.mock('axios')` does not intercept `fetch`. If you mock axios and the component uses `useDatastore`, your mock does nothing.

## QueryClientProvider

Templates that self-wrap with `withQueryProvider` (`Dataset`, `DatasetSearch`, `DatasetList`, `FilteredResource`, `DatasetListSubmenu`, `DatasetDataDictionaryTab`) do **not** need a `QueryClientProvider` in tests — they bring their own. Wrapping them again creates two clients in one tree.

Direct calls to `useDatastore`/`useMetastoreDataset`/`useSearchAPI` outside a wrapped export DO need a `QueryClientProvider` (or `withQueryProvider`).

## Routing

Tests that render anything using `Link`/`useNavigate`/`useSearchParams` need a router. Use `MemoryRouter` from `react-router-dom`.

## Fixtures

Live in [`src/tests/fixtures/`](../../src/tests/fixtures/). Reuse before authoring new ones.

## Running

- `npm test` — full suite
- `npm test -- path/to/file` — single file
- `npm test -- --watch` — watch mode

## Footguns

- Mocking axios when the code uses fetch (and vice versa). Check the service before writing the mock.
- Asserting on resolved data without `await waitFor(...)` — React Query is async even when fetches resolve synchronously.
- Using `jest.mock('axios')` at module scope but importing the real axios elsewhere — mock factory hoisting can drop calls.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ dist
.idea
.docz
docs
agent-docs-eval
.DS_Store
.parcel-cache
coverage
Expand Down
Loading