Read the user's own lesson notes, and the class register behind them
Schulcloud says what was uploaded and WebUntis says what was scheduled. Neither says what was *taught* — which point the teacher laboured, which example landed, what "will definitely come up". That lives in two places this server could not reach: the notes the user takes in the lesson, and WebUntis' class register. Notes are a directory of Markdown files (NOTES_DIR), not a table. They have to be writable from a phone in a classroom, readable when Postgres is down, and outlive this project, and files are the only shape that is all three — so the files are the truth and the index is a view of them, the same split as file_texts and the mirror. list_notes and get_note read disk, so they answer before the first crawl; search, what_changed and all three German prompts read them alongside the Schulcloud material. add_note writes one, and is the only thing in this server that writes anything. That is not a hole in the read-only invariant but a different store: it is bounded to NOTES_DIR by the same safeComponent/resolveWithin pair that stops a hostile Schulcloud filename escaping the mirror, so a note titled ../../.ssh/authorized_keys becomes a filename. Schulcloud and WebUntis stay GET-only and allowlisted respectively. NOTES_READONLY refuses writes outright. Appending targets the *lesson*, not the title: "halt das auch noch fest" mid-lesson carries a new title, and deriving the path from it would start a second note every time, which is the one thing append exists to prevent. Notes.app has no export — its bodies are compressed protobuf and the iCloud copy is encrypted — so scripting the app is not the clumsy route to the notes but the only one. scripts/export-apple-notes.js reads them through AppleScript into one JSON object per line, and `schulcloud note import` converts the HTML to Markdown, takes the Notes folder as the subject and the *creation* date as the lesson's date. Attachments cannot come across; a note that was a photo of the board imports as a line saying so, because importing it empty would hide the loss. The class register needed one API property to become cheap: getLessonTopic2017 answers per *series*, not per period, so a term is reconstructed by asking about the latest period of each lesson series and merging back by id — a few dozen calls for a school year rather than one per lesson. untis_lesson_topics now takes a subject as well as a period id, and UNTIS_HISTORY_DAYS of register goes into the index under a kind of its own, so "what did we actually do before the test" is searchable. Sharing the snapshot rather than duplicating it caught one thing on the way: the search tool's live path had to learn notes too, or fresh=true would have quietly disagreed with the index. 305 tests; 88/89 smoke against the local instance, the one failure being the H5P service that instance does not run. The live smoke could not be retaken: that session has lapsed and needs a fresh jwt cookie. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
34
docs/CLI.md
34
docs/CLI.md
@@ -39,6 +39,7 @@ schulcloud ls [--course <id>] [--long]
|
||||
schulcloud get <fileId> [--out <path>]
|
||||
schulcloud sync [--dry-run] [--full] [--prune] [--dir <path>] [--jobs <n>]
|
||||
schulcloud refresh [--course <id>] [--force]
|
||||
schulcloud note ... the notes you take in class — see below
|
||||
```
|
||||
|
||||
`ls --long` prints file ids, which is what `get` takes.
|
||||
@@ -114,6 +115,39 @@ while.
|
||||
`Persönliche Dateien/…`, `Team-Dateien/<team>/…` and `Geteilte Dateien/`, once
|
||||
the server's index includes them (`INDEX_FILE_MANAGER`, on by default).
|
||||
|
||||
### Your own lesson notes (`note`)
|
||||
|
||||
The notes you take in class, which Claude reads as context. The full story is in
|
||||
[NOTES.md](NOTES.md); these are the commands.
|
||||
|
||||
```
|
||||
schulcloud note ls [--subject <name>] [--since <date>] [--until <date>] [--long]
|
||||
schulcloud note show <path>
|
||||
schulcloud note add --title <title> [--subject <name>] [--date <date>]
|
||||
[--tags a,b] [--append] text on stdin, or --text
|
||||
schulcloud note import <export.ndjson> [--subject <name>] [--out <dir>] [--dry-run]
|
||||
```
|
||||
|
||||
```console
|
||||
$ pbpaste | schulcloud note add --title "Subnetting" --subject LF07
|
||||
Saved LF07/2026-09-16 Subnetting.md
|
||||
|
||||
$ schulcloud note ls --subject Deutsch --since 2026-09-01
|
||||
2026-09-22 [Deutsch] Sprachanalyse
|
||||
2026-09-15 [Deutsch] Erörterung — Aufbau
|
||||
|
||||
$ schulcloud note show "Deutsch/2026-09-15 Erörterung.md"
|
||||
```
|
||||
|
||||
`note add` reads the note from stdin, so it comes just as easily from a
|
||||
clipboard, an editor or another command; `--append` adds to the note already
|
||||
written for that subject and day rather than starting a second one.
|
||||
|
||||
`note import` takes the file `scripts/export-apple-notes.js` writes on a Mac.
|
||||
`--dry-run` shows what it would do and `--out <dir>` writes the Markdown
|
||||
locally instead of sending it to the server — the only note command that needs
|
||||
no server at all.
|
||||
|
||||
## How sync works
|
||||
|
||||
It is a **one-way mirror, not a two-way sync**, and that follows from the data
|
||||
|
||||
Reference in New Issue
Block a user