Lay the notes screen out like a notes app

The list of notes and the note being written are one screen now, two
panes: the notes on the left, the open one on the right, side by side
where there is room and one at a time on a phone, where the back button
returns to the list.

The search box moved into the top of that list, and its results *are*
the list — searching is a way of finding a note, not a separate place to
be, and a tab for it was a tab too many. Emptying the box brings the
whole list back. Opening a hit opens that day at the lesson that
matched, rather than at the top of a day with six of them.

A row has to say what the note holds, so the listing carries it: the
subjects a day covers, how many lessons, and the first line actually
written in it. One request for the whole list rather than one per note.

`plainText` is now one rule in one place for wherever a note is shown
rather than edited — the search snippet and the list row both went
through their own half-copy of it, and the row's copy rendered a table
as `| | |` and left `_Fazit_` wearing its markers. It strips one leading
marker, not each in turn, because `## 1. Deutsch` keeps its lesson
number and the list rule was eating it.

Driven in Firefox at both widths: the list, the search, opening a hit,
the jump to the lesson, and the phone's list-then-note. 390 unit tests,
116/117 smoke.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-09-21 18:22:47 +02:00
parent 5db098b67f
commit 48c6cf39d5
9 changed files with 478 additions and 190 deletions

View File

@@ -14,6 +14,7 @@ import {
subjectFromHeading,
notePathFor,
parseNote,
plainText,
readNoteAt,
readNotes,
renderNote,
@@ -466,3 +467,26 @@ describe('searchNotes', () => {
assert.equal(searchNotes([day], 'e', 1).length, 1);
});
});
describe('plainText', () => {
it('reads a table row as its cells', () => {
assert.equal(plainText('| 1NF | atomare Werte |'), '1NF · atomare Werte');
assert.equal(plainText('| --- | --- |'), '');
});
it('drops the markers but keeps the words', () => {
assert.equal(plainText('- **These**, Argument, _Fazit_'), 'These, Argument, Fazit');
assert.equal(plainText('## 1. Deutsch'), '1. Deutsch');
assert.equal(plainText('> Merksatz'), 'Merksatz');
assert.equal(plainText('- [x] erledigt'), 'erledigt');
});
it('leaves a word with an underscore in it alone', () => {
assert.equal(plainText('snake_case_name bleibt'), 'snake_case_name bleibt');
});
it('reads a link as its label and an escape as its character', () => {
assert.equal(plainText('[Arbeitsblatt](https://example.org/ab.pdf)'), 'Arbeitsblatt');
assert.equal(plainText('2 \\* 3'), '2 * 3');
});
});