Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

158 lines
27 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[
{ "id": "F001", "description": "Create migration file `server/migrations/20260513120000_create_app_search_index.cjs` (concrete timestamp; adjust only if a later date is needed at implementation time)", "prdRefs": ["9.1", "16"], "implemented": true },
{ "id": "F002", "description": "Migration enables the `pg_trgm` Postgres extension if not already present", "prdRefs": ["9.1", "13.1"], "implemented": true },
{ "id": "F003", "description": "Migration creates the `app_search_index` table with all columns defined in PRD §9.1 (tenant, object_type, object_id, parent_type, parent_id, title, subtitle, body, url, metadata, visible_to_user_ids, visible_to_roles, is_internal_only, is_private, client_scope_id, required_permission, search_vector, search_lang, source_updated_at, indexed_at, PK (tenant, object_type, object_id))", "prdRefs": ["9.1"], "implemented": true },
{ "id": "F004", "description": "Migration calls `create_distributed_table('app_search_index', 'tenant')` only when the `citus` extension is present", "prdRefs": ["9.1", "15"], "implemented": true },
{ "id": "F005", "description": "Migration creates GIN index on `search_vector`", "prdRefs": ["9.1"], "implemented": true },
{ "id": "F006", "description": "Migration creates GIN trgm indexes on `title` and `subtitle` for fuzzy fallback", "prdRefs": ["9.1", "13.1"], "implemented": true },
{ "id": "F007", "description": "Migration creates btree indexes on `(tenant, source_updated_at DESC)` and `(tenant, object_type)`", "prdRefs": ["9.1"], "implemented": true },
{ "id": "F008", "description": "Migration provides a `down` step that drops the table", "prdRefs": ["9.1"], "implemented": true },
{ "id": "F009", "description": "Define `SearchObjectType` TS union covering all 27 entity types in `packages/types/src/search.ts`", "prdRefs": ["7", "9.2"], "implemented": true },
{ "id": "F010", "description": "Define `SearchDoc` interface with title, subtitle, body, url, metadata, acl, sourceUpdatedAt in `packages/types/src/search.ts`", "prdRefs": ["9.2"], "implemented": true },
{ "id": "F011", "description": "Define `AclMetadata` interface (visibleToUserIds, visibleToRoles, isInternalOnly, isPrivate, clientScopeId, requiredPermission)", "prdRefs": ["9.2", "14"], "implemented": true },
{ "id": "F012", "description": "Implement `flattenBlockNote(json)` in `server/src/lib/search/normalize.ts` that walks BlockNote nodes, concatenates text leaves, and drops image data URIs", "prdRefs": ["9.3"], "implemented": true },
{ "id": "F013", "description": "Implement `flattenMarkdown(md)` that strips markdown tokens (headings, lists, links, bold/italic, code fences) leaving plain text", "prdRefs": ["9.3"], "implemented": true },
{ "id": "F014", "description": "Implement `flattenJsonbPayload(obj)` that recursively concatenates string leaves, skipping keys matching `password|secret|token|api_key|authorization`", "prdRefs": ["9.3"], "implemented": true },
{ "id": "F015", "description": "Implement `truncateForIndex(text, maxBytes=65536)` that does UTF-8-safe byte truncation without splitting code points", "prdRefs": ["9.3"], "implemented": true },
{ "id": "F016", "description": "Implement `buildTsvectorSql(title, subtitle, body)` that emits Postgres SQL using `setweight(to_tsvector('english', process_large_lexemes($x)), 'A|B|C')` for the three weight tiers", "prdRefs": ["13.2"], "implemented": true },
{ "id": "F017", "description": "Define `EntityIndexer` interface with `objectType`, `sourceEvents`, `loadOne`, `loadBatch` in `packages/types/src/search.ts`", "prdRefs": ["10"], "implemented": true },
{ "id": "F018", "description": "Build registry in `server/src/lib/search/index.ts` exporting `allIndexers()`, `getIndexer(objectType)`, and `registeredObjectTypes()`. Imports both `ceIndexers` (from `./indexers`) and `eeIndexers` (from `ee/server/src/lib/search/indexers` — stubbed to `[]` in CE builds) and merges them. See PRD §19.", "prdRefs": ["10", "19.1"], "implemented": true },
{ "id": "F019", "description": "Implement `upsertSearchDoc(knex, doc)` in `server/src/lib/search/upsert.ts` that inserts/updates a row and computes `search_vector` server-side", "prdRefs": ["9", "11.1"], "implemented": true },
{ "id": "F020", "description": "Implement `deleteSearchDoc(knex, tenant, objectType, objectId)`", "prdRefs": ["11.1"], "implemented": true },
{ "id": "F021", "description": "Provide a typed `composeAclHints(opts)` helper to consistently build the denormalized ACL columns from indexer code", "prdRefs": ["9.1", "14"], "implemented": true },
{ "id": "F022", "description": "Implement `clientIndexer` in `server/src/lib/search/indexers/client.ts` (title=client_name, subtitle=email/phone, body=notes, acl: required_permission='client:read')", "prdRefs": ["7", "10"], "implemented": true },
{ "id": "F023", "description": "Implement `contactIndexer` (title=full_name, subtitle=email/phone/role, acl: required_permission='contact:read')", "prdRefs": ["7"], "implemented": true },
{ "id": "F024", "description": "Implement `userIndexer` for team members (internal users only; title=full name; subtitle=username/email/title; acl: required_permission='user:read')", "prdRefs": ["7"], "implemented": true },
{ "id": "F025", "description": "Implement `ticketIndexer` (title=ticket.title, subtitle=denormalized client name + ticket_number, acl: required_permission='ticket:read', visible_to_roles from board scope, metadata.identifier=ticket_number)", "prdRefs": ["7", "13.3"], "implemented": true },
{ "id": "F026", "description": "Implement `ticketCommentIndexer` (parent_type='ticket', body=flattenMarkdown(note), acl inherits ticket + is_internal_only when `is_internal=true`, url with `#comment-{id}` anchor)", "prdRefs": ["7", "14"], "implemented": true },
{ "id": "F027", "description": "Implement `projectIndexer` (title=project_name, body=description, acl: required_permission='project:read', client_scope_id=client_id)", "prdRefs": ["7"], "implemented": true },
{ "id": "F028", "description": "Implement `projectPhaseIndexer` (inherits project ACL; subtitle=project name)", "prdRefs": ["7"], "implemented": true },
{ "id": "F029", "description": "Implement `projectTaskIndexer` (inherits project ACL; subtitle=project name)", "prdRefs": ["7"], "implemented": true },
{ "id": "F030", "description": "Implement `projectTaskCommentIndexer` — prefers `markdown_content`, falls back to `flattenBlockNote(note)`; inherits project ACL", "prdRefs": ["7", "9.3"], "implemented": true },
{ "id": "F031", "description": "Implement `assetIndexer` (title=name, subtitle=asset_tag + serial_number, body=location + flattenJsonbPayload(attributes), acl: required_permission='asset:read', optional client_scope_id, metadata.identifier=asset_tag)", "prdRefs": ["7", "13.3"], "implemented": true },
{ "id": "F032", "description": "Implement `invoiceIndexer` (title=invoice_number, subtitle=denormalized client name + status, acl: required_permission='invoice:read', client_scope_id, metadata.identifier=invoice_number)", "prdRefs": ["7", "13.3"], "implemented": true },
{ "id": "F033", "description": "Implement `invoiceItemIndexer` (parent_type='invoice', body=description, inherits invoice ACL, url with `#item-{id}` anchor)", "prdRefs": ["7"], "implemented": true },
{ "id": "F034", "description": "Implement `invoiceAnnotationIndexer` (parent_type='invoice', body=content, inherits invoice ACL, url with `#annotation-{id}` anchor)", "prdRefs": ["7"], "implemented": true },
{ "id": "F035", "description": "Implement `contractIndexer` against the `contracts` table (columns: contract_id, contract_name, contract_description, status, is_active). title=contract_name, body=contract_description, subtitle='Quote' when status='draft' else 'Contract'. acl: required_permission='contract:read'. metadata.identifier=contract_name", "prdRefs": ["7"], "implemented": true },
{ "id": "F036", "description": "Implement `clientContractIndexer` against `client_contracts` (PK (tenant, client_contract_id); columns client_id, contract_id, start_date, end_date, is_active). Title = `{client_name} {contract_name}` derived via joins to `clients` and `contracts`. Sets `client_scope_id` from `client_id` for ACL.", "prdRefs": ["7"], "implemented": true },
{ "id": "F037", "description": "Implement `documentIndexer` (title=document_name, body=truncateForIndex(flattenBlockNote(content), 64KB), acl: required_permission='document:read', optional client_scope_id from `documents.client_id` when set). NOTE: CE has no internal per-user document share mechanism — `is_private` and `visible_to_user_ids` are intentionally NOT set on document rows at v1.", "prdRefs": ["7", "9.3", "14"], "implemented": true },
{ "id": "F038", "description": "Implement `kbArticleIndexer` (joins documents; acl: required_permission='kb:read')", "prdRefs": ["7"], "implemented": true },
{ "id": "F039", "description": "Implement `serviceCatalogIndexer` (title=service_name, body=description + flattenJsonbPayload(attributes), acl: required_permission='service_catalog:read')", "prdRefs": ["7"], "implemented": true },
{ "id": "F040", "description": "Implement `serviceRequestSubmissionIndexer` (title=request_name, body=flattenJsonbPayload(submitted_payload), acl: required_permission='service_request:read', optional client_scope_id)", "prdRefs": ["7", "9.3"], "implemented": true },
{ "id": "F041", "description": "Implement `serviceRequestDefinitionIndexer` (title=name, body=description, acl: required_permission='admin')", "prdRefs": ["7"], "implemented": true },
{ "id": "F042", "description": "Implement `workflowTaskIndexer` against `workflow_tasks` (PK is `task_id` STRING alone — `tenant` is a regular text column, not in the PK; assignees stored as JSONB `assigned_users`). title=title, body=description, acl: required_permission='workflow_task:read', visible_to_user_ids parsed from assigned_users JSONB. All upserts/queries must still filter by `tenant`.", "prdRefs": ["7"], "implemented": true },
{ "id": "F043", "description": "Implement `interactionIndexer` (title=`interactions.title`; body=`flattenBlockNote(notes)` truncated to 64 KB; subtitle=interaction type name + counterparty (client/contact/ticket reference); acl: required_permission='interaction:read')", "prdRefs": ["7", "9.3"], "implemented": true },
{ "id": "F044", "description": "Implement `scheduleEntryIndexer` (title=title, body=notes, acl: required_permission='schedule:read', visible_to_user_ids from owner)", "prdRefs": ["7"], "implemented": true },
{ "id": "F045", "description": "Implement `timeEntryIndexer` (skip rows where `notes IS NULL OR notes = ''`; visible_to_user_ids from owner; acl: required_permission='time:read')", "prdRefs": ["7", "18-Q3"], "implemented": true },
{ "id": "F046", "description": "Implement `boardIndexer` (title=channel_name, acl: required_permission='ticket:read')", "prdRefs": ["7"], "implemented": true },
{ "id": "F047", "description": "Implement `categoryIndexer` (title=category_name, acl: required_permission='ticket:read')", "prdRefs": ["7"], "implemented": true },
{ "id": "F048", "description": "Implement `tagIndexer` (title=tag_text, acl: required_permission='ticket:read')", "prdRefs": ["7"], "implemented": true },
{ "id": "F049", "description": "Add `CLIENT_CREATED`, `CLIENT_UPDATED`, `CLIENT_DELETED` to `server/src/lib/eventBus/events.ts` and publish from existing client actions", "prdRefs": ["11.2"], "implemented": true },
{ "id": "F050", "description": "Add `CONTACT_*` event family and publish from contact actions", "prdRefs": ["11.2"], "implemented": true },
{ "id": "F051", "description": "Audit existing `USER_*` events; add missing CRUD events; publish from user provisioning + role-change actions", "prdRefs": ["11.2", "11.5"], "implemented": true },
{ "id": "F052", "description": "Add `PROJECT_*`, `PROJECT_PHASE_*`, `PROJECT_TASK_*`, `PROJECT_TASK_COMMENT_*` event families and publishes", "prdRefs": ["11.2"], "implemented": true },
{ "id": "F053", "description": "Add `ASSET_*` event family and publishes", "prdRefs": ["11.2"], "implemented": true },
{ "id": "F054", "description": "Add `INVOICE_*`, `INVOICE_ITEM_*`, `INVOICE_ANNOTATION_*` events and publishes", "prdRefs": ["11.2"], "implemented": true },
{ "id": "F055", "description": "Add `CONTRACT_*` and `CLIENT_CONTRACT_*` event families and publishes", "prdRefs": ["11.2"], "implemented": true },
{ "id": "F056", "description": "Add `DOCUMENT_*` and `KB_ARTICLE_*` events; publish on content change AND on `document_associations` insert/delete (changes client_scope_id derived from associations)", "prdRefs": ["11.2", "11.5"], "implemented": true },
{ "id": "F057", "description": "Add `SERVICE_CATALOG_*` event family and publishes", "prdRefs": ["11.2"], "implemented": true },
{ "id": "F058", "description": "Add `SERVICE_REQUEST_SUBMISSION_*` and `SERVICE_REQUEST_DEFINITION_*` events and publishes", "prdRefs": ["11.2"], "implemented": true },
{ "id": "F059", "description": "Audit `WORKFLOW_TASK_*` events; add missing CRUD + assignment-change events", "prdRefs": ["11.2", "11.5"], "implemented": true },
{ "id": "F060", "description": "Add `INTERACTION_*` event family and publishes", "prdRefs": ["11.2"], "implemented": true },
{ "id": "F061", "description": "Add `SCHEDULE_ENTRY_*` and `TIME_ENTRY_*` event families and publishes", "prdRefs": ["11.2"], "implemented": true },
{ "id": "F062", "description": "Add `BOARD_*`, `CATEGORY_*`, `TAG_*` event families and publishes", "prdRefs": ["11.2"], "implemented": true },
{ "id": "F063", "description": "Create `server/src/lib/eventBus/subscribers/searchIndexSubscriber.ts` and register it during eventBus initialization", "prdRefs": ["11.1"], "implemented": true },
{ "id": "F064", "description": "Subscriber resolves event → object_type → indexer via registry", "prdRefs": ["11.1"], "implemented": true },
{ "id": "F065", "description": "Subscriber handles CREATED/UPDATED by calling `indexer.loadOne` and `upsertSearchDoc`", "prdRefs": ["11.1"], "implemented": true },
{ "id": "F066", "description": "Subscriber handles DELETED by calling `deleteSearchDoc`", "prdRefs": ["11.1"], "implemented": true },
{ "id": "F067", "description": "Subscriber is gated by env var `SEARCH_INDEX_LIVE` (default `false`); when false, events are acknowledged but no DB writes occur", "prdRefs": ["11.1", "16"], "implemented": true },
{ "id": "F068", "description": "On TICKET_UPDATED, cascade a re-index of all the ticket's comments (denormalized parent title)", "prdRefs": ["11.1"], "implemented": true },
{ "id": "F069", "description": "On INVOICE_UPDATED, cascade re-index of its items and annotations", "prdRefs": ["11.1"], "implemented": true },
{ "id": "F070", "description": "On PROJECT_UPDATED, cascade re-index of phases, tasks, task-comments (paged async job)", "prdRefs": ["11.1"], "implemented": true },
{ "id": "F071", "description": "On `document_associations` insert/delete affecting a document, re-index the document row (its `client_scope_id` may have changed if the primary association was the source)", "prdRefs": ["11.5"], "implemented": true },
{ "id": "F072", "description": "On user role change, enqueue background job that re-indexes rows where the user is in `visible_to_user_ids`", "prdRefs": ["11.5"], "implemented": true },
{ "id": "F073", "description": "Create CLI script `server/src/scripts/search-backfill.ts`", "prdRefs": ["11.3"], "implemented": true },
{ "id": "F074", "description": "Backfill iterates all tenants by default; supports `--tenant=<uuid>` flag", "prdRefs": ["11.3"], "implemented": true },
{ "id": "F075", "description": "Backfill iterates all registered indexers; supports `--type=<object_type>` flag for a single entity", "prdRefs": ["11.3"], "implemented": true },
{ "id": "F076", "description": "Backfill pages source rows in batches of 500 via `loadBatch(cursor, limit)`", "prdRefs": ["11.3"], "implemented": true },
{ "id": "F077", "description": "Backfill is idempotent: re-runs produce identical row state in `app_search_index`", "prdRefs": ["11.3"], "implemented": true },
{ "id": "F078", "description": "Wire `npm run search:backfill` script in root package.json", "prdRefs": ["11.3"], "implemented": true },
{ "id": "F079", "description": "Create pg-boss job `search:reconcile` in workflow-worker or scheduled jobs registry", "prdRefs": ["11.4"], "implemented": true },
{ "id": "F080", "description": "Reconciliation queries source rows where `updated_at > max(source_updated_at) per (tenant, object_type)` and re-indexes them", "prdRefs": ["11.4"], "implemented": true },
{ "id": "F081", "description": "Reconciliation deletes `app_search_index` rows whose source row no longer exists", "prdRefs": ["11.4"], "implemented": true },
{ "id": "F082", "description": "Reconciliation inserts source rows that are missing from the index (covers backfill gaps)", "prdRefs": ["11.4"], "implemented": true },
{ "id": "F083", "description": "Schedule reconciliation to run daily; document the schedule in deploy runbook", "prdRefs": ["11.4", "16"], "implemented": true },
{ "id": "F084", "description": "Implement `parseQuery(raw)` that trims, lowercases identifier patterns, validates length ≤ 200 chars", "prdRefs": ["12", "13", "18-Q2"], "implemented": true },
{ "id": "F085", "description": "Build SQL query using `websearch_to_tsquery('english', $1)` and `ts_rank_cd(search_vector, tsq)` for FTS branch", "prdRefs": ["13.1"], "implemented": true },
{ "id": "F086", "description": "Add `pg_trgm` fallback branch using `similarity(title|subtitle, raw)` and `%` operator", "prdRefs": ["13.1"], "implemented": true },
{ "id": "F087", "description": "Detect identifier patterns (`^[A-Z]+-?\\d+$`, ticket-number-style, asset-tag-style) and pin exact `metadata->>'identifier'` matches at the top", "prdRefs": ["13.3"], "implemented": true },
{ "id": "F088", "description": "Apply time-decay multiplier `exp(-age_days/90)` with floor 0.05 in the ORDER BY composite score", "prdRefs": ["13.4"], "implemented": true },
{ "id": "F089", "description": "Implement opaque cursor encoding/decoding for stable pagination (encodes rank + object_id tiebreaker)", "prdRefs": ["12"], "implemented": true },
{ "id": "F090", "description": "Build snippet generation via `ts_headline('english', body, tsq, 'MaxFragments=2,StartSel=<mark>,StopSel=</mark>')`", "prdRefs": ["13.1"], "implemented": true },
{ "id": "F091", "description": "Sanitize ts_headline output server-side via controlled-sentinel rebuild: emit ts_headline with unique sentinel tokens (not `<mark>`); split on sentinels, HTML-escape each segment, re-wrap match segments in `<mark>` — guarantees no other HTML can survive", "prdRefs": ["6.3", "18-Q1"], "implemented": true },
{ "id": "F092", "description": "Skip snippet generation in the typeahead variant for sub-100ms p50", "prdRefs": ["12"], "implemented": true },
{ "id": "F093", "description": "Implement `aclPredicateSql(user)` that returns a parameterized SQL fragment combining required_permission, visible_to_user_ids, visible_to_roles, is_internal_only, is_private, client_scope_id checks", "prdRefs": ["14.1"], "implemented": true },
{ "id": "F094", "description": "Resolve the user's set of granted permissions once at request time and pass as an array parameter to `required_permission = ANY($perms)` style check", "prdRefs": ["14.1"], "implemented": true },
{ "id": "F095", "description": "Implement `visible_to_user_ids && ARRAY[$user_id]::uuid[]` overlap test as part of acl predicate", "prdRefs": ["14.1"], "implemented": true },
{ "id": "F096", "description": "Implement `is_internal_only` filter against user.is_internal AND `is_private` filter against share-list membership AND `client_scope_id` filter against accessible-clients", "prdRefs": ["14.1"], "implemented": true },
{ "id": "F097", "description": "Implement `verifyResultVisibility(user, rows)` that runs each row through the entity's authoritative permission check", "prdRefs": ["14.1", "14.3"], "implemented": true },
{ "id": "F098", "description": "Wire per-entity assert helpers: ticket (assertTicketReadable), project (assertProjectReadable), document (share-list), workflow_task (assignee)", "prdRefs": ["14.2"], "implemented": true },
{ "id": "F099", "description": "Emit telemetry `search.acl_drift` counter (server log + Sentry) when SQL filter and record-level check disagree", "prdRefs": ["14.3"], "implemented": true },
{ "id": "F100", "description": "Implement `searchAppAction` as a `withAuth` server action returning `SearchAppResult` (full results, snippets, grouping)", "prdRefs": ["12"], "implemented": true },
{ "id": "F101", "description": "Implement `searchAppTypeaheadAction` as a `withAuth` server action returning top-5 title-only suggestions", "prdRefs": ["12"], "implemented": true },
{ "id": "F102", "description": "Add Zod input schema validating query (1200 chars), types subset, limit (≤100), cursor", "prdRefs": ["12", "18-Q2"], "implemented": true },
{ "id": "F103", "description": "Add Zod output schema for `SearchAppResult` and `SearchResultRow` (or assert at action boundary)", "prdRefs": ["12"], "implemented": true },
{ "id": "F104", "description": "Create `server/src/components/search/SearchPalette.tsx` using `cmdk` for the sidebar typeahead", "prdRefs": ["6.1"], "implemented": true },
{ "id": "F105", "description": "Bind `Cmd/Ctrl+K` global shortcut to open the sidebar input", "prdRefs": ["6.1"], "implemented": true },
{ "id": "F106", "description": "Typeahead renders up to 5 title-only suggestions as native `<a href>` anchors so Cmd/Ctrl+click opens in new tab", "prdRefs": ["6.1"], "implemented": true },
{ "id": "F107", "description": "Typeahead's last row is `→ See all N results` linking to `/msp/search?q=...`", "prdRefs": ["6.1"], "implemented": true },
{ "id": "F108", "description": "Insert the SearchPalette launcher into `Sidebar.tsx` at the top, above the main nav", "prdRefs": ["6.1"], "implemented": true },
{ "id": "F109", "description": "Create `server/src/app/msp/search/page.tsx` (server component) that reads `q`, `type`, `cursor`, `sort` from search params and calls `searchAppAction`", "prdRefs": ["6.2"], "implemented": true },
{ "id": "F110", "description": "Create `SearchPageClient.tsx` that manages URL state via Next.js router (debounced 200ms on input change)", "prdRefs": ["6.2"], "implemented": true },
{ "id": "F111", "description": "Render filter chips on the results page: `All` plus one chip per entity type with count badge from `groups`", "prdRefs": ["6.2"], "implemented": true },
{ "id": "F112", "description": "Render grouped result lists (by entity type, max 10 per group) when `type=All`", "prdRefs": ["6.2"], "implemented": true },
{ "id": "F113", "description": "Render flat paginated list when a single `type` filter is selected", "prdRefs": ["6.2"], "implemented": true },
{ "id": "F114", "description": "Implement cursor-based prev/next pagination on the results page", "prdRefs": ["6.2"], "implemented": true },
{ "id": "F115", "description": "Implement loading skeleton AND a clear empty state that echoes the query", "prdRefs": ["6.2"], "implemented": true },
{ "id": "F116", "description": "Every result row on the page is a native `<a>` element with canonical `href` for new-tab support", "prdRefs": ["6.2"], "implemented": true },
{ "id": "F117", "description": "Add `sort` query param toggle: `relevance` (default) | `recent` (orders by source_updated_at DESC only)", "prdRefs": ["6.2"], "implemented": true },
{ "id": "F118", "description": "ARIA combobox semantics on sidebar input (role, aria-expanded, aria-controls, aria-activedescendant); ARIA region role on results page", "prdRefs": ["6.3"], "implemented": true },
{ "id": "F119", "description": "Keyboard navigation: ↑/↓ to move, Enter to open or submit, Esc to close typeahead, Tab to advance focus normally; works identically on sidebar and results page", "prdRefs": ["6.3"], "implemented": true },
{ "id": "F120", "description": "Stable kebab-case `id`s on every interactive element (e.g., `app-search-input`, `app-search-result-row-{type}-{id}`, `app-search-filter-chip-{type}`)", "prdRefs": ["6.3"], "implemented": true },
{ "id": "F121", "description": "Add `search.*` keys to `server/public/locales/en/msp/core.json` (placeholders, helper text, group labels, empty state, error states)", "prdRefs": ["6.3"], "implemented": true },
{ "id": "F122", "description": "Run lang-pack pipeline (`generate-pseudo-locales.cjs` + `validate-translations.cjs`) to propagate keys to all locales", "prdRefs": ["6.3"], "implemented": true },
{ "id": "F123", "description": "Wire `useTranslation('msp/core')` in `SearchPalette` and `SearchPageClient`; no hardcoded strings", "prdRefs": ["6.3"], "implemented": true },
{ "id": "F124", "description": "Document `SEARCH_INDEX_LIVE` env var in env reference doc and helm/values charts", "prdRefs": ["11.1", "16"], "implemented": true },
{ "id": "F125", "description": "Document deploy runbook: migrate → deploy w/ `SEARCH_INDEX_LIVE=false` → backfill → flip env → enable UI", "prdRefs": ["16"], "implemented": true },
{ "id": "F126", "description": "Emit telemetry counters: `search.query.count`, `search.query.empty`, `search.query.latency_ms`, `search.acl_drift`", "prdRefs": ["14.3"], "implemented": true },
{ "id": "F127", "description": "Add server-side rate limiting to `searchAppAction` and `searchAppTypeaheadAction` (per-user, e.g. 30/sec typeahead and 10/sec full)", "prdRefs": ["12"], "implemented": true },
{ "id": "F128", "description": "Ticket detail page honors `#comment-{id}` hash: scrolls into view and applies a brief highlight class", "prdRefs": ["6.2", "Acceptance #5"], "implemented": true },
{ "id": "F129", "description": "Invoice detail page honors `#item-{id}` and `#annotation-{id}` hashes (scroll + highlight)", "prdRefs": ["6.2"], "implemented": true },
{ "id": "F130", "description": "Project task page honors `#comment-{id}` hash (scroll + highlight)", "prdRefs": ["6.2"], "implemented": true },
{ "id": "F131", "description": "Create CE stub at `ee/server/src/lib/search/indexers/index.ts` exporting `export const eeIndexers: EntityIndexer[] = []`. Register the stub via the existing CE/EE stub-generator config (whichever mechanism the repo uses — see ce-ee-stub-fixer skill) so CE builds resolve the import to the empty array and EE builds resolve to the real module.", "prdRefs": ["19.1"], "implemented": true },
{ "id": "F132", "description": "Restrict search queries to `object_type = ANY(registeredObjectTypes())` so orphan rows from a previous EE deploy never surface in CE builds (orphan-safety).", "prdRefs": ["19.5"], "implemented": true },
{ "id": "F133", "description": "Make filter chips and group headers on `/msp/search` iterate over `registeredObjectTypes()` rather than a hard-coded list, with i18n fallback to a humanized object_type when the locale key is missing.", "prdRefs": ["19.4"], "implemented": true },
{ "id": "F134", "description": "Reconciliation job skips any `object_type` for which no indexer is currently registered (does not error, does not attempt to load source rows).", "prdRefs": ["19.5"], "implemented": true }
]