diff --git a/.github/agents/tsh-software-engineer.agent.md b/.github/agents/tsh-software-engineer.agent.md
index dbdd800d..dd7d73a8 100644
--- a/.github/agents/tsh-software-engineer.agent.md
+++ b/.github/agents/tsh-software-engineer.agent.md
@@ -67,6 +67,7 @@ When working from a `*.plan.md` file — whether implementing the full plan or a
- `tsh-sql-and-database-understanding` - when writing SQL queries, designing database schemas, creating migrations, implementing ORM-based data access, optimising query performance, or working with transactions and locking. Applies to PostgreSQL, MySQL, MariaDB, SQL Server, and Oracle.
- `tsh-implementing-frontend` - for UI tasks: component patterns, composition, design tokens, barrel files, and Figma-to-code workflow.
- `tsh-implementing-forms` - for form tasks: schema validation, field composition, error handling, multi-step form flows.
+- `tsh-implementing-filters` - for filter tasks: type-safe URL filter synchronization, bracket notation serialization, filter sync hooks, push/replace navigation strategies.
- `tsh-writing-hooks` - for custom hooks: naming, composition, stable returns, effect cleanup, testing.
- `tsh-ensuring-accessibility` - for WCAG 2.1 AA compliance: semantic HTML, ARIA, keyboard navigation, focus management, screen readers.
- `tsh-optimizing-frontend` - for frontend performance: code splitting, memoization, bundle size, rendering optimization, memory management.
diff --git a/.github/skills/tsh-implementing-filters/SKILL.md b/.github/skills/tsh-implementing-filters/SKILL.md
new file mode 100644
index 00000000..53fe06c5
--- /dev/null
+++ b/.github/skills/tsh-implementing-filters/SKILL.md
@@ -0,0 +1,330 @@
+---
+name: tsh-implementing-filters
+description: "Type-safe URL filter synchronization with clean path and query string routing. Headless filter logic — schema definition, bracket notation serialization, URL sync hooks, and navigation strategy patterns. Use when implementing filterable lists, search pages, faceted navigation, or any UI that persists filter state in the URL."
+---
+
+# Implementing Filters
+
+Provides patterns for type-safe URL filter synchronization — schema definition, bracket notation serialization/deserialization, router-bound hooks, and push/replace navigation strategies.
+
+## Table of Contents
+
+- [Principles](#principles)
+- [Filter Implementation Process](#filter-implementation-process)
+- [Serialization Quick Reference](#serialization-quick-reference)
+- [API Contract](#api-contract)
+ - [Query Parameter Structure](#query-parameter-structure)
+ - [Filter Logic](#filter-logic)
+ - [Example API Response Envelope](#example-api-response-envelope)
+ - [Backend Considerations](#backend-considerations)
+- [Filter Quality Checklist](#filter-quality-checklist)
+- [Anti-Patterns](#anti-patterns)
+- [Framework-Specific Patterns](#framework-specific-patterns)
+- [Connected Skills](#connected-skills)
+
+
+
+
+Filter state lives in the URL, not in component state. The URL is the single source of truth — components derive their state by parsing the URL. This enables shareable links, back/forward navigation, and SSR hydration without state mismatch. Never store canonical filter state in `useState` or a store and then sync it to the URL — read from the URL, write to the URL.
+
+
+
+If removing a parameter changes what page or resource you are looking at, it belongs in the path. If removing it just narrows or broadens the same list, it belongs in a query string. Path = identity; query string = modifier. Category hierarchies go in the path (`/products/shoes/running`). Filters, sort, pagination, and search terms go in query strings (`?filter[color]=blue&sort[price]=ASC&page=2`).
+
+
+
+Define a TypeScript schema for every filter set. Parse URL params through the schema on read (deserialize), and serialize through the schema on write. Bracket notation (`filter[field]=value`) is the standard serialization format — it aligns with the backend API contract. Never pass raw `string | null` from `searchParams` into application code — always validate and coerce to the schema type. Invalid or missing values fall back to schema defaults.
+
+
+
+The URL serialization format must match the API it talks to. Bracket notation (`filter[field]=value`) is the default for TSH-owned APIs. When consuming an external API that uses a different convention (flat keys like `color=blue`, JSON-encoded params, comma-separated values, or any other format), adapt the serialization layer to that API's expected format. The filter schema, hook shape, and navigation strategy remain the same — only the serialize/deserialize functions change.
+
+
+
+
+## Filter Implementation Process
+
+Use the checklist below and track progress:
+
+```
+Progress:
+- [ ] Step 1: Define the filter schema
+- [ ] Step 2: Choose the URL structure
+- [ ] Step 3: Implement serialization/deserialization
+- [ ] Step 4: Create the filter sync hook
+- [ ] Step 5: Wire up navigation strategy
+```
+
+**Step 1: Define the filter schema**
+
+Define a TypeScript type or interface for each filter set. Every filter parameter must have an explicit type and a default value.
+
+Supported filter parameter types:
+
+| Type | Example | Serialized |
+| -------------- | ------------------------------------ | ------------------------------------------------------------------- |
+| Single value | `color: string` | `filter[color]=blue` |
+| Multi-select | `tags: string[]` | `filter[tags]=a&filter[tags]=b` (repeated bracket key) |
+| Range | `priceMin: number; priceMax: number` | `filter[price_min]=10&filter[price_max]=50` (separate bracket keys) |
+| Boolean toggle | `inStock: boolean` | `filter[in_stock]=true` |
+| Numeric | `page: number` | `page=2` (top-level, not bracketed) |
+
+Rules:
+
+- Filters use bracket notation: `filter[field_name]=value`. Range filters use separate bracket keys: `filter[price_min]=10&filter[price_max]=50`.
+- Use `snake_case` for bracket key names — readable in URLs.
+- Use `camelCase` for TypeScript property names.
+- Define defaults for every param — defaults are used when a param is absent from the URL.
+
+Example schema shape:
+
+```typescript
+type SortDirection = "ASC" | "DESC";
+
+interface ProductSort {
+ field: "relevance" | "price" | "createdAt";
+ direction: SortDirection;
+}
+
+interface ProductFilters {
+ color: string; // default: "" → filter[color]=blue
+ tags: string[]; // default: [] → filter[tags]=a&filter[tags]=b
+ priceMin: number; // default: 0 → filter[price_min]=0
+ priceMax: number; // default: 10000 → filter[price_max]=10000
+ inStock: boolean; // default: false → filter[in_stock]=true
+ sort: ProductSort; // default: { field: "relevance", direction: "ASC" } → sort[relevance]=ASC
+ page: number; // default: 1 → page=1
+}
+```
+
+**Step 2: Choose the URL structure**
+
+Apply the path-vs-query golden rule to classify each piece of your URL:
+
+| Element | URL part | Example | Rationale |
+| ----------------------- | ------------ | ------------------------------------- | ------------------------------------------------ |
+| Resource category | Path segment | `/products/shoes` | Removing "shoes" changes what you are looking at |
+| Subcategory / hierarchy | Path segment | `/products/shoes/running` | Still defines identity |
+| Filter (color, size) | Query string | `?filter[color]=blue&filter[size]=10` | Removing it shows the same page, broader list |
+| Sort order | Query string | `?sort[price]=ASC` | Display modifier, not identity |
+| Pagination | Query string | `?page=2&limit=20` | Display modifier |
+| Search term | Query string | `?search=lightweight` | Display modifier |
+
+**API alignment rule**: The URL serialization must match the API it communicates with. For TSH-owned APIs, this means bracket notation (`filter[field]=value`). For external APIs with different conventions, adapt the serialize/deserialize functions to match the external format. Common external patterns:
+
+| External API Pattern | Example | Adaptation |
+| ---------------------- | -------------------------- | ------------------------------------------------------------------- |
+| Flat keys | `color=blue&price_min=10` | Serialize filters as top-level params without `filter[...]` wrapper |
+| Comma-separated values | `colors=blue,red,green` | Join arrays with commas instead of repeated keys |
+| JSON-encoded | `filters={"color":"blue"}` | JSON-stringify the filter object into a single param |
+| Custom prefix | `f_color=blue&f_size=10` | Use the API's prefix convention in serialize/deserialize |
+
+The filter schema, hook shape (`{ filters, updateFilters, resetFilters }`), and navigation strategy (push/replace) are API-agnostic — only the serialization layer changes per API.
+
+**Step 3: Implement serialization/deserialization**
+
+Build two functions tied to the filter schema: one to serialize the typed filter object into `URLSearchParams`, and one to deserialize `URLSearchParams` back into the typed filter object.
+
+**Serialize** (typed object → `URLSearchParams`):
+
+- Iterate over schema keys.
+- Skip params whose value matches the default — keeps the URL clean.
+- Wrap filter params in bracket notation: `filter[field_name]=value`. Use `snake_case` inside brackets.
+- Ranges use separate bracket keys: `filter[price_min]=10&filter[price_max]=50`.
+- Arrays use repeated bracket keys: `filter[tags]=a&filter[tags]=b`.
+- Sort params use `sort[field]=direction` format.
+- Booleans serialize as `"true"` / `"false"`.
+
+**Deserialize** (`URLSearchParams` → typed object):
+
+Deserialization is a two-phase pipeline:
+
+1. **Extract bracket keys** — parse `filter[...]` and `sort[...]` keys from `URLSearchParams`, producing a flat key-value object with `camelCase` property names. Handle repeated bracket keys (`getAll()`) for multi-value filters and separate range keys (`filter[price_min]`, `filter[price_max]`). This phase is URL-format-aware but type-unaware.
+2. **Validate and coerce** — pass the flat object through the filter schema. Coerce types (`Number()` for numerics, `=== 'true'` for booleans), apply defaults for missing params, and reject out-of-range or malformed values by falling back to schema defaults. Never propagate garbage into application code.
+
+> **Prefer schema validation**: Use a library like [Zod](https://zod.dev) or [Valibot](https://valibot.dev) for phase 2 — define a schema that mirrors the filter interface, with `.default()` for fallbacks and `.coerce` for type conversions. The schema validates domain types (`color: string`, `priceMin: number`) without knowing anything about URL serialization format. Fall back to manual `Number()` / `=== 'true'` only when adding a validation library is not feasible.
+
+Key rules:
+
+- Bracket notation for all filter params: `filter[field]=value`.
+- Repeated bracket keys for multi-value filters: `filter[field]=a&filter[field]=b`.
+- Omit default values from URL — cleaner URLs, same result.
+- Never trust raw URL params — always parse through the schema.
+
+**Step 4: Create the filter sync hook**
+
+Create a custom hook (e.g., `useFilters`) that connects the filter schema to the router. The hook:
+
+1. Reads current URL search params via the router.
+2. Deserializes them into the typed filter state using the schema.
+3. Provides an `updateFilters(partial)` function that merges partial updates with current filters, serializes the result, and navigates.
+4. Provides a `resetFilters()` function that navigates to the URL with all defaults (effectively clearing query params).
+
+The hook accepts configuration:
+
+- The filter schema defaults object.
+- The serialization/deserialization functions.
+
+The hook returns:
+
+- `filters` — the current typed filter state, derived from the URL.
+- `updateFilters(partial, options?)` — merges partial filter updates and navigates.
+- `resetFilters()` — clears all filters back to defaults.
+
+Framework binding:
+
+| Framework | Read params | Navigate |
+| ---------------- | ------------------- | -------------------------------------- |
+| React Router v6+ | `useSearchParams()` | `setSearchParams()` or `useNavigate()` |
+
+The hook must:
+
+- Handle partial updates — merge incoming changes with current filter state, not replace entirely. Use flat properties for ranges (e.g., `priceMin`, `priceMax`) so that partial updates merge correctly with shallow spread.
+- Be type-safe end-to-end — input partial → serialization → URL → deserialization → output typed.
+- Derive state from the URL on every render — never cache filter state in local `useState`.
+
+**Step 5: Wire up navigation strategy**
+
+Choose `push` or `replace` navigation based on the type of filter change:
+
+| Action | Strategy | Rationale |
+| ----------------------- | ---------------------------------------- | ---------------------------------------------------- |
+| Category toggle (major) | `push` | User can press Back to undo |
+| Sort change | `push` | Intentional user action, should be undoable |
+| Search-as-you-type | `replace` | Don't flood history with every keystroke |
+| Pagination | `push` | User expects Back to go to previous page |
+| Filter toggle (facet) | `push` | User expects Back to undo filter |
+| Debounced range slider | `replace` during drag, `push` on release | Balance between history cleanliness and undo-ability |
+
+The `updateFilters` function should accept an optional `{ replace?: boolean }` option. Default to `push`. Callers override to `replace` for as-you-type or continuous inputs.
+
+```typescript
+// Push (default) — for discrete filter actions
+updateFilters({ color: "blue" });
+
+// Replace — for as-you-type search input
+updateFilters({ search: searchTerm }, { replace: true });
+```
+
+For debounced inputs (range sliders, search fields), use `replace` during rapid changes and `push` on the final committed value.
+
+## Serialization Quick Reference
+
+| TypeScript type | Serialized format | Deserialize with |
+| -------------------------------- | ------------------------------------- | ------------------------------- |
+| `string` | `filter[key]=value` | Parse bracket key, `get()` |
+| `number` | `filter[key]=123` | Parse bracket key, `Number()` |
+| `boolean` | `filter[key]=true` | Parse bracket key, `=== 'true'` |
+| `string[]` | `filter[key]=a&filter[key]=b` | Parse bracket key, `getAll()` |
+| `keyMin: number; keyMax: number` | `filter[key_min]=1&filter[key_max]=9` | `Number()` for each key |
+| sort | `sort[field]=ASC` | Parse bracket key |
+| search | `search=text` | `get('search')` |
+| pagination | `page=1&limit=20` | `get('page')`, `get('limit')` |
+
+## API Contract
+
+The conventions below describe the **default TSH backend API contract**. When the API provider follows a different pattern (external APIs, third-party services, legacy backends), adapt the serialization layer to match that API's expected format — the filter schema and hook architecture remain unchanged.
+
+### Query Parameter Structure
+
+| Concern | Format | Example |
+| ------------------------- | ----------------------- | ------------------------------------------------ |
+| Filters | `filter[field]=value` | `filter[first_name]=Ewa` |
+| Multi-value filter (OR) | Repeated bracket key | `filter[first_name]=Ewa&filter[first_name]=Adam` |
+| Range filter | Separate bracket keys | `filter[price_min]=10&filter[price_max]=50` |
+| Partial-text match filter | Partial match value | `filter[first_name]=Nowak` |
+| Pagination | Top-level keys | `page=1&limit=100` |
+| Sort | `sort[field]=direction` | `sort[last_name]=ASC` |
+| Full-text search | Top-level key | `search=test` |
+
+### Filter Logic
+
+- **Same field, multiple values → OR**: `filter[first_name]=Ewa&filter[first_name]=Adam` — same field with multiple values implies OR — the response includes items matching any of the values.
+- **Different fields → AND**: `filter[first_name]=Ewa&filter[last_name]=Kowalska` — different fields imply AND — the response includes only items matching all field conditions.
+- **Partial-text match filter**: `filter[first_name]=Nowak` — backend determines the matching strategy (e.g., prefix, contains, fuzzy).
+
+### Example API Response Envelope
+
+APIs vary — the following is one common envelope structure. Adapt to match your project's actual API response format:
+
+```typescript
+interface ApiResponse {
+ meta: {
+ pagination: {
+ page: number;
+ total: number;
+ limit: number;
+ totalPages: number;
+ };
+ filter: Record;
+ sort: Record;
+ search: string;
+ };
+ data: T[];
+}
+```
+
+The `meta` object echoes back the applied filters, sort, search, and pagination — use it to verify that the URL state matches what the backend applied.
+
+If your API echoes applied filters in the response meta, verify that it matches the deserialized URL state to ensure frontend-backend sync.
+
+### Backend Considerations
+
+- **Pagination resets**: When filters change, reset the page parameter to 1 to avoid requesting pages beyond the new result set.
+- **Filter echoing**: If the API returns applied filters in the response (e.g., in a `meta` object), compare them against the URL state to detect sync issues.
+
+## Filter Quality Checklist
+
+```
+Filter:
+- [ ] Filter schema defined with TypeScript types and defaults
+- [ ] URL structure follows path-vs-query golden rule
+- [ ] Filters use bracket notation (filter[field]=value)
+- [ ] Sort uses bracket notation (sort[field]=ASC|DESC)
+- [ ] Serialization omits default values from URL
+- [ ] Deserialization validates and coerces types
+- [ ] Range filters use separate bracket keys (filter[price_min]=10&filter[price_max]=50)
+- [ ] Multi-value filters use repeated bracket keys (OR logic)
+- [ ] If external API, serialization adapted to match API's expected format
+- [ ] Cross-field filters combine as AND
+- [ ] Text search values contain only the raw search term — no backend-specific operators or wildcards
+- [ ] Hook returns typed filter state, updateFilters, resetFilters
+- [ ] Push/Replace strategy documented for each filter action
+- [ ] Search-as-you-type inputs use Replace navigation
+- [ ] No raw string params leak into application code
+- [ ] Back/Forward navigation correctly restores filter state
+- [ ] Shareable URL reproduces the exact filter state
+- [ ] API response meta echoed back matches URL state
+```
+
+## Anti-Patterns
+
+| Anti-Pattern | Instead Do |
+| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
+| Storing filter state in `useState` and syncing to URL | Derive filter state FROM the URL — URL is the source of truth |
+| Using flat keys for filters (`color=blue&price_min=10`) | Use bracket notation (`filter[color]=blue&filter[price_min]=10`) |
+| Hardcoding filter params as magic strings | Define a filter schema type and derive param names from it |
+| Using `push` for search-as-you-type | Use `replace` to avoid flooding browser history |
+| Putting optional filters in path segments (`/products/color/blue`) | Put filters in query strings — path is for resource identity only |
+| Serializing default values in URL (`?page=1&sort[relevance]=ASC`) | Omit defaults — cleaner URLs, same behavior |
+| Parsing URL params without type coercion | Validate with a schema library (Zod, Valibot) or manually coerce: `Number()`, `=== 'true'`, with fallback to defaults |
+| Building one monolithic filter hook for all pages | Create filter schemas per-page/per-feature, share the serialization utility |
+| Treating multi-value filter as AND | Same-field repeated values are OR; different fields are AND |
+| Ignoring API response `meta` object | Verify URL filter state matches what the backend echoed in `meta` |
+| Using bracket notation when the external API expects flat keys | Adapt serialization to match the API contract — bracket notation is the TSH default, not a universal rule |
+
+## Framework-Specific Patterns
+
+The patterns above are framework-agnostic. For framework-specific implementation guidance, load the appropriate reference:
+
+- **React Router v6+**: See `./references/react-patterns.md` — `useSearchParams`, `setSearchParams()` with functional updater, `useNavigate()` integration, hook binding.
+- **Next.js App Router**: See `./references/nextjs-patterns.md` — `useSearchParams`, `usePathname`, `router.push()`/`router.replace()` integration, server component considerations.
+
+## Connected Skills
+
+- `tsh-implementing-frontend` — for component patterns that consume the headless filter logic
+- `tsh-implementing-forms` — for form-based filter UIs that wire into the filter hook
+- `tsh-writing-hooks` — for hook composition patterns and return shape conventions applicable to filter hooks
+- `tsh-optimizing-frontend` — for rendering optimization for filter-heavy pages
+- `tsh-ensuring-accessibility` — for accessible filter controls (keyboard navigation, ARIA attributes for active filters)
+- `tsh-sql-and-database-understanding` — for backend query optimization behind the API contract
diff --git a/.github/skills/tsh-implementing-filters/references/nextjs-patterns.md b/.github/skills/tsh-implementing-filters/references/nextjs-patterns.md
new file mode 100644
index 00000000..d9050783
--- /dev/null
+++ b/.github/skills/tsh-implementing-filters/references/nextjs-patterns.md
@@ -0,0 +1,111 @@
+# Next.js Filter Patterns
+
+Next.js App Router-specific patterns for the tsh-implementing-filters skill. Load this reference when the project uses Next.js.
+
+## Table of Contents
+
+- [Reading Search Params](#reading-search-params)
+- [Navigation (Push/Replace)](#navigation-pushreplace)
+- [Server Component Constraints](#server-component-constraints)
+- [Bracket Notation with Next.js](#bracket-notation-with-nextjs)
+- [Anti-Patterns](#anti-patterns)
+
+## Reading Search Params
+
+Next.js App Router provides different APIs for reading URL search params depending on the context:
+
+| Context | API | Notes |
+| ---------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
+| Client Component | `useSearchParams()` from `next/navigation` | Returns `ReadonlyURLSearchParams`. Only available in Client Components. |
+| Server Component | `searchParams` prop from page/layout (async in Next.js 15+ — must be `await`ed) | Passed as a prop to `page.tsx`. Access `searchParams.filter` etc. |
+| Route Handler | `request.nextUrl.searchParams` | In `route.ts` handlers. |
+
+Key difference from React Router: `useSearchParams()` returns a **read-only** object. You cannot call `setSearchParams()` — you must construct a new URL and navigate.
+
+## Navigation (Push/Replace)
+
+How to navigate with updated filters in Next.js App Router:
+
+```typescript
+import { useRouter, usePathname, useSearchParams } from "next/navigation";
+
+const useFilterNavigation = () => {
+ const router = useRouter();
+ const pathname = usePathname();
+ const searchParams = useSearchParams();
+
+ const navigate = (
+ newParams: URLSearchParams,
+ options?: { replace?: boolean },
+ ) => {
+ const url = `${pathname}?${newParams.toString()}`;
+ if (options?.replace) {
+ router.replace(url);
+ } else {
+ router.push(url);
+ }
+ };
+
+ return { navigate, searchParams, pathname };
+};
+```
+
+`router.push()` and `router.replace()` use **soft navigation** by default — no full page reload, which is the desired behavior for filter changes.
+
+## Server Component Constraints
+
+- Server Components **cannot** use `useSearchParams()` — they receive `searchParams` as a page prop.
+- Deserialization logic (bracket notation parsing, type coercion) works identically in both contexts — only the source of raw params differs.
+- Pattern: Define serialization/deserialization as plain functions (not hooks). The `useFilters` hook wraps them for Client Components. Server Components call the deserialization function directly.
+
+```typescript
+// Shared — works in both contexts
+const deserializeFilters = (params: URLSearchParams): ProductFilters => {
+ /* ... */
+};
+
+// Client Component — via hook
+const useFilters = (defaults: ProductFilters) => {
+ const searchParams = useSearchParams();
+ const filters = deserializeFilters(
+ new URLSearchParams(searchParams.toString()),
+ );
+ // ...
+};
+
+// Server Component — direct call
+export default async function ProductsPage({
+ searchParams,
+}: {
+ searchParams: Promise>;
+}) {
+ const resolvedParams = await searchParams;
+ const params = new URLSearchParams();
+ // Convert resolvedParams record to URLSearchParams...
+ const filters = deserializeFilters(params);
+ // ...
+}
+```
+
+## Bracket Notation with Next.js
+
+Next.js does not natively parse bracket notation (`filter[color]=blue`) into structured objects. The `searchParams` prop gives flat key-value pairs where the key is the literal string `filter[color]`.
+
+The deserialization function must parse bracket keys manually. This logic is framework-agnostic — identical to the React Router approach. Only the source of `URLSearchParams` differs.
+
+```typescript
+// searchParams in Next.js page: { "filter[color]": "blue", "filter[tags]": ["a", "b"] }
+// Parse bracket keys to extract field names
+```
+
+## Anti-Patterns
+
+| Anti-Pattern | Instead Do |
+| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
+| Using `useSearchParams()` in Server Components | Use the `searchParams` page prop in Server Components |
+| Calling `setSearchParams()` like React Router | Construct new URL string, use `router.push()` / `router.replace()` |
+| Wrapping entire page in Client Component for filter access | Keep filter display in Server Components via `searchParams` prop; only filter controls need Client Component |
+| Using `router.push()` with full URL including origin | Use pathname-relative URLs: `` `${pathname}?${params}` `` |
+| Using nested objects for range filters (e.g., `{ price: { min, max } }`) | Use flat properties (`priceMin`, `priceMax`) so partial updates with shallow spread merge correctly |
+
+> **Note:** When composing filter update functions, use flat properties for ranges (e.g., `priceMin`, `priceMax` instead of nested `{ price: { min, max } }`) so that partial updates with shallow spread merge correctly.
diff --git a/.github/skills/tsh-implementing-filters/references/react-patterns.md b/.github/skills/tsh-implementing-filters/references/react-patterns.md
new file mode 100644
index 00000000..f88d9391
--- /dev/null
+++ b/.github/skills/tsh-implementing-filters/references/react-patterns.md
@@ -0,0 +1,147 @@
+# React Filter Patterns
+
+React Router v6+-specific patterns for the `tsh-implementing-filters` skill. Load this reference when the project uses React Router.
+
+## Table of Contents
+
+- [Reading Search Params](#reading-search-params)
+- [Navigation (Push/Replace)](#navigation-pushreplace)
+- [Hook Binding](#hook-binding)
+- [Bracket Notation Parsing](#bracket-notation-parsing)
+- [Anti-Patterns](#anti-patterns)
+
+## Reading Search Params
+
+| API | Usage | Notes |
+| -------------------------- | ----------------------------------------- | ---------------------------------------------------------------------- |
+| `useSearchParams()` | Returns `[searchParams, setSearchParams]` | Read-write tuple. `searchParams` is a `URLSearchParams` instance. |
+| `searchParams.get(key)` | Single value | Returns `string \| null` |
+| `searchParams.getAll(key)` | Multi-value (arrays) | Returns `string[]` — use for repeated bracket keys like `filter[tags]` |
+| `searchParams.toString()` | Full query string | For URL construction |
+
+Key difference from Next.js: React Router's `useSearchParams()` returns a **read-write** tuple — you can use `setSearchParams()` directly to update the URL without constructing a full URL string.
+
+## Navigation (Push/Replace)
+
+**Approach 1: `setSearchParams()` (preferred for filter updates)**
+
+```typescript
+import { useSearchParams } from "react-router-dom";
+
+const useFilterNavigation = () => {
+ const [searchParams, setSearchParams] = useSearchParams();
+
+ const navigate = (
+ newParams: URLSearchParams,
+ options?: { replace?: boolean },
+ ) => {
+ setSearchParams(newParams, { replace: options?.replace });
+ };
+
+ return { navigate, searchParams };
+};
+```
+
+`setSearchParams` also supports a functional updater for deriving from the previous params:
+
+```typescript
+setSearchParams(
+ (prev) => {
+ const next = new URLSearchParams(prev);
+ next.set("filter[color]", "blue");
+ return next;
+ },
+ { replace: true },
+);
+```
+
+**Approach 2: `useNavigate()` (when path + search need to change together)**
+
+```typescript
+import { useNavigate, useSearchParams } from "react-router-dom";
+
+const useFilterNavigation = () => {
+ const navigate = useNavigate();
+ const [searchParams] = useSearchParams();
+
+ const navigateWithFilters = (
+ newParams: URLSearchParams,
+ options?: { replace?: boolean; pathname?: string },
+ ) => {
+ navigate(
+ { pathname: options?.pathname ?? ".", search: newParams.toString() },
+ { replace: options?.replace },
+ );
+ };
+
+ return { navigateWithFilters, searchParams };
+};
+```
+
+Use `setSearchParams()` when only filters change. Use `useNavigate()` when both the path segment and query string need to update simultaneously (e.g., changing category + resetting filters). `setSearchParams()` with `{ replace: true }` maps directly to the skill's navigation strategy — use it for search-as-you-type, omit it (defaults to push) for discrete filter actions.
+
+## Hook Binding
+
+Concrete React Router binding for the generic `useFilters` hook described in SKILL.md Step 4:
+
+```typescript
+import { useSearchParams } from "react-router-dom";
+
+const useFilters = >({
+ defaults,
+ serialize,
+ deserialize,
+}: {
+ defaults: T;
+ serialize: (filters: T) => URLSearchParams;
+ deserialize: (params: URLSearchParams) => T;
+}) => {
+ const [searchParams, setSearchParams] = useSearchParams();
+
+ const filters = deserialize(searchParams);
+
+ const updateFilters = (
+ partial: Partial,
+ options?: { replace?: boolean },
+ ) => {
+ setSearchParams(
+ (prev) => {
+ const current = deserialize(prev);
+ const next = { ...current, ...partial };
+ return serialize(next);
+ },
+ { replace: options?.replace },
+ );
+ };
+
+ const resetFilters = () => {
+ setSearchParams(serialize(defaults));
+ };
+
+ return { filters, updateFilters, resetFilters };
+};
+```
+
+The hook reads `searchParams` for the current filter state and uses `setSearchParams` with a functional updater for writes. No `useEffect` or `useState` needed.
+
+Note: `{ ...filters, ...partial }` is a shallow merge. Use flat properties for ranges (e.g., `priceMin`, `priceMax` instead of nested `{ price: { min, max } }`) so that partial updates merge correctly.
+
+## Bracket Notation Parsing
+
+React Router does not parse bracket notation (`filter[color]=blue`) into structured objects — the `searchParams` instance treats `filter[color]` as a literal key string. The deserialization function must parse bracket keys manually. This logic is framework-agnostic. See SKILL.md Step 3 for the serialization/deserialization pattern.
+
+```typescript
+// searchParams.get('filter[color]') → 'blue'
+// searchParams.getAll('filter[tags]') → ['a', 'b']
+// Parse bracket keys to extract field names and structure
+```
+
+## Anti-Patterns
+
+| Anti-Pattern | Instead Do |
+| ------------------------------------------------------------------- | --------------------------------------------------------------------------- |
+| Using `useNavigate()` when only filters change | Use `setSearchParams()` — simpler, doesn't require path construction |
+| Mutating the `searchParams` object directly and expecting re-render | Create new `URLSearchParams` via `serialize()`, pass to `setSearchParams()` |
+| Using `setSearchParams` with string instead of `URLSearchParams` | Pass `URLSearchParams` object for type safety and correct encoding |
+| Wrapping `setSearchParams` in `useEffect` to sync state | Call `setSearchParams` directly in event handlers — no effect sync needed |
+| Using `navigate(-1)` to "undo" filter changes | Rely on browser Back button — push navigation already handles this |
diff --git a/.github/skills/tsh-implementing-forms/SKILL.md b/.github/skills/tsh-implementing-forms/SKILL.md
index acc00ed3..a2b96caf 100644
--- a/.github/skills/tsh-implementing-forms/SKILL.md
+++ b/.github/skills/tsh-implementing-forms/SKILL.md
@@ -149,3 +149,4 @@ Form:
- `tsh-ensuring-accessibility` — for WCAG compliance in form fields, labels, and error announcements
- `tsh-writing-hooks` — for custom form-related hooks/composables (useFormField, useMultiStepForm)
- `tsh-reviewing-frontend` — for form-specific review criteria during code review
+- `tsh-implementing-filters` — for filter UIs that wire form inputs into URL-synced filter hooks
diff --git a/.github/skills/tsh-implementing-frontend/SKILL.md b/.github/skills/tsh-implementing-frontend/SKILL.md
index aa74fe9d..6b90416c 100644
--- a/.github/skills/tsh-implementing-frontend/SKILL.md
+++ b/.github/skills/tsh-implementing-frontend/SKILL.md
@@ -148,5 +148,6 @@ The patterns above are framework-agnostic. For framework-specific implementation
- `tsh-ensuring-accessibility` — to ensure components meet WCAG 2.1 AA standards
- `tsh-optimizing-frontend` — for performance considerations during component implementation
- `tsh-implementing-forms` — for form-specific component patterns and validation
+- `tsh-implementing-filters` — for URL filter synchronization patterns consumed by list and search components
- `tsh-writing-hooks` — for custom hook patterns used within components
- `tsh-reviewing-frontend` — for frontend-specific code review of implemented components
diff --git a/.github/skills/tsh-writing-hooks/SKILL.md b/.github/skills/tsh-writing-hooks/SKILL.md
index 72660d02..24f428db 100644
--- a/.github/skills/tsh-writing-hooks/SKILL.md
+++ b/.github/skills/tsh-writing-hooks/SKILL.md
@@ -222,3 +222,4 @@ The patterns above are framework-agnostic. For framework-specific hook/composabl
- `tsh-optimizing-frontend` — for memoization strategies and performance patterns in hooks
- `tsh-reviewing-frontend` — for hook-specific code review criteria
- `tsh-implementing-forms` — for form-related custom hooks (field state, validation triggers)
+- `tsh-implementing-filters` — for the filter sync hook pattern (useFilters) that applies hook composition conventions
diff --git a/website/docs/skills/implementing-filters.md b/website/docs/skills/implementing-filters.md
new file mode 100644
index 00000000..f510242b
--- /dev/null
+++ b/website/docs/skills/implementing-filters.md
@@ -0,0 +1,35 @@
+---
+sidebar_position: 29
+title: Implementing Filters
+---
+
+# Implementing Filters
+
+**Folder:** `.github/skills/tsh-implementing-filters/`
+**Used by:** Software Engineer
+
+Type-safe URL filter synchronization with clean path and query string routing. Headless filter logic — schema definition, bracket notation serialization, URL sync hooks, and navigation strategy patterns.
+
+## Key Areas
+
+| Area | Coverage |
+| ----------------------- | ---------------------------------------------------------------------------------------- |
+| **Filter Schema** | TypeScript types, defaults, supported param types (single, multi-select, range, boolean) |
+| **URL Structure** | Path-vs-query golden rule, bracket notation (`filter[field]=value`) |
+| **Serialization** | Serialize/deserialize functions, bracket notation, snake_case keys, default omission |
+| **Filter Sync Hook** | `useFilters` hook — typed state from URL, `updateFilters`, `resetFilters` |
+| **Navigation Strategy** | Push vs replace per action type, debounced inputs, search-as-you-type |
+| **API Contract** | TSH bracket notation default, external API adaptation, response envelope |
+
+## When to Use
+
+- Implementing filterable lists, search pages, or faceted navigation
+- Persisting filter state in the URL for shareable links and back/forward navigation
+- Building filter sync hooks that derive state from URL search params
+- Adapting serialization for external APIs with non-bracket conventions
+
+## Connected Skills
+
+- `tsh-implementing-frontend` — component patterns that consume headless filter logic
+- `tsh-implementing-forms` — form-based filter UIs
+- `tsh-writing-hooks` — hook composition patterns for the filter sync hook
diff --git a/website/docs/skills/optimizing-frontend.md b/website/docs/skills/optimizing-frontend.md
index 7e8513ef..468b5bcb 100644
--- a/website/docs/skills/optimizing-frontend.md
+++ b/website/docs/skills/optimizing-frontend.md
@@ -1,5 +1,5 @@
---
-sidebar_position: 29
+sidebar_position: 30
title: Frontend Optimization
---
diff --git a/website/docs/skills/overview.md b/website/docs/skills/overview.md
index d32f17b6..34317877 100644
--- a/website/docs/skills/overview.md
+++ b/website/docs/skills/overview.md
@@ -5,7 +5,7 @@ title: Skills Overview
# Skills Overview
-Copilot Collections includes **31 reusable skills** — knowledge modules that provide specialized domain expertise, structured processes, and quality templates. They encode tested best practices for every phase of the product lifecycle. Skills are stored in `.github/skills/` and loaded automatically by agents when their domain applies to the current task.
+Copilot Collections includes **32 reusable skills** — knowledge modules that provide specialized domain expertise, structured processes, and quality templates. They encode tested best practices for every phase of the product lifecycle. Skills are stored in `.github/skills/` and loaded automatically by agents when their domain applies to the current task.
## How Skills Work
@@ -31,19 +31,20 @@ When an agent starts a task, it checks all available skills and decides which on
### 🛠 Development Skills
-| Skill | Description | Used By |
-| ------------------------------------------------------------------ | ------------------------------------------------------------- | -------------------------- |
-| [tsh-architecture-designing](./architecture-design) | Solution architecture design and implementation plan creation | Architect |
-| [tsh-technical-context-discovering](./technical-context-discovery) | Project conventions and pattern discovery | Architect, CR, SE, E2E, CE |
-| [tsh-implementing-frontend](./frontend-implementation) | UI component patterns, composition, design tokens | Software Engineer |
-| [tsh-implementing-forms](./implementing-forms) | Form architecture, schema validation, multi-step flows | Software Engineer |
-| [tsh-writing-hooks](./writing-hooks) | Custom hook/composable patterns, lifecycle, testing | Software Engineer |
-| [tsh-ensuring-accessibility](./ensuring-accessibility) | WCAG 2.1 AA compliance, semantic HTML, ARIA, keyboard nav | Software Engineer |
-| [tsh-optimizing-frontend](./optimizing-frontend) | Rendering optimization, code splitting, bundle size | Software Engineer |
-| [tsh-implementation-gap-analysing](./implementation-gap-analysis) | Gap analysis between plan and current state | Architect, CR, SE |
-| [tsh-sql-and-database-understanding](./sql-and-database) | Database engineering standards and ORM integration | Architect, CR, SE |
-| [tsh-codebase-analysing](./codebase-analysis) | Deep codebase analysis and dependency mapping | Architect, BA, CE, SE |
-| [tsh-engineering-prompts](./prompt-engineering) | LLM prompt design, optimization, security, and evaluation | PE, SE, Architect, CR |
+| Skill | Description | Used By |
+| ------------------------------------------------------------------ | ------------------------------------------------------------------ | -------------------------- |
+| [tsh-architecture-designing](./architecture-design) | Solution architecture design and implementation plan creation | Architect |
+| [tsh-technical-context-discovering](./technical-context-discovery) | Project conventions and pattern discovery | Architect, CR, SE, E2E, CE |
+| [tsh-implementing-frontend](./frontend-implementation) | UI component patterns, composition, design tokens | Software Engineer |
+| [tsh-implementing-forms](./implementing-forms) | Form architecture, schema validation, multi-step flows | Software Engineer |
+| [tsh-implementing-filters](./implementing-filters) | Type-safe URL filter sync, bracket notation, navigation strategies | Software Engineer |
+| [tsh-writing-hooks](./writing-hooks) | Custom hook/composable patterns, lifecycle, testing | Software Engineer |
+| [tsh-ensuring-accessibility](./ensuring-accessibility) | WCAG 2.1 AA compliance, semantic HTML, ARIA, keyboard nav | Software Engineer |
+| [tsh-optimizing-frontend](./optimizing-frontend) | Rendering optimization, code splitting, bundle size | Software Engineer |
+| [tsh-implementation-gap-analysing](./implementation-gap-analysis) | Gap analysis between plan and current state | Architect, CR, SE |
+| [tsh-sql-and-database-understanding](./sql-and-database) | Database engineering standards and ORM integration | Architect, CR, SE |
+| [tsh-codebase-analysing](./codebase-analysis) | Deep codebase analysis and dependency mapping | Architect, BA, CE, SE |
+| [tsh-engineering-prompts](./prompt-engineering) | LLM prompt design, optimization, security, and evaluation | PE, SE, Architect, CR |
### ☁️ Cloud & Infrastructure Skills
@@ -92,6 +93,7 @@ When an agent starts a task, it checks all available skills and decides which on
| tsh-ensuring-accessibility | | | | ✅ | | | | | | |
| tsh-implementing-ci-cd | | | ✅ | | | | | | ✅ | |
| tsh-implementing-forms | | | | ✅ | | | | | | |
+| tsh-implementing-filters | | | | ✅ | | | | | | |
| tsh-implementing-frontend | | | | ✅ | | | | | | |
| tsh-implementing-kubernetes | | | ✅ | | | | | | ✅ | |
| tsh-implementing-observability | | | ✅ | | | | | | ✅ | |
diff --git a/website/docs/skills/prompt-engineering.md b/website/docs/skills/prompt-engineering.md
index 3d8858e8..5599ea3b 100644
--- a/website/docs/skills/prompt-engineering.md
+++ b/website/docs/skills/prompt-engineering.md
@@ -1,5 +1,5 @@
---
-sidebar_position: 32
+sidebar_position: 33
title: Prompt Engineering
---
@@ -16,14 +16,14 @@ Reliable prompt structure separates concerns into distinct sections: system prom
## Optimization Techniques
-| Technique | Purpose |
-|---|---|
-| **Clarity and specificity** | Replace vague instructions with exact behavior, constraints, and output format |
-| **Constraint specification** | Explicit MUST / MUST NOT rules for model behavior |
-| **Output format control** | JSON schemas, structured output modes, explicit format instructions |
-| **Token efficiency** | Remove filler, use tables over paragraphs, cache static content in system prompts |
-| **Negative prompting** | Tell the model what NOT to do for better compliance |
-| **Temperature guidance** | Match temperature to task type (0.0–0.2 for extraction, 0.7–1.0 for creative) |
+| Technique | Purpose |
+| ---------------------------- | --------------------------------------------------------------------------------- |
+| **Clarity and specificity** | Replace vague instructions with exact behavior, constraints, and output format |
+| **Constraint specification** | Explicit MUST / MUST NOT rules for model behavior |
+| **Output format control** | JSON schemas, structured output modes, explicit format instructions |
+| **Token efficiency** | Remove filler, use tables over paragraphs, cache static content in system prompts |
+| **Negative prompting** | Tell the model what NOT to do for better compliance |
+| **Temperature guidance** | Match temperature to task type (0.0–0.2 for extraction, 0.7–1.0 for creative) |
## Security Patterns
@@ -54,14 +54,14 @@ Reusable templates for common patterns:
## Anti-Patterns
-| Anti-Pattern | Instead Do |
-|---|---|
-| Vague instructions ("be helpful") | Specify exact behavior and output format |
-| User input in system prompt | Separate with delimiters in dedicated section |
-| No output format specification | Define explicit schema or structure |
-| Prompt-only validation | Parse into typed models, validate schemas |
-| Hardcoded secrets in templates | Use environment variables or secret managers |
-| Testing only happy paths | Include adversarial, empty, long, and multilingual inputs |
+| Anti-Pattern | Instead Do |
+| --------------------------------- | --------------------------------------------------------- |
+| Vague instructions ("be helpful") | Specify exact behavior and output format |
+| User input in system prompt | Separate with delimiters in dedicated section |
+| No output format specification | Define explicit schema or structure |
+| Prompt-only validation | Parse into typed models, validate schemas |
+| Hardcoded secrets in templates | Use environment variables or secret managers |
+| Testing only happy paths | Include adversarial, empty, long, and multilingual inputs |
## Connected Skills
diff --git a/website/docs/skills/reviewing-frontend.md b/website/docs/skills/reviewing-frontend.md
index 933f2029..95f2ba3e 100644
--- a/website/docs/skills/reviewing-frontend.md
+++ b/website/docs/skills/reviewing-frontend.md
@@ -1,5 +1,5 @@
---
-sidebar_position: 30
+sidebar_position: 31
title: Frontend Review
---
diff --git a/website/docs/skills/writing-hooks.md b/website/docs/skills/writing-hooks.md
index de96abc4..49e6a879 100644
--- a/website/docs/skills/writing-hooks.md
+++ b/website/docs/skills/writing-hooks.md
@@ -1,5 +1,5 @@
---
-sidebar_position: 31
+sidebar_position: 32
title: Writing Hooks
---
diff --git a/website/package-lock.json b/website/package-lock.json
index 29bbca82..6dc385eb 100644
--- a/website/package-lock.json
+++ b/website/package-lock.json
@@ -161,7 +161,6 @@
"resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.49.0.tgz",
"integrity": "sha512-uGv2P3lcviuaZy8ZOAyN60cZdhOVyjXwaDC27a1qdp3Pb5Azn+lLSJwkHU4TNRpphHmIei9HZuUxwQroujdPjw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@algolia/client-common": "5.49.0",
"@algolia/requester-browser-xhr": "5.49.0",
@@ -287,7 +286,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -2090,7 +2088,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
},
@@ -2113,7 +2110,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -2223,7 +2219,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -2645,7 +2640,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -3626,7 +3620,6 @@
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz",
"integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@docusaurus/core": "3.9.2",
"@docusaurus/logger": "3.9.2",
@@ -3895,7 +3888,6 @@
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.2.tgz",
"integrity": "sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@docusaurus/mdx-loader": "3.9.2",
"@docusaurus/module-type-aliases": "3.9.2",
@@ -4779,7 +4771,6 @@
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz",
"integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/mdx": "^2.0.0"
},
@@ -5508,7 +5499,6 @@
"resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz",
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/core": "^7.21.3",
"@svgr/babel-preset": "8.1.0",
@@ -5881,7 +5871,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -6223,7 +6212,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6291,7 +6279,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -6337,7 +6324,6 @@
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.49.0.tgz",
"integrity": "sha512-Tse7vx7WOvbU+kpq/L3BrBhSWTPbtMa59zIEhMn+Z2NoxZlpcCRUDCRxQ7kDFs1T3CHxDgvb+mDuILiBBpBaAA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@algolia/abtesting": "1.15.0",
"@algolia/client-abtesting": "5.49.0",
@@ -6817,7 +6803,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -7789,7 +7774,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -9179,7 +9163,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -13643,7 +13626,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -14188,7 +14170,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -15092,7 +15073,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -15909,7 +15889,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -15922,7 +15901,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -15979,7 +15957,6 @@
"resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz",
"integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/react": "*"
},
@@ -16008,7 +15985,6 @@
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz",
"integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
@@ -17773,8 +17749,7 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD",
- "peer": true
+ "license": "0BSD"
},
"node_modules/tsyringe": {
"version": "4.10.0",
@@ -17855,7 +17830,6 @@
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"devOptional": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -18206,7 +18180,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -18405,7 +18378,6 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.2.tgz",
"integrity": "sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",