1 Commits (f7fa11071d08215e57149bace2223d006fe40bca)

Author SHA1 Message Date
Elijah Voigt bc4c952533 feat(pages): update a page in place via re-upload [pages-ytau]
Updating a page meant deleting it and re-creating it under the same name.
Add PUT /api/pages/:slug, which replaces an existing page's content while
keeping its URL, owner, trust flag, and created_at.

Authorized to owner-or-admin (core::origin::can_replace, same rule as
delete) *and* can_upload, so a revoked uploader can't keep pushing content
to pages they already own.

The replace can't roll back the way create does — R2 has no multi-object
transaction and the previous bytes aren't retained — so the ordering bounds
the damage instead: new objects are written before any old key is deleted (a
viewer mid-update sees old-or-new per file, never a blank page), stale keys
are pruned last and best-effort (a failure only orphans an object), and the
D1 stats are refreshed only once R2 succeeds. Design §4.6b covers the
reasoning; §5's "no page overwrite" scope cut is retired.

In the UI, the always-visible upload card becomes a modal driven by two
entry points: "+ New Page" (create) and a per-row "Update" (replace, with
the slug pre-filled and read-only).

Verified end to end against wrangler dev with local D1/R2 — create, serve,
replace, stale assets pruned, stats/trust/ownership preserved, plus the
401/403/404/400/413 paths — and the UI driven in jsdom (37 assertions).
152 unit + 19 doctests, both-target clippy clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 week ago