- 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 + <RouterView> 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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
34 lines
916 B
Python
34 lines
916 B
Python
"""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")
|