From ffc008bf4da4c5a5e6618658ee30fb5fadf61dd4 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 19 Jul 2026 22:03:19 -0400 Subject: [PATCH] M2 search: Postgres FTS backend + top search bar + results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Migration 0005: generated tsvector column (title A + body B) + GIN index on notes; GET /api/notes/search?q= (websearch_to_tsquery, ts_rank, ACL-scoped, excludes trash), labels merged into results. - Persistent AppShell layout (parent route + children) so the new top search box keeps focus across board/search/label navigation. - SearchView (debounced live search from the shell → /search?q=, results masonry, no-match empty state); BoardView/SearchView render inside the shared shell. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm --- alembic/versions/0005_note_search.py | 33 +++++++++++++ frontend/src/components/AppShell.vue | 44 ++++++++++++++--- frontend/src/router/index.ts | 31 ++++-------- frontend/src/views/BoardView.vue | 70 +++++++++++++--------------- frontend/src/views/SearchView.vue | 67 ++++++++++++++++++++++++++ src/thoughtsync/notes.py | 32 ++++++++++++- tests/test_notes.py | 6 +++ 7 files changed, 216 insertions(+), 67 deletions(-) create mode 100644 alembic/versions/0005_note_search.py create mode 100644 frontend/src/views/SearchView.vue diff --git a/alembic/versions/0005_note_search.py b/alembic/versions/0005_note_search.py new file mode 100644 index 0000000..2a00de2 --- /dev/null +++ b/alembic/versions/0005_note_search.py @@ -0,0 +1,33 @@ +"""notes full-text search vector + +Revision ID: 0005 +Revises: 0004 +Create Date: 2026-07-20 + +A generated tsvector column (title weight A, body weight B) + GIN index, so +search is index-backed and always in sync with the row (no trigger to maintain). +""" +from alembic import op + +revision = "0005" +down_revision = "0004" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute( + """ + ALTER TABLE notes ADD COLUMN search_vector tsvector + GENERATED ALWAYS AS ( + setweight(to_tsvector('english', coalesce(title, '')), 'A') || + setweight(to_tsvector('english', coalesce(body, '')), 'B') + ) STORED + """ + ) + op.execute("CREATE INDEX ix_notes_search ON notes USING GIN (search_vector)") + + +def downgrade() -> None: + op.execute("DROP INDEX IF EXISTS ix_notes_search") + op.execute("ALTER TABLE notes DROP COLUMN IF EXISTS search_vector") diff --git a/frontend/src/components/AppShell.vue b/frontend/src/components/AppShell.vue index 2abd2a1..e040704 100644 --- a/frontend/src/components/AppShell.vue +++ b/frontend/src/components/AppShell.vue @@ -1,5 +1,5 @@