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

@@ -742,7 +742,9 @@ console.log('\n== web app ==');
'the shell loads the app as a module, so its imports resolve',
/<script type="module" src="app\.js">/.test(shellText) &&
shellText.includes('data-command="bold"') &&
shellText.includes('id="search-form"'),
shellText.includes('id="search-form"') &&
// The note list and the editor are one screen now, not two tabs.
shellText.includes('id="rail-list"'),
);
const anonymousSession = await (await fetch(`${root}/app/session`)).json();
@@ -831,6 +833,21 @@ console.log('\n== web app ==');
`${bySubject.count} note(s)`,
);
// What a row in the app's note list shows, which is why the listing carries
// it: one request for the whole list rather than one per note.
const listing = await (await fetch(`${root}/api/notes?limit=5`, { headers: withSession })).json();
const dayRow = listing.notes?.find((note) => note.path === '2026/2026-09-18.md');
check(
'the listing says what a note holds, without its body',
dayRow !== undefined && dayRow.text === undefined && dayRow.lessons === 1 && dayRow.subjects?.includes('Geschichte'),
`${dayRow?.lessons} lesson(s), subjects ${dayRow?.subjects?.join('/')}`,
);
check(
'and a preview that reads as prose',
/Weimarer Republik/.test(dayRow?.preview ?? '') && !/[#*|]/.test(dayRow?.preview ?? ''),
dayRow?.preview,
);
// Full text over the files themselves — the app's search box. It reads disk,
// so a note written seconds ago is findable without a crawl, which is the
// whole reason it does not go through the index.