docs: update tag search spec (spec review fixes)

This commit is contained in:
Sven
2026-04-09 21:16:37 +02:00
parent 5aeb2d05dd
commit f2629a4ac3
@@ -19,14 +19,14 @@ Two patch scripts that wire up autocomplete end-to-end with no new endpoints and
**What it does:**
Inserts a `case "categories":` block before the existing `default:` case in `queryController`. When `q=category` is requested, instead of falling through to `config.categories` (which is empty), this case:
Inserts a `case "categories":` block before the existing `default:` case in `queryController`. When `q=category` is requested, instead of falling through to `config.categories` (which is an empty array — truthy, so the `default:` branch does not throw, it just returns `{categories:[]}`), this case:
1. Calls `postsCollection.distinct("properties.category")` to get every unique tag used across all published posts.
1. Calls `postsCollection.distinct("properties.category")` to get every unique tag used across all published posts. MongoDB `distinct()` on an array field correctly unwraps array values, so `["photography","travel"]` per-post becomes individual distinct entries.
2. Filters out falsy values (`Boolean` filter) and sorts alphabetically.
3. Passes the result through the existing `queryConfig(cats, { filter, limit, offset })` so the `filter=` query param works for substring matching.
3. Passes the result through `queryConfig(cats, { filter: filter?.toLowerCase(), limit, offset })` — note: `filter` is lowercased before passing to match `queryConfig`'s internal lowercase comparison, making search case-insensitive end-to-end.
4. Returns `{ categories: [...] }`.
If `postsCollection` is not available, returns an empty array gracefully.
If `postsCollection` is not available, returns `{ categories: [] }` gracefully.
**Example request/response:**
```
@@ -34,30 +34,45 @@ GET /micropub?q=category&filter=pho
→ { "categories": ["photography"] }
```
**OLD_SNIPPET anchor:** The patch uses the closing `break;` of the preceding `source:` case plus the `default: {` line as context for the OLD_SNIPPET, since the `default:` block itself does not contain error-throwing logic for the `categories` query (empty array is truthy).
---
## Patch 2 — Frontend: `patch-tag-input-autocomplete.mjs`
**Target:** `node_modules/@indiekit/frontend/components/tag-input/index.js`
**Micropub endpoint URL discovery:**
The patch reads the Micropub endpoint URL using the standard IndieWeb discovery selector:
```js
const micropubHref = document.querySelector('link[rel="micropub"]')?.href ?? "/micropub";
```
This is resolved once per component instantiation. On admin pages Indiekit does not inject the `<link rel="micropub">` element, so the fallback `/micropub` is always used in practice — which is correct for this deployment.
**What it does:**
After the `$tagInputInput` reference is obtained inside `TagInputFieldComponent.connectedCallback()`, appends the following behaviour:
1. Creates a `<datalist id="tag-suggestions-<uid>">` element and appends it to the component. The uid is derived from the input's `name` attribute to be stable per field.
2. Sets `$tagInputInput.setAttribute("list", datalistId)`the browser's native datalist autocomplete now applies to the text field.
3. Adds a debounced (300ms) `input` event listener:
- If the current value is ≥ 1 char, fetches `GET /micropub?q=category&filter=<value>` (same-origin, session cookie included by default).
- Replaces datalist `<option>` elements with the results from `response.categories`.
- If the value is empty, clears the datalist.
4. Clears the datalist on `blur` so stale suggestions don't reappear after a tag is confirmed.
1. Creates a `<datalist id="tag-suggestions-<name>">` element (uid from the input's `name` attribute for stability) and appends it to the component.
2. Sets `$tagInputInput.setAttribute("list", datalistId)`native browser datalist autocomplete applies to the text field.
3. Adds a debounced (300ms) `input` event listener: if the current value is ≥ 1 char, fetches `GET <micropubHref>?q=category&filter=<value>` and replaces datalist `<option>` elements with `response.categories`.
4. Adds a **`keydown` listener for the Comma key**: after the existing handler adds the tag, this listener fires next (FIFO) and calls `datalist.replaceChildren()` to clear suggestions.
5. Adds a **`blur` listener**: after the existing handler confirms the tag and clears the field, this listener fires next (FIFO) and calls `datalist.replaceChildren()` to clear suggestions.
**Why not rely on `input` event for cleanup:** The existing `keydown`/`blur` handlers clear the field via `$tagInputInput.value = ""` (programmatic assignment). Programmatic `.value` assignment does **not** fire the `input` event per the HTML Living Standard, so cleanup must be wired directly into both confirmation paths.
**Why a `blur` listener is safe here:** Clicking a datalist `<option>` fills the input value and fires `input` — it does not fire `blur` until the user takes a separate action (comma, enter, or clicking elsewhere). By that point our `blur` handler fires after the existing one, which has already processed the value. There is no race with datalist value-fill.
The existing `keydown` (comma) and `blur` (confirm) handlers are not modified — new listeners are added alongside them.
**Interaction flow:**
- User types "ph" → datalist shows "photography", "philosophy"
- User clicks "photography" → text field fills with "photography"
- User presses comma or tabs out → tag confirmed as a pill, text field cleared, datalist cleared
The existing `keydown` (comma) and `blur` (confirm) handlers are not modified.
- User clicks "photography" → text field fills with "photography", `input` fires, datalist refreshes with filtered results
- User presses comma → existing `keydown` confirms tag; our `keydown` clears datalist
- OR user tabs/clicks away → existing `blur` confirms tag; our `blur` clears datalist
---
@@ -81,5 +96,5 @@ Both scripts follow the standard marker pattern:
## Out of scope
- Selecting a suggestion does not auto-confirm the tag as a pill (user confirmed: fill field only, comma/blur to confirm)
- No server-side caching of the distinct query (MongoDB distinct on a small collection is fast)
- No server-side caching of the distinct query (MongoDB distinct on a small personal-blog collection is fast)
- No UI styling changes to the datalist (native browser rendering)