Repository navigation
Let a format name the viewport widths at which the page asks for it - #1370
Open
jwrosewell wants to merge 48 commits into
Open
jwrosewell wants to merge 48 commits into
jwrosewell wants to merge 48 commits into
Conversation
`CreativeOpportunityFormat` gains optional `min_viewport_width` and `max_viewport_width`, in CSS pixels. A slot with a bounded format carries `viewport_sizes` in its page data, one band per width at which the formats on offer change, widest first, and the Google ad tag's head bootstrap and the bundle's slot definition apply it as a size mapping when they define the slot, so a slot asks for a large size only where the page has room for one. A slot whose formats name no bound behaves exactly as before, and a server-side auction is still offered every format. A binary without the fields refuses a document carrying them, so it is promoted before any configuration uses them.
The bundle's refresh auction built its banner sizes from every format, so on a phone a bidder could win with a billboard the size mapping would not show. It now offers the sizes of the band the viewport is in, and leaves a slot whose band asks for nothing out of the auction. The band shape is stated once, on CreativeOpportunitySlot::viewport_sizes, and the guide drops the paragraph that repeated it.
# Conflicts: # CHANGELOG.md
This was referenced Oct 15, 2026
The head bootstrap and the bundle defined every slot and left an empty band to the size mapping, after the page had painted. No ad can serve at such a width, and GPT sizes a slot's div when it is displayed if the page's style gives it no size, so the only effect was to move the page. Both now define nothing there and release the first-impression claim, so the container keeps the space the page gave it. The band lookup is shared by the bundle's slot definition and its refresh auction, and the recorded page carries the new bootstrap.
…band The render bridge sized a winning bid that carried no width or height as the slot's first configured format, so on a phone a slot whose band asks for 320x50 rendered a 970x250 frame. It now takes the first size of the band the viewport is in, and falls back to the first format only where the slot names no band or the band asks for nothing. The comment on the server side that names the fallback says the same.
The refresh auction found a slot's page data by element id and offered the sizes of its band whoever defined the slot, so a slot the publisher defined first, which carries no size mapping, asked the ad server for its own sizes while bidders were offered only the band's, or left it out of the auction where the band was empty. The head bootstrap and the bundle now record each GPT slot they give the mapping, and the refresh auction narrows only those. Every other slot is offered its formats, as before the bands.
The head bootstrap and the bundle each define a slot in two places, the adInit pass and the fallback that runs when a publisher's first-impression claim lapses, and only the adInit pass was tested with bands. A test for each fallback now checks that a band with sizes gives the slot its mapping and records it, and that a band with no size defines nothing and releases the claim.
The calls that build and apply a slot's size mapping ran after defineSlot with nothing to catch an exception, so a throw skipped addService and the release of the first-impression claim, and stopped the rest of the adInit pass. The head bootstrap and the bundle now catch it, log a warning and return the slot as defined, asking for every format and not recorded as mapped, so the refresh auction offers it every format too. The claim-expiry fallback test also types its command queue.
`ts config ad-templates --details` printed each format as its size and media type, so a slot whose formats name viewport bounds looked the same as one whose formats name none. Each format now carries any `min_viewport_width` and `max_viewport_width` it names, under those names, and a test pins the output. The comment on `build_ad_slots_script` names `viewport_sizes` among the properties of the page data.
The changelog entry says that no slot is defined where the band asks for no size, that a failed mapping keeps every format, that the refresh auction narrows only a slot defined with the mapping, that a winning bid with no size takes the band's first size and that the details output prints each format's bounds. The guide says that a slot whose band asks for no size is not defined, so its container keeps the space the page's own style gives it.
This was referenced Oct 17, 2026
This branch has not been deployed
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.
Summary
Changes
crates/trusted-server-js/libcrates/trusted-server-core/srccrates/ad-tag/googlecrates/trusted-server-cli/srcdocs/guideCHANGELOG.mdCloses
Closes #1287
Spec
Spec: viewport-sizes
Test plan
CI on this pull request at 4404500 passed all 20 checks.
cargo test-fastly && cargo test-axumcargo clippy-fastly && cargo clippy-axumcargo fmt --all -- --checkcd crates/trusted-server-js/lib && npx vitest runcd crates/trusted-server-js/lib && npm run formatcd docs && npm run formatcargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1fastly compute serveChecklist
unwrap()in production code (clippy deniesunwrap_usedin CI)logmacros as AGENTS.md asks (notprintln!)Stacks on device signals (#1369).
Kept current. This update only merges the pull request below (#1369), which brings Tech Lab's
mainat 7aa5c99 and the changes made lower in the stack.What this pull request does
A format may name the viewport widths, in CSS pixels, at which the page asks for it, so a slot asks for a large size only where the page has room for one. Either bound may be left out, and a minimum above a maximum is refused when the settings load. When any format of a slot names a bound, the slot's page data carries
viewport_sizes, one entry for each band of widths, widest first. A slot whose formats name no bound asks for every format at every width, as before.The bands reach two places in the browser. The Google ad tag's head bootstrap and the bundle's slot definition apply them as a size mapping through
defineSizeMapping, so the ad server request carries only the band's sizes. The bundle's refresh auction offers a bidder only the sizes of the band the viewport is in, and leaves a slot whose band asks for no size out of the auction. The server-side auction does not filter its sizes and is still offered every format.A binary released before these fields refuses a format carrying either of them, which fails every request, so the binary that reads them is promoted before any configuration uses them. The configuration guide says so.
Why
A page that shows a billboard on a wide screen and a small banner on a phone has one slot and several formats, and without bounds the slot asks for every format at every width, so a bidder is offered a billboard the page cannot show. The bounds are the publisher's statement of what fits where. The ad tag passes them to the ad server as its size mapping, and the refresh auction holds a bidder to them.
How it was verified
The ad server's own handling of a size mapping is not exercised. The tests prove the mapping is defined with the bands the page data carries, through the bootstrap and the bundle.
References #1287.