From e129fd4b0ad1a4e3acc6c5feaa3a294b273111a8 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Mon, 21 Sep 2026 18:09:15 +0200 Subject: [PATCH] Keep a pasted note whole, and search the notes from the app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two things found by using this on real notes. **The paste.** Copying out of Apple Notes put most of the note on the floor. WebKit wraps a copied selection in a single span carrying the computed style of everything in it — `font-weight: 700` included — with the real blocks nested inside. The serializer read that span as inline, so every line collapsed into one paragraph and every word came out bold; switching to the Markdown view then showed what little had survived, which is what "most of the text was gone" was. And because the boldness came from a foreign span's style rather than a tag, the bold button could not remove it. The rule now is that an element holding blocks is a block whatever its tag, and that a container's style is not emphasis — only a span wrapping a single run of text is. A paste this editor cannot read at all (some engines withhold the clipboard from the event) is tidied afterwards instead, but only if something actually arrived, so an empty paste still costs nothing. **The search.** A Suche tab over the user's own notes, reading the files rather than the index: notes reach the index only on a full crawl, so a lesson written this morning would not be findable this morning, which is most of what anyone searches their own notes for. A result names the lesson it matched in, not the day, for the same reason the index indexes day notes per section. Tapping one opens that day in the editor. Driven in Firefox against the real app with a proxied session: the paste, six switches between the two views, bold and unbold on pasted text, the search, and opening a result. 386 unit tests, 114/115 smoke. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 12 ++- README.md | 4 + docs/NOTES.md | 40 +++++++- scripts/smoke.mjs | 39 +++++++- src/core/notes.ts | 117 +++++++++++++++++++++++ src/http/api.ts | 31 +++++++ src/http/app/app.css | 45 +++++++++ src/http/app/app.js | 189 ++++++++++++++++++++++++++++++++++++-- src/http/app/editor.js | 38 +++++++- src/http/app/index.html | 18 ++++ src/http/app/markdown.js | 43 ++++++++- test/app-markdown.test.ts | 43 +++++++++ test/notes.test.ts | 105 +++++++++++++++++++++ 13 files changed, 702 insertions(+), 22 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0fcbed6..bc10949 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -125,7 +125,11 @@ bin/cli.ts ──HTTP──────────┘ cli/{config,client,sync hit would read "my note, Monday" and "what did we do in Deutsch" would match a note whose other five lessons were something else. The files are the truth and the index is a view of them, so `list_notes`/`get_note` read disk and answer - before the first crawl and while Postgres is down. **The one thing anything + before the first crawl and while Postgres is down. `searchNotes` is full + text over those same files, behind `/api/notes/search` and the app's Suche + tab: notes reach the index only on a **full** crawl, so anything written + this week would be missing from it, and the app is exactly where "I wrote + that this morning" is the common case. **The one thing anything here writes** — see Invariants. `docs/NOTES.md` is the guide. - **`untis-history.ts`** — the class register read backwards, which is what puts "what did we actually cover" into the search index. Its whole reason @@ -163,7 +167,11 @@ bin/cli.ts ──HTTP──────────┘ cli/{config,client,sync it against `test/mini-dom.ts`, ~60 lines of read-only DOM, because losing a lesson's notes to a lossy serializer is not a bug anyone can recover from. Pasted HTML goes through Markdown before it reaches the document, which is - where sanitising and formatting are the same operation. + where sanitising and formatting are the same operation. **An element holding + blocks is a block, whatever its tag, and a container's style is not + emphasis** — WebKit wraps a copied selection in one span carrying the computed + style of everything in it, so reading that span as inline made a whole pasted + Apple note bold and flattened it into one paragraph. - **`http/web-auth.ts`** — the app's login, which is a different kind of credential from everything else here: a password a person types, not a token a program was configured with. scrypt at startup, a signed `HttpOnly` / diff --git a/README.md b/README.md index b81de63..6387233 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,10 @@ teachers, rooms, cancellations dropped and substitutions marked. Each heading is indexed as its own lesson, so a search answers "my own note, Deutsch, 18.09.2026" rather than "Friday". +A **Suche** tab searches your own notes straight from the files — no crawl in +between, so a lesson written this morning is findable this morning — and a +result names the lesson it matched in rather than the day. + Writing is **formatted, not Markdown**: headings, bold, lists, tick boxes, quotes, links and tables come from a toolbar, and `MD` shows the Markdown underneath when you want it. The file on disk stays Markdown either way — that diff --git a/docs/NOTES.md b/docs/NOTES.md index f08e930..e2789cf 100644 --- a/docs/NOTES.md +++ b/docs/NOTES.md @@ -105,11 +105,19 @@ tables as tables — and the toolbar above it writes the Markdown. Nobody types | `🔗` `▦` | a link (`Strg`/`Cmd` + K) and a table | | `MD` | the Markdown itself | -`Enter` starts a new paragraph, `Shift+Enter` a new line in the same one. A -paste from a web page or a PDF keeps its structure and loses its fonts, colours -and anything else that is not in the list above — pasted HTML is converted to -Markdown before it reaches the page, which is what keeps a copied page from -bringing its script along. +`Enter` starts a new paragraph, `Shift+Enter` a new line in the same one. In a +table, `Tab` walks the cells and a `Tab` out of the last one adds a row; +`Ctrl`/`Cmd`+`Enter` opens a paragraph after whatever block you are in. + +A paste from a web page, a PDF or Apple Notes keeps its structure and loses its +fonts, colours and anything else that is not in the list above — pasted HTML is +converted to Markdown before it reaches the page, which is what keeps a copied +page from bringing its script along. **A copy from Apple Notes arrives wrapped +in one span carrying the computed style of everything in it**, `font-weight: +700` included; that wrapper is a container, not emphasis, and the blocks inside +it are blocks. Reading it the other way made a whole pasted note bold and +flattened every line into one paragraph, which is the failure +`test/app-markdown.test.ts` now pins down. **The file is still Markdown.** `MD` shows it and lets you edit it directly, which is the way to write something the toolbar has no button for. There is no @@ -122,6 +130,28 @@ so rather than being quietly reduced. `test/app-markdown.test.ts` is what holds that promise up: every construct in this document goes in and comes back out unchanged. +### Suche — the notes themselves + +The **Suche** tab searches your own notes and nothing else. Every word has to +appear, in any order, ignoring case and accents; there is no stemming, so +*Argument* does not find *Argumente*. + +A result names the **lesson**, not the day — `1. LF10 — 08:00–08:45` with the +date beside it — because a day note holds five or six lessons and "Freitag" says +nothing about which one matched. Tapping a day note opens it in the editor; +anything else, such as a note from the Apple Notes import, opens read-only, +since the editor is day-shaped and those notes have no day. + +It reads the **files**, not the index. That is the point: notes reach the +Postgres index only on a full crawl, so a lesson written this morning would not +be there, and "what did I write this week" is most of what anyone searches their +own notes for. A few hundred small files answer instantly, and it keeps working +while Postgres is down — the same reason `list_notes` reads disk. + +The `search` tool in Claude is the other half: it spans the Schulcloud material +and the WebUntis class register as well, at the cost of being only as fresh as +the last crawl. + **Einstellungen** holds the Schulcloud token: how long it has left, and the box to paste a fresh `jwt` cookie into when it expires (the same thing `schulcloud token set` and the older `/token` page do). It also shows the index's state and diff --git a/scripts/smoke.mjs b/scripts/smoke.mjs index cbdc422..a009990 100644 --- a/scripts/smoke.mjs +++ b/scripts/smoke.mjs @@ -740,7 +740,9 @@ console.log('\n== web app =='); ); check( 'the shell loads the app as a module, so its imports resolve', - /