Add snippet-library search, sort, empty states, storage monitor (M6, §02/§09D)

This commit is contained in:
2026-06-07 20:00:40 +03:00
parent 9f7bf27b7a
commit 800a313be2
19 changed files with 1461 additions and 16 deletions
@@ -236,6 +236,45 @@ relative date and size), in a fixed-width leading slot so it never shifts adjace
_(Consulted via /council → GOV.UK Tag, Carbon status-indicator-pattern, APG. This bullet is the
contract; cite it, not the external source.)_
**Resolved — library search (Carbon active-search).** The snippet-library search (spec
§02) is an **unlabelled active-search input** pinned above the list: `type="search"` with a
leading magnifier and `aria-label="Search snippets"` (no visible label — the icon +
placeholder name it), filtering the list on **each keystroke** (no Search button, no results
page). A **clear (✕)** appears only when the box is non-empty; it empties the box **and
returns focus to the input** (NN/g #3 user control). Matching is case-insensitive substring
across **name + comment + draft spec text** (the pure `snippetMatchesQuery` in
`core/snippet-sort.ts`). Search affects **visibility only** — it never changes
`activeSnippetId` or any data. A **polite `aria-live` region announces the result count,
including no results** (Carbon: _"always include the number of results, including no
results"_); it stays silent for the default unfiltered list. _(Consulted via /council →
Carbon search/active-search, NN/g #3. This bullet is the contract; cite it, not the source.)_
**Resolved — library sort (APG menu-button + NN/g recognition).** The Sort control (spec
§02) reuses the **disclosure popover** primitive (the settings-popover model above), **not**
an ARIA menu — but its trigger shows the **current state for recognition** ("Sort: Modified
↓"), per NN/g #6 (recognition over recall), instead of a bare gear. Fields are **Modified /
Created / Name / Size**; the active field shows a **direction arrow** (↓ desc / ↑ asc) in both
the trigger and the field row, and the arrow's meaning is mirrored into the field's
`aria-label` ("Modified, descending") so it isn't carried by the glyph alone. **Selection
model** (spec §02): re-selecting the **active** field flips direction; selecting a
**different** field switches to it and **resets to descending** — encapsulated in
`SnippetStore.setSort` and tested in core. APG disclosure keyboard/focus is inherited from
the shared popover (Enter/Space open; **Esc closes and returns focus to the trigger**; outside
click closes; one open at a time). Field+direction **persist across sessions** (ux-prefs;
default Modified/desc); search is **not** persisted (it's a transient view narrowing).
_(Consulted via /council → WAI-ARIA APG disclosure + menu-button, NN/g #6. This bullet is the
contract; cite it, not the source.)_
**Resolved — the two library empty states (Carbon's two types).** The snippet list owes
**two distinct empty states** (Carbon empty-state types), chosen by cause and never conflated:
(a) a **genuinely empty library** (first run) is **positive and concise and guides to the
Create action** — title "No snippets yet" + a one-line nudge to create one; (b) **no search
matches** says "No snippets match your search" + a hint to try a different term. Both are
plain-language and minimal (NN/g aesthetic-and-minimalist; §3 empty ≠ error). The library
derives **which** to show from whether a query is active, not from the raw count alone.
_(Consulted via /council → Carbon empty-state, NN/g. This bullet is the contract; cite it,
not the source.)_
**Resolved — one live region per shared message.** When the same error feeds two surfaces
(the §1 "one producer, two subscribers" case — render errors via `PreviewStore`), exactly
**one** subscriber is the live region (`role="alert"` on the editor, where focus is); the