From 8f5d28fec4bf5bd64f80293ab5a94a81c76e1cfe Mon Sep 17 00:00:00 2001 From: Vasco Bettencourt Date: Mon, 18 May 2026 13:01:39 +0100 Subject: [PATCH 1/5] feat(instantsearch): add Source-of-truth check workflow step Inserts a mandatory Source-of-truth check between Discover and Build that requires reading installed types, fetching live Algolia docs, and grepping installed CSS before writing any code not covered by a baked pattern. This closes the long tail of training-data drift (renderState shapes, future.* flags, recently shipped widgets) without baking every prop into the skill. Adds references/react/source-of-truth.md with concrete .d.ts paths and doc URL patterns. Updates technology-rules, anti-patterns, and glossary with the matching rules and terms (renderState, middleware, SSR, virtual/dynamic widgets, future flags, InstantSearch instance). Adds eval 10 to exercise the new workflow on future-flag setup. --- skills/instantsearch/SKILL.md | 32 ++++++-- skills/instantsearch/evals/evals.json | 13 +++ .../references/react/anti-patterns.md | 24 ++++-- .../references/react/glossary.md | 8 ++ .../references/react/source-of-truth.md | 79 +++++++++++++++++++ .../references/react/technology-rules.md | 11 ++- 6 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 skills/instantsearch/references/react/source-of-truth.md diff --git a/skills/instantsearch/SKILL.md b/skills/instantsearch/SKILL.md index 0cdb2d1..0c9aaca 100644 --- a/skills/instantsearch/SKILL.md +++ b/skills/instantsearch/SKILL.md @@ -59,9 +59,30 @@ If starting fresh, go step by step, waiting for the user's answer before moving Do not proceed until you have credentials, schema, rendering preferences, and an understanding of the project's design. -### 2. Build +### 2. Source-of-truth check -This skill currently covers **autocomplete**. If the user asks for a different search pattern (e.g., a search results page, faceted search), let them know and ask how they'd like to proceed. +This skill bakes guidance for the highest-value patterns only. Whenever you need a widget, hook, connector, prop, middleware, future flag, or API that is **not explicitly documented in this skill**, you must consult the source of truth before writing code. Training data is stale, lossy, and frequently wrong on prop shapes. + +Follow the library's source-of-truth reference for the exact commands and URL patterns: + +| Library | Reference | +| ------- | ---------------------------------------------------------- | +| React | [source-of-truth.md](references/react/source-of-truth.md) | +| Vue | — | +| JS | — | + +The reference always covers, in order: + +1. **Read installed types** in `node_modules` for prop shapes, renderState, and connector contracts. +2. **Fetch the live Algolia docs** at the canonical URL pattern. +3. **Grep installed CSS / source** for class names you intend to style. +4. **Only then write code.** If a step fails (offline, 404, missing types), ask the user before guessing. + +Do this even when a pattern reference exists, if you need any prop or behavior the pattern reference does not explicitly cover. + +### 3. Build + +Pick the matching pattern reference for the library and the user's request. If no pattern reference exists for what the user asked for, fall back on the source-of-truth check (Step 2) and ask the user before scaffolding speculative widget trees. Patterns available for each library: @@ -77,13 +98,14 @@ Also read and apply the library-level references (apply regardless of pattern): | Anti-patterns | [anti-patterns.md](references/react/anti-patterns.md) | — | — | | Styling | [styling.md](references/react/styling.md) | — | — | | Glossary | [glossary.md](references/react/glossary.md) | — | — | +| Source of truth | [source-of-truth.md](references/react/source-of-truth.md) | — | — | -If you are unsure how to implement a feature with built-in widgets, ask the user. Do not fall back to legacy libraries or custom connectors. +Consult types and live docs first (Step 2). Ask the user only if both fail. Never fall back to legacy libraries or guessed APIs. -### 3. Style +### 4. Style Follow the library's styling guide step by step, then apply the pattern-specific styling guide. Match the site's existing CSS methodology, color scheme, typography, spacing, and component patterns. -### 4. Review +### 5. Review Review your work against the library's anti-patterns, the pattern-specific anti-patterns, and the features checklist. Fix any violations. diff --git a/skills/instantsearch/evals/evals.json b/skills/instantsearch/evals/evals.json index 4918394..61f9e52 100644 --- a/skills/instantsearch/evals/evals.json +++ b/skills/instantsearch/evals/evals.json @@ -75,6 +75,19 @@ "Matches the dark theme", "Does not guess ais-* class names" ] + }, + { + "id": 10, + "prompt": "Set up my React InstantSearch project with the correct future flags so it's ready for the next major release.", + "expected_output": "Future flags applied based on installed types and the live Upgrade guides, not training-data recall", + "files": [], + "expectations": [ + "Reads InstantSearchProps['future'] from node_modules/react-instantsearch types to enumerate available flags", + "Fetches https://www.algolia.com/doc/guides/building-search-ui/upgrade-guides/react to confirm current recommended flags and defaults", + "Applies flags on the InstantSearch (or InstantSearchNext) provider with values matching the installed package version", + "Does NOT enable a flag that does not appear in the installed type", + "Asks the user before enabling any flag whose default cannot be determined from types or docs" + ] } ] } diff --git a/skills/instantsearch/references/react/anti-patterns.md b/skills/instantsearch/references/react/anti-patterns.md index 67882d4..8d9e315 100644 --- a/skills/instantsearch/references/react/anti-patterns.md +++ b/skills/instantsearch/references/react/anti-patterns.md @@ -4,11 +4,19 @@ These apply across **all** React InstantSearch patterns. They supplement the tec If you catch yourself doing any of these, stop and reconsider: -| Anti-pattern | Why it's wrong | What to do instead | -| ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -| Using `` in a Next.js App Router project | Missing SSR/RSC integration | Use `` from `react-instantsearch-nextjs` | -| Using `react-instantsearch-router-nextjs` with App Router | Requires Pages Router's `singletonRouter` | Use the `routing` prop on `` directly | -| Building search with `algoliasearch` client directly | Bypasses the widget tree and state management | Use InstantSearch widgets | -| Hardcoding an index name like `instant_search` | Likely a demo index from training data | Ask the user | -| Styling widgets with arbitrary selectors (tag names, positional selectors, generated classes) | Brittle, breaks on updates | Write CSS targeting `ais-*` selectors. See the styling guide | -| Guessing `ais-*` class names without checking | Names are CamelCase and easy to get wrong. Broken styles with no error | Grep `node_modules/react-instantsearch` for actual class names before writing CSS | +| Anti-pattern | Why it's wrong | What to do instead | +| --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| Using `` in a Next.js App Router project | Missing SSR/RSC integration | Use `` from `react-instantsearch-nextjs` | +| Using `react-instantsearch-router-nextjs` with App Router | Requires Pages Router's `singletonRouter` | Use the `routing` prop on `` directly | +| Building search with `algoliasearch` client directly | Bypasses the widget tree and state management | Use InstantSearch widgets | +| Hardcoding an index name like `instant_search` | Likely a demo index from training data | Ask the user | +| Styling widgets with arbitrary selectors (tag names, positional selectors, generated classes) | Brittle, breaks on updates | Write CSS targeting `ais-*` selectors. See the styling guide | +| Guessing `ais-*` class names without checking | Names are CamelCase and easy to get wrong. Broken styles with no error | Grep `node_modules/react-instantsearch` for actual class names before writing CSS | +| Rebuilding ``, ``, ``, `` from `useHits` + local state | Reimplements behavior the widget already handles (URL sync, refinements API, accessibility) | Use the widget. Reach for the corresponding hook only when the rendering shape genuinely cannot be styled | +| Guessing prop shapes for refinement widgets (`limit`, `showMore`, `showMoreLimit`, `searchable`, `searchablePlaceholder`, `transformItems`, `operator`, `sortBy`) | Prop names and accepted values change between versions; silent runtime breakage | Read the widget's `.d.ts` from `node_modules/react-instantsearch/dist/es/widgets/` first | +| Writing a custom `useConnector` hook when a built-in widget + `classNames` would do | Adds maintenance burden and re-renders; loses built-in accessibility and routing wiring | Use the widget. Custom widgets are for cases the built-in widget genuinely cannot render. See [custom-widgets.md](custom-widgets.md) | +| Using middleware to sync state to the URL | `routing` already does this; middleware will conflict with it | Configure `routing={true}` (or `routing={{ router, stateMapping }}`). See [middleware.md](middleware.md) | +| Using middleware to set default refinements or query params | These belong in `` or refinement widget defaults, not in middleware | Use `` for index params; use the widget's default-selection prop where supported | +| Destructuring `useInstantSearch()` / `useXxx()` return values from training-data recall | `renderState` field names and nesting change across versions | Read the hook's return type before destructuring | +| Adding `getServerState` on top of `` in App Router | App Router SSR is already handled by ``; doubling up causes hydration mismatches | Drop `getServerState` for App Router. Use it only for Pages Router / non-Next React with SSR. See [ssr.md](ssr.md) | +| Enabling `future.*` flags from training-data recall | Flags change defaults and semantics; defaults flip across releases | Read `InstantSearchProps['future']` from installed types and the Upgrade guides before enabling | diff --git a/skills/instantsearch/references/react/glossary.md b/skills/instantsearch/references/react/glossary.md index ef697ff..ee644c3 100644 --- a/skills/instantsearch/references/react/glossary.md +++ b/skills/instantsearch/references/react/glossary.md @@ -12,3 +12,11 @@ Terms the AI must use correctly. Misusing these leads to wrong code or wrong lib | **Facet** | An attribute configured for filtering in the index settings. Used with widgets like `` or ``. | Not every attribute is a facet. Only those configured as `attributesForFaceting` in the index settings. | | **Refinement** | A filter actively applied by the user (e.g., selecting "Nike" in a brand facet). | "Refinement" is the Algolia/InstantSearch term. The AI may say "filter". That's fine conversationally, but use "refinement" in code (e.g., ``, ``). | | **Filter** | A broader term. In Algolia, filters can be set programmatically via `` or by the user via refinement widgets. | Don't confuse programmatic filters (set by the developer) with refinements (set by the user). | +| **renderState** | The shape returned by a connector / `useXxx` hook (and exposed under `useInstantSearch().renderState`). Field names depend on the connector and version. | Never destructure from training-data recall. Read the hook's `.d.ts` before pulling fields. | +| **InstantSearch instance** | The object returned by `useInstantSearch()` (also accessible as `instance` in middleware). Holds search state, helper, status, results, and `renderState`. | Not a React state object. Mutating it directly is unsupported. Drive it via widgets, hooks, or middleware. | +| **Middleware** | A pluggable hook into the InstantSearch lifecycle (`subscribe`, `started`, `unsubscribe`, `onStateChange`). Used for analytics, custom URL params, side effects. | Not a place to re-implement what `routing` or `` already does. See [middleware.md](middleware.md). | +| **getServerState / SSR provider** | `getServerState` (from `react-instantsearch`) walks the tree, performs initial searches, returns serializable state. `` re-hydrates it on the client. Used for Next.js Pages Router / Remix / other React SSR. | Not needed for App Router (`` handles SSR). Don't combine the two. See [ssr.md](ssr.md). | +| **Virtual widget** | A widget that registers search parameters with the InstantSearch instance without rendering UI (e.g., a `useSearchBox()` hook called in a parent that doesn't render the search input). | Useful for SSR or for splitting state from rendering. Not the same as ``, which is itself effectively a virtual widget. | +| **Dynamic widgets** | A pattern (``) that renders refinement widgets based on facets returned by the search response, using `attributesForFaceting` `searchable(...)` markers and `facetOrdering`. | Read the live doc before using; the rendering rules depend on index `renderingContent` settings. | +| **Future flags** | Opt-in switches under `` that toggle upcoming default behaviors (e.g., `preserveSharedStateOnUnmount`). | Defaults flip between releases. Read `InstantSearchProps['future']` and the Upgrade guides before enabling. | +| **Insights** | Algolia's click/conversion analytics, enabled via `insights={true}` on ``. | Not the same as the `algolia-mcp` analytics queries. This is in-app event tracking, not reporting. | diff --git a/skills/instantsearch/references/react/source-of-truth.md b/skills/instantsearch/references/react/source-of-truth.md new file mode 100644 index 0000000..0b7e95b --- /dev/null +++ b/skills/instantsearch/references/react/source-of-truth.md @@ -0,0 +1,79 @@ +# Source of Truth (React) + +Training data is stale and frequently wrong on prop shapes, renderState fields, future flags, and recently shipped widgets. Before using any widget, hook, connector, prop, middleware, or future flag that is **not explicitly documented in this skill**, complete the steps below in order. Do not skip and do not reorder. + +## 1. Read installed types + +The installed `react-instantsearch` types are the canonical source. They match the version actually running in the project, so they cannot drift. + +```bash +ls node_modules/react-instantsearch/dist/es/index.d.ts \ + node_modules/react-instantsearch/dist/es/widgets \ + node_modules/react-instantsearch/dist/es/connectors \ + node_modules/react-instantsearch/dist/es/lib +``` + +What to read, by use case: + +| You need to know | Read | +| ------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `` / `` props | `node_modules/react-instantsearch/dist/es/index.d.ts` (`InstantSearchProps`) | +| Widget props (``, ``, ...) | `node_modules/react-instantsearch/dist/es/widgets/.d.ts` | +| Hook return type / renderState | `node_modules/react-instantsearch/dist/es/connectors/use.d.ts` | +| `useConnector` contract | `node_modules/react-instantsearch-core/dist/es/lib/useConnector.d.ts` | +| Connector contract (for custom widgets) | `node_modules/instantsearch.js/es/connectors//connect.d.ts` | +| `future.*` flags | `InstantSearchProps['future']` in `index.d.ts`, then the connected `InstantSearchOptions` | +| Middleware contract | `node_modules/instantsearch.js/es/types/middleware.d.ts` | +| SSR helpers | `node_modules/react-instantsearch/dist/es/server/getServerState.d.ts` and `InstantSearchSSRProvider.d.ts` | + +For Next.js App Router, also read `node_modules/react-instantsearch-nextjs/dist/es/InstantSearchNext.d.ts` for the props that differ from ``. + +If a file path above does not match the installed package layout (the package may move things between minor versions), grep the package root: + +```bash +rg -l "interface ${SymbolName}" node_modules/react-instantsearch +rg -l "type ${SymbolName}" node_modules/react-instantsearch +``` + +Read the type definition **before** writing the prop. Do not copy a prop from training data and assume it still exists. + +## 2. Fetch the live Algolia docs + +Live docs cover behavior, defaults, and recently shipped widgets that may not be in your training data. Use the canonical URL patterns: + +| Topic | URL pattern | +| ----------------------------- | ------------------------------------------------------------------------------------ | +| Widget reference | `https://www.algolia.com/doc/api-reference/widgets//react` | +| Hook / connector reference | `https://www.algolia.com/doc/api-reference/widgets//react/#hook` | +| Guides (concepts, patterns) | `https://www.algolia.com/doc/guides/building-search-ui//react` | +| Custom widgets | `https://www.algolia.com/doc/guides/building-search-ui/widgets/create-your-own-widgets/react` | +| Routing | `https://www.algolia.com/doc/guides/building-search-ui/going-further/routing-urls/react` | +| SSR | `https://www.algolia.com/doc/guides/building-search-ui/going-further/server-side-rendering/react` | +| Upgrade guides / future flags | `https://www.algolia.com/doc/guides/building-search-ui/upgrade-guides/react` | + +The widget slug is the kebab-case form of the widget name: `` -> `refinement-list`, `` -> `hierarchical-menu`, `` -> `current-refinements`. + +If the URL 404s, search `site:algolia.com/doc ` as a fallback. Do not invent URLs. + +## 3. Grep installed CSS / source for class names + +`ais-*` class names are CamelCase and easy to get wrong. Always grep for the actual rendered classes before writing CSS: + +```bash +rg -o 'ais-[A-Za-z]+(-[a-z]+)?' node_modules/react-instantsearch | sort -u +rg -o 'ais-[A-Za-z]+(-[a-z]+)?' node_modules/instantsearch.js | sort -u +``` + +Scope by widget when you only care about one: + +```bash +rg -o 'ais-[A-Za-z]+(-[a-z]+)?' node_modules/instantsearch.js/es/widgets/refinement-list | sort -u +``` + +For Tailwind v4 specifics, see [styling.md](styling.md). + +## 4. Only then write code + +If any step fails (offline, 404, missing types, ambiguous results), **stop and ask the user**. Do not guess prop names, renderState shapes, connector contracts, or future flag defaults. The cost of a wrong guess is silent runtime breakage, which is worse than a clarifying question. + +When you do write the code, leave a brief comment above non-obvious props pointing to the type or doc URL you sourced them from, so reviewers can verify. Do not narrate the code, but do annotate provenance for anything beyond the documented baked patterns. diff --git a/skills/instantsearch/references/react/technology-rules.md b/skills/instantsearch/references/react/technology-rules.md index a6968ea..64c8995 100644 --- a/skills/instantsearch/references/react/technology-rules.md +++ b/skills/instantsearch/references/react/technology-rules.md @@ -4,6 +4,7 @@ These rules apply to **all** React InstantSearch patterns, not just autocomplete ## Always +- **Before using any widget, hook, connector, prop, middleware, or `future.*` flag not explicitly documented in this skill, complete the [Source-of-truth check](source-of-truth.md).** Read the installed types, fetch the live Algolia docs, grep installed CSS for class names. Do not write code from training-data recall alone. - If `react-instantsearch` and `algoliasearch` are not already installed, install the latest versions and **wait for the install to complete** before proceeding. If using Next.js App Router, also install `react-instantsearch-nextjs`. Use the same package manager as the rest of the project (npm, yarn, pnpm, bun, etc.). If the project uses a CDN (e.g., jsDelivr with script tags), follow that pattern instead. Do not read types or import from packages until the install has finished. Do not fetch types from the web (unpkg, GitHub, etc.). - Use `react-instantsearch` v7. Import from `react-instantsearch`, never from `react-instantsearch-dom`. **If using Next.js App Router**, use `InstantSearchNext` from `react-instantsearch-nextjs` instead of ``. The props are the same. - **Place the `` (or ``) provider high in the component tree**. At the layout level, not wrapping a single widget. Other widgets (search results, facets, etc.) will need to be nested inside it later. Decouple the provider from any specific widget. @@ -15,10 +16,18 @@ These rules apply to **all** React InstantSearch patterns, not just autocomplete - **Follow the [styling guide](styling.md) for all widget styling.** Write CSS targeting `ais-*` selectors. Grep for actual class names before writing CSS. - Set `insights={true}` on the `` wrapper to enable click analytics via Algolia Insights. - **Set up routing** so the search state is reflected in the URL. Search result URLs must be shareable and bookmarkable. Enable routing by passing `routing` or `routing={true}` on the `` (or ``) wrapper. For further customization, pass an object to `routing` where `router` accepts the same options as `history`, and `stateMapping` accepts the same options as in `InstantSearch`. Read the type definitions for full configuration options. +- **`future.*` flags must be looked up, not guessed.** Flags like `preserveSharedStateOnUnmount` change semantics across versions. Read `InstantSearchProps['future']` from the installed types and the [Upgrade guides](https://www.algolia.com/doc/guides/building-search-ui/upgrade-guides/react) before enabling, disabling, or assuming a default. +- **For SSR, pick the right tool for the framework**: + - **Next.js App Router**: `` from `react-instantsearch-nextjs` already handles SSR. Do not add `getServerState` on top. + - **Next.js Pages Router, Remix, or other React with SSR**: use `getServerState` + `` per [ssr.md](ssr.md). + - **CSR-only React (Vite, CRA-style)**: SSR is not applicable. Do not add SSR helpers. +- **Use middleware only for what `routing`, `Configure`, and built-in widgets cannot do.** Custom analytics tagging, syncing search state to a non-URL store, or instrumenting refinements are legitimate. Re-implementing URL sync, query persistence, or facet defaults is not. See [middleware.md](middleware.md). ## Never - **Never use v6 APIs.** Do not import from `react-instantsearch-dom`, do not use class-based connectors, do not use `connectSearchBox` or similar v6 patterns. - **Never guess credentials or index names.** Always ask the user. Never silently use a public demo index. - **Never start coding before completing discovery.** You need credentials, schema understanding, rendering preferences, and project design context first. -- **Never overuse connectors.** If you're reaching for `useSearchBox`, `useHits`, `useRefinementList` etc., first check whether the built-in widget with a `classNames` prop achieves the same result. Custom hooks/connectors are for genuinely custom rendering that widgets cannot handle. +- **Never overuse connectors.** If you're reaching for `useSearchBox`, `useHits`, `useRefinementList` etc., first check whether the built-in widget with a `classNames` prop achieves the same result. Custom hooks/connectors are for genuinely custom rendering that widgets cannot handle. See [custom-widgets.md](custom-widgets.md) for the contract and when it's justified. +- **Never guess `renderState` shape.** When using `useInstantSearch()` or any `useXxx` hook, read the hook's return type from `node_modules` before destructuring. Field names and nesting change across versions. +- **Never fall back to legacy libraries when something feels missing.** No `@algolia/autocomplete-js`, no `react-instantsearch-dom`, no `react-instantsearch-router-nextjs` on App Router. If the source-of-truth check still leaves a gap, ask the user. From 9b69b3d0bb958e19f194ccb9ffee639952a5c96b Mon Sep 17 00:00:00 2001 From: Vasco Bettencourt Date: Mon, 18 May 2026 14:03:14 +0100 Subject: [PATCH 2/5] review(instantsearch): address Sarah's review on PR #22 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - source-of-truth.md: append `.md` to canonical Algolia doc URLs so agents fetch markdown (no HTML chrome). Replace the duplicated CSS grep section with a one-liner pointing to styling.md. - technology-rules.md: append `.md` to the Upgrade-guides link. Fold all Next.js / SSR guidance into the v7 import rule (App Router, Pages Router, Remix/SSR, CSR-only). Drop the legacy-libraries rule (overlaps with v6 rule and autocomplete/anti-patterns.md). Drop the middleware rule and the `[custom-widgets.md]` cross-ref (those land with PR 3 alongside the dedicated reference files). - anti-patterns.md: drop the SSR-doubling and middleware-specific entries; they belong with PR 3 alongside ssr.md / middleware.md. - glossary.md: keep the new terms but drop the cross-reference links to middleware.md / ssr.md (added back in PR 3). Net effect: PR 1 is now self-contained — all internal links resolve on this branch alone — and stops restating rules that already live in autocomplete/anti-patterns.md. --- .../references/react/anti-patterns.md | 5 +-- .../references/react/glossary.md | 4 +- .../references/react/source-of-truth.md | 37 +++++++------------ .../references/react/technology-rules.md | 18 ++++----- 4 files changed, 25 insertions(+), 39 deletions(-) diff --git a/skills/instantsearch/references/react/anti-patterns.md b/skills/instantsearch/references/react/anti-patterns.md index 8d9e315..5f04698 100644 --- a/skills/instantsearch/references/react/anti-patterns.md +++ b/skills/instantsearch/references/react/anti-patterns.md @@ -14,9 +14,6 @@ If you catch yourself doing any of these, stop and reconsider: | Guessing `ais-*` class names without checking | Names are CamelCase and easy to get wrong. Broken styles with no error | Grep `node_modules/react-instantsearch` for actual class names before writing CSS | | Rebuilding ``, ``, ``, `` from `useHits` + local state | Reimplements behavior the widget already handles (URL sync, refinements API, accessibility) | Use the widget. Reach for the corresponding hook only when the rendering shape genuinely cannot be styled | | Guessing prop shapes for refinement widgets (`limit`, `showMore`, `showMoreLimit`, `searchable`, `searchablePlaceholder`, `transformItems`, `operator`, `sortBy`) | Prop names and accepted values change between versions; silent runtime breakage | Read the widget's `.d.ts` from `node_modules/react-instantsearch/dist/es/widgets/` first | -| Writing a custom `useConnector` hook when a built-in widget + `classNames` would do | Adds maintenance burden and re-renders; loses built-in accessibility and routing wiring | Use the widget. Custom widgets are for cases the built-in widget genuinely cannot render. See [custom-widgets.md](custom-widgets.md) | -| Using middleware to sync state to the URL | `routing` already does this; middleware will conflict with it | Configure `routing={true}` (or `routing={{ router, stateMapping }}`). See [middleware.md](middleware.md) | -| Using middleware to set default refinements or query params | These belong in `` or refinement widget defaults, not in middleware | Use `` for index params; use the widget's default-selection prop where supported | +| Writing a custom `useConnector` hook when a built-in widget + `classNames` would do | Adds maintenance burden and re-renders; loses built-in accessibility and routing wiring | Use the widget. Custom widgets are for cases the built-in widget genuinely cannot render | | Destructuring `useInstantSearch()` / `useXxx()` return values from training-data recall | `renderState` field names and nesting change across versions | Read the hook's return type before destructuring | -| Adding `getServerState` on top of `` in App Router | App Router SSR is already handled by ``; doubling up causes hydration mismatches | Drop `getServerState` for App Router. Use it only for Pages Router / non-Next React with SSR. See [ssr.md](ssr.md) | | Enabling `future.*` flags from training-data recall | Flags change defaults and semantics; defaults flip across releases | Read `InstantSearchProps['future']` from installed types and the Upgrade guides before enabling | diff --git a/skills/instantsearch/references/react/glossary.md b/skills/instantsearch/references/react/glossary.md index ee644c3..3627396 100644 --- a/skills/instantsearch/references/react/glossary.md +++ b/skills/instantsearch/references/react/glossary.md @@ -14,8 +14,8 @@ Terms the AI must use correctly. Misusing these leads to wrong code or wrong lib | **Filter** | A broader term. In Algolia, filters can be set programmatically via `` or by the user via refinement widgets. | Don't confuse programmatic filters (set by the developer) with refinements (set by the user). | | **renderState** | The shape returned by a connector / `useXxx` hook (and exposed under `useInstantSearch().renderState`). Field names depend on the connector and version. | Never destructure from training-data recall. Read the hook's `.d.ts` before pulling fields. | | **InstantSearch instance** | The object returned by `useInstantSearch()` (also accessible as `instance` in middleware). Holds search state, helper, status, results, and `renderState`. | Not a React state object. Mutating it directly is unsupported. Drive it via widgets, hooks, or middleware. | -| **Middleware** | A pluggable hook into the InstantSearch lifecycle (`subscribe`, `started`, `unsubscribe`, `onStateChange`). Used for analytics, custom URL params, side effects. | Not a place to re-implement what `routing` or `` already does. See [middleware.md](middleware.md). | -| **getServerState / SSR provider** | `getServerState` (from `react-instantsearch`) walks the tree, performs initial searches, returns serializable state. `` re-hydrates it on the client. Used for Next.js Pages Router / Remix / other React SSR. | Not needed for App Router (`` handles SSR). Don't combine the two. See [ssr.md](ssr.md). | +| **Middleware** | A pluggable hook into the InstantSearch lifecycle (`subscribe`, `started`, `unsubscribe`, `onStateChange`). Used for analytics, custom URL params, side effects. | Not a place to re-implement what `routing` or `` already does. | +| **getServerState / SSR provider** | `getServerState` (from `react-instantsearch`) walks the tree, performs initial searches, returns serializable state. `` re-hydrates it on the client. Used for Next.js Pages Router / Remix / other React SSR. | Not needed for App Router (`` handles SSR). Don't combine the two. | | **Virtual widget** | A widget that registers search parameters with the InstantSearch instance without rendering UI (e.g., a `useSearchBox()` hook called in a parent that doesn't render the search input). | Useful for SSR or for splitting state from rendering. Not the same as ``, which is itself effectively a virtual widget. | | **Dynamic widgets** | A pattern (``) that renders refinement widgets based on facets returned by the search response, using `attributesForFaceting` `searchable(...)` markers and `facetOrdering`. | Read the live doc before using; the rendering rules depend on index `renderingContent` settings. | | **Future flags** | Opt-in switches under `` that toggle upcoming default behaviors (e.g., `preserveSharedStateOnUnmount`). | Defaults flip between releases. Read `InstantSearchProps['future']` and the Upgrade guides before enabling. | diff --git a/skills/instantsearch/references/react/source-of-truth.md b/skills/instantsearch/references/react/source-of-truth.md index 0b7e95b..d2e0049 100644 --- a/skills/instantsearch/references/react/source-of-truth.md +++ b/skills/instantsearch/references/react/source-of-truth.md @@ -41,36 +41,25 @@ Read the type definition **before** writing the prop. Do not copy a prop from tr Live docs cover behavior, defaults, and recently shipped widgets that may not be in your training data. Use the canonical URL patterns: -| Topic | URL pattern | -| ----------------------------- | ------------------------------------------------------------------------------------ | -| Widget reference | `https://www.algolia.com/doc/api-reference/widgets//react` | -| Hook / connector reference | `https://www.algolia.com/doc/api-reference/widgets//react/#hook` | -| Guides (concepts, patterns) | `https://www.algolia.com/doc/guides/building-search-ui//react` | -| Custom widgets | `https://www.algolia.com/doc/guides/building-search-ui/widgets/create-your-own-widgets/react` | -| Routing | `https://www.algolia.com/doc/guides/building-search-ui/going-further/routing-urls/react` | -| SSR | `https://www.algolia.com/doc/guides/building-search-ui/going-further/server-side-rendering/react` | -| Upgrade guides / future flags | `https://www.algolia.com/doc/guides/building-search-ui/upgrade-guides/react` | +| Topic | URL pattern | +| ----------------------------- | ------------------------------------------------------------------------------------------------ | +| Widget reference | `https://www.algolia.com/doc/api-reference/widgets//react.md` | +| Hook / connector reference | `https://www.algolia.com/doc/api-reference/widgets//react.md#hook` | +| Guides (concepts, patterns) | `https://www.algolia.com/doc/guides/building-search-ui//react.md` | +| Custom widgets | `https://www.algolia.com/doc/guides/building-search-ui/widgets/create-your-own-widgets/react.md` | +| Routing | `https://www.algolia.com/doc/guides/building-search-ui/going-further/routing-urls/react.md` | +| SSR | `https://www.algolia.com/doc/guides/building-search-ui/going-further/server-side-rendering/react.md` | +| Upgrade guides / future flags | `https://www.algolia.com/doc/guides/building-search-ui/upgrade-guides/react.md` | + +Append `.md` to the URL so the agent fetches the markdown source instead of the rendered HTML page (saves tokens, no layout chrome). The widget slug is the kebab-case form of the widget name: `` -> `refinement-list`, `` -> `hierarchical-menu`, `` -> `current-refinements`. If the URL 404s, search `site:algolia.com/doc ` as a fallback. Do not invent URLs. -## 3. Grep installed CSS / source for class names +## 3. Grep installed source for class names -`ais-*` class names are CamelCase and easy to get wrong. Always grep for the actual rendered classes before writing CSS: - -```bash -rg -o 'ais-[A-Za-z]+(-[a-z]+)?' node_modules/react-instantsearch | sort -u -rg -o 'ais-[A-Za-z]+(-[a-z]+)?' node_modules/instantsearch.js | sort -u -``` - -Scope by widget when you only care about one: - -```bash -rg -o 'ais-[A-Za-z]+(-[a-z]+)?' node_modules/instantsearch.js/es/widgets/refinement-list | sort -u -``` - -For Tailwind v4 specifics, see [styling.md](styling.md). +For `ais-*` CSS class names you intend to style, follow [styling.md](styling.md). The same rule applies here: grep installed source, never guess. ## 4. Only then write code diff --git a/skills/instantsearch/references/react/technology-rules.md b/skills/instantsearch/references/react/technology-rules.md index 64c8995..0817714 100644 --- a/skills/instantsearch/references/react/technology-rules.md +++ b/skills/instantsearch/references/react/technology-rules.md @@ -6,7 +6,13 @@ These rules apply to **all** React InstantSearch patterns, not just autocomplete - **Before using any widget, hook, connector, prop, middleware, or `future.*` flag not explicitly documented in this skill, complete the [Source-of-truth check](source-of-truth.md).** Read the installed types, fetch the live Algolia docs, grep installed CSS for class names. Do not write code from training-data recall alone. - If `react-instantsearch` and `algoliasearch` are not already installed, install the latest versions and **wait for the install to complete** before proceeding. If using Next.js App Router, also install `react-instantsearch-nextjs`. Use the same package manager as the rest of the project (npm, yarn, pnpm, bun, etc.). If the project uses a CDN (e.g., jsDelivr with script tags), follow that pattern instead. Do not read types or import from packages until the install has finished. Do not fetch types from the web (unpkg, GitHub, etc.). -- Use `react-instantsearch` v7. Import from `react-instantsearch`, never from `react-instantsearch-dom`. **If using Next.js App Router**, use `InstantSearchNext` from `react-instantsearch-nextjs` instead of ``. The props are the same. +- Use `react-instantsearch` v7. Import from `react-instantsearch`, never from `react-instantsearch-dom`. **For Next.js, pick the provider by router**: + - **App Router**: use `` from `react-instantsearch-nextjs` instead of ``. It already handles SSR — do not add `getServerState` on top. + - **Pages Router**: use `` together with `getServerState` and `` from `react-instantsearch`. Do not use `react-instantsearch-router-nextjs`. + - **Remix or other React frameworks with SSR**: same as Pages Router (`getServerState` + ``). + - **CSR-only React (Vite, CRA-style)**: plain ``. Do not add SSR helpers. + + The props of `` and `` match. - **Place the `` (or ``) provider high in the component tree**. At the layout level, not wrapping a single widget. Other widgets (search results, facets, etc.) will need to be nested inside it later. Decouple the provider from any specific widget. - Use `algoliasearch` v5 (the latest) to create the search client. Do not use older v4/v3 patterns. If already installed, check `package.json` for the version. The API changed between v4 and v5. If not installed, install the latest version. For web applications, prefer the lite client for a smaller bundle: `import { liteClient as algoliasearch } from "algoliasearch/lite";` - **Declare the search client outside of React components.** The client's reference must remain stable to preserve its internal cache. Do not inline `algoliasearch(...)` inside the `searchClient` prop, and do not create it inside a component with `useCallback` or `useMemo` unless there is no alternative. @@ -16,18 +22,12 @@ These rules apply to **all** React InstantSearch patterns, not just autocomplete - **Follow the [styling guide](styling.md) for all widget styling.** Write CSS targeting `ais-*` selectors. Grep for actual class names before writing CSS. - Set `insights={true}` on the `` wrapper to enable click analytics via Algolia Insights. - **Set up routing** so the search state is reflected in the URL. Search result URLs must be shareable and bookmarkable. Enable routing by passing `routing` or `routing={true}` on the `` (or ``) wrapper. For further customization, pass an object to `routing` where `router` accepts the same options as `history`, and `stateMapping` accepts the same options as in `InstantSearch`. Read the type definitions for full configuration options. -- **`future.*` flags must be looked up, not guessed.** Flags like `preserveSharedStateOnUnmount` change semantics across versions. Read `InstantSearchProps['future']` from the installed types and the [Upgrade guides](https://www.algolia.com/doc/guides/building-search-ui/upgrade-guides/react) before enabling, disabling, or assuming a default. -- **For SSR, pick the right tool for the framework**: - - **Next.js App Router**: `` from `react-instantsearch-nextjs` already handles SSR. Do not add `getServerState` on top. - - **Next.js Pages Router, Remix, or other React with SSR**: use `getServerState` + `` per [ssr.md](ssr.md). - - **CSR-only React (Vite, CRA-style)**: SSR is not applicable. Do not add SSR helpers. -- **Use middleware only for what `routing`, `Configure`, and built-in widgets cannot do.** Custom analytics tagging, syncing search state to a non-URL store, or instrumenting refinements are legitimate. Re-implementing URL sync, query persistence, or facet defaults is not. See [middleware.md](middleware.md). +- **`future.*` flags must be looked up, not guessed.** Flags like `preserveSharedStateOnUnmount` change semantics across versions. Read `InstantSearchProps['future']` from the installed types and the [Upgrade guides](https://www.algolia.com/doc/guides/building-search-ui/upgrade-guides/react.md) before enabling, disabling, or assuming a default. ## Never - **Never use v6 APIs.** Do not import from `react-instantsearch-dom`, do not use class-based connectors, do not use `connectSearchBox` or similar v6 patterns. - **Never guess credentials or index names.** Always ask the user. Never silently use a public demo index. - **Never start coding before completing discovery.** You need credentials, schema understanding, rendering preferences, and project design context first. -- **Never overuse connectors.** If you're reaching for `useSearchBox`, `useHits`, `useRefinementList` etc., first check whether the built-in widget with a `classNames` prop achieves the same result. Custom hooks/connectors are for genuinely custom rendering that widgets cannot handle. See [custom-widgets.md](custom-widgets.md) for the contract and when it's justified. +- **Never overuse connectors.** If you're reaching for `useSearchBox`, `useHits`, `useRefinementList` etc., first check whether the built-in widget with a `classNames` prop achieves the same result. Custom hooks/connectors are for genuinely custom rendering that widgets cannot handle. - **Never guess `renderState` shape.** When using `useInstantSearch()` or any `useXxx` hook, read the hook's return type from `node_modules` before destructuring. Field names and nesting change across versions. -- **Never fall back to legacy libraries when something feels missing.** No `@algolia/autocomplete-js`, no `react-instantsearch-dom`, no `react-instantsearch-router-nextjs` on App Router. If the source-of-truth check still leaves a gap, ask the user. From 965beb1959c4a7cff26162a1e03d11751b460515 Mon Sep 17 00:00:00 2001 From: Vasco Bettencourt <32492444+vascobettencourt@users.noreply.github.com> Date: Wed, 10 Jun 2026 19:36:51 +0100 Subject: [PATCH 3/5] feat(instantsearch): add React search results page pattern (2/4) (#23) --- skills/instantsearch/SKILL.md | 7 +- skills/instantsearch/evals/evals.json | 33 ++++ .../search-results-page/anti-patterns.md | 19 ++ .../react/search-results-page/features.md | 179 ++++++++++++++++++ .../react/search-results-page/styling.md | 70 +++++++ 5 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 skills/instantsearch/references/react/search-results-page/anti-patterns.md create mode 100644 skills/instantsearch/references/react/search-results-page/features.md create mode 100644 skills/instantsearch/references/react/search-results-page/styling.md diff --git a/skills/instantsearch/SKILL.md b/skills/instantsearch/SKILL.md index 0c9aaca..26dee52 100644 --- a/skills/instantsearch/SKILL.md +++ b/skills/instantsearch/SKILL.md @@ -86,9 +86,10 @@ Pick the matching pattern reference for the library and the user's request. If n Patterns available for each library: -| Pattern | React | Vue | JS | -| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | --- | -| Autocomplete | [features](references/react/autocomplete/features.md), [styling](references/react/autocomplete/styling.md), [anti-patterns](references/react/autocomplete/anti-patterns.md) | — | — | +| Pattern | React | Vue | JS | +| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --- | --- | +| Autocomplete | [features](references/react/autocomplete/features.md), [styling](references/react/autocomplete/styling.md), [anti-patterns](references/react/autocomplete/anti-patterns.md) | — | — | +| Search results page (incl. faceted search, sort, pagination) | [features](references/react/search-results-page/features.md), [styling](references/react/search-results-page/styling.md), [anti-patterns](references/react/search-results-page/anti-patterns.md) | — | — | Also read and apply the library-level references (apply regardless of pattern): diff --git a/skills/instantsearch/evals/evals.json b/skills/instantsearch/evals/evals.json index 61f9e52..29f00ee 100644 --- a/skills/instantsearch/evals/evals.json +++ b/skills/instantsearch/evals/evals.json @@ -76,6 +76,39 @@ "Does not guess ais-* class names" ] }, + { + "id": 6, + "prompt": "Add a search results page at /search for my Next.js App Router site. Users should be able to filter by brand and category, sort by price ascending or descending, and paginate. The index has replicas products_price_asc and products_price_desc.", + "expected_output": "A results page using InstantSearchNext with the canonical widget tree, refinements, sort, pagination, routing, insights, and a no-results boundary, with all non-trivial props sourced from installed types", + "files": [], + "expectations": [ + "Uses InstantSearchNext from react-instantsearch-nextjs at the layout level, not wrapping a single page", + "Sets routing={true} and insights={true} on the provider", + "Uses Configure to set hitsPerPage, attributesToHighlight, and attributesToSnippet", + "Uses RefinementList for brand and HierarchicalMenu (or RefinementList/Menu, justified) for category", + "Uses SortBy with the provided replica index names (products, products_price_asc, products_price_desc)", + "Uses Pagination, not InfiniteHits, since the user did not ask for infinite scroll", + "Renders a NoResultsBoundary using useInstantSearch().results.nbHits", + "Uses Highlight on hit text attributes; does not render raw strings", + "Reads RefinementList and SortBy props from node_modules/react-instantsearch/dist/es/widgets before writing prop values it has not used before", + "Does NOT add getServerState (App Router handles SSR via InstantSearchNext)", + "Does NOT combine Pagination with InfiniteHits" + ] + }, + { + "id": 7, + "prompt": "I want a searchable refinement list for brand (so the user can type to filter brands) and active filter chips that show as removable pills at the top of the results.", + "expected_output": "RefinementList with searchable + searchablePlaceholder configured from the type, plus CurrentRefinements wired with widget-aware classNames for the chip layout", + "files": [], + "expectations": [ + "Reads RefinementList .d.ts to confirm the searchable, searchablePlaceholder, limit, and showMore props before using them", + "Adds searchable={true} and a searchablePlaceholder on RefinementList", + "Uses CurrentRefinements (not a custom hook) for the active-filter chips", + "Pairs CurrentRefinements with ClearRefinements where appropriate, with excludedAttributes considered when needed", + "Greps installed source for ais-CurrentRefinements-* class names before writing chip CSS", + "Does not rebuild a refinement list with useRefinementList when RefinementList suffices" + ] + }, { "id": 10, "prompt": "Set up my React InstantSearch project with the correct future flags so it's ready for the next major release.", diff --git a/skills/instantsearch/references/react/search-results-page/anti-patterns.md b/skills/instantsearch/references/react/search-results-page/anti-patterns.md new file mode 100644 index 0000000..9c68f9f --- /dev/null +++ b/skills/instantsearch/references/react/search-results-page/anti-patterns.md @@ -0,0 +1,19 @@ +# Search Results Page Anti-patterns + +These are in addition to the [library-level anti-patterns](../anti-patterns.md). + +| Anti-pattern | Why it's wrong | What to do instead | +| ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| Adding a refinement widget for an attribute not in `attributesForFaceting` | Runtime error: "facet not configured" | Confirm the index's faceting config first. Ask the user to add the attribute via `algolia-cli` if missing | +| Combining `` and `` on the same index | Both widgets manage page state; they fight | Pick one. `` for shareable URLs, `` for catalog scroll | +| Adding `` without verified replicas | Switching to a missing replica throws on first selection | Confirm replica index names with the user or via `algolia-cli` before adding the widget | +| Skipping a no-results component | Page renders nothing when the user mistypes; looks broken | Render a no-results boundary using `useInstantSearch().results.nbHits` (see features.md) | +| Putting refinement widgets outside their target `` in a multi-index page | Refinements apply to the wrong index (the parent) | Place each refinement widget inside the `` it should refine | +| Using `` to set refinement defaults the user can change | `` is for fixed index params; user-changeable defaults belong on the refinement widget | Use the widget's default-selection prop (read its type), or set `uiState` via `routing.stateMapping` | +| Hardcoding `hitsPerPage` in the URL via middleware | Conflicts with `routing` and the `` widget | Use `` for a fixed value, `` for a user-chosen value | +| Re-implementing `` from `useHits` + a manual page counter | Loses URL sync, accessibility, and edge-case handling (last page, single page, no results) | Use ``. If the rendering is custom, use `usePagination` and read its return type | +| Wrapping each result column section in its own `` provider | Each provider creates a separate search instance; widgets inside don't see siblings | One provider at the page (or layout) level, multiple widgets and `` blocks inside | +| Mounting the same refinement widget twice with the same `attribute` and different `limit`s | Two virtual widgets fight; only one set of params wins | Mount once. If desktop/mobile differ, render the same widget instance via CSS, not two instances | +| Calling `setUiState` from `useEffect` to "preselect" refinements | Causes an extra search per mount and fights `routing` | Use the widget's default-selection prop, or `routing.stateMapping` to derive defaults from the URL | +| Showing raw text for `name` / `title` on hits | Loses query highlighting; users can't see why a hit matched | Use `` per technology rules | +| Long descriptions rendered with `` instead of `` | Renders the full text with marks; visually noisy and slow | Use `` and set `attributesToSnippet` in `` (e.g., `description:30` for 30 words) | \ No newline at end of file diff --git a/skills/instantsearch/references/react/search-results-page/features.md b/skills/instantsearch/references/react/search-results-page/features.md new file mode 100644 index 0000000..ba2f381 --- /dev/null +++ b/skills/instantsearch/references/react/search-results-page/features.md @@ -0,0 +1,179 @@ +# Search Results Page (React) + +A search results page is the page users land on after submitting a query (from autocomplete, a header search box, or a deep link). It typically combines: a search input, a hits list, pagination, refinements (facets), sort, stats, and a no-results state. It is also the natural home for faceted search. + +This file scaffolds the structural skeleton. **Prop details and any prop you have not used before must come from the [Source-of-truth check](../source-of-truth.md), not from this file.** + +## Discovery (in addition to SKILL.md Discover step) + +Confirm before scaffolding: + +- **Route**: which URL renders this page? Verify it actually reads query params and renders dynamic results (not a static listing). +- **Refinement attributes**: which record fields should become facets? They must be in the index's `attributesForFaceting`. If the user is unsure, ask before adding refinement widgets to avoid a "facet is not configured" error at runtime. +- **Sort replicas**: does the index have replicas (e.g., `_price_asc`, `_price_desc`)? If yes, ask the user to confirm the replica names and labels for the sort dropdown. If no, do not add `` (or ask whether they want to set replicas up via the `algolia-cli` skill first). +- **Pagination vs. infinite scroll**: ask the user. Both are supported. Pagination is the default for shareable URLs; infinite scroll is common for catalog-style UX. +- **Empty/error states**: what should the page show when there are no hits, no query, or the network fails? Ask if not obvious from the design. +- **Mobile refinements**: how should refinements appear on mobile? Common pattern: drawer or modal, opened by a "Filters" button. Ask if the design isn't explicit. + +## Canonical widget tree + +This is the structural skeleton. Resolve any non-trivial prop via the [Source-of-truth check](../source-of-truth.md). + +```tsx +import { + Configure, + CurrentRefinements, + ClearRefinements, + HierarchicalMenu, + Hits, + HitsPerPage, + Pagination, + RangeInput, + RefinementList, + SearchBox, + SortBy, + Stats, + ToggleRefinement, + useInstantSearch, +} from "react-instantsearch"; + +function SearchResultsPage() { + return ( + <> + +
+ + + + +
+ + + +
+ }> + + + +
+ + ); +} + +function NoResultsBoundary({ children, fallback }: { children: React.ReactNode; fallback: React.ReactNode }) { + const { results } = useInstantSearch(); + if (!results.__isArtificial && results.nbHits === 0) { + return <>{fallback}; + } + return <>{children}; +} + +function NoResults() { + const { indexUiState } = useInstantSearch(); + return
No results for "{indexUiState.query}".
; +} + +function Hit({ hit }: { hit: Record & { objectID: string } }) { + // Render with per technology-rules.md + return null; +} +``` + +The `` shape is the version-stable pattern from the official guide. Confirm `useInstantSearch` return fields before destructuring (per the intro source-of-truth rule). + +## Refinement widgets: which one when + +| Use case | Widget | Notes | +| ------------------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| Multi-select categorical filter | `` | For long lists, look up `searchable`, `searchablePlaceholder`, `limit`, `showMore`, `showMoreLimit`. | +| Single-select categorical filter | `` | Mutually exclusive selection. Often used with `transformItems` for ordering. | +| Hierarchical / nested categories | `` | Requires `lvl0/lvl1/lvl2` attributes in the records. Read the guide. | +| Numeric range with input fields | `` | Numeric attribute. Two inputs (min/max). | +| Numeric range with a slider | (no built-in) | Use `useRange` and your component library's slider. Read [custom-widgets.md](../custom-widgets.md). | +| Boolean toggle (free shipping, etc.) | `` | Toggles a single facet value on/off. | +| Active-filter chips | `` | Renders all active refinements as removable chips. Useful at the top of results. | +| Clear-all button | `` | Pairs with ``. Look up `excludedAttributes` to keep some refinements pinned. | + +## Pagination vs. infinite scroll + +Two mutually exclusive options: + +- ``: shareable URL state per page. Default for results pages. +- ``: replaces both `` and ``. Loads more on scroll or on a "Load more" button. Look up `showPrevious` and `translations` if you need them, and verify the widget's interaction with `routing`. + +## Sort + +`` switches the active index between the primary and its replicas. The replicas must already exist on the Algolia side; this widget only chooses among them. + +```tsx + +``` + +If replicas don't exist, do not fabricate them. Tell the user they need to be created first (point them to the `algolia-cli` skill). + +## URL state, sharing, deep linking + +`routing={true}` on the provider already syncs SearchBox query, refinements, page, and sort to the URL. Verify by changing a refinement and reloading; state should persist. + +For custom URL formats (e.g., `/search/brand/nike` rather than `?brand=nike`), pass `routing={{ router, stateMapping }}` and read both types. This is non-trivial; consult the live [Routing guide](https://www.algolia.com/doc/guides/building-search-ui/going-further/routing-urls/react) before scaffolding. + +## Multi-index results pages + +If the page shows results from more than one index (e.g., products + articles), nest `` blocks. Each index gets its own ``, refinements, hits, and pagination, scoped to that index. + +```tsx + + + + + + + + + + + + + + + +``` + +Refinement widgets target the **enclosing index**. Putting `` outside any `` refines the root index; putting it inside `` refines only that one. + +## Features checklist + +Consider each. Include when the index and use case support it: + +- [ ] `` sets `hitsPerPage`, `attributesToHighlight`, `attributesToSnippet` +- [ ] `` (or wired to autocomplete that submits here) +- [ ] `` for "X results in Y ms" +- [ ] `` (or ``) with a typed `hitComponent` using `` / `` +- [ ] `` (or ``, not both) +- [ ] `` + `` +- [ ] One or more refinement widgets matching the index's `attributesForFaceting` +- [ ] `` if replicas exist +- [ ] `` if the design needs it +- [ ] No-results boundary with a helpful message +- [ ] Mobile refinement drawer / modal +- [ ] `routing={true}` and `insights={true}` on the provider +- [ ] SSR if the framework supports it (see [ssr.md](../ssr.md)) + +For pattern-specific styling, see [styling.md](styling.md). For pattern-specific anti-patterns, see [anti-patterns.md](anti-patterns.md). diff --git a/skills/instantsearch/references/react/search-results-page/styling.md b/skills/instantsearch/references/react/search-results-page/styling.md new file mode 100644 index 0000000..40781b8 --- /dev/null +++ b/skills/instantsearch/references/react/search-results-page/styling.md @@ -0,0 +1,70 @@ +# Search Results Page Styling + +This covers results-page-specific layout. For the general approach, see the [styling guide](../styling.md). Always grep `node_modules` for actual `ais-*` class names before writing CSS; do not guess. + +## Page-level layout + +A typical results page has three regions: a header (search box, stats, sort), a refinements panel (sidebar on desktop, drawer on mobile), and a results column (hits + pagination). Use the host site's grid/flex tokens; the InstantSearch widgets do not impose a layout. + +What matters at the layout level: + +- The **refinements panel** should not collapse or overflow when refinement lists grow. Set a `max-height` with `overflow-y: auto` per panel section, and a sticky header above each panel. +- The **results column** should reserve a minimum height so a transition from "many results" to "few results" doesn't shift content. +- The **search box** should keep a visible focus state and match the autocomplete input from the rest of the site. + +## Refinement panel + +Each refinement widget renders a labeled list. Common targets: + +- `ais-RefinementList` (root), `ais-RefinementList-list`, `ais-RefinementList-item`, `ais-RefinementList-label`, `ais-RefinementList-checkbox`, `ais-RefinementList-labelText`, `ais-RefinementList-count`, `ais-RefinementList-showMore`. +- `ais-HierarchicalMenu` follows the same shape, with nested `ais-HierarchicalMenu-list` for child levels. +- `ais-CurrentRefinements` renders chips: `ais-CurrentRefinements-list`, `ais-CurrentRefinements-item`, `ais-CurrentRefinements-label`, `ais-CurrentRefinements-category`, `ais-CurrentRefinements-categoryLabel`, `ais-CurrentRefinements-delete`. +- `ais-ClearRefinements-button`. + +Verify all of these against the installed source before writing CSS. Names occasionally change. + +Common rules: + +- Hide the count when zero (`ais-RefinementList-count` with conditional styling) only if the design demands it. +- Style `:hover`, `:focus-visible`, and the checked state on `ais-RefinementList-checkbox` consistently. +- Reserve right padding on items so long labels do not collide with the count. + +## Hits grid + +`` renders an `ais-Hits` root, an `ais-Hits-list`, and `ais-Hits-item` per hit. The list is a `
    ` by default; style it with grid or flex on the host site's tokens. The hit component you provide is rendered inside `ais-Hits-item`; its inner DOM is yours. + +For consistent height, set a fixed image aspect ratio on the hit image and clamp text lines (`-webkit-line-clamp` or the host's utility) on titles and descriptions. + +## Pagination + +`ais-Pagination`, `ais-Pagination-list`, `ais-Pagination-item`, `ais-Pagination-link`, plus modifier classes (`ais-Pagination-item--selected`, `ais-Pagination-item--disabled`, `ais-Pagination-item--firstPage`, `ais-Pagination-item--lastPage`, `ais-Pagination-item--previousPage`, `ais-Pagination-item--nextPage`, `ais-Pagination-item--page`). + +Style the modifier classes explicitly. The `--selected` state must be visually distinct from hover. + +## Mobile refinements drawer + +The widgets do not ship a drawer. Common pattern: + +1. Render the refinements panel inside a `` or a custom modal component the project already has. +2. Toggle it via a "Filters" button shown only at the mobile breakpoint. +3. Inside the drawer, render the same refinement widgets you render on desktop. Do not duplicate `` providers. + +If the project already has a drawer/sheet component, use it. Do not introduce a new modal pattern just for refinements. + +## Sort and HitsPerPage + +`ais-SortBy` and `ais-HitsPerPage` render a `