diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..31b1afb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,127 @@ +# Changelog + +What changed, newest first. One entry per round that went out as an image, with +the digests, so it stays possible to tell what is actually running on the server. + +Digests are recorded from the registry (`docker buildx imagetools inspect`), +not from the output of `docker push`. + +--- + +## 2026-08-11 — The writing column + +**Images** +- `inknote-web` — see the entry below this one; the web image was rebuilt after + the revert, and the digest is written in when it is pushed +- `inknote-server` — unchanged, `sha256:1648498f…` + +**Changed** +- The note column is centred again. It had been pinned to the left margin so the + start of every line would always be in the same place; on a wide window that + left the whole right half of the pane as one dead block. +- A page surface was drawn behind the text to give that block a meaning, and + reverted the same day: it made the sheet sit hard against the left edge, which + was worse than the problem it solved. + +--- + +## 2026-08-11 — Pages, typefaces, alignment + +**Images** — web `sha256:3b040d99…`, server `sha256:1648498f…` + +**Added** +- **Page count while writing.** The toolbar says *Page 2 of 4*, in the same words + the PDF reader uses. Pages are counted, never stored: the note stays one + document, and a test asserts no page ever reaches the stored body. +- **The typeface is a per-account setting** — serif, sans or mono, chosen under + Account → Profile. On the account rather than in the browser, so it follows you + to the phone. +- **Text alignment**, with its own TipTap extension rather than the official one, + so the schema stays in files this project owns. Left is the default and is + never written into the document. In Markdown, non-left blocks travel as + `
` — the one form most readers still honour — and the importer + reads it back onto the paragraphs. + +**Fixed** +- The Markdown exporter did not put a blank line between top-level blocks, so + consecutive paragraphs ran together in other readers. + +--- + +## 2026-08-11 — English throughout, and the PDF toolbar + +**Added** +- **Search inside a notebook**, from the note list. The sidebar's global search + was removed: searching from inside a notebook should mean searching *that* + notebook. +- **New note / Upload PDF / Import Markdown** all live under the one `+` button. + +**Changed** +- The interface is English throughout. It had drifted into two languages. +- The two visibility switches — drawing and margin notes — sit together at the + far right of the PDF toolbar, because they mean the same kind of thing. +- The drawing tools appear next to the *Draw* button, not past everything else. +- A separator is renamed and removed by right-click, with a confirmation for the + removal. + +**Fixed** +- Hiding the drawing also hid the margin notes. Each switch now hides only its + own thing. + +--- + +## 2026-08-10 — Notes on passages + +**Added** +- **Pinned notes in a PDF.** A remark can be attached to a passage, and is shown + in the margin beside the page — not written on top of it. On a phone, where + there is no margin, the same notes open as a bottom sheet. +- **Margin notes can be linked from the notes panel**, so a pinned remark can be + written about at length and still lead back to the passage it came from. + +**Fixed** +- A grey drag indicator stayed visible in the note list after a drag. It was a + `borderTop` sitting beside a `border` shorthand in one style object. +- The server stored an overlay holding only pinned notes as `NULL`, throwing + every one of them away, because it counted strokes and nothing else. + +--- + +## 2026-08-09 — Arranging a notebook + +**Added** +- **Sorting per notebook**, remembered on the notebook itself, including an + arrangement made by hand — which comes back intact after filtering. +- **Separators** to group notes inside a hand-made arrangement. +- **Markdown import**, single files or a ZIP this app exported. +- **Moving notes between notebooks.** + +**Fixed** +- Switching to Draw shrank the page from 733px to 460px. The editor was a flex + item at the default `flex: 0 1 auto`, so it was only as wide as its widest row + — the toolbar, which loses a dozen buttons in Draw. + +--- + +## 2026-08-08 — PDFs + +**Added** +- Import a PDF, read it, select its text, draw on each page, and export it with + the drawings written in. +- Notes written beside a PDF. +- Uploaded files are encrypted at rest, like note bodies already were, and stored + as deduplicated, content-addressed attachments. +- Unused attachments are freed; versions are kept by count rather than by age. +- Export as Markdown or PDF; the browser suites moved into the repository. + +**Changed** +- Vector clocks were never read. Replaced with a conflict check that works. +- Public sign-up removed — an administrator creates the accounts. + +--- + +## Earlier + +TipTap editor, version history, full-text search and attachments (2026-08-07); +drawing sync with WebP/JPEG support (2026-06-21); the first syncing alpha and PWA +(2026-02-18). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..84ba6b8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,77 @@ +# InkNote – Projektkonventionen für Claude Code + +Selbst gehostete Notiz-App, privat produktiv im Einsatz. Änderungen laufen auf +echten Daten – im Zweifel erst fragen, nicht erst ausprobieren. + +Die ausführliche Beschreibung steht im [readme.md](readme.md); diese Datei hält +nur fest, was beim Arbeiten gilt und sich nicht aus dem Code ergibt. + +## Feste Regeln + +Diese Punkte sind Entscheidungen von Marc, keine offenen Fragen: + +- **Backend immer mit Docker starten**, nie mit `go run` o. Ä. + `docker compose -f docker-compose.dev.yml up -d --build` +- **Direkt auf `main` pushen.** Keine Feature-Branches, kein PR-Umweg. +- **Kein Backup-Feature bauen.** Die Daten liegen über Bind-Mounts auf dem Host + (`INKNOTE_DATA_DIR`, Standard `/srv/inknote`), und was den Host sichert – + Proxmox Backup – sichert damit auch InkNote. +- **Die Verschlüsselung bleibt.** Notizinhalte, Zeichnungen und hochgeladene + Dateien liegen AES-256-GCM-verschlüsselt auf dem Server. Nichts davon + entfernen, um etwas anderes einfacher zu machen. +- **Keine öffentliche Registrierung.** Konten legt ein Administrator an. Ein + Admin darf anlegen, löschen und Passwörter zurücksetzen – aber **keine** + weiteren Admins ernennen und **keine** fremden Konten umbenennen. +- **Bundle-Größe bleibt, wie sie ist.** Bewusst so entschieden. +- **Dashboard und Seitenleiste sind vertagt.** Die offenen Befunde dazu sind + bekannt; nicht ungefragt damit anfangen. + +## Tests + +Beides muss vor einem Push grün sein. + +```bash +# Unit (224 Tests, ~4 s) +docker run --rm -v "$PWD/frontend:/w" -w /w node:20-alpine npx vitest run + +# Browser, zwölf Suiten (~263 Checks) – braucht den Dev-Stack +./e2e/run.sh # alle +./e2e/run.sh editor drawing # einzelne +``` + +Die Browser-Suiten melden sich mit Marcs Admin-Konto an (`INKNOTE_ADMIN` / +`INKNOTE_ADMIN_PASS`, Standard `marc`/`geheim123`) und legen sich damit +Wegwerf-Nutzer an. Sie laufen gegen `./data`, nicht gegen `/srv/inknote`. +Screenshots landen in `e2e/shots/` und sind gitignored. + +`attachments-lifecycle` ruft `POST /admin/housekeeping` auf – das schreibt auf +echten Daten. Nicht gegen die Produktionsinstanz laufen lassen. + +## Ausliefern + +```bash +./build.sh # baut beide Images und schiebt sie ins Gitea-Registry +``` + +Nur das Image bauen, das sich geändert hat. Nach dem Push den Digest bei der +Registry gegenprüfen, nicht nur der Ausgabe von `docker push` glauben: + +```bash +docker buildx imagetools inspect gitea.honeyy.duckdns.org/docker/inknote-web:latest +``` + +Auf dem Server: `docker compose pull && docker compose up -d`. + +## Änderungen festhalten + +Jede Runde, die als Image rausgeht, bekommt einen Eintrag in +[CHANGELOG.md](CHANGELOG.md) – mit den Image-Digests, damit später nachvollziehbar +ist, was auf dem Server läuft. Der `readme.md` beschreibt den *jetzigen* Stand +und wird mitgeändert, wenn sich Verhalten ändert; das Changelog beschreibt den +Weg dahin. + +## Stil + +Der Code erklärt in Kommentaren das *Warum*, nicht das *Was* – oft mit dem +Fehler, den die Zeile verhindert. Neue Kommentare in dem Ton, englisch wie der +Rest. Die Oberfläche ist durchgehend englisch, das Gespräch mit Marc deutsch. diff --git a/readme.md b/readme.md index 55a4c0c..f0907f1 100644 --- a/readme.md +++ b/readme.md @@ -47,6 +47,8 @@ inknote/ ├── docker-compose.dev.yml # Build from source for local work ├── .env.example # Copy to .env — Compose reads it from here ├── build.sh # Build & push images to Gitea +├── CHANGELOG.md # What changed per released image, with digests +├── CLAUDE.md # Conventions and standing decisions for this repo ├── backend/ │ ├── server.js # Express API + WebSocket server │ ├── sync-client.js # Client-side sync engine library @@ -371,10 +373,17 @@ state of their own, so they can be read on their own. ## Setting a note -**Left-aligned, with the column against the margin.** A column that floats in the -middle moves as the window is resized, so the start of every line lands somewhere -new; against the margin it is always in the same place. About 73 characters to -the line — measured, not guessed. +**A centred column, about 73 characters to the line** — measured, not guessed. +Past roughly 84 the eye stops reliably finding the start of the next line, so the +column is capped well before the pane runs out. + +It was pinned to the left margin for a while, on the reasoning that a column +which floats in the middle moves as the window is resized. True, but it traded +that for something worse: on a wide window the entire right half of the pane +became one dead block. A page surface was then drawn behind the text to give that +block a meaning, which only moved the problem — the whole sheet sat hard against +the left edge. Centred, the same leftover space reads as two margins and needs no +explaining. **The typeface is a per-account setting** (Account → Profile): serif, sans or mono. On the account rather than in the browser, because a choice about how your @@ -383,7 +392,7 @@ every device. The server accepts those three values and drops anything else — that column is written by a browser, so it is an input like any other. **Pages are counted, not stored.** The toolbar says *Page 2 of 4* in the same -words the PDF reader uses, and a hairline marks where each one ends. A page is +words the PDF reader uses, and a dashed rule marks where each one ends. A page is the same 3:4 the canvas uses, so "page 2" means the same thing writing or drawing. The note itself stays one continuous document — Markdown has no page, and a test asserts none ever reaches the stored body. The current page is the one