m4.5: titles optional — every note gets an auto display name
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
This commit is contained in:
+33
-1
@@ -2,7 +2,13 @@ import pytest
|
||||
|
||||
from thoughtsync.app import create_app
|
||||
from thoughtsync.models.note import NOTE_COLORS, Note
|
||||
from thoughtsync.notes import is_empty_note, normalize_color, parse_link_titles, rewrite_link_title
|
||||
from thoughtsync.notes import (
|
||||
derive_display_title,
|
||||
is_empty_note,
|
||||
normalize_color,
|
||||
parse_link_titles,
|
||||
rewrite_link_title,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -97,6 +103,32 @@ def test_rewrite_link_title_noop():
|
||||
assert rewrite_link_title("no links here", "alpha", "Gamma") == "no links here"
|
||||
|
||||
|
||||
def test_derive_display_title_explicit_wins():
|
||||
assert derive_display_title("My Title", "some body line") == "My Title"
|
||||
assert derive_display_title(" Padded ", "body") == "Padded"
|
||||
|
||||
|
||||
def test_derive_display_title_from_first_body_line():
|
||||
assert derive_display_title(None, "first line\nsecond line") == "first line"
|
||||
assert derive_display_title("", " spaced first \nnext") == "spaced first"
|
||||
# leading blank/whitespace lines are skipped to the first line with content
|
||||
assert derive_display_title(None, "\n \nreal line\nmore") == "real line"
|
||||
# a whitespace-only title falls through to the body
|
||||
assert derive_display_title(" ", "body wins") == "body wins"
|
||||
|
||||
|
||||
def test_derive_display_title_empty():
|
||||
assert derive_display_title(None, None) == ""
|
||||
assert derive_display_title("", "") == ""
|
||||
assert derive_display_title(" ", " \n ") == ""
|
||||
|
||||
|
||||
def test_derive_display_title_caps_length():
|
||||
long = "x" * 300
|
||||
assert derive_display_title(None, long) == "x" * 200
|
||||
assert derive_display_title(long, "body") == "x" * 200
|
||||
|
||||
|
||||
async def test_titles_requires_auth(app):
|
||||
client = app.test_client()
|
||||
resp = await client.get("/api/notes/titles")
|
||||
|
||||
Reference in New Issue
Block a user