Repository navigation
Attachments per version: a removal drops from the next version only (#7) - #80
Merged
Merged
Conversation
…blygger#7) POST /api/media with an item_id hashes the bytes (SHA-256) before the R2 write. A row on that item with the same hash, MIME type and placement is returned as 200 { id, url, mime, duplicate: true } and nothing is stored. Migration 0028 adds a nullable media.sha256 (no backfill) with an (item_id, sha256) index, and recreates change_media_update in the remote-safe shape with the new column watched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lygger#7) Migration 0031 adds version_media(item_id, version, media_id), written at publish with the version and never changed. The item document, RSS, feed cards and item pages read the version's own list, so a later version can go without an attachment while the earlier ones keep listing and serving it (§5.4). The backfill keeps every live document as it was. DELETE /api/media/{id} now detaches anything a version lists, not only what a version's text shows: an appended attachment's bytes were deleted while its version still listed them. An upload to a published item is listed from the next version on and marks the item dirty. GET /api/items/{id} gains published_media, and the editor offers "drop from the next version". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vgururao
added a commit
that referenced
this pull request
Oct 20, 2026
…awal mutants cover version_media Same-second uploads tie on media.created; through the version_media join the tie fell back to random media ids, so #80's first test failed in CI. rowid breaks the tie in every attachment list. #80's version_media clause is a new route keeping published bytes after withdrawal, so both withdrawal mutants now restrict it as well (both caught). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BqkoZVrageDUQrG94Km4C
vgururao
added a commit
that referenced
this pull request
Oct 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The second half of #7: attachments per version, as you suggested once the guard was in. It's stacked on #75 (migration 0028), so this PR's diff includes #75's commit until that merges.
The gap. Every row attached to an item was listed on every version. The live document, the feed, the cards and the item pages all read
media WHERE item_id = ?. So an attachment could not leave a later version without leaving the live one too. It also led to a §5.4 bug. An attachment that's appended below the post, rather than placed in the text, never appears incontent_html.DELETE /api/media/{id}checked onlycontent_html, so it deleted the bytes of an attachment a published version still listed, and that URL then 404'd.The change.
0031_version_media.sqladdsversion_media(item_id, version, media_id), written once at publish and never changed, like the version itself. It joinsmediaby id, so a row detached since still lists on the versions that showed it.julianday().INSERT OR IGNOREmakes the backfill safe to rerun.0030is taken by another branch in flight (a change classifier).visibleMedia: appended, or placed in the text), and only rows still attached at the write, so a removal racing a publish wins.buildItemJson, the RSS description, the feed cards (loadFeedData) and the fragment and thread pages, includingog:image.DELETE /api/media/{id}now means "drop from the next version". It detaches the row when any version lists or shows it, and marks the itemdirtywhen the live version lists it. The bytes are deleted only when nothing published references them./media/:filealso serves anything a version lists.GET /api/items/{id}gainspublished_media, what the live version lists. It's additive and stays within the one detail query.One behaviour change to flag. An attachment uploaded to a published item now appears with that item's next version, not at once. The upload marks the item
dirtyso the Studio shows unpublished changes. This follows §5.4, where a new attachment is part of a new version. But a client that uploaded to a live item and expected it to show immediately will now need to publish. Three existing tests did exactly that and now publish after attaching.Not in this PR. There is no undo for a drop: re-attaching needs a route, and I didn't want to add one without asking. Restoring an old version's text doesn't bring back attachments dropped since.
Tests.
test/version-media.test.ts:e2e/studio.spec.ts: drop an appended attachment from a published item in the editor, and see it listed as leaving.public-page-performancefixtures addversion_mediarows, andmedia/pagestests publish after attaching.Results. Typecheck clean, and
sdk:generateis stable.An earlier full run on a heavily loaded machine timed out three unrelated tests at the 5 s limit. They pass alone, take about the same time on #75's branch, and passed in the clean rerun.
Refs #7. Depends on #75.
🤖 Generated with Claude Code