A filed note stays filed, and the handles are asked for

Three things that all came from chapters being thought through too late.

A note put into a chapter came back out of it a few seconds later. The
server had it right the whole time; every sync rebuilds the local copy
from the server's answer, and the function doing that never carried
chapter_id — so each sync handed back a note belonging to no chapter and
the notebook took it in again. The bin's query left the column out too,
which would have unfiled anything restored from it.

A separator drawn in a notebook vanished when a chapter was opened.
"Which chapter is open" and "which list is on screen" were one value.
Opened beside or below, the list still shows the notebook — but
everything around it had switched to the chapter, so the notebook's own
separators were filtered away and a drag among its rows was measured
against the chapter's notes, found nothing, and did nothing.

And where a new note lands is now visible and yours to choose: pressing
in either place makes it the one being worked in, marked with a line
along its top and the word "here", and the + button names it.

Rearranging becomes a mode, switched on beside the sort menu — until
then no row wears a grip and none can be dragged. Filing a note is back
in the row's own menu, where starring and exporting already are; the
button that had appeared in every row is gone with its dialog. The
account panel closes with Escape.

Thirteen suites green: 335 browser checks, 241 unit tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-12 16:59:29 +02:00
1 parent 4bb86bc5ac
commit b2a5f051fe
12 files changed
+406 -291

No files matched your search

+46 -5
View File
@@ -332,6 +332,14 @@ devices get different starts, because they want different things:
The row under the finger is found with `elementFromPoint`, not with enter/leave
handlers — a finger generates no `pointerover` on anything it did not press.
**Rearranging is a mode**, switched on by the handle button beside the sort menu
and off again by itself when the list is left or the order stops being a
hand-made one. Only while it is on does a row wear its grip or move at all. Rows
are read constantly and dragged rarely, so six dots beside every title was
clutter that also made an ordinary press able to carry a row off. Filing a note
somewhere else is not part of it: that is one of the things done to a row from
the row's own menu, like starring, exporting and discarding.
**The context menu** was placed with `innerWidth - 220`, a stand-in for a width
nobody had measured, and its submenu was pinned to `left: 100%` and opened only
on hover. On a phone the parent already sits against the right edge, so the list
@@ -431,8 +439,8 @@ docker compose -f docker-compose.dev.yml up -d --build # must be running
| `attachments-lifecycle` | 10 | a picture is kept while any version shows it, and freed once none does |
| `conflict` | 10 | a late write wins but the state it buries survives as a version |
| `export` | 21 | the downloaded bytes are real Markdown, ZIP and PNG, unpacked and read back |
| `organise` | 52 | a hand-made order survives a search; separators hold their place; a note exported and imported comes back whole |
| `chapters` | 21 | a note filed into a chapter leaves the notebook's list, and removing the chapter gives every note back |
| `organise` | 53 | a hand-made order survives a search; separators hold their place; a note exported and imported comes back whole |
| `chapters` | 31 | a filed note stays filed across a sync; the notebook's separators survive a chapter being opened; handles appear only while rearranging |
| `offline` | 17 | a cold page load with the network off still opens notes and searches them — and a note made, written and drawn on offline arrives whole once there is a connection |
| `sync` | 7 | a PDF written on one device — document, drawing and pinned notes — reaches a second one, which can find it by its contents without opening it |
@@ -881,9 +889,28 @@ on every keystroke. They are now separate blobs; the body only keeps an id.
| POST | `/api/attachments` | Upload a base64 data URL |
| GET | `/api/attachments/:id` | Fetch the bytes (owner only) |
> Removing an image from a note does not yet delete its blob: the body is
> encrypted, so the server cannot tell what is still referenced. Unused blobs
> cost disk space, nothing else.
### Where it all actually sits
Two directories, both bind-mounted from the host so that whatever backs up the
host backs up InkNote:
```
/srv/inknote/db/inknote.db every note, encrypted body and all
/srv/inknote/uploads/attachments/<sha> one file per distinct upload, encrypted
/srv/inknote/uploads/drawing_<id>.png legacy raster drawings
```
**There is no folder per user, and there is deliberately nothing on disk to read.**
Who owns what is a column in the database (`attachments.user_id`), not a path.
The file name is the SHA-256 of the *plaintext* — encryption uses a fresh IV each
time, so hashing the ciphertext would store the same photo twice — and the bytes
beside it are AES-256-GCM. So a file name tells you that two people uploaded the
same document, and nothing else: not who, not what, not which note.
Sorting uploads into per-user folders would name the owner in the clear on a disk
where nothing else is readable, and it would break the sharing that keeps one copy
of a file two accounts happen to have. Deleting stays safe without it: a blob goes
only when **no** row in `attachments` points at it any more, whoever owned them.
---
@@ -990,6 +1017,20 @@ should follow you to the phone.
A phone has room for neither. There the chapter fills the list and the header
button becomes a way back out, which is what it did before there was a choice.
**Which list is on screen is not the same as which chapter is open.** Beside and
below, the middle column still shows the notebook; only the phone's fallback
replaces it. The two were one value for a while, and a separator drawn in a
notebook vanished the moment a chapter was opened — the list still showed the
notebook, but everything around it had switched to the chapter, so the notebook's
own separators were filtered out and a drag in that list wrote nothing at all.
**Which of the two you are in is a separate question again**, because both are on
screen at once. Opening a chapter puts you in it; pressing in the notebook's own
list puts you back in the notebook without closing the chapter. Whichever it is
wears a line along its top and the word *here*, and the + button names the place
it would make the note in. Before, a new note went into the chapter however far
away you had since clicked, and nothing on screen said so.
### A chapter is a row like any other
Chapters sit at the same level as notes and separators: same grip, same drag,