Skip to content

fix(cli): stop a body file with no content from wiping the page - #38

Merged
marcoarnulfo merged 1 commit into
mainfrom
fix/body-file-bom-wipe
Aug 6, 2026
Merged

marcoarnulfo merged 1 commit into
mainfrom
fix/body-file-bom-wipe

Conversation

@marcoarnulfo

Copy link
Copy Markdown
Owner

🇬🇧 English

Summary

Closes #37.

--body-file replaces: the new blocks are appended, then every old one is deleted. loadBody's empty check is the only thing between a file with no content and a body that is gone with nothing in its place — and it was strings.TrimSpace, which does not strip U+FEFF (it left Unicode's White_Space in 6.3).

A BOM-only file therefore read as content. The chain from there, reproduced in a test:

GET  /v1/blocks/page1/children     ← snapshot
DELETE /v1/blocks/old1             ← and nothing written between them

goldmark returns zero blocks, ValidateAppendable ranges over an empty slice and returns nil, splitIntoRequests emits no batch so no PATCH goes out at all, and replaceBody deletes every non-sub-page child anyway. Exit 0.

#36 fixed the same bypass on the append path, where it costs a false appended:true. On the replace path it costs the body. isBlank already existed from that fix; this reuses it.

Two more things, same failure

  • The twin hole after --expand. loadAppendBody re-checks emptiness after expanding; loadBody did not. A file of nothing but {{ticket}} addressed by --page-id expanded to nothing and took the same path.
  • replaceBody now refuses a request with no blocks, whatever the caller passed. Both guards above are argument handling inside one command, and the content they protect is not recoverable — so the property belongs next to the deleting, not two packages away from it.

Also fixes the SKILL.md clause that #36 made stale: the over-limit message names a request size only when it built one, and a floor on the file size when it did not.

Type of change

  • Bug fix
  • New feature
  • Docs
  • Refactor / chore

Checklist

  • go test ./... -race passes
  • gofmt, go vet, staticcheck are clean
  • Both README.md and README.it.md updated if behavior changed (no user-facing README claim changed)
  • Commit messages follow Conventional Commits with a scope

🇮🇹 Italiano

Riepilogo

Chiude la #37.

--body-file sostituisce: scrive i blocchi nuovi e poi cancella tutti i vecchi. Il controllo sul vuoto in loadBody è l'unica cosa fra un file senza contenuto e un corpo sparito senza nulla al suo posto — ed era strings.TrimSpace, che non toglie U+FEFF (uscito da White_Space in Unicode 6.3).

Un file di solo BOM passava quindi per contenuto. La catena, riprodotta in un test: goldmark produce zero blocchi, ValidateAppendable itera su una slice vuota e ritorna nil, splitIntoRequests non emette batch quindi nessuna PATCH parte, e replaceBody cancella comunque ogni figlio che non sia una sub-page. Exit 0.

La #36 ha corretto lo stesso bypass sul path append, dove costa un appended:true falso. Sul path replace costa il corpo della pagina. isBlank esisteva già da quella fix: qui viene riusata.

Altre due cose, stesso guasto

  • Il buco gemello dopo --expand. loadAppendBody ricontrolla il vuoto dopo l'espansione, loadBody no. Un file di solo {{ticket}} indirizzato con --page-id si espandeva a nulla e prendeva la stessa strada.
  • replaceBody ora rifiuta una richiesta senza blocchi, qualunque cosa passi il chiamante. Le due guardie sopra vivono nella gestione degli argomenti di un solo comando, e il contenuto che proteggono non è recuperabile: la proprietà sta bene accanto al codice che cancella, non due package più in là.

Corretta anche la subordinata di SKILL.md resa obsoleta dalla #36: il messaggio di superamento limite nomina la dimensione della richiesta solo quando l'ha costruita, e un pavimento sulla dimensione del file quando non l'ha fatto.

A --body-file replaces: the new blocks are appended, then every old one
is deleted. loadBody's empty check is the only thing standing between a
file with no content and a body that is gone with nothing in its place,
and it was strings.TrimSpace, which does not strip U+FEFF -- that left
Unicode's White_Space in 6.3.

So a BOM-only file -- what an editor on Windows writes for an empty file
saved as UTF-8-with-BOM, and what `> notes.md` produces in PowerShell --
read as content. From there: goldmark returns zero blocks,
ValidateAppendable ranges over an empty slice and returns nil,
splitIntoRequests emits no batch so no PATCH goes out at all, and
replaceBody deletes every non-sub-page child anyway. The wire trace is
GET children, DELETE, and no write between them. Exit 0.

PR #36 fixed the same bypass on the append path, where it costs a false
appended:true. On the replace path it costs the body. isBlank already
existed from that fix; this reuses it.

The twin hole went with it: loadAppendBody re-checks emptiness after
--expand, loadBody did not, so a file of nothing but "{{ticket}}"
addressed by --page-id expanded to nothing, parsed to zero blocks, and
took the same path.

Both are argument handling in one command, though, and the content they
protect is not recoverable. replaceBody now refuses a request with no
blocks whatever the caller passed, so the property lives next to the
deleting rather than two packages away from it.

Also fixes the SKILL.md clause that PR #36's own change made stale: the
over-limit message names a request size only when it built one, and a
floor on the file size when it did not.

Closes #37
@marcoarnulfo
marcoarnulfo merged commit 20663ca into main Aug 6, 2026
2 checks passed
@marcoarnulfo
marcoarnulfo deleted the fix/body-file-bom-wipe branch August 6, 2026 14:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A --body-file holding only a BOM wipes the page body and writes nothing

1 participant