diff --git a/.claude/commands/expose-option.md b/.claude/commands/expose-option.md new file mode 100644 index 0000000000..7601e7a56e --- /dev/null +++ b/.claude/commands/expose-option.md @@ -0,0 +1,49 @@ +--- +description: Thread a new option on an existing connector through all three flavors (JS, React, Vue) + common tests +argument-hint: [short description of what it does] +--- + +You are exposing a new option through the InstantSearch fan-out. The architecture is **connector (logic) → flavor wrapper → shared UI**, so the option is defined once in the connector and surfaced by each flavor. + +**Input:** `$ARGUMENTS` +- First token = the connector (e.g. `connectRefinementList` or just `refinementList`). +- Second token = the new option name (e.g. `clearOnChange`). +- Remaining text = a short description of the behavior, if given. + +If the connector or option name is missing or ambiguous, ask before changing files. If the behavior isn't specified, ask what the option should do — do **not** guess at semantics. + +This is a checklist for a cross-flavor option rollout. The connector is the single source of truth; React and Vue only *wrap* it, so do the connector first, then the wrappers. + +## Steps + +1. **Scope the contract first.** Read the connector `packages/instantsearch.js/src/connectors//connect.ts` and its JS widget `packages/instantsearch.js/src/widgets//.tsx`. Confirm the option doesn't already exist and settle the exact semantics + the option's type signature. This is the contract every flavor must match. + +2. **Connector first** (in `packages/instantsearch.js`): + - Add the option to the connector's widget-params type with a TSDoc comment. + - Implement the behavior in the lifecycle (`init`/`render`/`getWidgetSearchParameters`/etc.). + - Surface it on the **typed render state** if consumers need it (untyped render state is an oxlint error). + - Thread it through the JS widget. Keep it framework-agnostic — no React/Vue/DOM-framework code in the connector. + + For a presentational-only variant, **reuse the existing connector** with a distinct `$$widgetType` — never fork connector logic. + +3. **If the option changes shared markup/layout** (not just behavior), update the shared `createComponent` in `packages/instantsearch-ui-components` *before* wiring the flavors — its output is the contract they consume. + +4. **Thread it through the wrappers** (independent of each other once the connector is settled): + - **React** — the hook `packages/react-instantsearch-core/src/connectors/use.ts` and the component `packages/react-instantsearch/src/widgets/.tsx`, kept in sync and typed. + - **Vue** — `packages/vue-instantsearch/src/components/.{vue,js}` (and `src/mixins/` if wiring lives there), working for **both Vue 2 and Vue 3**. + +5. **Tests.** Cross-flavor behavior → add/extend `tests/common/connectors//` (or `.../widgets//`) and register it in each flavor's `common-*.test.*` (see `tests/common/README.md`); flavor-specific assertions stay in each package's co-located `__tests__/`. If browser-level behavior is affected, add an e2e spec (see `.claude/rules/e2e.md`). + +6. **Verify.** Confirm all three flavors expose the same option with consistent naming/types, then: + ```bash + yarn jest packages/instantsearch.js/src/connectors/ + yarn jest + yarn type-check + yarn lint:changed + ``` + (Or run `/preflight`.) + +## Notes + +- Why this order: the connector is the contract; React and Vue only *wrap* it, so they're independent of each other and safe to do once the connector is settled. +- Don't hand-edit changelogs (Ship.js generates them). Commit message: `feat(): add option`. diff --git a/.claude/commands/preflight.md b/.claude/commands/preflight.md new file mode 100644 index 0000000000..b54556a7f1 --- /dev/null +++ b/.claude/commands/preflight.md @@ -0,0 +1,30 @@ +--- +description: Run the pre-push checklist — lint, types, relevant tests, and a Conventional-Commit sanity check +allowed-tools: Bash(yarn lint:changed), Bash(yarn lint:fix), Bash(yarn type-check), Bash(yarn type-check:*), Bash(yarn jest:*), Bash(git status:*), Bash(git diff:*), Bash(git log:*) +--- + +Run the InstantSearch pre-push checks against the **current changes** and report a concise pass/fail summary. Don't fix anything silently — surface failures and propose fixes. + +## Steps + +1. **Scope the change.** `git status` + `git diff --name-only` (and vs. the base branch if on a feature branch) to see which packages/files are touched. + +2. **Lint the changed files:** `yarn lint:changed`. If it fails, show the violations; offer `yarn lint:fix` for auto-fixable ones (note that oxlint bans `for-in`/`for-of`/`async` and implicit `any` in library code — those need manual fixes). + +3. **Type-check:** `yarn type-check`. If any touched code interacts with the legacy algoliasearch versions, also run `yarn type-check:v3` / `yarn type-check:v4`. + +4. **Run the relevant tests** — not the whole suite. Map changed files to their `yarn jest ` (e.g. a connector → `yarn jest packages/instantsearch.js/src/connectors/`; a React widget → its `__tests__` path). If a connector's cross-flavor behavior changed, include the matching `tests/common/` path. + +5. **Conventional-Commit sanity check.** Look at staged changes / recent commits and confirm the message fits `type(scope): description` (scope = widget/connector or topic like `deps`/`ci`). Flag if it doesn't; suggest a corrected message. Reference issues with `fix #1234` in the body when applicable. + +## Output + +Report a short checklist: +- ✅/❌ lint:changed +- ✅/❌ type-check (+ v3/v4 if run) +- ✅/❌ tests (list the paths run) +- ✅/❌ commit message + +For any ❌, show the failing output and the smallest fix. Don't run the full `yarn test` or `yarn build` unless asked — this is the fast pre-push loop. + +Run the checks directly. If a check fails, fix it in the package that owns the failing file before re-running. diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000000..681311eb9c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..a16d60a9d7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,80 @@ +# InstantSearch monorepo + +Search UI libraries for Algolia across three flavors (vanilla JS, React, Vue), sharing one connector + UI-component core. Yarn 1 workspaces + Lerna (independent versioning). + +## Repo map + +| Package | What it is | +|---|---| +| `packages/instantsearch.js` | **Core.** Vanilla JS library. **All connectors live here** (`src/connectors/`) and are shared by every flavor. | +| `packages/react-instantsearch-core` | Headless React: hooks (`useSearchBox`, …) that wrap connectors. Framework-agnostic (web/RN). | +| `packages/react-instantsearch` | React DOM widgets = hooks + UI from `instantsearch-ui-components`. | +| `packages/vue-instantsearch` | Vue 2/3 components wrapping connectors. | +| `packages/instantsearch-ui-components` | **Shared UI layer.** Framework-agnostic widget markup + `ais-*` classes via factory components (`createComponent({ createElement, Fragment })`); reused by React directly, Vue via `renderCompat`, JS via the Preact renderer. Newer widgets get their layout here; older ones still define it in `instantsearch.js/src/components/`. | +| `packages/algoliasearch-helper` | Low-level search-parameter/state manager underneath connectors. Mature, separately-versioned; **rarely the place for a change** — fix the connector instead. See its `CLAUDE.md`. | +| `packages/instantsearch.css` | Default themes. | +| `packages/react-instantsearch-nextjs` / `-router-nextjs` | Next.js App Router / Pages Router integrations. | +| `packages/create-instantsearch-app`, `instantsearch-cli`, `instantsearch-codemods` | Scaffolding / CLI / migration tooling. | + +Architecture in one line: **connector (logic, in instantsearch.js) → flavor wrapper (JS widget / React hook+component / Vue component) → shared UI components**. Change behavior in the connector; change markup in the UI layer. + +## Where work goes (per layer) + +Because behavior lives in the connector and the flavors only wrap it, most changes have a natural home — and the per-package `CLAUDE.md` files carry each layer's conventions: + +| Work in… | Package | +|---|---| +| Connectors, JS widgets, runtime (`src/lib`), legacy Preact components (`src/components`), types | `packages/instantsearch.js` | +| Shared framework-agnostic markup / `ais-*` classes | `packages/instantsearch-ui-components` | +| React widgets/hooks + Next.js integrations | `react-instantsearch` / `react-instantsearch-core` / `*-nextjs` | +| Vue components (Vue 2 **and** 3) | `vue-instantsearch` | + +For a **cross-flavor change**, settle the contract in the connector first, then update the React and Vue wrappers, then the common tests. If the change is to **shared markup/layout** (not behavior), it lives in `instantsearch-ui-components` and the flavors consume the result — a class/structure change there ripples to all flavors and `instantsearch.css` at once. + +**Adding or surfacing a connector option is always cross-flavor work** — settle the contract in the connector first, then thread it through React **and** Vue, then add/extend common tests. Don't wire one flavor and stop. The **`/expose-option