Skip to content

docs(ui): design global search over the board and chat corpora (RIG-1621) - #1001

Open
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-ux/1621-global-search-design
Open

docs(ui): design global search over the board and chat corpora (RIG-1621)#1001
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-ux/1621-global-search-design

Conversation

@rigel-mintaka

Copy link
Copy Markdown
Contributor

Adds the design record for RIG-1621: a persistent center-of-top-bar search
input alongside the D5-frozen Cmd+K palette, covering agents, issues, PRs and
chat messages. Both surfaces feed ONE provider set, so neither can drift from
the other, and the corpus stays server-side per Matt's ruling that the client
cannot hold the full text. Scope is the whole board, not the caller's
assigned slice.

Chat search is nearly free: the server already ships
rpc SearchMessages with a tsvector index, and no UI code references it.
Issue search is the real new work — a generated search_tsv column, a GIN
index, and a SearchIssues RPC mirroring the messages pipeline.

Two schema findings, both measured against PostgreSQL 18.4 rather than
reasoned about, and both of which would have shipped a broken gate:

The obvious spelling of the column does not compile:

ERROR: generation expression is not immutable

A STORED generation expression must be IMMUTABLE, and array_to_string is
only STABLE (labels::text fails the same way — array_out is STABLE
too). The cause is polymorphism, not arrays: anyarray must stay
conservative for element types whose output reads a GUC. So labels route
through a compass_labels_text wrapper declared IMMUTABLE with a pinned
search_path — honest, because for TEXT[] the concrete element output
function textout is itself IMMUTABLE. The one alternative that both
compiles and is genuinely immutable, array_to_tsvector, bypasses the
parser: labels land as raw lexemes, so a search for triage against a
Needs-Triage label matches NOTHING. It fails silently, which is worse
than the error. T1 therefore requires a label-only test.

And the column must weight its fields with setweight. Unlike the messages
column it spans a 40-character title and a multi-kilobyte body, and
ts_rank applies no length normalization by default: flat concatenation
scores a title hit and a hit buried in 10 KB of body EXACTLY equal
(0.06079 vs 0.06079; weighted 0.60793 vs 0.24317). The task list already
asserted 'title hit above body hit', which was unsatisfiable as specified.
The column is STORED, so deferring this would cost a table rewrite.

PR results are scoped honestly as fixture-only at v1: Issue.prs has zero
Go writers, so PR search looks complete against fixtures and returns nothing
in production. Not worse than today's PRs tab, but no live-mode PR assertion
may gate the release.

Review found three executability gaps, all fixed here rather than left for
an executor to hit: providers cannot reach an RPC client at all today (the
AppStore exposes none, and hanging one on it is forbidden by DL-128), so
the seam is now OQ-7; the message destination cannot navigate through
store.openTopic, which no-ops on any topic outside the client-held set,
so it routes on wire data; and the debounce moved from T6 to T4, where the
effect it wraps is written, removing a T4-T6 ordering knot. Ledger
citations dropped their line numbers — DECISIONS.md is append-only, so a
line number into it rots, and four were already off by three rows.

Mints DL-344. Leaves DL-233's assigned-issue scope for Matt as OQ-6, and
whether the palette itself re-scopes as OQ-8: this design reverses a frozen
decision, and frozen decision prose is never edited in place.

@linear-code

linear-code Bot commented Sep 8, 2026

Copy link
Copy Markdown

RIG-1621

@trunk-io

trunk-io Bot commented Sep 8, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@rigel-mintaka
rigel-mintaka marked this pull request as ready for review September 8, 2026 00:35
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-ux-1621-global-searc.compass-eng-docs.pages.dev

Deployed from compass-ux/1621-global-search-design at 7060fed.

Changed pages:

…621)

Adds the design record for RIG-1621: a persistent center-of-top-bar search
input alongside the D5-frozen Cmd+K palette, covering agents, issues, PRs and
chat messages. Both surfaces feed ONE provider set, so neither can drift from
the other, and the corpus stays server-side per Matt's ruling that the client
cannot hold the full text. Scope is the whole board, not the caller's
assigned slice.

Chat search is nearly free: the server already ships
`rpc SearchMessages` with a tsvector index, and no UI code references it.
Issue search is the real new work — a generated `search_tsv` column, a GIN
index, and a `SearchIssues` RPC mirroring the messages pipeline.

Two schema findings, both measured against PostgreSQL 18.4 rather than
reasoned about, and both of which would have shipped a broken gate:

The obvious spelling of the column does not compile:

  ERROR:  generation expression is not immutable

A STORED generation expression must be IMMUTABLE, and `array_to_string` is
only STABLE (`labels::text` fails the same way — `array_out` is STABLE
too). The cause is polymorphism, not arrays: `anyarray` must stay
conservative for element types whose output reads a GUC. So labels route
through a `compass_labels_text` wrapper declared IMMUTABLE with a pinned
`search_path` — honest, because for TEXT[] the concrete element output
function `textout` is itself IMMUTABLE. The one alternative that both
compiles and is genuinely immutable, `array_to_tsvector`, bypasses the
parser: labels land as raw lexemes, so a search for `triage` against a
`Needs-Triage` label matches NOTHING. It fails silently, which is worse
than the error. T1 therefore requires a label-only test.

And the column must weight its fields with `setweight`. Unlike the messages
column it spans a 40-character title and a multi-kilobyte body, and
`ts_rank` applies no length normalization by default: flat concatenation
scores a title hit and a hit buried in 10 KB of body EXACTLY equal
(0.06079 vs 0.06079; weighted 0.60793 vs 0.24317). The task list already
asserted 'title hit above body hit', which was unsatisfiable as specified.
The column is STORED, so deferring this would cost a table rewrite.

PR results are scoped honestly as fixture-only at v1: `Issue.prs` has zero
Go writers, so PR search looks complete against fixtures and returns nothing
in production. Not worse than today's PRs tab, but no live-mode PR assertion
may gate the release.

Review found three executability gaps, all fixed here rather than left for
an executor to hit: providers cannot reach an RPC client at all today (the
`AppStore` exposes none, and hanging one on it is forbidden by DL-128), so
the seam is now OQ-7; the message destination cannot navigate through
`store.openTopic`, which no-ops on any topic outside the client-held set,
so it routes on wire data; and the debounce moved from T6 to T4, where the
effect it wraps is written, removing a T4-T6 ordering knot. Ledger
citations dropped their line numbers — DECISIONS.md is append-only, so a
line number into it rots, and four were already off by three rows.

Mints DL-346 (DL-344/345 were already claimed by open PR #984, six hours ahead of this one — allocated, not merged, so main's max would not have shown them). Leaves DL-233's assigned-issue scope for Matt as OQ-6, and
whether the palette itself re-scopes as OQ-8: this design reverses a frozen
decision, and frozen decision prose is never edited in place.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant