POST /_screenshot-card persists captures to MediaCache and returns served URLs - #5844
Open
lukemelia wants to merge 1 commit into
Conversation
Contributor
Preview deploymentsHost Test Results 1 files ±0 1 suites ±0 2h 39m 27s ⏱️ + 1m 19s Results for commit 61abdb9. ± Comparison against earlier commit 7fa6b92. Realm Server Test Results 1 files ±0 1 suites ±0 16m 21s ⏱️ + 1m 37s Results for commit 61abdb9. ± Comparison against earlier commit 7fa6b92. |
lukemelia
force-pushed
the
cs-12561-post-screenshot-card-persists-captures-to-mediacache
branch
from
August 21, 2026 04:00
8186a3f to
7fa6b92
Compare
…rved URLs
The endpoint stops being ephemeral-only. A capture of an indexed card
persists under the same canonical identity the GET _screenshot/ DSL
resolves — instance URL, canonical spec hash, the instance's current
index generation — so a POST-published capture serves on the durable URL
immediately, gated realms included (publish-by-POST). The response gains
captures: [{name, url, width, height, deviceScaleFactor, base64?}]
while the top-level base64/width/height keep mirroring captures[0], so
current-shape requests stay byte-compatible; includeBase64: false opts
out of the bytes ahead of the default flipping once callers migrate to
URLs.
A request whose identity already has a ledger entry answers from the
store with zero render work (dimensions now recorded on the ledger so
the mirror never decodes bytes), an edited card misses its stale entry
and re-captures at the current generation, and the wait is bounded like
the GET DSL: over budget answers 503 + Retry-After while the job
persists its capture anyway, so the retry is a pure ledger hit. Cards
the index doesn't know still capture with the legacy response shape.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lukemelia
force-pushed
the
cs-12561-post-screenshot-card-persists-captures-to-mediacache
branch
from
August 22, 2026 16:08
7fa6b92 to
61abdb9
Compare
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.
Stacked on #5843 (GET DSL) → #5840 (serving) → #5838 (store). This PR completes M1's write side: the POST endpoint stops being ephemeral-only.
What this adds
Persistence under the DSL's canonical identity. A capture of an indexed card enqueues its
screenshot-cardjob with a realpersisttarget — instance URL (extensionless), canonical spec hash, and the instance's current index generation — the exact key the GET_screenshot/route resolves. A POST-published capture therefore serves on the durable URL immediately, including on realms whoseallowArbitraryScreenshotsgate is closed: publish-by-POST, now actually wired (the base-card gate doc regains that claim here, where it's true). The endpoint itself stays deliberately ungated — an authenticated surface under realm-read trust.Response shape, byte-compatible.
data.attributeskeeps the rawstatus/base64/width/height/contentTypefields exactly as before, and gainscaptures: [{name, url, width, height, deviceScaleFactor, base64?}]when the capture persisted —urlis the durable_screenshot/form with the spec's canonical query (?format=embeddedappears; the default spec elides).name/deviceScaleFactorare null for ad-hoc captures (they populate with declared batches and the M2 engine).includeBase64: falseomits the bytes everywhere, ahead of the default flipping once callers migrate to URLs. Cards the index doesn't know (or a server without a store) still capture with the legacy shape — nocaptures, no persist.Store-first reads and bounded waits, mirroring the GET DSL. A request whose canonical identity already has a ledger entry answers with zero render work — the ledger now records the capture's
width/height(additive migration) so the mirror never decodes image bytes, and the object streams back asbase64only when requested. An edited card misses its stale entry and re-captures at the current generation. The HTTP wait is bounded (same 25s budget, injectable for tests); over budget answers503+Retry-Afterwhile the job persists its capture regardless, so the client's retry is a pure ledger hit — never a second render. A newcardId must be within realmURL400 protects the persist identity.Threading: the
MediaCacheAdapternow reaches the realm-server's route layer (RealmServer→CreateRoutesArgs), from the same env-configured instance the realms and workers share.Test plan
Seven new handler tests against real Postgres with the in-memory store fake and a scriptable job queue: the persist identity published for the DSL to hit (hash-equal to the GET's key), the served-URL shapes for default and non-default specs,
includeBase64: false, ledger hits with zero jobs enqueued, generation-bump re-capture, 503 + Retry-After on a stalled job, and the unindexed-card legacy shape. The pre-existing byte-compat test passes untouched, and the full media-cache/DSL/serving/GC sweep is green (66 tests). Typechecks clean across runtime-common, realm-server, host.🤖 Generated with Claude Code