test_notes.py and derive.rs each kept the same six lines of prose that look
like a checklist item. Four were already task_lines cases in grammar.json;
the other two (empty brackets, no bullet) join them, so the web suite now
tests them too, and both hand lists go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The server unfurls what detect_urls finds, trailing .,;:!? trimmed, and files the
preview under that. The web and Android cards looked a lone link's preview up
under body.trim(), punctuation included, so a note reading
"https://example.com/a." never showed its card.
- grammar.json gains a urls section: what the server finds in a body, and the
link a lone-link note is filed under.
- The web's rule moves out of NoteCard into notes/links.ts loneUrl(); Android's
LinkPreviewRow gets the same loneUrl(); both trim like the server.
- The server and web suites run the cases; Android's JVM test pins them by hand,
as it does the tint.
Fixes#5399. DRY pass #2, batch 3 (#5372).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
daily/weekly/monthly/yearly was written out in the server (REMINDER_RECURRENCES),
the core (recur::RECURRENCES), the web editor's <option>s and Android's picker,
with nothing holding them together. grammar.json now has a recurrences list; the
server, core and web suites each check theirs against it, and the web's options
come from notes/recurrence.ts rather than the template. Android's picker pins the
list by hand with its localised labels, as it does the tint: its JVM tests do not
read the fixture.
DRY pass #2, batch 3 (#5372).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The server and the core each derived display_title and disagreed twice: the
server cut it at 200 characters and the core didn't, and the server split lines
with splitlines(), which also breaks on a lone \r or a U+2028, where the core and
every other reading of the grammar split on \n alone.
- grammar.json gains a display_titles section: blank lines, markers, an empty
item, \r\n, a lone \r, U+2028, and a 201-character line of 'é' (the cut is
characters, not bytes).
- derive::display_title and DISPLAY_TITLE_CAP are the core's half, moved next to
strip_marker. The server splits on "\n". Both suites run the cases.
Behaviour: a device now names a note with a first line over 200 characters the
way the web always has, and the server names a note containing a lone \r or a
U+2028 the way devices always have.
Fixes#5398. DRY pass #2, batch 3 (#5372).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared fixture went red on the server (run 8468: 5 failed), because the three
tag rules disagreed:
- core and web: any non-tag character counts as a boundary, so `(#todo)`
and `end.#tag` are tags, and so is the `/#section` of a pasted URL;
- server: only whitespace counts, but `#1st` and `#_x` are tags.
A note's labels could therefore change every time it synced.
All three now share the strict rule: start of line or whitespace, then a
letter, then letters, digits, `_` and `-`. Nothing becomes a tag that wasn't
already one everywhere, and URL anchors stop becoming labels on desktop and
Android. The server's existing `http://x/#nope` test already expected this.
derive.rs's boundary, markdown.ts's lookbehind and tags.py's regex change
together; `_is_tag` goes because the regex now requires the letter. The
fixture flips `(#todo)`, `end.#tag` and its lift case, and adds the URL case.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The checklist grammar and the #tag rule are implemented three times (derive.rs,
checklist.py/tags.py, markdown.ts), and the tag colour twice (colors.ts,
DerivedTint.kt). Only Rust and Kotlin had tests. core/testdata/grammar.json now
holds one set of cases (task lines, rendered items, tags, standalone-tag lifts
and the tint hashes), and every suite reads it.
- web: vitest, a dev dependency approved for #5166, with `npm test`.
grammar.test.ts runs the fixture, and titles.test.ts pins #5165's palette fix.
- ci.yml runs the web tests in the job the image build needs. desktop.yml's
verify job runs them too, because the installers embed this frontend and
can't see ci.yml's verdict (rule 177).
- core: derive.rs reads the fixture. server: tests/test_grammar_fixture.py.
- Android keeps its hand-written tint values; its doc now points at the fixture.
The server is expected red here, on purpose. tags.py only takes a tag after
whitespace and lets it start with a digit or `_`, while the core (the
definition) takes any non-tag boundary and needs a letter. So `(#todo)` is a
label on the phone and plain text on the server. The fix follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>