SQLite's lower() folds ASCII only, so a device could hold "Café" and "CAFÉ" as two tags while the server held one. derive::fold (to_lowercase) is now the one comparison. It is registered as the deterministic SQL function fold() on every connection (schema::migrate), and find_or_create, the rename clash, the pull clash and the unique index all use it. derive's push_unique used eq_ignore_ascii_case and now folds the same way. v14 merges pairs a device already holds before rebuilding the index. The older row survives, as in rename_label. Memberships move with via_tag kept, affected notes go dirty, and the merged-away row is a pending delete. It is written out rather than calling store::merge_labels so the migration doesn't depend on store code that later versions may change. rusqlite gains its "functions" feature (operator-approved, 2026-10-08). grammar.json gains "#café and #CAFÉ" -> ["café"], which all three implementations run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
123 lines
7.8 KiB
JSON
123 lines
7.8 KiB
JSON
{
|
|
"_about": [
|
|
"The cases every implementation of the note grammar is tested against.",
|
|
"One file, read by four test suites: core (derive.rs), the server (checklist.py, tags.py, helpers.py), the web (markdown.ts, colors.ts) and, for the tint, Android (DerivedTint.kt, which pins the same values by hand).",
|
|
"A difference between any two implementations is a note that changes shape when it syncs. Add a case here, not to one suite.",
|
|
"Lives under core/ because core is the definition, and because core/ is in the desktop, Android and server file sets, so changing a case reruns every suite that reads it.",
|
|
"display_titles: a note's name is its first line that says anything once a task marker is stripped, cut to 200 characters (not bytes). Lines split on \\n alone, as the grammar does everywhere.",
|
|
"urls: the links in a body the server unfurls (found), and the one a note that is nothing but a link files its preview under (lone). Trailing .,;:!? is the sentence's, not the link's, on every surface.",
|
|
"recurrences: the repeat rules a reminder can carry, as stored. The server, the core and the web's editor each hold the list; Android's picker pins it by hand, as it does the tint.",
|
|
"Tag cases are plain text on purpose. The web's renderer lets `code` and **bold** claim a `#` first, which the derivers do not; that is a rendering choice, not grammar, and is tested where it lives."
|
|
],
|
|
|
|
"task_lines": [
|
|
{ "line": "- [ ] milk", "item": { "checked": false, "text": "milk" } },
|
|
{ "line": "- [x] milk", "item": { "checked": true, "text": "milk" } },
|
|
{ "line": "- [X] milk", "item": { "checked": true, "text": "milk" } },
|
|
{ "line": "* [ ] star bullet", "item": { "checked": false, "text": "star bullet" } },
|
|
{ "line": " - [ ] indented", "item": { "checked": false, "text": "indented" } },
|
|
{ "line": "\t- [ ] tab indent", "item": { "checked": false, "text": "tab indent" } },
|
|
{ "line": "- [ ] two spaces after the bullet", "item": { "checked": false, "text": "two spaces after the bullet" } },
|
|
{ "line": "- [ ]", "item": { "checked": false, "text": "" } },
|
|
{ "line": "- [ ] ", "item": { "checked": false, "text": "" } },
|
|
{ "line": "- [ ] trailing space kept ", "item": { "checked": false, "text": "trailing space kept " } },
|
|
{ "line": "- [ ]x", "item": null },
|
|
{ "line": "-[ ] x", "item": null },
|
|
{ "line": "+ [ ] plus bullet", "item": null },
|
|
{ "line": "- [y] x", "item": null },
|
|
{ "line": "- [ ] x", "item": null },
|
|
{ "line": "- [] empty brackets", "item": null },
|
|
{ "line": "[ ] no bullet at all", "item": null },
|
|
{ "line": "text - [ ] x", "item": null },
|
|
{ "line": "- milk", "item": null },
|
|
{ "line": "", "item": null }
|
|
],
|
|
|
|
"rendered_items": [
|
|
{ "text": "milk", "checked": false, "line": "- [ ] milk" },
|
|
{ "text": "milk", "checked": true, "line": "- [x] milk" },
|
|
{ "text": "", "checked": false, "line": "- [ ]" },
|
|
{ "text": "", "checked": true, "line": "- [x]" }
|
|
],
|
|
|
|
"tags": [
|
|
{ "body": "#todo buy milk", "tags": ["todo"] },
|
|
{ "body": "buy #milk and #eggs", "tags": ["milk", "eggs"] },
|
|
{ "body": "#Todo and #todo", "tags": ["Todo"] },
|
|
{ "body": "#café and #CAFÉ", "tags": ["café"] },
|
|
{ "body": "line one\n#two", "tags": ["two"] },
|
|
{ "body": "#tag, and #more.", "tags": ["tag", "more"] },
|
|
{ "body": "#x-y_z9", "tags": ["x-y_z9"] },
|
|
{ "body": "#café #über", "tags": ["café", "über"] },
|
|
{ "body": "(#todo)", "tags": [] },
|
|
{ "body": "end.#tag", "tags": [] },
|
|
{ "body": "https://x.com/#section", "tags": [] },
|
|
{ "body": "#tag#more", "tags": ["tag"] },
|
|
{ "body": "a#b", "tags": [] },
|
|
{ "body": "x-#tag", "tags": [] },
|
|
{ "body": "##x", "tags": [] },
|
|
{ "body": "#1st", "tags": [] },
|
|
{ "body": "#2024", "tags": [] },
|
|
{ "body": "#_x", "tags": [] },
|
|
{ "body": "# heading", "tags": [] },
|
|
{ "body": "see https://x.com/page#frag", "tags": [] },
|
|
{ "body": "", "tags": [] }
|
|
],
|
|
|
|
"lifts": [
|
|
{ "body": "#todo\nreorganize the homepage", "standalone": ["todo"], "inline": [], "lifted": "reorganize the homepage" },
|
|
{ "body": "buy milk #grocery", "standalone": [], "inline": ["grocery"], "lifted": "buy milk #grocery" },
|
|
{ "body": "first\n\n#todo\n\nsecond", "standalone": ["todo"], "inline": [], "lifted": "first\n\nsecond" },
|
|
{ "body": "#todo\ncall #todo later", "standalone": [], "inline": ["todo"], "lifted": "call #todo later" },
|
|
{ "body": "#a #b", "standalone": [], "inline": ["a", "b"], "lifted": "#a #b" },
|
|
{ "body": "```\n# comment #todo\n```", "standalone": [], "inline": ["todo"], "lifted": "```\n# comment #todo\n```" },
|
|
{ "body": "(#todo)\nnotes", "standalone": [], "inline": [], "lifted": "(#todo)\nnotes" }
|
|
],
|
|
|
|
"display_titles": [
|
|
{ "body": "", "title": "" },
|
|
{ "body": "\n\n Shopping \nmilk", "title": "Shopping" },
|
|
{ "body": "- [ ] milk\n- [x] eggs", "title": "milk" },
|
|
{ "body": "- [ ]\n- [ ] bread", "title": "bread" },
|
|
{ "body": " \n\t\nlate start", "title": "late start" },
|
|
{ "body": "one\r\ntwo", "title": "one" },
|
|
{ "body": "a\rb\nc", "title": "a\rb" },
|
|
{ "body": "a\u2028b\nc", "title": "a\u2028b" },
|
|
{ "body": "ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé", "title": "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé" }
|
|
],
|
|
|
|
"urls": [
|
|
{ "body": "https://example.com/a", "found": ["https://example.com/a"], "lone": "https://example.com/a" },
|
|
{ "body": " https://example.com/a \n", "found": ["https://example.com/a"], "lone": "https://example.com/a" },
|
|
{ "body": "https://example.com/a.", "found": ["https://example.com/a"], "lone": "https://example.com/a" },
|
|
{ "body": "https://example.com/a?q=1!", "found": ["https://example.com/a?q=1"], "lone": "https://example.com/a?q=1" },
|
|
{ "body": "see https://example.com/a, then https://example.com/b.", "found": ["https://example.com/a", "https://example.com/b"], "lone": null },
|
|
{ "body": "<https://example.com/a>", "found": ["https://example.com/a"], "lone": null },
|
|
{ "body": "https://example.com/a)", "found": ["https://example.com/a"], "lone": null },
|
|
{ "body": "https://example.com/a https://example.com/a", "found": ["https://example.com/a"], "lone": null },
|
|
{ "body": "ftp://example.com/a", "found": [], "lone": null },
|
|
{ "body": "", "found": [], "lone": null }
|
|
],
|
|
|
|
"recurrences": ["daily", "weekly", "monthly", "yearly"],
|
|
|
|
"tint": {
|
|
"keys": ["red", "orange", "yellow", "green", "teal", "blue", "purple", "pink", "gray"],
|
|
"hashes": [
|
|
{ "id": "00000000-0000-0000-0000-000000000000", "hash": 3192360657, "color": "purple" },
|
|
{ "id": "11111111-1111-1111-1111-111111111111", "hash": 1031130113, "color": "blue" },
|
|
{ "id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "hash": 4043892016, "color": "orange" },
|
|
{ "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "hash": 1533351232, "color": "orange" }
|
|
],
|
|
"names": [
|
|
{ "name": "todo", "color": "pink" },
|
|
{ "name": "grocery", "color": "blue" },
|
|
{ "name": "work", "color": "green" },
|
|
{ "name": "home", "color": "gray" },
|
|
{ "name": "ideas", "color": "green" },
|
|
{ "name": "reading", "color": "gray" },
|
|
{ "name": "urgent", "color": "red" }
|
|
]
|
|
}
|
|
}
|