6.9.1 Reusable server-side issue quick-search read — key + title (trgm), workspace + 6.4-permission scoped, bounded + relevance-ordered
Estimate: 28m · Depends on: 6.1.1
The shared read both pickers (and, later, the cmd-K palette) consume. Pure backend, no UI.
workItemSearchRepository (or an addition to workItemRepository) — a single query-op: given workspaceId, a query string, and a limit, return non-archived work items whose key matches (prefix / exact, case-insensitive) OR whose title ILIKE-contains the query, using the 6.1.1 pg_trgm GIN index (a contains-scan over 10k titles must not table-scan), ordered relevance-first (exact-key → key-prefix → title match), bounded to limit. Read-only → the db singleton; explicit workspaceId gate (finding #26 — the primary tenant filter, since RLS is inert under the dev/CI superuser).
workItemsService.quickSearch(query, ctx, opts) (4-layer) — trims/guards the query (empty → empty result, no scan; a minimum-length guard), applies the 6.4 project-access filter so a user only finds issues in projects they may read (reuse the shipped projectAccessService predicates — the same gate the issue list rides), maps rows to WorkItemSummaryDto, and bounds the page. No new route is required by THIS subtask (6.9.2 wires it behind the existing link action); expose it as a service method the actions/route layer calls.
Acceptance criteria
- The read matches on key (prefix/exact) AND title (trgm ILIKE), is bounded, and is relevance-ordered; EXPLAIN shows the
pg_trgmindex used (no full scan) on the large seed. quickSearchis workspace-scoped AND 6.4-permission-scoped (a user cannot find an issue in a project they cannot read — asserted per role); empty / whitespace / below-min-length query returns empty without scanning.- Returns
WorkItemSummaryDto[]; no schema change (migrate dev= "No difference detected");pnpm test:coverage≥ 90% on the new files.
Context refs
- 6.1.1
lib/filters/*+ thepg_trgmindex migration (the substrate this reuses); the 2.5 issue-listcontainsread (findByProjectFiltered/ the list query — the closest shipped text-match precedent) lib/services/projectAccessService.ts+ the 6.4 read predicates (the permission gate); finding #26 (the explicitworkspaceIdtenant filter)WorkItemSummaryDto+toWorkItemSummaryDto(the return shape)motir-core/CLAUDE.md(4-layer, required-tx on writes — N/A here, read-only)