m4.5: inline #tags — hashtags in a note become labels (two-way sync)
Fast, cross-device labelling: type #groceries in a note and it becomes the "groceries" label. The body is the source of truth for tag-labels; manual picker labels stay independent (rule 28 — additive). - note_labels.via_tag (migration 0013) marks tag-sourced attachments. - parse_tags(): #tag at start-of-body or after whitespace, needs a letter (so #2024, URL #frags, mid#word are ignored). unit-tested. - _reconcile_tags() on create + body-update: attach labels for current #tags (find-or-create, case-insensitive), detach tag-labels whose tag was removed; never touches manual (via_tag=false) rows. - label picker (set_note_labels + editor onLabelsChange) now preserves tag-labels on save, so a picker action can't strip a label the #tag still mandates. - serialize via_tag; card/editor chips render tag-labels as "#name", and the editor hides the × on them (remove by editing the tag text). - LabelPicker builds manual NoteLabels (via_tag:false). Fourth and final item of M4.5. 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:
@@ -9,6 +9,7 @@ from thoughtsync.notes import (
|
||||
normalize_color,
|
||||
parse_link_titles,
|
||||
parse_list_items,
|
||||
parse_tags,
|
||||
rewrite_link_title,
|
||||
)
|
||||
|
||||
@@ -131,6 +132,16 @@ def test_derive_display_title_caps_length():
|
||||
assert derive_display_title(long, "body") == "x" * 200
|
||||
|
||||
|
||||
def test_parse_tags():
|
||||
assert parse_tags("buy milk #groceries and #to-do now") == ["groceries", "to-do"]
|
||||
# case-insensitive dedup, first spelling wins
|
||||
assert parse_tags("#Work then #work") == ["Work"]
|
||||
# url fragments, mid-word #, purely-numeric, and a bare # are not tags
|
||||
assert parse_tags("frag http://x/#nope mid#word #2024 #") == []
|
||||
assert parse_tags(None) == []
|
||||
assert parse_tags("#a #b #a") == ["a", "b"]
|
||||
|
||||
|
||||
def test_parse_list_items():
|
||||
assert parse_list_items(["milk", " eggs ", "", " ", "bread"]) == ["milk", "eggs", "bread"]
|
||||
assert parse_list_items("not a list") == []
|
||||
|
||||
Reference in New Issue
Block a user