9.8 KiB
Search-Result Date Meta + Estimated-Count Copy — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Thread recording_date through the search index/hit/view (Rust), show it on the result row, and soften the estimated-count copy.
Architecture: Backend adds recording_date: Option<String> (YYYY-MM-DD) to SearchDocument (projected in build_document), SearchHit (mapped in search_objects), and SearchHitView (API). The frontend type comes from the regenerated/edited schema.d.ts; the row renders the date and the count copy gains a ~.
Tech Stack: Rust (search + api crates, Meilisearch, utoipa), React + TS + pnpm. Tests need the docker stack (Postgres :5442, Meilisearch :7700 — already up). Rust test runner: cargo nextest. Frontend: pnpm test.
Conventions: cargo +nightly fmt + cargo clippy --workspace --all-targets -- -D warnings for Rust; pnpm + no any/eslint-disable + en/sv parity for web; no codename; manage deps via cargo-mcp if any (none here). Run a single test pass.
Spec: docs/superpowers/specs/2026-06-08-search-row-date-design.md
Key facts:
crates/search/src/lib.rs:SearchDocument(:30),SearchHit(:52),build_document(:302, returns theSearchDocument { … }literal around:363),search_objectshit map (~:220).domain::DateDisplay = ISOYYYY-MM-DD;CatalogueObject.recording_date: Option<Date>.crates/api/src/admin_search.rs:SearchHitView(:26) + map closure (:100).crates/search/tests/search.rs: adoc(...)helper builds aSearchDocumentliteral;search_objects_returns_hits_…(:55) seeds 3 docs + asserts.tests/sync.rs/tests/reindex.rsconstructCatalogueObject(already hasrecording_date— unaffected) but may also buildSearchDocument— check.web/src/api/schema.d.tsSearchHitViewblock (:601): keys are alphabetized — insertrecording_date?: string | null;betweenobject_numberandsnippet.web/src/search/search-result-row.tsx: meta line<span>{hit.object_number}</span> <VisibilityBadge/>.- i18n
search.resultCount_one/_other;web/src/test/fixtures.tssearchHits;web/src/search/search.test.tsxasserts/25 results/i.
Task 1: Backend — recording_date through index → hit → view (+ schema)
Files: crates/search/src/lib.rs, crates/api/src/admin_search.rs, crates/search/tests/search.rs (+ any test with a SearchDocument/SearchHit literal), web/src/api/schema.d.ts.
-
Step 1:
SearchDocument+build_document(crates/search/src/lib.rs):- Add
pub recording_date: Option<String>,toSearchDocument(afterrecorder, beforevisibility— keep a sensible order). - In the
Ok(SearchDocument { … })literal at the end ofbuild_document, addrecording_date: object.recording_date.map(|d| d.to_string()),.
- Add
-
Step 2:
SearchHit+search_objectsmapping (crates/search/src/lib.rs):- Add
pub recording_date: Option<String>,toSearchHit(aftervisibility, beforesnippetor near it). - In
search_objects, theSearchHit { id, object_number, object_name, brief_description, visibility, snippet }literal: addrecording_date: doc.recording_date,.
- Add
-
Step 3:
SearchHitView(crates/api/src/admin_search.rs):- Add
pub recording_date: Option<String>,toSearchHitView. - In the
.map(|h| SearchHitView { … })closure: addrecording_date: h.recording_date,.
- Add
-
Step 4: Fix test literals + extend the search test (
crates/search/tests/search.rs):- The
doc(...)helper builds aSearchDocumentliteral — addrecording_date: None,to it (compile fix). Grep the search + api test dirs for otherSearchDocument {/SearchHit {literals and addrecording_date: Nonewhere needed (object fixtures that buildCatalogueObjectare unaffected — they already haverecording_date). - In
search_objects_returns_hits_with_highlight_filter_and_paging, set a date on one seeded doc before indexing:bronze_a.recording_date = Some("1962-04-03".to_string());and after the existinghitlookup assert:assert_eq!(hit.recording_date.as_deref(), Some("1962-04-03"));.
- The
-
Step 5: Build + lint + test (stack is up).
cd /Users/olsson/Laboratory/biggus-dickus
cargo build --workspace
cargo +nightly fmt
cargo clippy --workspace --all-targets -- -D warnings
cargo nextest run -p search -p api
Expected: compiles, fmt clean, clippy clean, tests pass (incl. the new recording_date assertion). Run the test suite ONCE (single Postgres — no concurrent cargo test runs). If clippy/fmt change files, include them.
- Step 6: Update
web/src/api/schema.d.ts— add the field to theSearchHitViewblock (alphabetical, betweenobject_numberandsnippet):
SearchHitView: {
brief_description?: string | null;
id: string;
object_name: string;
object_number: string;
recording_date?: string | null;
snippet?: string | null;
visibility: components["schemas"]["Visibility"];
};
(This mirrors what pnpm gen:api would emit now that the backend returns the field; a later regen reproduces it identically. Optionally verify with pnpm gen:api if a server is running — not required.)
- Step 7: Commit
git add crates/search/src/lib.rs crates/api/src/admin_search.rs crates/search/tests/search.rs web/src/api/schema.d.ts
# (+ any other test files you fixed)
git commit -m "feat(search): index + return recording_date on search hits (#61)"
Task 2: Frontend — row date + softened count + tests + gate
Files: web/src/search/search-result-row.tsx, web/src/i18n/en.json, web/src/i18n/sv.json, web/src/test/fixtures.ts, web/src/search/search.test.tsx.
- Step 1:
search-result-row.tsx— show the date on the meta line when present:
<div className="mt-0.5 flex items-center gap-2 text-xs text-muted-foreground">
<span>{hit.object_number}</span>
{hit.recording_date && <span>· {hit.recording_date}</span>}
<VisibilityBadge visibility={hit.visibility} />
</div>
(Render the YYYY-MM-DD string verbatim — it's a recording date, not a UTC timestamp.)
-
Step 2: i18n — soften the count (both locales, parity; the
#60parity test guards this):- en.json
search:"resultCount_one": "~{{count}} result","resultCount_other": "~{{count}} results". - sv.json
search:"resultCount_one": "~{{count}} träff","resultCount_other": "~{{count}} träffar". (No code change insearch-panel.tsx— the key already interpolatescount.)
- en.json
-
Step 3: Fixtures (
web/src/test/fixtures.ts) — addrecording_dateto the firstsearchHitsentry:recording_date: "1962-04-03",(the generated rest can berecording_date: nullor omit it — it's optional; setnullon the mappedArray.from(...)items to satisfy the type cleanly). -
Step 4: Tests (
web/src/search/search.test.tsx):- Assert the first result row shows the date: after results render,
expect(screen.getByText("1962-04-03")).toBeInTheDocument();(or scope within the row). - Tighten the count assertion to require the
~: changegetByText(/25 results/i)→getByText(/~\s*25 results/i)(or assertgetByText(/~25 results/i)). Keep the existing load-more + visibility-filter tests green.
- Assert the first result row shows the date: after results render,
-
Step 5: FULL FRONTEND GATE (run tests EXACTLY ONCE):
cd web && pnpm typecheck && pnpm lint && pnpm test && pnpm build && pnpm check:size && pnpm check:colors
All green. Report test totals, largest chunk, check:colors line.
- Step 6: Codename + status:
cd /Users/olsson/Laboratory/biggus-dickus
git grep -in 'biggus\|dickus' -- web/src crates; echo "codename-exit=$?"
git status --short
-
Step 7: Manual smoke (recommended). With the stack + server + web dev up: search the collection — result rows show the recording date (for objects that have one and have been (re)indexed); the count reads "~N results".
-
Step 8: Commit
git add web/src/search/search-result-row.tsx web/src/i18n/en.json web/src/i18n/sv.json web/src/test/fixtures.ts web/src/search/search.test.tsx
git commit -m "feat(web): show recording_date on search rows; flag estimated count as approximate (#61)"
Self-Review (completed)
Spec coverage: recording_date in SearchDocument/build_document (T1 S1), SearchHit/search_objects (T1 S2), SearchHitView (T1 S3), schema.d.ts (T1 S6); Rust test literal fixes + flow assertion (T1 S4); row date (T2 S1); softened count i18n (T2 S2); fixtures + tests (T2 S3–S4); Rust + frontend gates (T1 S5, T2 S5). Acceptance criteria 1–4 mapped. ✓
Placeholder scan: the schema.d.ts edit is given verbatim; the test changes name exact assertions; "grep for other SearchDocument/SearchHit literals" is a concrete compile-driven step (the compiler names them if missed). No vague steps. ✓
Type/consistency: recording_date: Option<String> (Rust) ↔ recording_date?: string | null (TS) consistent; object.recording_date.map(|d| d.to_string()) (YYYY-MM-DD) matches AdminObjectView; the frontend reads hit.recording_date from the regenerated type. ✓
Notes
- No new dependency. en/sv parity preserved (only
resultCount_*values change; guarded by #60). - Backfill: already-indexed objects show no date until
reindex_all; new/edited objects index it viasync_object. AreindexCLI is a follow-up. - Rust tests need the docker stack (up); run cargo tests ONCE (shared Postgres).
cargo nextest run -p search -p apicovers the touched crates; a fullcargo build --workspaceensures nothing else broke from the struct change.