docs: tag entry autocomplete design spec
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
# Tag Entry Autocomplete — Design Spec
|
||||||
|
|
||||||
|
**Date:** 2026-04-09
|
||||||
|
**Status:** Approved
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The tag/category input in the Indiekit post compose form is a plain text field with no suggestions. The `q=category` Micropub endpoint returns an empty array because `publication.categories` is not configured — tags only exist in the `posts` MongoDB collection as `properties.category` values.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Two patch scripts that wire up autocomplete end-to-end with no new endpoints and no changes to the Indiekit config.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Patch 1 — Backend: `patch-micropub-category-from-posts.mjs`
|
||||||
|
|
||||||
|
**Target:** `node_modules/@indiekit/endpoint-micropub/lib/controllers/query.js`
|
||||||
|
|
||||||
|
**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:
|
||||||
|
|
||||||
|
1. Calls `postsCollection.distinct("properties.category")` to get every unique tag used across all published posts.
|
||||||
|
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.
|
||||||
|
4. Returns `{ categories: [...] }`.
|
||||||
|
|
||||||
|
If `postsCollection` is not available, returns an empty array gracefully.
|
||||||
|
|
||||||
|
**Example request/response:**
|
||||||
|
```
|
||||||
|
GET /micropub?q=category&filter=pho
|
||||||
|
→ { "categories": ["photography"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Patch 2 — Frontend: `patch-tag-input-autocomplete.mjs`
|
||||||
|
|
||||||
|
**Target:** `node_modules/@indiekit/frontend/components/tag-input/index.js`
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Auth
|
||||||
|
|
||||||
|
The Micropub `GET /micropub` endpoint is mounted after `indieauth.authenticate()` in `routes.js`. The admin UI is session-authenticated. A same-origin `fetch` from the browser includes the session cookie, so no Bearer token is needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Patch script conventions (per CLAUDE.md)
|
||||||
|
|
||||||
|
Both scripts follow the standard marker pattern:
|
||||||
|
- Define `MARKER`, `OLD_SNIPPET`, `NEW_SNIPPET`
|
||||||
|
- Read file, skip if marker present, warn if old snippet not found, replace and write
|
||||||
|
- Include both candidate paths: `node_modules/@indiekit/...` and `node_modules/@indiekit/indiekit/node_modules/@indiekit/...`
|
||||||
|
- Register in both `postinstall` and `serve` scripts (both patches are startup-safe)
|
||||||
|
- Run each patch script immediately after writing to verify it applies cleanly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 UI styling changes to the datalist (native browser rendering)
|
||||||
Reference in New Issue
Block a user