Capture starts in the body, so forcing a title feels odd and body-only notes had no name — which made them unlinkable. Fix both: persist a display_title = explicit title if set, else the note's first non-empty body line (deterministic, no AI). The title field stays optional. - migration 0012: notes.display_title (NOT NULL, best-effort backfill; the app recomputes precisely on next save) - derive_display_title() helper, set on create + update - drive the /titles index, backlinks, graph edges + node labels, and [[wiki-link]] resolution off display_title so body-only notes are nameable, findable (command palette / [[ autocomplete), and linkable - rename-repoint generalized: inbound [[Old Name]] links now survive a name change via the first body line too, not just an explicit title - unit tests for the derivation (explicit wins, first non-empty line, blank/empty, length cap) - frontend: display_title on the Note type; title field placeholder now reads "Title (optional)" First item of M4.5 (frictionless input & recall); unblocks the linking work. Card rendering unchanged (no first-line duplication). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
37 lines
1.0 KiB
Python
37 lines
1.0 KiB
Python
"""notes.display_title
|
|
|
|
Revision ID: 0012
|
|
Revises: 0011
|
|
Create Date: 2026-07-22
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision = "0012"
|
|
down_revision = "0011"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# The note's display NAME: explicit title if set, else the first body line.
|
|
op.add_column("notes", sa.Column("display_title", sa.Text(), nullable=False, server_default=""))
|
|
# Best-effort backfill (kept intentionally simple + unambiguous — CI has no
|
|
# Postgres lane, so this only runs on a real deploy). The app recomputes the
|
|
# precise value via derive_display_title on the next save (which additionally
|
|
# skips leading blank lines). Capped at 200 chars.
|
|
op.execute(
|
|
r"""
|
|
UPDATE notes SET display_title = LEFT(btrim(
|
|
CASE
|
|
WHEN title IS NOT NULL AND btrim(title) <> '' THEN title
|
|
ELSE split_part(body, E'\n', 1)
|
|
END
|
|
), 200)
|
|
"""
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_column("notes", "display_title")
|