nodes: a write changes only what it sends

`PUT /api/nodes/{id}` wrote the whole node, and a field left out was taken as
empty: a rename sent without the body erased the text. The browser always sent
it, which was the other half of the trouble. To rename a document it sent the
body it had — the draft for one open here, a copy fetched a moment earlier for
any other — and without the version it had seen, so a save made in another
tab, on the phone or over the mount in between was written over, kept only as
a revision nobody was told about.

Now `name` and `body`, like the rest, change only when they are sent. A rename
sends the name alone, and for a document open in a tile the `updated_at` that
tile last saw, so a rename of a document saved elsewhere since is refused the
way `:w` is, rather than stamping a new version over it that the next autosave
would then write the draft past. Toggling a space's board sends only that.
This commit is contained in:
nvrl committed 2026-10-21 12:18:26 +02:00
1 parent 10103b81be
commit 3a6ad9453e
6 files changed
+79 -26

No files matched your search

+3 -2
View File
@@ -478,6 +478,7 @@ export const api = {
request<{ prev: DiaryEntry | null; next: DiaryEntry | null }>(`/diary/around/${id}`),
/**
* Only what is sent changes: a rename sends the name alone.
* `base_updated_at`: refused with 409 if the node was saved since then.
* `restore`: the text is a revision brought back, so what it replaces is
* kept as a revision whatever the last one was.
@@ -485,8 +486,8 @@ export const api = {
updateNode: (
id: number,
input: {
name: string
body: string
name?: string
body?: string
created_at?: number
has_board?: boolean
base_updated_at?: number
+9 -12
View File
@@ -1163,19 +1163,16 @@ class Workspace {
}
/**
* Rename anything in the tree — a document, a folder or a space. The body
* has to ride along because the update is a whole-node write; for the node
* being edited that is the draft, which would otherwise be rolled back to
* whatever the server still holds.
* Rename anything in the tree — a document, a folder or a space. Only the
* name is sent, so the text on the server stays as it is and a draft still
* being typed is saved by autosave as before. When the document is open
* here, the rename carries the version this tab last saw: one saved
* somewhere else since is not stamped over, which would let the next
* autosave write the draft over that save without a word.
*/
async renameNode(id: number, name: string) {
const current =
this.open?.id === id ? { ...this.open, body: this.draft.body } : await api.getNode(id)
const saved = await api.updateNode(id, {
name: name.trim(),
body: current.body,
created_at: current.created_at,
})
const open = this.tilesWith(id)[0]?.open
const saved = await api.updateNode(id, { name: name.trim(), base_updated_at: open?.updated_at })
for (const tile of this.tilesWith(id)) {
tile.open = saved
tile.draft.name = saved.name
@@ -1712,7 +1709,7 @@ class Workspace {
async setBoard(on: boolean) {
const space = this.space
if (!space) return
await api.updateNode(space.id, { name: space.name, body: '', has_board: on })
await api.updateNode(space.id, { has_board: on })
this.spaces = await api.listSpaces()
if (on) await this.openBoard()
else {