---
title: "aadhar.sh/garage/horizon"
description: "Test-driving text-box-trim, contrast-color, interestfor and anchor positioning before any of them touch the live aadhar.sh homepage."
path: "/garage/horizon"
section: "garage"
kind: "content"
updated: "2026-06-07"
source: "https://aadhar.sh/garage/horizon"
---

> Site index: https://aadhar.sh/llms.txt
> Section index: https://aadhar.sh/garage/llms.txt
> This is the Markdown twin of a page on aadhar.sh. The HTML at the source
> URL below is the original, and is hand-written and unminified on purpose.

# Horizon

Upcoming web-platform features, test-driven here *before* any of them touch the live homepage. A flag being on in **my** browser means nothing to real visitors, so the rule is simple: nothing graduates from this page to [aadhar.sh](https://aadhar.sh/) until it ships in more than one engine. Each card says where it actually stands, and the chips below light up for *your* browser specifically. Cross-engine status triangulates three sources: Chrome flags, chromestatus, and the green/red WebKit chips that quote Safari's team directly from [webkit.org/standards-positions](https://webkit.org/standards-positions/), so Safari answers "will Safari ever do this" instead of me inferring it.

**Last swept 2026-10-08**, against Chrome 155 (156 in beta), Safari 27 plus [Technology Preview][safari-technology-preview], and Firefox 157 (158 in beta), with every card's badge checked against MDN browser-compat-data 8.1.5. The sweep before it was 2026-08-10, and three cards moved between the two from a canary reading (below). A page like this rots in two directions and only one of them is obvious: features arrive, and features I already wrote up quietly change status underneath me. The 07-26 sweep moved four cards (`InstallEvent.addRoutes()`, [compression dictionaries][compression-dictionaries], `appearance: base-select`, [Temporal][temporal]) and demoted one of them off "shipped on the site" entirely, which is the more interesting half. Dates here are when I checked, never when I guessed.

**2026-10-08:** the Firefox team's [Interop 2027 ranking](https://interop-rank.fxdx.dev/) lists 100 proposals, and 61 of them had no card here. Each of the 61 has one now, and all 100 sit in one index at the bottom with engine versions read from today's MDN compat data and a live probe of your browser. Writing the cards turned up three build traps: an `inherit()` inside `calc()` swallows the next rule in Chrome 155, and Lightning CSS rejects `var()` in an `@container` condition and folds `none` in `color-mix()` to zero. The demos that hit them keep that CSS in `style` attributes or insert it on click. Reading it moved three cards: Safari 27 and Firefox 157 are stable now, so `appearance: base-select`, `:heading` and `@supports at-rule()` lost a "beta" or gained an engine.

**The 10-08 sweep** then read the badge on each of the 90 cards that predate the Interop index against MDN's compat data, instead of only the cards I expected to move, and 25 badges changed. Seven cards reached a second stable engine: JPEG XL and `margin-trim` in Chrome 155, typed `attr()` and `font-width` in Firefox 155, multi-colour `color-mix()` in Safari 27, text module imports in Firefox 153 and Chrome 155, and Document Picture-in-Picture in Firefox 151. Five were never right, and they are the useful half. Three were wrong when written: Firefox 146 took `-webkit-fill-available` rather than `stretch`, Firefox shipped `shape()` in 148 rather than 136, and text imports shipped unflagged rather than behind a pref. Two were forecasts that didn't land: Firefox 157 shipped without JPEG XL, and Chrome moved the media pseudo-classes from 152 to 158. One feature went backwards, which no release post frames as news: Firefox 155 pulled most of its `::-webkit-scrollbar` subset.

The 08-10 pass found a *fourth* direction, and unlike the other three it is a flaw in the method rather than in the sources. Every line here was written reading Chrome first, so a feature that shipped in WebKit or [Gecko][gecko] years earlier gets recorded as "Chrome N, others no" and stays wrong until something forces a re-read. Two cards were exactly that. Separately, Safari **Technology Preview 249** moved six cards in one build, which no "New in Safari N" post will ever mention, and one card (`CloseWatcher`) turned out to have had *both* of its version numbers wrong since it was written. Nine new cards, ten corrected status lines, and the `<install>` card retired: aadhar.sh is not a [PWA][pwa] and isn't becoming one, so a card forecasting an install button for it was forecasting nothing.

Since 2026-09-14 the page no longer waits for me to sweep it. A nightly job runs every probe below in stable [Chromium][chromium], Firefox and WebKit and again in Chrome Beta and Edge Dev, and files one issue per leg naming any probe that answers differently in the prerelease. Its first two findings moved the `margin-trim` and [JPEG XL][jpeg-xl] cards on 2026-09-17: both flip on in Chrome 155, three weeks before stable, and a third moved the CSS `random()` card on 2026-09-30, the day Chrome 156 reached beta. That catches the direction a sweep structurally cannot, a feature that ships between sweeps, and it catches it before the release rather than after. It reads only what a probe can measure, so a card whose status lives in a standards position or a flag description still rots at the old rate.

The 07-30 pass found a *third* rot direction I hadn't accounted for, and it is the worst of the three: features that finish everywhere without ever showing up as news. Diffing this page against all 377 features chromestatus tags `shipping_year:2026` turned up six that already ship in two or three engines, which is this page's own bar for graduating, sitting here uncovered. Reading a frontier from Chrome's release notes structurally cannot see them. It also caught a wrong claim on the document-patching card, corrected in place. Version numbers in the new batch come from MDN's [browser-compat-data][browser-compat-data] v8.0.8 rather than chromestatus, whose Safari and Firefox columns are filled in by the Chrome feature author and had drifted on three of them.

---

## text-box-trim / text-box-edge Chrome · Safari · Firefox

✓ shipped on the site

Trims the [half-leading][leading] above the cap line and below the baseline, so text sits flush in its box. The highlight shows the line box; on the right it hugs the actual letters.

default leading

Outlook Express

`text-box-edge: cap alphabetic`

Outlook Express

My Documents

On the site: this cleanly fixes the optical-centering I hand-tuned with magic-number padding on the XP title bars, and ends the long Safari `<h2>` baseline saga. Now shipped in all three: Chrome 133, Safari 18.2, Firefox 149.

Your browser doesn't support it yet, so the two samples above look identical.

## contrast-color() Chrome 147 · Safari · Firefox

✓ shipped on the site

Auto-picks a legible (black/white) text color for a given background. Top label in each swatch is naive fixed-white; bottom is `contrast-color()`. Watch it flip to black on the light chips.

white

auto

white

auto

white

auto

white

auto

white

auto

white

auto

On the site: serendipity derives each attendee avatar's text colour from its hue with `contrast-color()` (behind an `@supports` guard). [Baseline][baseline] since April 2026: Chrome 147, Safari 26, Firefox 146. The homepage title bars deliberately *don't* use it: that Luna blue sits right on the black/white contrast crossover, so the hand-picked white is the safer call there.

Your browser doesn't support it yet, so the "auto" labels fall back to black.

## interestfor + anchor positioning Chrome 142 · Safari opposes · FF no

The declarative dream: `interestfor="id"` shows a [popover][popover-api] on hover / focus / long-press, with built-in (configurable) delay and accessibility wiring, and **zero JavaScript**. [Anchor positioning][anchor-positioning] places it relative to the link. The demo sets `interest-delay: 0s` so tooltips spawn instantly, matching the site's cursor tooltips (the default is `0.5s`). Hover a car below:

Resto-mods like [Singer](https://www.google.com/search?q=Singer+Porsche+911), the [HWA EVO](https://www.google.com/search?q=HWA+AMG+EVO), or the [Evoluto 355](https://www.google.com/search?q=Evoluto+Automobili+355).

\[ Singer 911 \]

native popover, no JS

\[ HWA EVO \]

anchored to the link

\[ Evoluto 355 \]

interest-delay: 0s

Delay lives in CSS: `interest-delay-start` / `interest-delay-end` (shorthand `interest-delay`). `0s` spawns instantly like ours, but the trade is that every incidental hover-pass over a grid would fire a tooltip (why the 0.5s default exists). The native-menu compromise: a delay for the first, but instant between adjacent invokers once one is already showing:

```
/* first hover waits; gliding between neighbours is instant */
.photos:has(:interest-source) .photos a { interest-delay-start: 0s; }
```

On the site: this comes closest to natively replacing the ~70-line cursor-tooltip layer: it handles hover/focus detection, the show/hide delay, and accessibility on its own. **But** Safari *opposes* the current design and it's Chrome-only (shipped default-on in Chrome 142, Firefox hasn't), so it is *not* a migration path while the site cares about Safari (WebKit standards-position: [oppose, #464](https://github.com/WebKit/standards-positions/issues/464)). And it anchors to the *element*, so the cursor-following `--x/--y` loop stays even in a perfect world. Strictly a forward-look.

what it would replace (today → declarative)

```
// today: ~70 lines of JS
pointerover  -> findTarget -> buildContent -> showPopover()
pointermove  -> write --x/--y
pointerout   -> hidePopover()

<!-- tomorrow (if it ever lands cross-browser) -->
<a interestfor="tip">Singer</a>
<div id="tip" popover>...</div>
/* CSS: position-area: bottom; interest-delay: 0s; */
```

Your browser doesn't support `interestfor`, the links above just navigate on click and show no popover. It ships default-on in Chrome/Edge 142+, so open this page there to see it (Safari opposes, Firefox hasn't shipped).

## anchor positioning / position-area + position-try Chrome · Safari · Firefox

✓ shipped on the site

Tether one element to another in pure CSS: give the target an `anchor-name`, point a popover at it with `position-anchor`, and place it with `position-area` (a 3×3 grid around the anchor: `top center`, `bottom span-right`…). No `getBoundingClientRect()`, no scroll/resize listeners, no JS coordinate loop. The killer feature is `position-try-fallbacks`: when the chosen side would overflow, the browser *flips* to the opposite side on its own. Click a button below: the left popover sits above (and flips below if it runs out of room near the top of the viewport); the right one asks for the right edge and flips inline when crowded.

position-area: top

Anchored above the button. Scroll me to the viewport edge and `flip-block` drops me below, pure CSS.

position-area: right

Asks for the right edge; `flip-inline` moves me left when there isn't room.

On the site: this is *already live* here in a small dose. The keyboard-focus path of the homepage tooltip tags the focused element with `anchor-name: --xp-tip`, and the `.xp-tooltip.anchored` rule uses `position-anchor` to tether it, so Tab-navigating the photo grid gets a properly-placed Fuji-LCD popover with no cursor to follow. It wins everywhere I'd otherwise reach for JS positioning: a future `/coffee` slot-picker dropdown, the artist popovers, any menu, all could anchor + auto-flip in CSS. It does **not** replace the pointer-following `--x/--y` loop (anchors attach only to elements), so the two coexist: cursor-tracking for mouse, anchored for keyboard. Now cross-engine: Chrome 125, Safari 26, Firefox 147 (`position-anchor` FF 151), WebKit support [#167](https://github.com/WebKit/standards-positions/issues/167).

Your browser doesn't anchor yet, the popovers above still open (native Popover API), they just fall back to a fixed corner of the demo box instead of tethering + flipping. Chrome 125+, Safari 26+, or Firefox 147+ shows the real behavior.

## scroll-driven animations animation-timeline: scroll() / view() Chrome · Safari · FF flag

Tie a CSS animation's playback to scroll position instead of the clock: no `scroll` listener, no `requestAnimationFrame`, no JS at all. `scroll()` tracks a scroller's offset; `view()` tracks an element crossing the scrollport. Scroll the box below: the XP progress strip fills via a named scroll-timeline, and each row fades up on its own view-timeline as it enters view.

↓ scroll this box ↓

scroll() drives the strip above, tracks this scroller's y-offset.

view() drives each row: one timeline per element, scoped to the scrollport.

animation-range: entry 0% cover 35%, reveal as the row enters.

Runs on the compositor; the main thread stays free while you scroll.

Degrades cleanly: where it's missing, rows just sit visible.

prefers-reduced-motion pins everything to its end state.

~ end ~

On the site: this retires any scroll-position JS I'd otherwise hand-write (a reading-progress strip on `/garage` long-reads, or lazy fade-up reveals for the 3×3 photo grid) in pure CSS that the compositor runs off the main thread (no jank competing with the cursor-tooltip `--x/--y` loop). It's the rare frontier feature that's already *cross-browser* (Chrome 115, Safari 26, Firefox 110+; WebKit standards-position [support, #152](https://github.com/WebKit/standards-positions/issues/152)). So it could graduate to the homepage today, strictly as decoration, gated behind `@supports` and `prefers-reduced-motion`, because the content must read fine with the animation pinned to its end state.

Your browser doesn't support `animation-timeline` yet, the progress strip sits at a fixed third and the rows just stay visible instead of revealing on scroll.

## popover=hint Chrome 151 · FF 153 · Safari TP 249

A third popover *type*, sitting between `auto` and `manual`, purpose-built for tooltips. WebKit had said nothing about it for two years; Technology Preview 249 added it, which is the whole difference between "a third engine may never come" and "this is one Safari release from Baseline". Safari 27 then shipped without it, so it is still Technology Preview only. The other two versions moved as well: Chrome 133 and Firefox 149 implemented an older draft, and the current behaviour arrived in Chrome 151 and Firefox 153. A `hint` popover is light-dismissed (click outside, `Esc`) **and** won't force-close an open `auto` popover the way another `auto` would, so a hover-tip can float over a menu without nuking it. Open each below and click elsewhere to feel the difference:

auto

Light-dismisses on outside click / `Esc`. Only one `auto` open at a time, a second one closes me.

hint

Also light-dismisses, but does *not* close an open `auto`. Built for transient tooltips.

manual

No light-dismiss at all: only JS `hidePopover()` closes me. (This is what the homepage tooltip uses today.)

On the site: the live cursor-tooltip (`#xp-tooltip` in `index.html`) is declared `popover="manual"` and driven entirely by a JS `pointerover`/`pointermove` loop that writes `--x`/`--y` and calls `showPopover()`/`hidePopover()`. Switching it to `hint` would buy native light-dismiss, but the site only has *one* tooltip and it already manages its own lifecycle, so the win is marginal. I'd still run the same JS to follow the cursor and pick the content. `hint` really pays off in the multi-popover case (a tip that coexists with an open menu) the homepage doesn't have. Filed as "safe to adopt, low value here."

Your browser doesn't support `popover="hint"`, the middle button falls back to `auto` semantics (the attribute parses to the default), so it behaves like the first one. To try it in older Chrome, enable `chrome://flags#enable-experimental-web-platform-features`.

## field-sizing : content Chrome 123 · Safari 26.2 · FF 152

Lets a form control size itself to its *content* instead of a fixed `rows`/`cols` or magic-number height. A `<textarea>` auto-grows line by line as you type and shrinks back when you delete; an `<input>` hugs its value. Type into both fields below: the left one is a stock textarea pinned at `rows="2"`, the right one tracks the text (`min-height`/`max-height` keep it from collapsing or running off the card).

default: fixed `rows="2"`, scrolls · `field-sizing: content`, auto-grows

single-line input that hugs its value:

On the site: this is the native kill-switch for the classic `input`/`scrollHeight` auto-resize handler: the JS pattern that listens on `input`, zeroes the height, reads `scrollHeight`, and writes it back every keystroke. The coffee booker benefits most directly: `cal/`'s "Your info" message box on the booking form can drop its resize script entirely and just say `field-sizing: content; max-height: 9em` on the sunken XP textarea, with the layout reflowing for free. Now shipped in Chrome 123, Safari 26.2, and Firefox 152 (June 2026), so it is Baseline and can graduate from the garage to the live booking form. Pair it with a static `rows` attribute as the fallback height and there's nothing to feature-detect.

Your browser doesn't support `field-sizing` yet, the right-hand textarea behaves exactly like the left (fixed `rows="2"`, scrolls), and the email input keeps its default width. The `rows` attribute is the graceful fallback.

## View Transitions / document.startViewTransition() Chrome · Safari · FF 144

Wrap a DOM mutation in `document.startViewTransition(cb)` and the browser snapshots before + after, then cross-fades (or morphs, for same-named elements) between them: an animated state change with **no manual tweening, no double-buffering, no FLIP math**. Same-document is shipped in Chrome 111 and Safari 18; the cross-document `@view-transition { navigation: auto }` variant (Chrome 126, Safari 18.2) animates whole navigations. Firefox shipped same-document in 144 (Oct 2025) but not the cross-document variant yet. Tap the toggle: the panel below has its own `view-transition-name`, so only it animates:

My Documents folder view, click toggle to swap

Live, self-contained: the swap is a real `startViewTransition()` call, gated so unsupported browsers still toggle instantly (just without the morph).

On the site: nowhere, as of 2026-07-30. The Run palette, the `/writing` note popovers, and the `/lens` view tabs each wrapped their DOM mutation in `startViewTransition()`; all three now apply it directly. The honest reason is that every one of those elements is already composited and on screen the moment it is asked for, so the transition could only add latency to an interaction that had none. It also cost more than the animation: `startViewTransition` *defers* its callback, so every caller had to move focus and measurement inside the callback, and the ones that forgot ran a frame early against a still-hidden element. The API is genuinely good, and the demo above is real; this site just ran out of places where the trade paid.

Your browser doesn't support `document.startViewTransition` (Firefox, today), the toggle still works, it just swaps the panel instantly with no cross-fade. That instant-swap *is* the graceful fallback: the feature is purely a progressive enhancement.

## appearance: base-select / customizable \<select\> Chrome 135 · Safari 27 · FF 153 parser

For two decades the `<select>` popup has been a sealed OS widget: you could style the trigger, and the OS drew the list. `appearance:
        base-select` opts the control into a fully styleable model: the popup becomes real DOM you can theme, `::picker(select)` and `::picker-icon` are addressable, and each `<option>` can hold arbitrary markup. The select below is a true native control (keyboard, typeahead, form value all intact) reskinned into an XP combobox with alternating Outlook-Express rows, a blue-gradient highlight, a rotating drop-arrow, and a color swatch inside every option. Open it:

On the site: this natively answers the future `cal/` booking dropdowns (timezone, slot length, meeting type). Today those would mean a hand-rolled JS listbox to escape the un-styleable OS popup, or a system widget that shatters the XP illusion. base-select lets one real `<select>` carry the bevel, the alternating rows, and inline swatches/icons while keeping full keyboard + form semantics for free. It went cross-engine in 2026: Chrome shipped it (~m135) and **Safari 27** now ships it too ([WebKit #386](https://github.com/WebKit/standards-positions/issues/386)). Firefox still renders the native widget, so keep it progressive: wrap the enhancement in `@supports (appearance: base-select)`. Gecko is moving though, and the tell is a parser change: Firefox 153 now parses *all* nested elements inside a `<select>` into the DOM rather than discarding everything that is not an `<option>`, `<optgroup>`, or `<hr>`. That is groundwork with no user-visible effect, but it is exactly what shipping this later requires. WebKit's **golden rule**: always give every `<option>` real text content or an accessible text attribute, because a base-select stays a genuine native `<select>` underneath (keyboard, typeahead, form value, screen-reader text all intact). The options are display-only, so keep buttons and links out of them.

Your browser doesn't support `appearance: base-select` yet, the dropdown above falls back to your OS's native `<select>` widget (which is exactly the intended graceful degradation). To try the XP-themed version, open this page in Chrome 135+ or Safari 27.

## corner-shape + superellipse() Chrome 139 · Safari TP · FF Nightly

A second axis for `border-radius`. The radius sets *where* a corner's two endpoints sit; `corner-shape` sets the *curve drawn between them*: `round` (the default), `bevel` (a flat cut), `notch` (a square step in), `scoop` (concave bite), or `squircle` / `superellipse(k)` for the Apple-style continuous curve. Every box below shares the *same* `border-radius: 18px`; only the keyword differs. Resolved via WebKit standards-positions [#229](https://github.com/WebKit/standards-positions/issues/229).

round · (default)

bevel

notch

scoop

squircle

superellipse(2.4)

My Documents

A window frame with `scoop` top corners: one declaration, no SVG mask, no `clip-path` polygon.

On the site: this natively replaces the `clip-path:
        polygon()` hack on the title-bar icon, and any time I'd reach for an SVG mask to cut a corner. A real squircle on the `.window` and photo-frame corners reads more 2006-Aqua than the flat `border-radius` arc does, and it stays a live border (shadow, outline, focus ring all follow the new shape) instead of a clip that eats the box-shadow. **Chrome 139 only**: Safari and Firefox haven't shipped it, so it can't graduate to the homepage yet; it needs a plain `border-radius` fallback (which every box above already degrades to).

Your browser doesn't support `corner-shape` yet, all six boxes above fall back to identical plain rounded corners, and the framed window keeps ordinary rounded tabs. To try it in Chrome 139+: no flag needed; older Chrome via `chrome://flags#enable-experimental-web-platform-features`.

## calc-size() / interpolate-size Chrome-only

The decades-old missing piece: you *cannot* transition `height: 0 → auto`: the browser can't interpolate to an intrinsic keyword, so it snaps. `calc-size()` makes `auto`/`min-content`/`fit-content` computable, and one declaration (`interpolate-size: allow-keywords`) opts a subtree in, so every keyword transition under it tweens instead of jumping. Click the group box below; in Chrome it eases open, elsewhere it snaps.

Now Playing, details · ›

This panel's height animates from `0` to its natural content height: no `max-height` guess, no JS `scrollHeight` measurement, no ResizeObserver.

The `<details>` still works as a real disclosure widget for keyboard and AT; the transition is pure progressive enhancement layered on top.

… or fully declarative via `::details-content`

Same animation with zero extra markup, just `::details-content { height: 0 → auto }` plus `interpolate-size`. The XP group box above wraps the body in an explicit `div` only so the bevel clips cleanly.

On the site: this natively fixes every expand/collapse where I currently animate `max-height` to a hand-picked too-big number (which throws off the easing curve: the timing spends itself on empty space) or measure `scrollHeight` in JS. The collapsible disclosure sections (a code viewer, an FAQ, the garage cards here) would drop their measuring code entirely. But it's **Chrome 129 only**: Safari and Firefox have not shipped it and WebKit has taken no position ([#348](https://github.com/WebKit/standards-positions/issues/348)), so the rule stays in the garage. The honest fallback is fine, though: without the keyword the panel just snaps open instantly, which is exactly how `<details>` behaves today, so this can ship as pure enhancement the moment a second engine lands it.

Your browser doesn't support `interpolate-size`, the panels above still open and close correctly, they just snap instead of easing. To try the animation in Chrome 129+, no flag needed.

## scheduler.yield() / main-thread responsiveness Chrome 129 · FF 142 · no Safari

WebKit: unstated A promise you `await` in the middle of a long task: it hands the main thread back to the browser to service pending input/paint, then resumes your loop, and crucially resumes at the *front* of the queue, where `setTimeout(0)` would rejoin at the back. So you stay responsive without losing your turn to unrelated work. It's shipped in Chrome and Firefox 142 (no flag), so the demo below is live: the concept, then a real measured run, then the code.

### one long synchronous task

the red tap waits for the whole block to finish, janky.

### broken with `await scheduler.yield()`

the same tap lands in a gap and gets serviced, smooth.

live rAF dot, it freezes whenever the main thread is blocked · frames:

Click a button and watch the dot. The single 600 ms task freezes it; the chunked version yields every ~20 ms so it keeps gliding, same total work, responsive throughout.

```
// today on the site: cold-KV tracklist fallback builds ~50 rows
// in one go, blocking input on a slow phone.
for (const t of tracks) renderTrackRow(t);   // one long task

// with scheduler.yield(), guarded so it ships everywhere:
const yieldNow = (window.scheduler && scheduler.yield)
  ? () => scheduler.yield()
  : () => new Promise(r => setTimeout(r, 0));  // fallback

let i = 0;
for (const t of tracks) {
  renderTrackRow(t);
  if (++i % 8 === 0) await yieldNow();   // breathe every 8 rows,
}                                        // resume at front of queue
```

On the site: the offender is the client-side Spotify tracklist fallback (the cold-KV path that renders full-fidelity rows: artist links, cover art, tooltip wiring). Today that's one synchronous burst. `scheduler.yield()` would let it chunk while keeping taps and scroll alive, without the back-of-queue penalty of a `setTimeout(0)` shim. It graduates to the homepage trivially because it's a guarded enhancement (`typeof` check, same-shape fallback), so Safari and Firefox just take the plain loop.

Your browser doesn't expose `scheduler.yield()`, the live demo above still runs and stays responsive, it just takes the `setTimeout(0)` fallback path instead. To get the real primitive: Chrome 129+, shipped, no flag.

**Even further out**: flag-gated in Chrome, not shipped by default in any engine. Bleeding edge. (To exercise the supported ones above, flip the master switch in Canary: `chrome://flags#enable-experimental-web-platform-features`.)

## HTML-in-Canvas / ctx.drawElementImage() Origin trial Ch148-150 · WICG

[WebKit: no position](https://github.com/WebKit/standards-positions/issues) Draws a *live, laid-out* DOM subtree straight into a 2D canvas with `ctx.drawElementImage(el, x, y)` (WebGL gets `texElementImage2D`, WebGPU `copyElementImageToTexture`), after opting the element in with a `layoutsubtree` attribute. The element keeps real text layout, fonts, and accessibility. Unlike `foreignObject`+SVG, it isn't tainted, doesn't silently drop styles, and can be composited, transformed, and post-processed pixel-by-pixel. Below: a real XP title bar on the left, rasterized into the canvas on the right and tilted: one source of truth, two surfaces.

live DOM (the source element)

My Documents · \_□×

`drawElement` → canvas (skewed)

Same markup, now compositable pixels.

On the site: this is the only sanctioned way to take the hand-built XP chrome (the title bars, the contact-sheet photo frames, the Fuji LCD tooltip) and run it through canvas effects (a tilt, a CRT scanline pass, a drop-shadow bake) *without* re-drawing any of it in canvas primitives or shipping an html2canvas-style screenshotting library. Picture a one-off "rendered on a 2006 CRT" hero, or baking a share-card OG image of the live now-playing list. But it is a single-engine flag with no WebKit position, so it stays a garage toy: the homepage cannot depend on it for anything Safari/Firefox visitors need to see.

the call (what the canvas on the right is doing)

```
const el  = document.getElementById("hic-source");
const ctx = canvas.getContext("2d");

// tilt the whole surface, then rasterize the LIVE element into it
ctx.translate(20, 14);
ctx.transform(1, 0.06, -0.10, 1, 0, 0);   // slight skew
ctx.drawElementImage(el, 0, 0);            // <- the new primitive (el has layoutsubtree)

// from here it's normal canvas: ctx.filter, getImageData, WebGL texElementImage2D…
```

Your browser doesn't support `drawElementImage` yet, the canvas shows a placeholder instead of the live rasterization. It is in origin trial in Chrome 148 through 150; enable `chrome://flags#canvas-draw-element` to see it for real.

## CSS Masonry / Grid Lanes Safari 26.4 · Chrome flag

Lays tiles of *uneven height* into columns, each new tile dropping into the shortest column so the bottom edge stays ragged and the gaps close: the Pinterest / contact-sheet look, but native. After years of a syntax fight (a `grid-template-rows: masonry` shorthand vs. a separate `display: masonry`), it has converged into **Grid Lanes**: [masonry][masonry] expressed *inside* CSS Grid so you keep `grid-template-columns`, gaps, and (eventually) subgrid. **Safari 26.4 shipped it** as `display: grid-lanes` (on by default) with a `flow-tolerance` knob (the old `item-tolerance`, renamed); Chrome keeps it behind the experimental flag. The grid below is *real* grid-lanes where the engine supports it (you, on Safari 26.4+) and falls back to a CSS multi-column mockup elsewhere. The mockup reads down-then-across; true masonry packs into the shortest lane.

Live in Safari 26.4+ (`display: grid-lanes`, on by default). In Chrome it's behind `chrome://flags#enable-experimental-web-platform-features` (the `#css-grid-lanes-layout` entry). Everywhere else: the mockup above.

Grid Lanes syntax (vs. today's fixed 3×3)

```
/* today on aadhar.sh — rigid, every frame the same box */
.photos {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.photos img { aspect-ratio: 1; object-fit: cover; } /* crops the Fuji frames */

/* now shipping: Grid Lanes (Safari 26.4; Chrome + Firefox behind a flag) */
.photos {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  display: grid-lanes;           /* Safari 26.4 ships this; grid-lanes won the naming, not grid-template-rows:masonry */
  gap: 8px;
  flow-tolerance: 2em;           /* how ragged a lane may get before reflow (was item-tolerance) */
}
.photos img { height: auto; }    /* native aspect ratio preserved, no crop */
```

On the site: the homepage photo grid is a hard `3×3` of square-cropped thumbnails: portrait Fuji frames get center-cropped and tall Leica shots lose their composition. Masonry would let each thumbnail keep its native aspect ratio (the orientation-corrected width/height already in `metadata.json`) and pack with no crop and no row-height gridlock. Until it ships in two engines it stays here: the live grid keeps cropping, because a Canary-flag layout that falls back to a broken stack for every real visitor is exactly what this page exists to *avoid* shipping.

Your browser can't show real masonry yet (only Safari 26.4+ ships it by default in mid-2026). The grid above is a CSS multi-column *approximation* of the live feature; enable the Canary / Safari TP flags above to drive the genuine layout.

## JPEG XL image/jxl Safari 17 · Chrome 155 · Firefox Nightly

The format the photo pipeline actually wants: smaller than AVIF at the *high* quality end (where this site lives), genuinely progressive (a low-res preview paints from the first few KB, then sharpens), and it can losslessly *re-wrap* an existing JPEG ~20% smaller with the original bytes recoverable. No live decode below: there's no `.jxl` asset on the site, so this is an honest mock plus the wiring it'd take. The probe table on this page cannot carry the decode check either, because an image decode is asynchronous and every probe here is a synchronous `() => boolean`; the nightly browser canary carries it instead, and that is where the news below comes from. [WebKit: support (shipped)](https://github.com/WebKit/standards-positions/issues/72)

### JPG (jpegli)

baseline, today's universal fallback

everywhere

### AVIF

~25% smaller, but softens fine grain at high q

Cr 85+ · Saf 16+ · FF 93+

### JPEG XL

smaller still at the high-q end + progressive

Saf 17+ · Cr 155+ · FF Nightly

Bars are illustrative (matched perceptual quality, photographic content); I never measured these specific shots.

- **Progressive by default**: the Fuji-grain photos paint a blurry frame from the first KB and resolve as bytes arrive, instead of AVIF's all-or-nothing decode.
- **Lossless JPEG transcode**: `cjxl in.jpg out.jxl` shrinks the existing jpegli output ~20% and is byte-reversible, so no quality decision to re-litigate.
- **High-fidelity sweet spot**: at the q82-ish quality this site targets, JXL keeps grain and tonal gradients that AVIF starts to smear.

how it'd slot into the \<picture\> ladder (a 4th, top-priority source)

```
<!-- add jxl ABOVE avif; browser picks the first type it claims to support.
     Safari and Chrome take jxl, Firefox skips to avif, ancient takes the jpg src. -->
<picture>
  <source type="image/jxl"  srcset="/images/<stem>.jxl?v=11">
  <source type="image/avif" srcset="/images/<stem>.avif?v=11">
  <img src="/images/<stem>.jpg?v=11" loading="lazy" decoding="async">
</picture>

# pipeline: one more encode step after the jpegli stage in add-photos.sh
cjxl --lossless_jpeg=1 <stem>.jpg <stem>.jxl   # byte-reversible re-wrap
# OR re-encode from the 1200px source for max ratio:
cjxl -q 90 -e 7 <stem>.png <stem>.jxl
```

On the site: a *third* tier above AVIF in every photo-grid `<picture>`, plus a `cjxl` step in `add-photos.sh` after the jpegli encode and another stem format in `public/i/` (120 stems × *3* formats). But the `<picture>` type-fallback decode trap from the CLAUDE.md gotcha applies double here, because the format split is uneven. Safari has decoded it by default since Safari 17. Chrome dropped it in 2023, reintroduced a decoder (the Rust `jxl-rs`) behind `#enable-jxl-image-format` in Chrome 145, and turned it on in Chrome 155, stable since 2026-10-06. That flip was measured before it shipped: on 2026-09-17 the nightly canary fed the same 190-byte `data:image/jxl` to Chromium 153 and Chrome Beta 155, and only the Beta decoded it, with a PNG control decoding in both. Firefox is the half this card got wrong. Firefox 152 shipped the same `jxl-rs` decoder behind a Labs flag, Nightly has had it on by default since 153, and this card said Firefox 157 would flip it on for everyone on 2026-09-29. It didn't: the Firefox 157 release notes never mention JPEG XL, and MDN's compat data (8.1.5, 2026-10-08) still lists Firefox as Nightly only. So it ships in two stable engines, which clears this page's bar, and it is the first graduation candidate here to reach that point on a canary reading. The SOOC originals already live in R2 to re-encode from, and Firefox visitors would keep getting the AVIF source.

Nothing decodes live here, whatever your browser: this card is a static mock by design. To actually *view* a `.jxl` today you need Safari 17+, Chrome 155+, or Firefox Nightly. In Chrome 145–154 enable `chrome://flags#enable-jxl-image-format`; in Firefox stable (157 included) set the `image.jxl.enabled` pref, which Nightly has carried on by default since 153.

## HDR adaptive tone-mapping / AGTM · #hdr-agtm Chrome flag · not shipped

A gain-map HDR image carries two things: a normal SDR base picture, plus a per-pixel *gain map* that says how much extra luminance each spot should gain on a display with headroom. **Adaptive Global Tone Mapping** (AGTM, SMPTE ST 2094-50) lets the image author ship a tone-mapping curve so the same file renders correctly across a dim laptop, a bright phone, and a 1600-nit XDR panel. The highlights bloom on capable screens and fold back gracefully on SDR ones, instead of either clipping to flat white or being globally dimmed. This is the next rung above the P3 wide-gamut already used on the photo grid: P3 buys *wider* color, HDR buys *brighter* highlights.

**Can't be shown live here.** It needs a real gain-map asset, an HDR-capable display, and the experimental flag all at once, and a screenshot would just be SDR pixels lying about it. So the cells below are an honest *mockup* of the idea, drawn in SDR: left, an SDR ramp whose sun clips to paper-white; right, the same scene with the specular headroom the gain map would restore. The bar shows where the SDR ceiling sits and the hatched region is the extra headroom AGTM maps into.

SDR, highlights clip flat

base, clipped sun

HDR + gain map (mock), specular headroom

base × gain map

pipeline sketch (what add-photos.sh would gain)

```
# today: SOOC -> sips resize -> jpegtran rotate -> cjpegli q82 -> avifenc CQ30
#         single SDR-tonemapped AVIF + JPG, P3-tagged.

# with AGTM: keep the SDR base, ALSO author a gain map + 2094-50 curve.
#   X-T50 / Leica raw already holds the highlight data cjpegli throws away.
avifenc --gainmap hdr.avif base.jpg -o photo-hdr.avif   # gain-map AVIF
# OR ultra-HDR JPEG (libultrahdr) for the universal <img> fallback:
ultrahdr_app -m 0 -i base.jpg -g gainmap.jpg -o photo-uhdr.jpg

<picture>
  <source type="image/avif" srcset="/images/<stem>-hdr.avif?v=N">
  <img src="/images/<stem>.jpg?v=N">  <!-- SDR base = the fallback -->
</picture>
/* gate the glow so SDR panels never see a washed-out frame: */
@media (dynamic-range: high) { .photos img { /* let it bloom */ } }
```

On the site: a forward-look for the photo grid, with nothing to replace. The gain-map base layer *is* the existing SDR JPG/AVIF, so the fallback is free and the pipeline is purely additive: one extra encode step in `add-photos.sh`, no new client JS. It pairs with the `@media (color-gamut: p3)` upgrades already in the stylesheet via a sibling `@media (dynamic-range: high)` gate, so SDR visitors keep the exact frame they have now. Honest blockers: AGTM is a Chrome flag (`#hdr-agtm`), not shipped anywhere by default, and naive HDR photos in a feed are a known eye-searing UX problem, so it stays in the garage until it ships cross-engine *and* the curves can be tuned to glow without blinding.

Your browser / display reports no HDR headroom (`dynamic-range: high` is false), so even a real AGTM asset would render as its plain SDR base, which is exactly the graceful fallback that makes this safe to ship. The two cells above are a mockup either way.

## Declarative routing / route-matching Chrome flag · building blocks cross-browser

The pitch: declare your app's URL space once as a set of `URLPattern` route rules, then let CSS style whichever route is currently active: an "active nav tab" with *zero* JavaScript and no per-link `aria-current` bookkeeping. The routing flag itself is Chrome-only and still unrated, but it's assembled from two shipped, cross-engine primitives: `URLPattern` ([WebKit #61: support](https://github.com/WebKit/standards-positions/issues/61)) and the Navigation API ([WebKit #34: support](https://github.com/WebKit/standards-positions/issues/34)).

code sketch (today's primitives → the declarative dream)

```
// TODAY, cross-browser: URLPattern + Navigation API, ~a dozen lines of JS.
// This is what aadhar.sh would actually use right now.
const routes = [
  { id: "home",   p: new URLPattern({ pathname: "/" }) },
  { id: "garage", p: new URLPattern({ pathname: "/garage/:rest*" }) },
  { id: "photo",  p: new URLPattern({ pathname: "/images/full/:file" }) },
];
navigation.addEventListener("navigate", (e) => {
  const url = new URL(e.destination.url);
  const hit = routes.find(r => r.p.test(url));
  document.documentElement.dataset.route = hit ? hit.id : "404";
});
/* CSS picks up the active route via the data-attribute: */
[data-route="garage"] .nav-garage { font-weight: bold; }

<!-- TOMORROW (Chrome flag #route-matching, not cross-browser): -->
<!-- routes declared in markup; CSS styles the active one directly,  -->
<!-- no navigate listener, no data-attribute plumbing.              -->
<a href="/garage" class="nav-tab">garage</a>
/* :route-active { font-weight: bold; }  (sketch syntax) */
```

On the site: this is overkill *today*: aadhar.sh is deliberately one handwritten file per page, with routing that lives in the Worker's route table (`ROUTES` in `src/worker/index.ts`) rather than in the client. There's no SPA and no active-nav state to track. But it's the right thing to watch *if* the garage ever grows a shared client-side nav. The cross-browser half (`URLPattern` + Navigation API) could already replace any hand-rolled `location.pathname` string-matching with declared patterns. The CSS half (once it ships in more than one engine) would let the active garage tab style itself with no JS at all. Strictly a forward-look while it's Chrome-flag-only.

The chip above always reads "no," whatever your browser does: no engine ships the declarative CSS side yet, so there is nothing for a probe to find. The building blocks are a separate question: `URLPattern` (Chrome 95, Safari 26, Firefox 142) and the Navigation API (Chrome 102, Safari 26.2, Firefox 147) ship in all three engines, so the JS-today version above runs in any current browser. The mock tab-bar is static either way. To try the flag in Chrome: `chrome://flags#route-matching`.

## CSS Carousel ::scroll-marker · ::scroll-button Chrome 135

A scroll-snap strip that grows its own **prev/next buttons and dot markers in pure CSS**: `scroll-marker-group` on the scroller mints a `::scroll-marker` per item, `:target-current` lights the active dot, and `::scroll-button(left/right)` page it. No JS, no library, no `scroll` listener. Drag, click a dot, or use the buttons:

resto-mod · Porsche 911 · Tuthill · Evoluto 355 · HWA EVO

On the site: the photo grid could become a swipeable contact sheet with a Luna dot-strip underneath, and the browser draws the markers and arrows. Gated behind `@supports (scroll-marker-group: after)`; where it's missing the same markup is just a plain scroll-snap rail (still swipeable, minus the dots and arrows).

Your browser doesn't mint `::scroll-marker` yet, the strip above still scroll-snaps and swipes, it just has no dots or arrow buttons.

## Invoker Commands command / commandfor Chrome 135 · Safari 26.2 · FF 144

A button that drives another element declaratively: `commandfor` points at a target, `command` says what to do (`toggle-popover`, `show-modal`, `close`…), the sibling of `interestfor`, but for clicks instead of hover. Zero JavaScript wired below:

**No click handler ran.** This panel toggled purely from `command="toggle-popover" commandfor="ic-pop"`. The same attributes cover `<dialog>` via `show-modal`/`close`, and you can author custom commands (`command="--like"`) handled by a single `commandEvent` listener.

On the site: the caption-bar minimize/close and any future booking dialog in `cal/` become declarative: markup is the behavior, so there's less inline JS to ship and nothing to rehydrate.

Your browser doesn't parse `command`/`commandfor` yet, the button above is inert (no fallback handler is wired, on purpose).

## `@scope` scoped styles + donut Chrome 118 · Safari 17.4 · FF 146

Style a subtree without a naming convention, and stop the styles at an inner boundary: the "donut." `@scope (.card) to (.inner)` applies between the two. Proximity wins over specificity, so a closer scope beats a more-specific selector. Live:

Inside the scope root, above the hole: styled blue + bold.

Inside `.scope-inner`, the donut hole: the scoped rule stops here, so this stays default.

On the site: every page is one file of inline CSS, so a stray `.controls` or `.title-bar` rule leaks into demo windows (it just bit me wiring the garage prototypes). `@scope` fences a component's styles to its own block: no BEM, no prefixes.

Your browser doesn't support `@scope`, the inner line below the hole inherits nothing special; both paragraphs look the same.

## Custom Highlight API CSS.highlights · ::highlight() Chrome · Safari · Firefox

Paint arbitrary text ranges: no wrapper `<span>`s, no DOM mutation. Build `Range`s, hand them to a `Highlight`, register it in `CSS.highlights`, and style via `::highlight(name)`. The two phrases below are lit without touching the markup:

This site is modeled after the recent wave of resto-mod cars, where you take a beloved chassis and formula and modernize it while retaining its soul.

On the site: a code viewer could syntax-highlight without wrapping every token in a tag: cheaper DOM, and search-term highlighting that never disturbs the text it marks.

Your browser lacks the Custom Highlight API, the sentence above renders with no emphasis (the text is identical, just unpainted).

## `hidden=until-found` + scroll-to-text Chrome 102 · Safari 26 · FF 148

✓ shipped on the site

Content that is collapsed *but still findable*: `hidden="until-found"` hides a region, yet in-page find (`Ctrl/⌘-F`) and `#:~:text=` deep links will auto-expand it, fire `beforematch`, and scroll it into view. Try it: search this page for **aircooled**:

● Found me. This line was `hidden="until-found"`, collapsed in the layout, but the browser searched inside it anyway and revealed it. The aircooled keyword lived here the whole time.

On the site: long `/garage` write-ups could ship fully collapsed yet stay Ctrl-F-able and deep-linkable, honest with crawlers (the text is really in the DOM) while keeping the page short.

Your browser doesn't support `hidden=until-found`, the line above stays hidden and find-in-page can't reveal it.

## Grouped `<details>` name= Chrome 120 · Safari 17.2 · FF 130

✓ shipped on the site

A shared `name` turns a set of `<details>` into an exclusive accordion: open one and the browser closes the rest. Radio-button semantics for disclosures, with no `toggle` listener and no open state to track. They only have to share a document. Open the second one and watch the first fold:

964 → Singer

Keep the chassis, rebuild everything hung off it. The soul survives because the bones did.

Luna → this site

Same move, different chassis: 2003 chrome over OKLCH, native fonts, and a Worker.

Why exclusive?

Because a stack of independently open panels is a page that only ever grows taller.

On the site: the six "what it would replace" sketches on this page all carry `name="sketch"`, so opening one folds whichever was open before and a 3,600-line sweep stops growing under you as you dig. They sit in six different cards, which is fine: the group is every `<details>` sharing that name in the document, so siblinghood was never the requirement. Measured in Chrome at 1280×900, folding a panel that sits 10,000px above the reading position moves the page **0px**, because every `.demo` card here is `content-visibility: auto` and a skipped card keeps its remembered height while the panel inside it closes.

Your browser ignores `name` on `<details>`, so the panels above open independently and stay open. That is the graceful case: the disclosures still work, they just stop being exclusive.

## relative color syntax oklch(from …) Chrome 119 · Safari 16.4 · FF 128

✓ shipped on the site

Derive a color *from another color*: pull out its `l c h` channels and recombine them. One base, every tint, shade, and hue-rotation computed from it. All five chips below descend from the same leftmost Luna blue.

base

+L

−L

+130°h

+chroma

On the site: the hover/active/border shades of every bevel are currently hand-picked OKLCH values. This computes them from one token: change the base, the whole bevel set follows.

Your browser doesn't support relative color syntax, the derived chips fall back to the base blue.

## @property / registered custom props Chrome · Safari · Firefox

✓ shipped on the site

Registering a custom property with a `syntax` type makes it *animatable*. The gradient angle below is a `<angle>` custom prop being keyframed: impossible with a plain unregistered variable (the browser can't interpolate an untyped string).

--hz-ang animating 0° → 360°

On the site: lets the OKLCH palette tokens (and the `--ab` avatar hue over on serendipity) transition smoothly instead of snapping.

Your browser doesn't support `@property`, the gradient sits static (the angle can't animate).

## @starting-style + transition-behavior: allow-discrete Chrome 117 · Safari 18 · FF 129

✓ shipped on the site

Animate an element *as it appears* from `display: none`: no JS, no double-rAF hacks. `@starting-style` gives the entry its "before" values; `allow-discrete` lets `display` itself participate in the transition so the exit animates too. Toggle it:

I fade + slide in from `display:none`, and back out, purely declarative.

On the site: the cursor tooltips and any popover could ease in/out instead of hard-cutting, with zero animation JS.

Your browser doesn't support it, the box snaps in and out with no transition.

## light-dark() Chrome 123 · Safari 17.5 · FF 120

One declaration, both schemes: `color: light-dark(black, white)` resolves by the element's `color-scheme`. The two panels share the *exact same* background + color rules, only their `color-scheme` differs.

color-scheme: light

color-scheme: dark

On the site: a single XP "High Contrast"/dark variant becomes a one-token flip instead of a parallel stylesheet.

Your browser doesn't support `light-dark()`, both panels fall back to unstyled colors.

## declarative shadow DOM \<template shadowrootmode\> Chrome · Safari 16.4 · FF 123

A shadow root written in *markup*: the parser attaches it with no JavaScript. The pill below is a component whose styles are fully encapsulated (a page-level `span` rule can't touch them) and whose label is `<slot>`-projected from the light DOM.

rendered server-side, zero JS

On the site: the XP window chrome is copy-pasted across ~6 files. As a DSD `<xp-window>` it'd be defined once, encapsulated, and still render on first paint with JS off.

Your browser doesn't support declarative shadow DOM, you see the raw light-DOM text ("rendered server-side…") instead of the styled pill.

## \<model\> element Safari 27 · Apple-led

A native, declarative 3D viewer: `<model src="scene.usdz">` with built-in orbit/zoom and AR hand-off, no WebGL/three.js. Shipped in **Safari 27** on iOS, iPadOS, and macOS (a flagged prototype back in Safari 26); no Chromium or Gecko signal. No live model is loaded here, just the placeholder it would occupy:

\<model\>: native 3D / AR viewer (no asset loaded)

On the site: a SOOC lens or a camera body could spin in 3D in the photo tooltip, declaratively, the way `<video>` handles motion.

Your browser doesn't expose `HTMLModelElement`, the element is inert here (placeholder only).

## CloseWatcher API Chrome 126 · FF 149 · Safari TP 249

One abstraction for "close requests": `Esc` on desktop *and* the Android back gesture, so custom UI (sidebars, lightboxes) dismisses the same way native dialogs do. Open the panel, then press `Esc`:

This card read "Chrome 120 · Safari 26" until the 08-10 sweep and both numbers were wrong. Chrome shipped it in 126, Safari has never shipped it in a release at all (Technology Preview 249 is the first build that answers), and the engine the old line omitted entirely, **Firefox 149**, is the one that actually made this two-engine. A status line assembled once and never re-derived drifts in whichever direction nobody is looking.

Press `Esc` (or Android back) to close, one handler covers both.

On the site: the full-res photo lightbox could register a CloseWatcher so Esc and mobile back both dismiss it, with the platform managing the stack.

Your browser lacks `CloseWatcher`, the demo falls back to a manual `Esc` key listener.

## HTML switch control \<input type=checkbox switch\> Safari 17.4 · Apple-led

A native toggle switch: just a `switch` attribute on a checkbox. Same form semantics, switch affordance, no ARIA plumbing. Shipped in Safari (the "HTML switch control" flag in your screenshot); not yet in Chromium/Gecko, where it degrades to a normal checkbox:

On the site: a real switch for any future toggle (sound on/off, reduced motion) instead of a faux-CSS slider.

Your browser ignores the `switch` attribute, you see a standard checkbox above (the graceful fallback).

## if() & sibling-index() / CSS Values 5 sibling-index() cross · if() Chrome + Safari TP

Two bleeding-edge CSS primitives: `if()` for inline conditionals (`style()`/`media()` queries as values) and `sibling-index()`, which yields an element's position among its siblings, so a staggered animation needs *no* per-item variables. The two are drifting apart in status: `sibling-index()` has quietly finished (Chrome 138, Safari 26.2, Firefox 154 unflagged, after a spell behind `layout.css.tree-counting-functions.enabled` in 153), while `if()` is still Chrome plus a Technology Preview. Two features this far apart under one heading is how a status line goes stale with nobody editing it, so read the chip as the sibling functions and the `if()` half as the frontier. Each row below delays by `sibling-index() × 90ms` (reload to replay):

- row one
- row two
- row three
- row four
- row five

On the site: the photo grid or tracklist could cascade in on load with one rule, and `if()` could fold tiny media-query overrides inline.

Your browser doesn't support `sibling-index()`, the delay resolves to 0, so all rows appear at once (no stagger).

## scroll-state container queries @container scroll-state(stuck) Chrome 133 · others no

Style an element by its *scroll condition*, e.g. whether a `position: sticky` header is currently stuck. The header below turns Luna-blue the moment it pins to the top. Scroll the inner box:

sticky header, blue when stuck

scroll down…

…and the header above detects that it's pinned

via `@container scroll-state(stuck: top)`

no scroll-event JavaScript involved

keep going

nearly there

bottom.

On the site: the page title bar could shed its shadow / tighten when it sticks, declaratively, no scroll listener.

Your browser doesn't support scroll-state queries, the sticky header stays grey even when pinned.

## reading-flow / reading-order Chrome 137 · others no

Decouples *focus order* from source order for flex/grid. The three links below are reordered visually with `order` (DOM is 1-2-3, you see 2-3-1). With `reading-flow: flex-visual`, `Tab` follows the order you *see* (left to right) and ignores the DOM order. Tab through them:

DOM #1 · DOM #2 · DOM #3

On the site: lets CSS-driven responsive reordering stay keyboard-accessible without ARIA gymnastics: the long-standing tension between visual and focus order, resolved.

Your browser doesn't support `reading-flow`, Tab follows DOM order (1→2→3) regardless of the visual shuffle.

## Document Picture-in-Picture Chrome 116 · FF 151 desktop · Safari no

An *always-on-top window you fill with arbitrary HTML*, going beyond a video frame. Firefox 151 shipped it on desktop, which makes it two engines; Safari has no implementation. Click below to pop this box into a floating mini-window (close it to bring it home):

A real DOM node living in a Picture-in-Picture window. Peak resto-mod desktop-OS energy.

On the site: pop the **now-playing** tracklist into a floating window that survives tab-switching: a literal always-on-top Outlook-Express widget.

Your browser lacks `documentPictureInPicture`, the button is disabled.

## Temporal / the date/time rewrite ES2027 · Chrome 144 · FF 139 · no Safari

✓ shipped on the site

The long-awaited replacement for `Date`: immutable objects, first-class time zones and calendars, no more month-zero footguns. It reached **[TC39][tc39] stage 4** in March 2026, after the 2026 edition's feature set had closed, so it lands in ECMAScript 2027 and already sits in the living draft at [tc39.es/ecma262](https://tc39.es/ecma262/). Shipping is a separate question from standardising, and Safari is the reminder: Blink and Gecko both shipped it in spring 2026 and Safari 27 beta does *not* include it. That changed in the direction you want on 2026-07-30: **Technology Preview 249** added `Temporal`, unflagged, which puts it a Safari release away from cross-engine rather than a decision away. Live output from `Temporal.Now.zonedDateTimeISO()` in your browser:

checking for Temporal…

On the site: photo "uploaded" stamps in the hover card become zone-correct and DST-safe without a date library. This is the one card on the page that breaks the two-engine rule on purpose. It gets away with it because every use is a progressive enhancement over a working `Date` path: the taskbar clock, the Notepad F5 stamp and the photo tooltip all try `Temporal` and fall back. Safari visitors get the fallback and notice nothing.

The Worker is the exception, and it uses `Date` only. Cloudflare's runtime carries V8's `Temporal` switched off, with no compatibility flag to turn it on. The one time it was switched on in production ([workerd#6907](https://github.com/cloudflare/workerd/issues/6907)), `Temporal.Now` read the Unix epoch, so a server-side `typeof Temporal` guard would have taken the broken branch for every visitor at once. The /serendipity event times dropped their `Temporal` branch for that reason, and a nightly probe now reports the day workerd exposes one whose clock agrees with `Date.now()`.

Your browser doesn't expose `Temporal` yet, the box above says so.

---

Newer arrivals: the bleeding edge (shipped or not), plus the networking layer the rest of this page was missing.

## Built-in AI / Prompt + Summarizer API Chrome 148 · on-device

On-device AI as a plain web primitive. `window.LanguageModel` (the Prompt API, stable in Chrome 148) takes text, image, and audio input plus sampling params and runs Gemini Nano locally; `window.Summarizer` (stable Chrome 138) does key-points / tl;dr / teaser / headline. No server, no key, no GPU bill. Chrome-only for now: Mozilla and Apple both objected to shipping it, so there is no Safari or Firefox implementation.

On the site: it could caption the 146-photo grid or summarize a [/lens](https://aadhar.sh/lens) reader page entirely client-side, with no cf-garage Workers-AI round trip. Chrome-only, so it could only ever ride on top of the server path as an enhancement.

## Document patching / declarative partial updates Chrome 150 streaming · methods flag

Out-of-order HTML streaming and in-place DOM patching as a browser primitive instead of a framework feature. A `<template for>` element plus `<?marker>` / `<?start>` / `<?end>` processing instructions stream HTML into named targets with no script (Chrome 150, and the processing-instruction parsing underneath it has positive positions from both Mozilla and WebKit). That half is on by default: on 2026-10-08, Chromium 152 with no flags parsed `<?marker?>` as a processing-instruction node where older parsers make a bogus comment, and a `<template for>` landed its content at the marker. A separate explainer in the same repo revamps imperative insertion: positional methods that take HTML (`before` / `after` / `append` / `prepend` / `replaceWith`, retiring `insertAdjacentHTML`), streaming methods `streamHTML()` / `streamAppendHTML()` that return a `WritableStream`, and a `{ runScripts: true }` option that finally gives `createContextualFragment`'s behaviour a documented name. Chrome 153, behind `#enable-experimental-web-platform-features`.

**Correction (2026-07-30):** this card previously credited `setHTML()` to this proposal, alongside an `appendHTML()` that isn't a method in any of these explainers. `setHTML()` belongs to the Sanitizer API, a different WICG spec that has already shipped (Chrome 146, Firefox 148) and has its own card below. The confusion is understandable and worth keeping on the page: both specs write into an element from a string, they deliberately share an options bag, and the streaming work here extends the sanitizer's vocabulary rather than replacing it. They ship on completely different timelines, though, so treating them as one feature would have you waiting on a flag for something that is already two engines deep.

On the site: the homepage did exactly this partial render by hand until August 2026, an HTMLRewriter pass in the Worker injecting the now-playing tracks and the random photo grid into `index.html`. It serves a fixed document now and fetches both as fragments (`/rn/tracks.html`, `/photos/grid.html`) from two inline scripts, which is the scripted half of what `<template for>` would make a declared contract. It shares a WICG repo with the route-matching work above.

## WebMCP / page tools for agents Origin trial Ch149-156

A proposed open standard that lets a page expose structured tools (JavaScript functions and HTML forms) to browser-resident agents, so an in-browser agent calls a site's own actions instead of scraping the DOM. The origin trial began in Chrome 149 and chromestatus lists it running through 156. Early and Chrome-led, no other engine yet.

On the site: a machine-facing sibling to the [AadharshBot](https://aadhar.sh/bot) crawler and the `_index._agents` DNS-AID discovery record, exposing the [/coffee](https://aadhar.sh/coffee) booking or photo search as callable tools rather than pages to parse.

## WebNN / navigator.ml Origin trial Ch147-149

A graph API that runs ML inference on the client's NPU or GPU through the OS ML stack (DirectML, Core ML), the acceleration layer beneath ONNX Runtime Web and TensorFlow.js. The origin trial slipped twice and now runs Chrome 147 through 149 on all Blink platforms; Edge ships alongside Chromium and Firefox has stated intent, so it is cross-engine-bound rather than Chrome-locked. Distinct from WebGPU: WebNN targets the neural accelerator, where WebGPU drives the GPU compute path.

## focusgroup attribute Chrome 150 · Safari/FF no

Declarative arrow-key navigation for a set of controls: put `focusgroup="toolbar"` on a container and the browser gives its children a single tab stop, arrow-key movement between them, and last-focused memory, the roving-`tabindex` pattern every toolbar and menu hand-rolls in JS. The value is a behavior token naming the widget pattern (`toolbar`, `tablist`, `radiogroup`, `listbox`, `menu`, `menubar`); a bare `focusgroup` is rejected with a console error and buys you nothing. Shipped default-on in Chrome 150; no Safari or Firefox yet, where it degrades to each control being its own tab stop. Tab into the toolbar below, then use the arrow keys (in Chrome 150):

On the site: aimed straight at the XP shell in `nav.js`. The taskbar app-buttons, the Start menu, and the row of draggable desktop icons are exactly the composite widgets `focusgroup` is for, and their keyboard handling is hand-written today. It would hand the arrow-key + single-tab-stop wiring to the browser. Single-engine, so it stays a garage experiment until a second one ships it.

## background-clip : border-area Safari 18.2 · Chrome 150 · FF no

Paint a background into the border box *itself*, so a gradient border is one declaration instead of the old `border-image` slice dance or a stacked-background hack: give the element a gradient background, a transparent border, and `background-clip: border-area`, and the gradient fills only the border stroke. The version order is worth noticing, because this page usually reads left to right from Chrome: **Safari 18.2 shipped it first**, in December 2024, and Chrome 150 caught up eighteen months later. Firefox has neither. The frame below is a transparent border filled by a clipped gradient:

background-clip: border-area

On the site: the whole Luna look is 3D bevels and blue-gradient title bars, currently built from stacked `box-shadow`s and hand-mixed OKLCH border colours. A gradient-framed window or a two-tone bevel collapses to one background plus one clip, no `border-image`. The existing bevels already render fine, so this is a simplification rather than a new capability.

Your browser doesn't support `background-clip: border-area`; the gradient above fills the whole box instead of just the border.

## text-fit CSS property Chrome 150 · Safari/FF no

Scale a text node's font size to exactly fill the width of its box, in pure CSS, with no JS measuring the string and setting a size per element. Chrome 150, default-on; not in Safari or Firefox yet, where the text just keeps its declared size.

On the site: the XP title bars and folder labels truncate long names with an ellipsis today. `text-fit` could instead shrink an over-long window title to fit its bar, the way a real Luna caption never clipped. Minor and single-engine, so it parks here for now.

Your browser doesn't support `text-fit` yet; nothing here changes size.

## CSS random() Safari 26.2 · Chrome 156 beta · FF Nightly

A random value straight in CSS: `random(25%, 100%)` resolves to a number in that range at computed-value time, with an element-scoped keyword so each element can draw its own. Shipped in **Safari 26.2**, and only Nightly in Firefox. Chrome kept it behind the experimental flag from about 148 until chromestatus named milestone 156 (stable 2026-10-20), and the nightly browser canary measured the flip on 2026-09-30: this card's `CSS.supports("width: random(1px, 2px)")` probe reads false in Chromium 153 and true in Chrome Beta 156. So it is two engines on 2026-10-20, and a Safari-first CSS Values 5 feature until then.

On the site: pure decoration, filed next to WebGPU under "because we can." A hand-strewn desktop-icon jitter, per-photo grain seeds, or a scatter of Bliss clouds could pick their own offsets with no JS random loop. Nothing here needs it, which is exactly why it belongs on the horizon.

Your browser doesn't support `random()` yet, so a fixed fallback value is used.

## sizes="auto" on lazy images Chrome 126 · FF 150 · Safari 27

A responsive-images `sizes` attribute is normally a promise you make at authoring time: *this image will render about this wide*, written as media queries the browser trusts before layout exists. `sizes="auto"` deletes the promise. Because a `loading="lazy"` image is fetched after layout has already run, the browser knows the tile's real width and picks from `srcset` against the measured box. The attribute only works on lazy images, which is the constraint that makes it sound rather than a shortcut.

On the site: the photo grid picks its tier by *viewport* today, a `<source media="(max-width: 560px)">` that swaps the 600px square for the 400px mobile AVIF. That breakpoint is a hand-guess about how wide a tile ends up in a 3×3 contact sheet, and it is wrong in every window the guess did not anticipate: a resized desktop window, the Run palette's photo preview, a tablet in portrait. Width descriptors plus `sizes="auto"` would replace the guess with the measurement. Two tiers bounds the prize (there are only so many wrong answers available), so the win is accuracy, with few bytes at stake. It degrades perfectly: an engine that ignores `sizes` falls back to the `<source>` ladder already there, which is why this is the closest thing on this page to a graduation candidate.

The chip above always reads "no," whatever your browser does, because `img.sizes` is an unvalidated reflected string: assigning `"auto"` and reading it back returns `"auto"` everywhere. An honest test has to lay out a lazy image and wait on `currentSrc`, which is more machinery than a chip deserves. Chrome 126+ and Firefox 150+ have it, and Safari 27 ships it with one known bug: an SVG image that sets both `sizes="auto"` and `object-fit` renders distorted ([webkit.org/b/324661](https://webkit.org/b/324661)). The photo tiers are raster, so that bug misses this site.

## Gap decorations column-rule / row-rule Chrome 149 · Safari TP · FF no

`column-rule` has drawn a line between multicol columns since forever. Gap decorations extend it to grid and flex, and add `row-rule` for the horizontal gaps, so a separator between items stops requiring a border on each item or an empty pseudo-element per seam. The rules paint in the gap that already exists, so nothing about the layout moves. The grid below draws both:

IMG\_0421IMG\_0422IMG\_0423 IMG\_0424IMG\_0425IMG\_0426

On the site: the homepage photo grid is a literal contact sheet, and a contact sheet has seams. Those seams are per-tile frames today: nine borders drawing what is really four lines, with the outer edge of the sheet drawn by the same rule that draws the inside. `row-rule` and `column-rule` separate the two ideas: the frame belongs to the photo, the seam belongs to the sheet. Chrome-only for now, so the per-tile borders stay.

Your browser doesn't support gap decorations, so the grid above shows no seams.

## border-shape non-rectangular borders Chrome 147 · Safari/FF no

`corner-shape` (above) bends the corners of a rectangle. `border-shape` replaces the rectangle: it sets the geometry of the border box itself, so the border, the border image, the focus ring, and the shadow all follow an arbitrary `shape()`, `polygon()`, or `circle()`. That is the part `clip-path` never did. Clipping cuts the box and takes the border with it, leaving you to fake a stroke; `border-shape` keeps a real border and clips only the inside.

On the site: honestly, close to nothing, and that is worth saying out loud. Luna is rectilinear on purpose. A window that isn't a rectangle reads as a skin, and the whole point of the resto-mod is that the chrome stays period-correct. Where it could matter is the focus ring: the site's bevels are stacked `box-shadow`s, and an outline around a bevelled control currently traces the box rather than the bevel. Filed next to `corner-shape` as the other half of that spec, and parked.

Your browser doesn't support `border-shape` yet.

## :heading and :heading() Safari 27 · FF 142 flag · Chrome no

`:heading` matches any of `h1` through `h6`, and `:heading(2, 3)` matches the levels you name. Selectors 5. Safari 27 ships `:heading` (stable since 2026-09-14) but not yet `:heading()`; it sits behind `layout.css.heading-selector.enabled` in Firefox, absent in Chrome.

On the site: every garage page opens its stylesheet with some variant of `h1, h2, h3` repeated across a handful of rules, because Luna styles headings as a family and then varies the size. On a page whose CSS ships inline in the HTML, that repetition is real bytes on every request. It is a small win per rule and a boring one, which is exactly the kind that compounds across fifteen garage pages. Waiting on a second engine.

Your browser doesn't support `:heading`; the selector never matches.

## width: stretch sizing keyword Chrome 138 · Safari 27 · FF alias only

`width: 100%` resolves against the containing block and then adds your margins on top, which is why a full-width element with side margins overflows. `stretch` fills the available space and applies the result to the *margin* box, so the margins come out of the width instead of adding to it. It standardises two old prefixes that did the same job: `-webkit-fill-available` and `-moz-available`. Chromium shipped it first and Safari 27 unprefixes it. Firefox 146 took a different step: it accepts `-webkit-fill-available` as an alias, and its release notes say the unprefixed `stretch` isn't supported yet. This card read Firefox 146 as unprefixing until the 2026-10-08 sweep checked MDN's compat data.

On the site: the `calc(100% - 2 * var(--pad))` pattern shows up wherever a pane has to fill its window and keep its inset, and every one of those is a subtraction the browser could do. Two engines take the keyword, and all three take `-webkit-fill-available`, so a two-line declaration (`width: -webkit-fill-available; width: stretch;`) would cover every engine today, and the first line can go the day Firefox unprefixes.

Your browser doesn't support the unprefixed `stretch` keyword.

## ::-webkit-scrollbar the compat surrender Chromium · Safari · FF 155 site list

Firefox 153 began implementing a limited subset of `::-webkit-scrollbar`, a vendor prefix from 2009 that never became a standard. It did so because enough of the web styles scrollbars through it that ignoring it was the bigger compat problem. Firefox 155 then pulled most of it back. The subset is deliberately narrow: a rule with a non-zero `width`/`height` disables overlay scrollbars, and `display: none` behaves like `scrollbar-width: none`. It still broke sites, because they styled the other `-webkit-scrollbar-*` pseudo-elements Firefox never took. So Firefox 155 limits the subset to a short list of domains in the `layout.css.fake-webkit-scrollbar.enabled-domains` pref, and everywhere else the selector is unknown again. The standard `scrollbar-width` and `scrollbar-color` remain the actual answer.

On the site: `nav.js` paints a custom XP scrollbar through the `::-webkit-scrollbar` family, since a Luna scrollbar needs a raised 3D thumb and stepper arrows that `scrollbar-color` cannot express. Firefox visitors get the standards-track properties and a plain scrollbar. This change never fixed that, and the card stays as a warning about its own chip. In Firefox 153 and 154, `@supports` answered yes for a selector whose styling mostly did not land, so feature-detecting it told you the selector parsed and nothing about whether the thumb rendered. Firefox 155 answers no again outside its domain list, so the chip reads no in current Firefox.

Your browser doesn't recognise the `::-webkit-scrollbar` selector at all.

## Reference Target cross-root ARIA Chrome 152 · Safari 26 flag · FF 144 flag

The oldest real hole in shadow DOM: an ID reference cannot cross the shadow boundary. A `<label for="x">` outside a component can never reach the `<input id="x">` the component hides, and the same breaks `aria-labelledby`, `aria-controls`, `popovertarget`, and `commandfor`. The usual escape is to give up encapsulation. Reference Target lets the component nominate one inner element as the thing outside references resolve to, declaratively via `shadowrootreferencetarget` on the template or imperatively via `ShadowRoot.referenceTarget`, so the reference lands and the internals stay private.

Unusually for this page, **all three engines have it behind a flag**: Chromium since 133 and default-on in 152, WebKit since Safari 26, Gecko since Firefox 144. That is a better sign than the "Safari/FF no" this card carried before the 08-10 sweep, because a flag is an implementation somebody wrote and an absence is not. It is still not shippable, since a flag reaches nobody.

On the site: this is the missing piece under the declarative-shadow-DOM card above. An `<xp-window>` that encapsulates its own title bar is appealing right up to the moment something outside it needs to label or control that window, and today the answer is to stop encapsulating. It is Chrome-only and the accessibility of the current markup is already fine, so this stays a prerequisite I am watching rather than a change I want.

No `referenceTarget` on `ShadowRoot` here.

## Scoped custom element registries Chrome 146 · Safari 26 · FF Nightly

`customElements.define()` writes into one global map keyed by tag name, so two copies of a component library on one page fight over `<my-button>` and the loser throws. `new
        CustomElementRegistry()` makes a registry you hand to `attachShadow({ registry })`, so definitions apply to that subtree alone and identical tag names coexist. In two engines already, which is further along than most of this page; Firefox has not shipped it. (One sharp edge: a scoped registry rejects the `extends` option, so customized built-ins stay global.)

On the site: nothing today, because the site defines no custom elements at all, and that is the interesting part. It is on this page as a precondition. The reason the XP chrome is server-rendered markup plus one shared `nav.js` instead of a component library is partly that web components have historically been a worse deal than the thing they replace. Scoped registries and Reference Target are the two repairs that would change that arithmetic, and they are both landing at once. Worth knowing before the next rewrite tempts anyone.

No `CustomElementRegistry` constructor here.

## WebAssembly JSPI JS Promise Integration Chrome 137 · FF 153 · Safari 27

Wasm code is synchronous and the web is not, so any Wasm module that wants to `fetch` has had to be rewritten inside out (Emscripten's Asyncify) at real cost in size and speed. JSPI lets a Wasm stack *suspend* on a JavaScript Promise and resume when it settles: `WebAssembly.Suspending` wraps the async import, `WebAssembly.promising` wraps the export, and the C code in between keeps its blocking call. Chrome 137 shipped it, Firefox 153 turned it on by default on 2026-07-21, and Safari 27 shipped it on 2026-09-14, so it went from one engine to all three in eight weeks. It is an Interop 2026 focus area.

On the site: no Wasm ships here, so this changes nothing directly. It matters one level down. The photo pipeline's encoders (`zenc` wrapping zenjpeg, `avifenc`) are native binaries that run on my machine at build time; the reason they are not a browser-side tool is the cost of making a synchronous codec behave in an async page. JSPI is what would make a "drop a HIF here and watch it encode" page cheap to build. Filed against the encoding study rather than the homepage.

No `WebAssembly.Suspending` here, so JSPI is unavailable.

## Text module imports with { type: "text" } TC39 stage 3 · Chrome 155 · FF 153

Import attributes gave JSON modules a safe syntax (`with { type: "json" }`); the text proposal adds the other obvious one. `import banner from "./banner.txt" with { type: "text" }` hands you the file as a string, with the type asserted at load time so a server that answers with something else fails the import instead of executing it. That assertion is the entire security argument for import attributes, and it is why this is a language feature rather than a bundler trick. Firefox 153 shipped it unflagged and Chrome 155 followed, so it is two engines now. (This card said Firefox kept it behind a pref until the 2026-10-08 sweep read MDN's compat data and the 153 release notes.)

On the site: `/writing` is plain `.txt` files plus a `posts.json` registry, and the worker reads each one and seeds it into a Notepad `<textarea>`. Today that is a fetch and a string. As a text module it would be an import the bundler can see, which matters because the worker is bundled at deploy. A post would become a build-time dependency instead of a runtime read, and a missing file would fail the deploy rather than the request. Tempting. Also a real coupling change, and with two engines unflagged, the coupling is the only thing it still waits on.

## Element.moveBefore() state-preserving move Chrome 133 · FF 144 · Safari positive

Reparenting a node with `appendChild` tears its state down: iframes reload, running animations restart, focus and form state reset. `moveBefore()` moves it *without* the teardown. The iframe below counts its own uptime: move it between the trays and watch the counter survive a `moveBefore()` but reset on the `appendChild` fallback.

tray A

tray B

On the site: the desktop shell reparents `.window` nodes when you drag them or pop content into a popover. `moveBefore()` does that without reloading their contents or interrupting a running View Transition.

No `moveBefore()` here, so the button falls back to `appendChild`: the counter resets to 0 each move.

## Cross-document transitions @view-transition Chrome 126 · Safari 18.2 · FF building

The multi-page sibling of View Transitions. One rule (`@view-transition { navigation: auto }` on both pages) animates a *real* navigation between two separate documents, no SPA, no JS. (Can't demo it inside one page; it fires on the navigation itself. [/garage/vt-check](https://aadhar.sh/garage/vt-check) is a bare two-page pair that does, if you want to see whether your browser runs one at all.)

On the site: **tried, then removed**. It shipped here for most of 2026 and every route opted in inline, with a hand-tuned XP minimize/restore instead of a cross-fade. It came out on 2026-07-30 for a reason worth stating plainly, because it is the same reason twice. The Speculation-Rules prerender two cards down already loads the next page on hover-intent, so by click time the document is rendered and activation is free. The transition then spent ~200ms animating over a navigation that had nothing left to hide. Two features that each look like a win can want opposite things, and the prerender won: it makes the page arrive sooner, while the transition could only make it *appear* later. Firefox never implemented the opt-in and always cut instantly. It turned out to be right.

## shape() responsive clip-path Chrome 135 · Safari 18.4 · Firefox 148

A `path()` you can write with percentages and any length unit, so the clip scales with the box instead of being frozen at one pixel size. The pennant below is a single `clip-path: shape(…)`: resize the window and it stays sharp.

shape()

On the site: the hand-written polygon `clip-path`s (the title-bar icon, notched frames, the cone mark) could become resolution-independent shapes. Baseline since Feb 2026.

No `shape()` here, so the pennant falls back to its plain rectangle.

## text-wrap : pretty Chrome 117 · Safari 26 · FF no

The browser looks at the whole paragraph and avoids ugly last lines: orphans, a lonely single word, rivers. Left is default wrapping; right is `text-wrap: pretty`. Same text, same width:

default

The browser breaks lines greedily and can leave one lonely word stranded on the final line.

`text-wrap: pretty`

The browser breaks lines greedily and can leave one lonely word stranded on the final line.

On the site: the `/writing` Notepad text and the photo captions get tidier last lines for free. Pure progressive enhancement, since older engines just wrap as before.

## @function custom CSS functions Chrome 139 · Safari TP 249 · FF no

Reusable, parameterised CSS logic, like Sass mixins but native and live: `@function --bevel(--l) { result: oklch(from var(--c) calc(l - var(--l)) c h) }`. Chrome 139 shipped it, and Technology Preview 249 put it in WebKit, so the second engine is a Safari release away rather than "in progress". Gecko has neither.

On the site: the repeated `oklch(from … calc(l − 0.19) …)` bevel math (every raised/sunken edge derives its shade this way) would collapse into one named function. A "watch it" card until a second engine lands.

No `@function` here yet; Chrome leads.

---

Networking: the transport + delivery layer, the part of the platform a static site usually never touches.

## WebTransport HTTP/3 + QUIC Baseline 2026 · Chrome · Firefox · Safari 26.4

WebSocket's successor: bidirectional streams *and* unreliable datagrams over HTTP/3, multiplexed on QUIC so one slow stream doesn't head-of-line-block the rest. Went Baseline in March 2026 once Safari 26.4 shipped it. (No live demo, because it needs an HTTP/3 server endpoint to dial.)

On the site: overkill for a mostly-static page today, but it's the right transport the day anything here goes real-time, whether a live `/serendipity` presence feed or streaming a long crawl in `/around` instead of buffering it.

No `WebTransport` constructor in this browser.

## SW static routing InstallEvent.addRoutes() Chrome 123 · Safari 27 · Gecko positive

Declare routing rules at install time so the browser can *skip booting the service worker entirely* for paths that always go the same place (straight to network or straight to cache), shaving the SW startup cost off those requests. Safari 27 ships it, which moves this from Chrome-only to two engines.

On the site: **it used to be, and the feature outlived the thing it optimised.** `sw.js` did declare static routes sending `/`, the crawlers, and the JSON APIs straight to network without booting the worker. Then the service worker itself retired in build v136 (July 2026). Immutable content-addressed assets, bfcache, and speculation-rules prerender already made repeat visits instant, so the SW's remaining job was insurance priced at a cache-version ritual per deploy plus a second poisonable cache. `sw.js` is now a fifteen-line unregister stub, and the routes went with it. Worth keeping the card, because the lesson is the useful part: static routing made a service worker cheaper to keep, but the better answer turned out to be not having one. A feature that optimises a layer is not an argument for the layer.

No `addRoutes()` on `InstallEvent` here.

## 103 Early Hints server-side Chrome · Firefox · Safari (preconnect)

A response the server sends *before* the real one (status `103`), carrying `Link: rel=preload/preconnect` hints so the browser starts fetching critical assets during server think-time. It plays entirely at the header level, so there's no chip above. Cloudflare can emit it from the edge.

On the site: the homepage's one shared asset is `/nav.js`; a 103 could `preload` it (and preconnect the photo R2 origin) while the worker is still assembling the page. Firefox + Chrome honor preload; Safari does preconnect only.

## Speculation Rules prefetch / prerender Chrome · Safari 26.2 (off) · FF no

✓ shipped on the site

A `<script type="speculationrules">` JSON block tells the browser which links to prefetch or fully *prerender* ahead of the click, so the next page is already painted by the time you navigate: the trick behind McMaster-Carr feeling instant. **Chrome 151** adds a `form_submission` field to prerender rules: an ordinary form submission could never activate a prerender, because it carries extra navigation state the speculation was not prepared for, so a GET search form had to be declared as such up front. GET only. A third action, `prerender_until_script`, is still origin-trial-only at **152** (the trial opened at Chrome 144 in January 2026 and was extended through 154). It prefetches the document and starts rendering, so the preload scanner pulls stylesheets and images, then parks at the first `<script>` and executes nothing until the click lands. That buys most of a prerender's head start while leaving analytics, ads, and anything else that mutates client state unrun on a page nobody has asked for yet. The cost is that such a page can't read `document.prerendering`, never gets a `prerenderingchange` event, and can't be promoted to a full prerender later.

On the site: live already. The homepage and garage pages prerender internal links (eagerness "moderate", minus the live `/around` + `/whoareyou` crawlers), which is most of why navigation here feels instant. That one word sets two limits worth naming out loud. Chrome caps "moderate" at 2 prefetches and 2 prerenders and evicts FIFO, so at any moment this site is speculating on the last two links you lingered over and no more. And "hover for 200ms" means nothing on a touchscreen, so phones get viewport heuristics instead: since August 2025 Chrome speculates 500ms after you stop scrolling, for anchors sitting within 30% of the vertical distance from your last tap that are at least half the size of the largest anchor on screen. Same rule block, two quite different machines underneath, and the phone one is the half I can least predict from a desk. Cloudflare sells the managed version as [Speed Brain](https://developers.cloudflare.com/speed/optimization/content/speed-brain/), which injects a hosted rule set from the edge via a `Speculation-Rules` response header. This site sends the same header naming a hand-written rule set instead (an inline block on every page until September 2026), to keep eagerness where I want it and to skip the two live crawlers that an opinionated edge config wouldn't know to leave alone.

No Speculation Rules here; links just load on click.

## Compression dictionaries dcb / dcz Chrome 130 · Firefox 153 · transport-level

Use a file you've already downloaded (or a shipped static dictionary) as the Brotli/Zstandard *dictionary* for later fetches, so an updated asset arrives as a tiny **delta** against the old copy instead of the whole thing. The transport left draft and is now [RFC 9842](https://www.rfc-editor.org/rfc/rfc9842.html): it negotiates entirely through `Accept-Encoding: dcb/dcz` + `Use-As-Dictionary` / `Available-Dictionary` headers (Chrome/Edge 130+), so there's no chip above. **Firefox 153** (July 2026) enabled it by rollout, so this is now two engines rather than one and a plan.

On the site: marginal, honestly. The homepage is ~22 KB brotli and `nav.js` is small, so the delta savings stay tiny in absolute terms. It'd earn its keep if those grew. Cloudflare shipped its edge half as [Shared Dictionaries](https://blog.cloudflare.com/shared-dictionaries/), still **passthrough open beta** on all plans (Apr 30 2026): my origin owns the dictionary lifecycle and the edge just forwards the headers and `dcb/dcz` bytes untouched, varying the cache per delta. Cloudflare pitched it as compression "for the agentic web," which is the angle that actually fits here: the machine surfaces ([/llms-full.txt](https://aadhar.sh/llms-full.txt), the JSON endpoints a returning crawler refetches) are where a delta would earn more than a human loading the homepage once.

## stale-while-revalidate async at the edge RFC 5861 · Cloudflare async 2026

A `Cache-Control` directive ([RFC 5861](https://www.rfc-editor.org/info/rfc5861/)): keep serving the *expired* copy for a grace window while a fresh one is fetched in the background, so nobody waits on the revalidation. Cloudflare made its handling **fully asynchronous** in Feb 2026: the first hit after expiry used to block on the origin (a `REVALIDATED`/`EXPIRED` status); now it returns the stale copy instantly with an `UPDATING` status and revalidates behind the scenes. It rides on the [new Pingora-based cache proxy](https://developers.cloudflare.com/changelog/post/2026-05-04-pingora-powers-cache/) (Rust, shipped May 2026), which also cut per-request overhead and tightened RFC compliance. Header-level, so there's no chip above.

On the site: I already hand-roll exactly this pattern in KV. The now-playing tracks carry a `tracks:<id>:fresh` freshness sentinel next to their value (the photo manifest used to be the second example, until it left KV for the worker bundle entirely). In `cal/`, `fetchBusySWR` keeps a last-good calendar snapshot so a slow ICS feed never gates the booking page. Those tracks are the cleanest candidate to hand back to the platform: set `stale-while-revalidate` on that cached JSON and the edge serves the last-good playlist instantly while it re-scrapes Spotify in the background, retiring the sentinel bookkeeping. The homepage stays `no-store` (it opts out by design), so this lives entirely in the JSON layer underneath it.

---

And two from the far edge: one perfectly on-brand, one just because.

## Local Font Access queryLocalFonts() Chrome desktop · Safari & Firefox oppose

Enumerate the fonts actually installed on the machine (with permission). The thematically perfect feature for a site that ships *zero* font bytes and leans entirely on local Verdana / Tahoma / Trebuchet. Apple and Mozilla oppose it on fingerprinting grounds, so it may stay Chrome-only forever: a fitting horizon entry.

(not run yet)

On the site: it could verify a visitor actually has the XP stacks installed before relying on them, then gracefully swap when they don't. Mostly it's here because a no-web-fonts site asking the OS what fonts exist is too on-the-nose to skip.

No `queryLocalFonts()` here; the button will say so.

## WebGPU navigator.gpu Chrome · Safari 26 · Firefox 141

Modern GPU compute + render in the browser: the WebGL successor, built on Metal / Vulkan / D3D12. Crossed into all three engines in 2026 (Safari 26, Firefox 141). Wildly overpowered for an XP homage, which is exactly why it belongs on the horizon.

On the site: realistically nothing, since the whole aesthetic is CSS gradients and 2003 restraint. But a GPU-rendered Bliss wallpaper or a procedural dithered backdrop is the kind of thing you'd reach for it. Filed under "because we can."

No `navigator.gpu` in this browser.

## Post-quantum WebCrypto ML-KEM · ML-DSA · X-Wing Chrome 155 · FF Nightly · Safari no

The Web Cryptography API is getting the NIST post-quantum primitives natively: **ML-KEM** (key encapsulation, formerly Kyber), **ML-DSA** (signatures, formerly Dilithium), the **X-Wing** hybrid key exchange, and ChaCha20-Poly1305 alongside them. Chrome ran an origin trial in 151–154 and ships it in Chrome 155, stable since 2026-10-06; Firefox Nightly has the new encapsulate and decapsulate methods. The two halves carry very different urgency, and conflating them is the usual mistake. Key exchange is urgent because of harvest-now-decrypt-later: traffic recorded today is decryptable the day a cryptographically relevant quantum computer exists, so hybrid key agreement had to arrive early. Signatures are not, because a signature only has to resist forgery during the window it is trusted in, and nobody can retroactively forge one.

On the site: AadharshBot signs its outbound requests with **Ed25519** per RFC 9421, and publishes the public half as a JWKS at [/.well-known/http-message-signatures-directory](https://aadhar.sh/.well-known/http-message-signatures-directory). ML-DSA is the thing that eventually replaces that key. Eventually is doing real work in that sentence: a bot signature covers one request and expires in seconds, which puts it squarely in the "not urgent" column above, and the Web Bot Auth draft pins Ed25519 today, so switching unilaterally would produce a signature no verifier recognises. The half that already affects visitors is the key exchange, and it is not mine to ship. TLS terminates at Cloudflare's edge, which has been negotiating hybrid post-quantum key agreement for a while now. [/whoareyou](https://aadhar.sh/whoareyou) prints the `kex` field from `/cdn-cgi/trace`, so you can read what your own connection actually negotiated rather than take my word for it.

## navigator.cpuPerformance device tier Chrome 152 · Safari/FF no

A read-only integer from 1 (weak) to 4 (fast), with 0 meaning the browser could not classify the device. It exists because the alternatives are worse: sites currently infer device class from `hardwareConcurrency`, from the user-agent string, or from timing a synthetic benchmark during page load, and all three are some mix of inaccurate and user-hostile. Coarse buckets also leak far less fingerprinting surface than a core count. Chrome 152; no signal from the other engines, and a permission-free hardware read is exactly the kind of thing they tend to push back on. Your device reports:

On the site: the site already makes this judgement, just blindly. The tooltip layer toggles `will-change: transform` on and off around the hover lifecycle because leaving a compositor layer allocated measurably hurts Low Power mode and variable-refresh displays, and the `@property` gradient on this very page pauses itself when off-screen because it repaints every frame. Those are both guesses at a device tier, applied to every visitor. A real tier would let the expensive effects stay off on a 1 and stop apologising on a 4. Single-engine, and `prefers-reduced-motion` plus the existing IntersectionObserver already cover the honest majority of the win, so this parks.

Your browser doesn't expose `navigator.cpuPerformance`.

## Long Animation Frames LoAF Chrome 123 · Safari/FF no

A `PerformanceObserver` entry for every rendering update that slips past 50ms. The older Long Tasks API could tell you the main thread was busy and almost nothing about why; LoAF reports the *frame*, so work split across several small tasks that together miss a frame still shows up. Any script that ran for over 5ms inside it comes back with `sourceURL`, `sourceFunctionName`, `sourceCharPosition`, and an `invokerType` naming what called it, alongside `forcedStyleAndLayoutDuration` for layout thrash and `pauseDuration` for the synchronous stalls. Chrome 123 since February 2024, nothing from the other two engines, so it is not Baseline and the field data it gathers is Chromium's view of your users rather than all of them. The sharp edge is in the spec rather than the docs: recording bails out early [if the document is hidden](https://w3c.github.io/long-animation-frames/). A background tab reports nothing, so a zero here means either that nothing was slow or that nobody was watching, and the API will not tell you which. I walked into exactly that measuring this site through an automated browser whose tab reported `hidden`. Load, opening the Run palette, and typing into it all came back with zero long frames, and so did a deliberate 180ms blocking frame I added to check the instrument. A clean result and a dead probe look identical.

On the site: the gap is real and it stays open for now. Nothing here collects field data at all, and `nav.js` carries about fifty `addEventListener` calls driving window drag, resize, the taskbar, and a Run palette that filters 158 photos, none of it instrumented. LoAF only earns its keep against real visitors on real hardware, which is precisely the thing a site with no RUM does not have; measuring it once from my own desk would mostly prove that an M-series Mac is fast. There is a second wrinkle if I ever wire it up. Attribution keys on `sourceURL`, and the shell ships as `/a/nav.<hash8>.js` precisely so it can be cached for a year, so the URL changes on every deploy and nothing aggregates across releases without normalising the hash back out first. The cache win and the attribution story pull against each other, which is worth knowing before rather than after.

No `long-animation-frame` entry type here, so this browser has no view of which frames ran long.

## Container Timing containertiming attribute Chrome flag · FF 156 flag · WICG

Largest Contentful Paint tells you when the single biggest element painted, which is a page-level number that a component cannot ask about. Container Timing lets you mark a subtree with `containertiming` and get a `PerformanceObserver` entry when *that section* finishes its initial paint, the way `elementtiming` works for one element. Behind the experimental flag in Chrome and a pref in Firefox since 156; a WICG proposal rather than a shipped feature.

On the site: the homepage's LCP is a photo tile, so LCP is already roughly "when did the grid start." What it cannot answer is when the grid *finished*, and that is the number that matters here, because the worker server-renders twelve tiles into the HTML and the interesting failure is nine of them arriving fast while three straggle. That distinction is invisible to LCP and would be one `containertiming` attribute on `<section class="photos">`. The same applies to the now-playing list, which is the other server-rendered chunk. Genuinely useful, genuinely single-engine, and measurement rather than user-facing behaviour, so it costs nothing to wait.

No `container` entry type in this browser's `PerformanceObserver`.

## Sweep note: features that already cleared the bar

The cards below came out of diffing this page against all 377 features chromestatus tags `shipping_year:2026`, on 2026-07-30. The uncomfortable half of the result is the first six: they already ship in two or three engines, which is this page's own graduation rule, and they were missing here anyway. A page that sorts the frontier by engine count can still miss things that quietly finished, because nothing about a feature landing everywhere generates the kind of news that gets a feature written up. Versions below come from MDN's browser-compat-data bundle v8.0.8 (2026-07-24). The Chrome feature author fills in chromestatus's Safari and Firefox columns, and they drifted on three of these.

## Sanitizer API Element.setHTML() Chrome 146 · FF 148 · Safari positive

A safe-by-default HTML parser built into the platform. `el.setHTML(str)` parses untrusted markup and drops anything that can execute, so scripts, event-handler attributes, and `javascript:` URLs never reach the DOM. The unsafe twin `setHTMLUnsafe()` has been around since Chrome 124 and does no filtering; the sanitizing one is the new part. Type a hostile string and watch both paths:

`setHTMLUnsafe()` · `setHTML()`

On the site: the honest answer is that this site has almost no user-supplied HTML to sanitize, which is a design choice rather than luck. The one place it would matter is `/lens`, which fetches arbitrary URLs and renders what comes back; that path builds text nodes and escapes on the way out rather than parsing hostile markup, so it sidesteps the question instead of answering it. Where this genuinely changes the calculus is DOMPurify: a ~20KB dependency whose whole job the platform now does, which is the kind of subtraction this site optimises for. Two engines, so by the rule at the top it has graduated.

No `Element.setHTML` here, so the right-hand pane falls back to escaping the string.

## Media element pseudo-classes :playing · :paused · :buffering Safari 15.4 · FF 150 · Chrome 158 planned

`:playing`, `:paused`, `:seeking`, `:buffering`, `:stalled`, `:muted`, and `:volume-locked` match `<audio>` and `<video>` by their actual state, so a custom transport control can style itself from CSS instead of from a pile of `timeupdate` and `waiting` listeners keeping a class in sync. It was one of only two features in chromestatus's 2026 set that names **Interop 2026** as a focus area, and it is the older feature on this page by some margin: Safari has had it since March 2022. This card said Chrome 152; chromestatus now names Chrome 158, and Chromium 152 does not parse `:playing` (measured 2026-10-08).

On the site: nothing here plays media, so this is a card about the sweep rather than about a plan. It earns its place because it is the clearest example of the failure mode the note above describes. It shipped in WebKit four years ago, shipped in Gecko this year, is an Interop priority, and still never crossed my desk, because features that arrive quietly in the engine I do not develop against are exactly the ones a Chrome-flag-driven reading list cannot surface.

This browser doesn't parse `:playing`, so the selector never matches.

## Three TC39 methods that finished sumPrecise · getOrInsert · Iterator.concat Chrome · Safari · Firefox

Already finished: all three shipped in all three engines while this page wasn't looking. `Math.sumPrecise` (Chrome 147, Safari 26.2, Firefox 137) sums an iterable without accumulating float error. `Map.prototype.getOrInsert` and its `getOrInsertComputed` sibling (Chrome 145, Safari 26.2, Firefox 144) collapse the has/get/set dance into one call. `Iterator.concat` (Chrome 146, Safari 26.4, Firefox 147) chains iterators lazily, without materialising arrays.

```
press the button
```

On the site: `getOrInsertComputed` is the one with real call sites, grouping rows by key in the Worker's `/around`, census and inbox code where a has/get/set dance used to sit. `Math.sumPrecise` is the one still waiting, and what it waits on is a server: the Worker runs it fine, but the test suite imports the same modules under Node 26.9, which lacks it. All three are ECMAScript 2026 and three engines deep, so they are past this page's bar and into "just use them."

One or more of the three is missing here; the output says which.

## image-rendering: crisp-edges Safari 7 · FF 65 · Chrome 148

Scale an image without smoothing it. The wrinkle is that Chromium supported the *neighbouring* keyword `pixelated` since Chrome 41 and rejected `crisp-edges` until 148, while WebKit and Gecko took `crisp-edges` years earlier. Spec-wise the two differ (`pixelated` asks for nearest-neighbour specifically, `crisp-edges` asks only to preserve contrast and edges); in every shipping implementation they do the same thing. Same 8×8 source bitmap, scaled 12×:

default (smoothed) · `crisp-edges`

On the site: this is the most on-brand feature in the whole sweep and the page had no card for it. Every icon here is authored as inline SVG precisely to dodge the scaling question, but the XP source material is bitmap art, and the honest 2003 reproduction of a 16×16 toolbar icon at 2× is a hard-edged upscale rather than a redrawn vector. The reason to keep writing `pixelated` rather than switching is Chrome 41 versus Chrome 148: the older keyword has the wider floor, and the two are indistinguishable in practice.

No `crisp-edges` support, so both swatches render smoothed.

## text-decoration-skip-ink: all Safari 15.4 · FF 75 · Chrome 148

The default `auto` breaks an underline around descenders that would collide with it. `all` makes that skipping unconditional, which matters for scripts where the glyphs regularly dip into the underline and the browser's heuristic under-skips. The property itself is old news (Chrome 64); the `all` keyword is what Chrome 148 added, and Safari and Firefox have had it since 15.4 and 75.

`auto`

judgment paging quietly

`all`

judgment paging quietly

These two most likely look identical to you, which is the honest result of a working demo: on Latin text at this size `auto` already skips every descender that crosses the rule, so `all` has nothing left to do. The keyword earns its keep where the heuristic under-skips, which is scripts with deep or frequent descenders rather than Verdana running English.

On the site: underlines here are Verdana and Tahoma running Latin text, where `auto` already does the right thing, so this is a no-change. It is on the page because it is a clean example of the pattern the sweep kept turning up: a property that shipped everywhere years ago, with one keyword that Chromium only just caught up on, so the feature reads as "new" from a Chrome-shaped vantage point and as ancient from anywhere else.

This browser doesn't accept the `all` keyword, so both lines use `auto`.

## focus({ focusVisible }) Safari 18.4 · FF 104 · Chrome 145

Programmatic focus has always had a guessing problem. The browser decides whether to draw the focus ring from a heuristic about how focus arrived, and script that moves focus deliberately has no way to say what it wants. `el.focus({ focusVisible: true })` forces the ring on and `false` forces it off, so a script that moves focus for keyboard users can show the indicator without also flashing it at someone who clicked.

On the site: `nav.js` moves focus in two places where this is the exact missing control. Opening the Run palette with ⌘K focuses the input, which is unambiguously a keyboard action and should show a ring; clicking Start opens the same palette from a mouse, where the ring is noise. Today both paths get whatever the heuristic decides. Same story for the Notepad popovers in `notepad.js`. Three engines, so this one is usable now.

This browser ignores the `focusVisible` option, so both buttons behave identically.

## margin-trim Safari 16.4 · Chrome 155 · FF no

Drops the margin on a container's first and last children, so a box with uniform padding stops getting extra space at its top and bottom edges from the paragraphs inside it. The usual workaround is a `:first-child` / `:last-child` pair per component, which every stylesheet on the web has written at least once. Safari-only since 16.4, which made it the inverse of most cards here: WebKit shipped it three years ago and nobody followed, until Chrome 155 reached stable on 2026-10-06. The nightly browser canary measured the flip three weeks earlier, on 2026-09-17: this card's own `CSS.supports("margin-trim", "block")` probe reads false in Chromium 153 and Edge 153 and true in Chrome Beta 155 and Edge Dev 155. Firefox has a positive position and no milestone.

default

first

last

`margin-trim: block`

first

last

On the site: the `.content` workspace inside every XP window carries exactly the hand-written first-child/last-child pair this replaces, and it is still hand-written as of this sweep. The fallback here is harsher than on most of this page's cards: without support the margins collide and the window padding looks wrong, rather than merely unstyled. With Chrome 155 stable, the pair can go behind `@supports not (margin-trim: block)` and the property takes over in two engines, with Firefox still on the hand-written path.

No `margin-trim` here (expected outside Safari and Chrome 155+), so both boxes look the same.

## Speculation Rules, the measurement half getSpeculations() · on-prefetch-activation Chrome only · several still proposals

The Speculation Rules card above covers firing a prefetch. Four separate 2026 entries cover finding out whether it worked, which is the half that has never existed. `performance.getSpeculations()` exposes preloads, prefetches, and prerenders as measurable entries. The `on-prefetch-activation` response header names a telemetry endpoint the browser pings when a prefetched document is actually used for a navigation, so the signal reaches the *server* rather than a script in a page that may never run. A `form_submission` field lets a prerender be activated by a real form POST or GET. And an external `<script type=speculationrules src>` lets one rule set be shared across documents instead of inlined into each.

On the site: this is the most useful thing in the sweep, because the speculation rules here are hand-tuned and *unmeasured*. The rules in `index.html` were written from reasoning about which links a visitor is likely to take next. The only evidence that they help is that the pages feel fast when I click them, which is not evidence. `on-prefetch-activation` is the one that fits this architecture best: it is a response header, so the Worker can set it and receive the callback, and the accounting lands next to the existing Analytics Engine counters rather than in a client script. Precision is the number worth knowing, since a prefetch that never activates is bandwidth spent on someone else's behalf.

## The QUERY method RFC 10008 Published Jun 2026 · every engine sends it · no cache stores it

HTTP has had a hole in it since the beginning: a request that is safe and idempotent like `GET`, carrying a body like `POST`. `QUERY` fills it. The query lives in the request content, the method is declared safe and idempotent, so an intermediary may retry it after a timeout and a cache may store the response. Servers picked it up quickly, and a resource advertises which body formats it takes through a new `Accept-Query` response header. The headline everywhere is "a GET with a body", but the interesting part of this one is the layer underneath, because a cache that stores a `QUERY` response has to key on the request *body*, and almost nothing does yet.

Measured against this origin on 2026-08-12, since the support tables describe engines and the thing that decides whether you can use it is the CDN in front of you:

| request | answer | what it tells you |
| --- | --- | --- |
| QUERY /whoareyou.json | 200 | Cloudflare passes the method to the Worker |
| FLOOP /whoareyou.json | 501 | the control: there is a method allowlist, and QUERY is on it |
| SEARCH /whoareyou.json | 200 | so is the old WebDAV verb, so this is a known-methods list rather than QUERY support |
| QUERY /nav.js | 405 | the static-asset layer is GET and HEAD only |
| QUERY /mcp | 405 | refused on purpose; MCP is a POST transport |

In the browser half, `new Request(url, {method:'QUERY', body})` throws in no engine and the request goes out on the wire, because `fetch` forbids a body only on `GET` and `HEAD`, and blocklists only `CONNECT`, `TRACE` and `TRACK`. What browsers are still missing is the storing half: the HTTP cache holds `GET` responses, and cross-origin `QUERY` needs a preflight because it is not CORS-safelisted.

On the site: `/mcp` is the one surface where this would ever land, and the classification work is already done. Every tool in `lib/mcp-tools.js` carries `readOnlyHint` and `idempotentHint`, and the two that write to D1 (`representation_capture`, `representation_compare`) declare `readOnlyHint: false` beside the INSERT. That pair of annotations is exactly the eligibility test for `QUERY`. Sharper tell: the server already emits `ttlMs` and `cacheScope` on list and read results, which exist because a POST transport cannot be cached, so the protocol rebuilt caching one layer up. `QUERY` would hand that job back to HTTP, and the change would live once in `lib/mcp-protocol.js` where both servers on this origin would inherit it.

The reason it stays a forward-look rather than a migration: everything cacheable here is keyed by URL. Workers Cache, `caches.default`, the edge, and both compression-dictionary tiers all key on the URL, and the per-page dictionary offers match by `URLPattern`. A `QUERY` response has no URL to key on, and the varying part sits in a body where no `Vary` header reaches. This page's own routes lean the other way on purpose too: the terminal tools (`/finger`, `/dict`, `/cache`) keep their state in query parameters so a frame can be forked, bookmarked and replayed, and `/lens` takes `?url=`. Adopting `QUERY` would trade that away. The signal worth waiting for is the MCP specification adopting it, because no client will send one before then.

The chip below reads no for every visitor, and it is hardcoded. A round-trip probe (construct a `QUERY` `Request`, read the method back) returns true in every engine, since `fetch` never validates a method name past its blocklist. That is the same trap as `sizes=auto` further up. The capability that actually matters, whether any cache along the path stores the response, is not observable from a page at all.

## @supports at-rule() + named-feature() Chrome 148 · FF 157 · Safari TP

`@supports` has always been able to test a property/value pair and a selector, but has never been able to test an *at-rule*. There is no way to ask "does this engine understand `@container`" in CSS. `at-rule(@container)` closes that, and the companion `named-feature()` covers a small set of capabilities that no syntactic probe can reach at all.

Live probe of this browser:

```
checking…
```

On the site: this is the fix for a limitation this page documents against itself. The capability chips below every card are `CSS.supports()` probes, and six of them (`jpeg-xl`, `route-matching`, `sizes-auto`, `import-text`, `pq-webcrypto`, `http-query`) are hardcoded to return `false` under a comment explaining that no synchronous probe exists. The reasoning: a chip reading "no" is more honest than a probe that pretends to know. `at-rule()` would convert some of that class of question into a real answer. Firefox 157 shipped `at-rule()` on 2026-09-29, so it now clears two engines; `named-feature()` is still Chrome 150 only, and the honest-false convention stays until a probe can actually replace each one.

No `at-rule()` in this browser's `@supports`, which the probe above reports directly.

## window-drag Chrome 152 · FF positive · Safari no

Marks a region of an installed desktop web app as titlebar: dragging there moves the OS window instead of doing anything in the page. `window-drag: move` opts a region in, `window-drag: none` carves an exception back out, which is how you keep the close button clickable inside a draggable strip. Chrome 152 took it to beta, so the "preview channels only" note this card carried is spent; `-webkit-app-region` has done roughly this for Electron for years, and this is the standards-track version.

On the site: `nav.js` implements window dragging by hand. Every `.window` and `.np-window` is draggable by its title bar via pointer listeners, with a hard top boundary, and the same file carves out the `_ □ ×` controls so a close click doesn't start a drag. That carve-out is precisely what `window-drag: none` is for. The catch is that this property only does anything for an *installed* app moving a real OS window, and the XP windows here are divs moving inside a page, so it would replace none of that JavaScript. It is on the page because the naming collision is a trap worth writing down: the feature that sounds exactly like what this site does is solving a different problem.

## paintTime / presentationTime on paint, LCP + LoAF entries Chrome 145 · FF 140 · Safari 26.2 LCP only

Paint timing entries have reported one timestamp, and it has been ambiguous which of two very different moments it meant. `paintTime` is when the browser finished the rendering phase and began painting; `presentationTime` is when the pixels actually reached the screen. On a slow compositor or a variable-refresh-rate display those diverge by a frame or more. Exposing both, across paint timing, element timing, LCP, and Long Animation Frames, turns a number that needed a footnote into two numbers that don't.

```
press the button
```

On the site: this lands in the middle of a problem already written up in this repo. The Workers Traces work found that a span cannot measure CPU, because the runtime's clock advances across I/O and never during synchronous execution, so `home.grid.render` honestly reports 0 ms after doing real work. That is the server-side version of exactly this ambiguity, and `presentationTime` is the client-side fix for it: the homepage's LCP is a photo tile, and "when did the tile paint" and "when did the visitor see the tile" have been the same field. Two engines, so the RUM beacon could start reading it, with one catch for exactly this use. Safari 26.2 added `paintTime` to its new LCP entries, but on LCP entries both Safari and Firefox expose `presentationTime` and always return `null`, so for the homepage's LCP tile only Chrome reports when the visitor saw it.

No `paintTime` on this browser's paint entries; the output shows what is there instead.

## Soft navigations + interaction-contentful-paint Chrome 151 · Safari/FF no

Two new `PerformanceEntry` types, shipped in Chrome 151 after an origin trial in 139–144. Chromium 152 lists `soft-navigation` in `PerformanceObserver.supportedEntryTypes` on a page with no trial token (measured 2026-10-08), even though chromestatus still labels the feature "Origin trial". `soft-navigation` marks a JS-driven navigation that changed the URL and re-rendered without a document load, which every Core Web Vital currently cannot see: LCP is measured once, at the real navigation, so every route change after it is invisible. `interaction-contentful-paint` reports the largest paint caused by a specific interaction, which is the LCP idea rebuilt around an interaction instead of a page load.

On the site: mostly a card about why this site doesn't need it, and that is the interesting part. Every route here is a real document served by the worker, and the View Transitions are cross-document, so LCP already fires per navigation and soft navigations do not exist in this architecture. The exception is `/writing`, where the folder notes deliberately open as popovers that composite over the index *without* touching the address bar. That was the honest call, since one URL cannot name three open windows, and it means the cost of opening a note is unmeasured by anything. `interaction-contentful-paint` is the entry that would price it, and it prices interactions rather than URLs, which is the right shape for that decision.

## contentType in Resource Timing + Declarative PerformanceObserver Chrome 148 · FF 129 · Safari positive

`PerformanceResourceTiming.contentType` adds the server's content type to each resource entry, so a script can group its own loading by kind without re-deriving it from file extensions. Alongside it, the Declarative Performance Observer proposal moves collection into a response header, so metrics still arrive when the renderer is killed by the OS or the request fails, which is the population that RUM currently drops silently.

```
press the button
```

On the site: `contentType` is the smaller half and still the one with a use here. The serving story on this site is unusually type-dependent, since `/a/*` assets are q11 brotli, the document tiers ship dcz deltas against two different dictionaries, and the photo tiers are AVIF with a JPG fallback chosen by the browser before any script runs. Which encoding a visitor actually received is currently inferred. The declarative half is the more interesting idea and the further out. A response header is a shape this worker is already built around, and its whole pitch is capturing the sessions that die before a beacon fires: exactly the sample RUM is missing and cannot know it is missing.

No `contentType` on resource entries here; the grouping falls back to counting entries.

## Sweep note: one preview build, six cards

The 08-10 pass turned up a rot direction the three already written up here don't cover, and it is entirely self-inflicted. Every status line on this page was assembled reading Chrome first, so a feature that landed in WebKit or Gecko *years earlier* gets recorded as "Chrome N, others no" and then sits there being wrong. Two cards above were exactly that: background-clip: border-area shipped in Safari 18.2 in December 2024 and Chrome only caught up in 150, and the system-color card below has been three-engine since Firefox 103. Both errors were mine: I read each status from the wrong end.

The other half of the pass was one build. **Safari Technology Preview 249** moved six cards on this page at once: `Temporal`, `CloseWatcher`, `popover=hint`, `if()`, `@function`, and the `attr()` function below. Technology Preview notes aren't release notes, so a sweep that reads "New in Safari N" posts structurally cannot see any of it, and a card can sit on "Safari no" for a year after WebKit started answering. Versions here are MDN browser-compat-data v8.0.10 (2026-08-06), cross-checked against the STP 249 release notes.

## AccentColor · AccentColorText Firefox 103 · Safari 16.5 · Chrome 150

Two CSS system colors that resolve to *the accent the visitor picked in their OS*, plus a foreground guaranteed to be legible on it. No media query, no JS, no `-webkit-` anything: write `background: AccentColor` and the browser reads the platform. Gecko has had it since Firefox 103 in 2022 and WebKit since Safari 16.5; Chrome 150 is the straggler, which is why it took a Chrome-first read to miss it for this long. The left box is the visitor's accent, the right is this site's hardcoded Luna blue:

AccentColor

oklch(50% 0.225 263)

On the site: tempting and **declined**, and this is the clearest case on the page of the doctrine beating the feature. Luna's blue is the subject. A title bar that turned graphite because the visitor set macOS to graphite would be a more "native" site and a worse reproduction of Windows XP. Every bevel here derives its shade from that one hue by `oklch(from …)` arithmetic, so the accent would have to win everywhere or nowhere. Where it does belong is the *chrome around* the reproduction: focus rings and selection highlights already ought to match the OS, and those are the two places this is worth wiring.

Your browser doesn't resolve `AccentColor`, so the left box falls back to its declared color.

## caret-shape block · underscore · bar Chrome 144 · Safari/FF no

The text cursor has been a thin blinking bar in every browser since there were browsers, and `caret-shape` is one property that makes it a filled block or an underscore instead. It is a small thing that is unusually load-bearing for one specific look, because on a terminal the block caret is how you know it is a terminal. Click into the field below (Chrome 144+ to see it):

On the site: this was aimed at the PowerShell console that lived at `/terminal`, where the caret was a bar pretending to be a console cursor. That page retired on 2026-09-16 (it rendered per request to show bytes that never changed), and nothing else here has a field that reads as a terminal, so the demo above is the only home this has now. It still degrades to exactly today's behaviour everywhere else, which makes it a rare frontier feature with a zero-cost fallback by construction.

No `caret-shape` here, so the field above blinks a normal bar.

## ::search-text styling find-in-page Chrome 144 · Safari/FF no

Ctrl+F highlights have been the one piece of text on a page an author could never touch. `::search-text` styles every match and `::search-text:current` styles the one you are on, so find-in-page finally answers to the page's palette instead of the browser's yellow. Press `Ctrl`+`F` (`⌘`+`F` on a Mac) and search for **aircooled**:

Only the first half of that is actually demoed here, and the reason is a build failure worth keeping. **Lightning CSS 1.33 cannot parse `::search-text:current`**: it accepts the bare pseudo-element without so much as a warning, then hard-errors on a pseudo-class after a pseudo-element it doesn't know (`InvalidPseudoClassAfterPseudoElement`), which fails this site's deploy outright. The carousel selectors two dozen cards up hit the softer version of the same gap, where the minifier warns and emits the selector verbatim. So the current-match rule is printed below rather than applied, and the lesson generalises past this selector: shipping a new one needs the toolchain to have heard of it as well as the browser.

```
.prose::search-text          { background: gold; }
.prose::search-text:current  { background: orangered; color: #fff; }
/* the second line is what the minifier here refuses to parse */
```

A resto-mod keeps the aircooled character and modernizes everything under it. The aircooled flat-six is the part nobody wants replaced, so the aircooled engine stays and the brakes, bushings and electronics don't.

On the site: the natural partner to the `hidden=until-found` card above, which is already about find-in-page reaching content the layout collapsed. Together they are the two halves of treating `Ctrl`+`F` as a first-class way to navigate a page rather than a browser feature happening next to one. Parked for the usual reason, one engine, and now for a second one: the build would reject half of it.

No `::search-text` here, so find-in-page keeps the browser's own highlight.

## revert-rule the fourth global keyword Chrome 148 · Safari 27 · FF 150

CSS has had `initial`, `inherit`, `unset` and `revert` for years, and none of them can say "undo *this rule* and keep whatever the cascade had before it". `revert-rule` does exactly that, which matters most inside `@media` and `@supports` blocks where the alternative is restating the value you were trying not to restate. Three engines already, and the third one is quiet enough that this page had never written it down.

Both lines below declare `color: crimson`. The second reverts it inside a media query, and gets the cascade's earlier value back rather than the initial one.

```
.note { color: oklch(45% 0.03 250); }        /* the cascade's answer */
.note.warn { color: crimson; }

@media (prefers-contrast: more) {
  .note.warn { color: revert-rule; }         /* back to oklch(45% ...) */
  /* NOT `revert`, which would jump to the UA sheet's black */
}
```

On the site: the Luna design tokens are a cascade three layers deep (token, component, per-page override), and every place a media query needs to back one layer off currently re-declares the value underneath it by hand. That is the exact duplication this keyword deletes. Cross-engine, cheap, and the only reason it isn't already in `luna.css` is that nobody knew it had finished.

No `revert-rule` here; the keyword is dropped as invalid and the declaration above it wins.

## attr() with type() outside content Chrome 133 · FF 155 · Safari TP 249

`attr()` has existed since CSS 2 and has been useless since CSS 2, because it only worked in `content` and only produced a string. The rewrite lets it read any attribute, cast it with `type()`, and take a fallback: `height: attr(data-h type(<length>), 8px)`. That is the whole "write the number into a CSS custom property with JS" pattern, deleted. The bars below carry no inline styles and no custom properties, only `data-h`:

**data-h only**

On the site: the photo tooltip's histogram bars and the [dyno](https://aadhar.sh/garage/dyno) charts both do the JS dance today, writing a computed height into a style attribute per element. This turns that into markup the server can emit and the browser can lay out with no script at all, which is worth more here than it sounds: those bars are the one thing on the page that currently needs JS to have a shape. Firefox 155 shipped it, so it is two engines now, with Safari's in Technology Preview.

No typed `attr()` here, so the bars fall back to their 8px default height.

## text-decoration-inset Firefox 146 · Safari TP 249 · Chrome no

Shortens an underline in from both ends, so a rule can stop before the descenders and the padding rather than running the full width of the text run. Worth a card mostly for the shape of its adoption: **Gecko shipped it first**, WebKit has it in preview, and Chrome hasn't started. Several of the 90 cards here are WebKit-first, since Apple ships plenty nobody else has yet. This is the only one that is *Gecko*-first, which is a useful corrective to reading the frontier as a Chrome release-notes feed with a Safari column bolted on.

An inset underline stops short of the word's edges (Firefox 146+).

On the site: marginal. The one place it would help is the Notepad and folder-index link styling, where an underline currently butts against adjacent punctuation. Filed as a taste improvement rather than anything structural, and it can ship the day Chrome moves, since an unsupported `text-decoration-inset` is simply dropped and the underline stays full-width.

No `text-decoration-inset` here, so the underline above runs edge to edge.

## animation-trigger scroll-triggered vs. scroll-driven Chrome 146, `none` only · Safari/FF no

The sibling of the scroll-driven animations card above, and the distinction is the point. A scroll-*driven* animation is scrubbed by scroll position: you scroll back, it plays back. A scroll-*triggered* one *starts* when the element comes into view and then runs on its own clock, which is what almost every "fade in on scroll" script has actually wanted since about 2013. `animation-trigger: view() alternate` is meant to be that, declaratively.

There is no demo here, and finding out why is the reason this card is worth more than its feature. Both chromestatus and MDN report `animation-trigger` as Chrome 146, so it reads shipped. Probing the actual value grammar in Chrome 148 says otherwise: the property parses `none` and rejects **every value that would trigger anything**, with the `animation-trigger-behavior` and `animation-trigger-timeline` longhands absent too. Live from your browser rather than from a table:

probing animation-trigger…

So "shipped in Chrome 146" means a property name that accepts the one keyword meaning *do nothing*. That is a real trap for a page like this: a compat table records the property, a status line copies the table, and the card claims a feature that cannot be used. The chip beside this card probes `view()` specifically, so it stays red until the part worth having arrives.

On the site: nothing, deliberately. Scroll-triggered entrance animations are the single most over-applied effect on the modern web and an XP desktop has no vocabulary for them. Worth a card because the naming is a genuine trap: `animation-timeline` and `animation-trigger` look like variations on one feature and are opposites, and picking the wrong one gives you an animation that runs backwards when the visitor scrolls up. The second reason is the probe above: this is the clearest instance on the page of the difference between a feature existing and a feature being usable.

No `animation-trigger` here, so the box above sits still.

## scrollIntoView() returns a promise Chrome 150 · Safari/FF no

`scrollIntoView({ behavior: "smooth" })` has never told you when it finished. The entire ecosystem workaround is a `scrollend` listener with a timeout beside it, because a smooth scroll that was already at its target fires nothing at all and the listener hangs. Chrome 150 makes the method return a promise that settles when the scroll lands, or rejects if something interrupts it.

Click to compare the promise against the scrollend dance.

On the site: `nav.js`'s Run palette scrolls a matched result into view and then wants to focus it, and today that is a `setTimeout` with a number picked by feel. A promise turns a guess into an await. One engine, so the guess stays, but this is the kind of card that graduates quietly the moment a second engine lands, because the fallback and the feature are the same two lines.

No promise returned here, so the demo reports what it got back instead.

## XSLT being removed from the platform removal in progress · all three engines agree

Every card on this page is something arriving. This one is the other direction, and a page about a 2003 aesthetic ought to carry at least one. Browsers have shipped an [XSLT][xslt] 1.0 processor since 2000; it is a large, unsandboxed, rarely-exercised C++ surface that has produced a steady drip of security bugs, and Chromium, WebKit and Gecko have all now agreed to take it out. Chromium has the deprecation staged across 2026.

What makes it worth a card rather than a footnote is the migration story, which is unusually honest for a removal: the replacement is a [WebAssembly build of libxslt](https://github.com/mfreed7/xslt_polyfill), so the code moves out of the browser's trusted computing base and into the page's own sandbox. The platform loses an attack surface, sites that need it keep working, and it costs a download.

On the site: no XSLT anywhere, so the removal costs nothing here directly, and the reason to write it down is a rule this page keeps re-learning. Old and widely deployed is not the same as safe to depend on. The retired service worker, the `THUMB_VERSION` query-string cache-buster and the View Transitions that shipped for most of 2026 were each removed for a version of the same argument, and each one felt permanent while it was in.

No `XSLTProcessor` in this browser, so the removal has already reached you.

## CSS alpha() Chrome 151 · Safari 27 · FF 155

Set a colour's opacity without restating the colour: `alpha(from <any colour> / 40%)` takes any colour value, a `var()` token included, and returns it at that alpha. The `from` is the same keyword relative colour syntax uses, and inside the slash the old alpha is in scope as `alpha`, so `calc(alpha * 0.5)` halves whatever it was. Until now the only way to do that to a variable was relative colour syntax, `oklch(from <token> l c h / 40%)`, which works and reads like a ritual. Per cssdb 8.11 it landed in all three engines within a month of each other (Chrome 151, Safari 27, Firefox 155), which is the cleanest cross-engine arrival on this page.

On the site: `luna.css` carries 15 colours with an alpha channel, every one spelled out as a full `oklch()` literal beside the token it is really derived from (the window drop shadow is `oklch(46% 0.16 <luna hue> / 0.35)`, a Luna blue at 35%, with the hue pulled from the knob and the rest restated by hand). Each of those becomes `alpha(from <token> / 35%)`, so a hue knob turn reaches the shadows too. Nothing to demo, because the whole feature is the sentence above.

Your browser doesn't parse `alpha()` yet; the relative-colour spelling above is the fallback and does the same thing.

## color-mix() with any number of colours FF 150 · Safari 27 · Chrome no

`color-mix()` shipped everywhere taking exactly two colours. CSS Color 5 now lets it take one, or three, or more: `color-mix(in oklch, red 20%, blue, green)` weights them and blends the set, and the one-colour form is a colour-space conversion in disguise (`color-mix(in oklch, var(--x))` hands back `--x` as OKLCH). Firefox 150 shipped it first and Safari 27 followed, so the chip reads yes in two engines and no in Chrome.

On the site: nothing needs it, which is the honest answer for most of the colour additions here. The Bliss-wallpaper gradient and the title-bar highlight are hand-picked stops, and a three-colour mix could derive a midpoint from both ends plus the hover tint. Filed with `random()` as decoration that waits for a reason.

Your browser's `color-mix()` takes two colours and no more; the three-colour form parses as invalid and the declaration is dropped.

## font-width / the property font-stretch became Safari 18.4 · FF 155 · Chrome no

CSS Fonts 4 renamed `font-stretch` to `font-width`, because it never stretched anything: it selects a face from a family's width axis (`condensed`, `expanded`, or a percentage on a variable font). Safari 18.4 ships the new name with the old one kept as an alias, and Firefox 155 followed. Chrome still answers only to `font-stretch`, so this is the rare card where the OLD spelling is the safe one.

On the site: nothing, and the card is here to say so. Tahoma, Verdana and Trebuchet MS carry no width axis, and the site ships zero font bytes, so there is no face for either property to select. It stays on the page so the rename shows up in the canary diff the day Chrome follows, rather than in a stylesheet that quietly stopped matching.

Your browser doesn't know `font-width`; write `font-stretch` and both spellings resolve to the same face wherever a family has one.

## Sweep note: the Interop 2027 proposals, 61 new cards

The Interop 2027 index below started as a list: 39 of its 100 proposals had a card on this page and 61 didn't. The cards from here to the index are those 61, written 2026-10-08 against MDN browser-compat-data 8.1.5 and each proposal's own issue. A card with no chip has no honest synchronous probe, and says why.

## CSS inherit() / inherit(--x) Chrome flag · Safari/FF no

`inherit(--x)` reads the parent element's computed value of a custom property, where `var(--x)` reads the element's own. That lets a child build on what it inherited: `--depth: calc(inherit(--depth) + 1)`. Written with `var()`, the same line is a cycle and the property goes invalid. Chromium filed its intent to prototype on 2025-10-14. It sits behind Chrome's Experimental Web Platform features flag (Chromium's runtime flag reads `experimental`), for custom properties only, and chromestatus names no milestone. Firefox has an open bug (1703986); Safari shows no public work. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1453) points at the WPT `inherit-function` tests. One trap, measured on 2026-10-08: without the flag, Chrome 155 and Canary 157 mis-parse `calc(inherit(--x) + 1)` in a stylesheet and swallow the next rule whole. This demo keeps its `inherit()` in style attributes for that reason. Each box below adds 1 to its parent's depth and indents by it:

depth 1

depth 2

depth 3

On the site: nothing waits on it. No component here derives a value from its parent's copy of the same variable, the one thing `inherit()` adds. The closest shape is the `/access` skill tree, and its nesting indents through plain `ul` margins (15px a level) that already add up without any function.

No `inherit()` here (expected outside Chrome with the experimental flag), so the three boxes sit flush left.

## CSS ident() / ident("item-" 1) Chromium test flag · Safari/FF no

Builds an identifier out of strings, integers and other idents, so `view-transition-name: ident("card-" sibling-index())` names every card in a list with one rule. Today each name is typed by hand or stamped in by a server or a script. It's an arbitrary substitution function from CSS Values 5, the family `var()` belongs to. Chromium filed its intent to prototype on 2025-05-13. On Chromium's main branch the runtime flag still reads `status: "test"`, one rung below the Experimental Web Platform features switch. No Chrome channel exposes it to a visitor. chromestatus names no milestone. Firefox has an open bug (1942078); Safari shows no public work. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1452) cites the WPT `ident-function` tests.

On the site: `/garage/tooltips` names its three anchor slots by hand, `.slot:nth-child(1) { --anchor-name: --slot-a; }` and then `-b` and `-c`. `ident("--slot-" sibling-index())` folds those into one rule, and a fourth slot would name itself. The headline use doesn't apply here: the site turned view transitions off on 2026-07-30, because prerendered navigations made the animation pure latency.

No `ident()` in this browser, which is every shipping browser today.

## baseline-source / auto | first | last Chrome 111 · FF 115 · Safari no

[WebKit: no position](https://github.com/WebKit/standards-positions/issues/118) Picks which baseline a multi-line inline-level box uses to line up with the text around it. Without it the choice falls out of `display`: an `inline-block` aligns on its last line, an `inline-flex` on its first. Switching one to get the other changes the layout too. Chrome 111 shipped it in 2023 and Firefox 115 followed, with Mozilla's position at positive. Safari has none, and WebKit's position issue is open without a stance. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1450) asks for the third engine. Both boxes are `inline-block`; the second sets `baseline-source: first`:

Default two  
lines sits on its last line.

First two  
lines sits on its first line.

On the site: no use today. Every inline-block here holds one line: the `.xp-button` controls and the `.cite` links on the LWE pages. On a single line the first and last baselines are the same baseline, so the property changes nothing. It earns a place the day a two-line button sits inside a sentence.

No `baseline-source` here (expected in Safari), so both boxes align on their last line.

## CSS filter() / filter(\<image\>, \<filters\>) Safari 9.1 · Chrome/FF no

Runs the `filter` property's functions (`blur()`, `grayscale()`, `drop-shadow()`) over an image value and returns a new image. `background-image: filter(url(hero.jpg), blur(4px))` blurs the background and leaves the text on top sharp, where the `filter` property hits the whole element, children included. The usual workaround is a pseudo-element that carries the image and the filter. Safari has shipped it unprefixed since 9.1 in 2016. Chrome (crbug 41208242) and Firefox (bug 1191043) have open bugs and nothing BCD records. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1449) cites 42 developer-signal votes. One gradient, then the same gradient through `filter(…, grayscale(1))`:

image

`filter(image, grayscale(1))`

On the site: the Bliss desktop behind every page (`#axp-desktop` in luna.css) is an inline SVG background, written out twice, once in sRGB and once for P3 screens. A dimmed or greyed variant (XP greys the desktop behind Turn Off Computer) would be a third copy today, and `filter()` derives it from the same image. The site draws no such dialog, so this stays a note.

No `filter()` here (expected outside Safari), so the second strip keeps its color.

## @counter-style / custom list markers Chrome 91 · FF 33 · Safari 17

Defines a named counter style (its symbols, counting system, prefix, suffix, padding, range and fallback) that `list-style` and `counter()` then use by name. Before it, a custom marker meant `::before`, `counter()` and hand-built strings. Firefox 33, Chrome 91 and Safari 17 all ship it, and web-features marks it Baseline Widely Available since 2026-03-18. BCD records two gaps: Safari skips the `speak-as` descriptor, and Chrome and Firefox reject images in `symbols`. Patrick Brosset asked on the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1446) which part still breaks for developers, given three engines and passing tests; as of 2026-10-08 the thread has no answer. This list uses `system: extends decimal` with a prefix and `pad: 2 "0"`:

1. Read the proposal
2. Check the compat data
3. Write the card

On the site: the ordered lists keep plain decimal markers (the mule hall of fame on `/garage`, the numbered findings on `/garage/compression`). The ranked bar charts on `/garage/pqc` and `/garage/octane` switch markers off with `list-style: none`. Nothing needs a custom counter, so the built-ins stay.

No `CSSCounterStyleRule` here, so the list falls back to plain decimal.

## CSS symbols() / inline counter styles Chrome 155 · FF 35 · Safari no

An anonymous counter style written in place: `list-style-type: symbols(symbolic "*" "†" "‡")` gives footnote markers without a named `@counter-style` rule. It takes a system (`cyclic`, `numeric`, `alphabetic`, `symbolic` or `fixed`) and a list of strings. Firefox has had it since 35 in 2015, alone for 11 years. Chrome 155 shipped it on 2026-10-06: BCD 8.1.5 records 155 on the `list-style-type` key, chromestatus names milestone 155, and Chromium's runtime flag reads `stable`. Safari has none. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1432) cites the WPT `symbols-function` tests. Four footnotes; the fourth doubles to `**`:

1. Chrome 155, per chromestatus
2. BCD 8.1.5
3. Chromium's runtime flag
4. Firefox 35

On the site: no footnote lists to number. Asides here are `a.term` glossary links that pop a tooltip (the LWE pages carry dozens), and the ordered lists that do exist use decimal. No use today.

No `symbols()` here (expected in Safari, and in Chrome before 155), so the footnotes number 1 to 4.

## scroll-snap-type: pair / one target, both axes Chrome 156 beta · Safari/FF no

[WebKit: support](https://github.com/WebKit/standards-positions/issues/716) `scroll-snap-type: both` snaps each axis on its own, so a 2D scroller can land on one item's column and a different item's row. `pair` makes the container snap to the same element in x and y, which a board of tiles or a spreadsheet-style grid of cards wants. Chrome 156, in beta now, turns it on: BCD records 156, chromestatus names the same milestone, and Chromium's runtime flag reads `stable`. WebKit's standards position is support; Mozilla's issue is still open. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1438) points at WPT tests named `scroll-snap-type-pair`.

On the site: the one two-axis scroller is the `/access` skill-tree board at 100% zoom (`.mmc-canvas`, scrolling in x and y). A presenter pans it freely across 68 nodes, and snapping would fight that, so the board stays unsnapped whichever engines ship `pair`.

No `scroll-snap-type: pair` here (expected before Chrome 156).

## Single-axis scroll containers / overflow: auto clip Chrome 156 beta · Safari/FF no

[WebKit: no position](https://github.com/WebKit/standards-positions/issues/680) Until now, scrolling one axis forced the other to scroll too: beside `overflow-x: auto`, `clip` computed to `hidden`. A sticky header inside a sideways-scrolling table wrapper therefore pins to the wrapper, which never scrolls vertically. CSS Overflow 3 now keeps `clip` as `clip`, so `overflow: auto clip` makes a container that scrolls in x only, and sticky resolves y against the page. chromestatus lists Chrome 156, now in beta, and Chromium's runtime flag reads `stable`. MDN has no key yet. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1436) notes Chrome's compat check: a site that breaks can swap `clip` for `hidden`. The page can't probe this in its head, because the answer is a computed value on an element in the document. The button reads one:

Reads the computed `overflow-y` of a hidden box styled `overflow: auto clip`.

On the site: `/access` has exactly this bug. Its device table header (`table.devs thead th`) is `position: sticky; top: 0` inside `.tw-scroll`, which sets `overflow-x: auto` for the 1020px table. The header pins to a wrapper that never scrolls vertically, so it scrolls away with the rows. `overflow: auto clip` on the wrapper fixes it in Chrome 156 and leaves other engines where they are today.

## polygon() vertex rounding / polygon(round 6px, …) Chrome 150 · Safari/FF no

[WebKit: no position](https://github.com/WebKit/standards-positions/issues/474) `polygon(round 6px, …)` rounds every vertex of a polygon by one radius, clamped so neighboring curves never overlap. Rounded triangles, hexagons and speech-bubble tails are the use; today they take extra points that approximate the curve, an SVG `path()`, or a mask. CSS Shapes 1 specifies it after a CSSWG resolution (csswg-drafts#9843). Chrome 150 shipped it on 2026-06-30, and BCD and chromestatus agree. Safari and Firefox have nothing, and both position issues are open. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1418) scopes parsing, serialization, radius clamping and `clip-path` rendering against existing WPT tests. The Luna title-bar triangle at 3x, then with `round 6px`:

sharp

`round 6px`

On the site: the default title-bar icon in luna.css is exactly `clip-path: polygon(50% 0, 100% 100%, 0 100%)`, a 16px amber triangle with knife-point corners. The homepage draws the same triangle, and `/garage` one more polygon. XP's icons were pixel art with hard corners, so the sharp version is the faithful one. Rounding them would modernize the look, which the design doctrine declines.

No `polygon(round …)` here (expected outside Chrome 150+), so both triangles keep sharp corners.

## align-content in form controls / \<button\>, \<input\> align-content ships · buttons ignore it

Since 2024, `align-content` moves content inside a plain block in all three engines (Chrome 123, Firefox 125, Safari 17.4). Buttons and text inputs opt out: engine code centers them, so `align-content: start` on a `<button>` does nothing. Underneath, WebKit lays a button out as a flexbox around an anonymous block, while Gecko and Blink use a plain block. The CSSWG resolved (csswg-drafts#14190) to move that centering into UA stylesheet rules authors can override. Firefox landed it for 156, then pref'd it back to Nightly when `all: unset` on Instacart's buttons wiped the UA rule and the labels jumped to the top. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1410) asks the engines to ship together, because a lone engine breaks sites built against the others ([WPT](https://wpt.fyi/results/html/rendering/non-replaced-elements/form-controls/align-content.tentative.html)). Both boxes set `align-content: end`:

`<div>`

label

`<button>`

On the site: no fight to settle. `.xp-button` is an inline-block with one line of text and even padding, and XP buttons center their caption, so the hardwired centering is the look Luna wants. A centered label on the right is your browser's hardwired behavior; a label at the bottom means the new rule.

## box-decoration-break / slice | clone, unprefixed Chrome 130 · FF 32 · Safari -webkit-

[WebKit: no position](https://github.com/WebKit/standards-positions/issues/366) When an inline box breaks across lines, `slice` cuts its border, padding and background at the break, and `clone` gives each fragment its own full set. Firefox has had it unprefixed since 32 in 2014; Chrome 130 unprefixed it in 2024 and extended it to block fragments. Safari still reads only `-webkit-box-decoration-break`, on inline boxes only per BCD, so Autoprefixer keeps emitting the prefix. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1391) is the third ask after 2024 and 2025, and counts it among the last prefixed properties beside `initial-letter` and `line-clamp`. One wrapped phrase per setting, unprefixed only:

`slice`

a bordered phrase that wraps

`clone`

a bordered phrase that wraps

On the site: the `.wk` position chips in this page's prose carry `white-space: nowrap`, so a bordered chip never splits across a line and never shows a sliced border. `clone` would let a long chip wrap with both halves boxed, which matters at 375px. iOS Safari still needs the prefix, so it would ship as a prefixed pair until WebKit moves.

Your browser doesn't parse unprefixed `box-decoration-break` (expected in Safari), so both phrases render sliced.

## object-view-box / crop a replaced element Chrome 104 · Safari TP · FF no

Crops and zooms an `img`, `video` or `canvas` to an inset region: `object-view-box: inset(25% 20% 15% 0%)` shows that rectangle scaled to fill the box, as if the file had been cropped. Today that takes a wrapper with `overflow: hidden` around a scaled, offset image, or a second cropped file. Chrome shipped it in 104 in 2022. Safari Technology Preview 251 added it on 2026-08-26, and no stable Safari has it, Safari 27 included. Firefox has nothing in BCD. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1385) reuses last year's, which wasn't picked. The same SVG twice; the second sets `object-view-box: inset(0 50% 50% 0)`:

whole

![A square split into four numbered quarters](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='80'%20height='80'%20viewBox='0%200%2080%2080'%3E%3Crect%20width='40'%20height='40'%20fill-opacity='.08'/%3E%3Crect%20x='40'%20width='40'%20height='40'%20fill-opacity='.2'/%3E%3Crect%20y='40'%20width='40'%20height='40'%20fill-opacity='.32'/%3E%3Crect%20x='40'%20y='40'%20width='40'%20height='40'%20fill-opacity='.44'/%3E%3Cg%20font-family='Tahoma,Verdana,sans-serif'%20font-size='18'%20text-anchor='middle'%3E%3Ctext%20x='20'%20y='27'%3E1%3C/text%3E%3Ctext%20x='60'%20y='27'%3E2%3C/text%3E%3Ctext%20x='20'%20y='67'%3E3%3C/text%3E%3Ctext%20x='60'%20y='67'%3E4%3C/text%3E%3C/g%3E%3C/svg%3E)

`inset(0 50% 50% 0)`

![The same square cropped to quarter 1](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='80'%20height='80'%20viewBox='0%200%2080%2080'%3E%3Crect%20width='40'%20height='40'%20fill-opacity='.08'/%3E%3Crect%20x='40'%20width='40'%20height='40'%20fill-opacity='.2'/%3E%3Crect%20y='40'%20width='40'%20height='40'%20fill-opacity='.32'/%3E%3Crect%20x='40'%20y='40'%20width='40'%20height='40'%20fill-opacity='.44'/%3E%3Cg%20font-family='Tahoma,Verdana,sans-serif'%20font-size='18'%20text-anchor='middle'%3E%3Ctext%20x='20'%20y='27'%3E1%3C/text%3E%3Ctext%20x='60'%20y='27'%3E2%3C/text%3E%3Ctext%20x='20'%20y='67'%3E3%3C/text%3E%3Ctext%20x='60'%20y='67'%3E4%3C/text%3E%3C/g%3E%3C/svg%3E)

On the site: the photo pipeline squares every photo at encode time, so each crop on the homepage grid is baked into its own files (`object-fit:
        cover` there only trims sub-pixel rounding). `object-view-box` could show a zoomed detail of a photo from the file the grid already loaded. One engine, so the baked crops stay.

No `object-view-box` here (expected outside Chrome), so the second image shows the whole square.

## line-clamp / unprefixed Chrome/FF flag · Safari TP

Unprefixed `line-clamp: 3` cuts a block to 3 lines with an ellipsis, and the longhands add custom end text through `block-ellipsis`. Everyone writes the legacy recipe instead: `display: -webkit-box`, `-webkit-box-orient: vertical`, `-webkit-line-clamp`, `overflow: hidden`. It works in all three engines and drags the 2009 flexbox along. The unprefixed property ships in no stable browser: Chrome and Firefox keep it behind flags, and Safari Technology Preview carries it, with clamp fixes in releases 251 and 252. Safari 18.2 exposed it by accident and 18.4 pulled it. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1379) is the fourth ask, after 2022, 2025 and 2026. The legacy recipe clamping to 2 lines, then `line-clamp: 2` alone:

The legacy recipe clamps this paragraph to two lines in every engine, because all three still honor the 2009 box model it borrows.

The unprefixed property clamps this paragraph to two lines only where a flag or a preview build turned it on.

On the site: no multi-line clamp anywhere, and grep finds no `-webkit-line-clamp` in `src`. Truncation here is single-line: `text-overflow: ellipsis` on the taskbar pin labels (`.axp-pin .lbl`, 92px wide at most) and the `/dotfiles` labels, which already works everywhere. Nothing to migrate.

Your browser doesn't parse unprefixed `line-clamp` (expected in every stable release), so the second paragraph shows in full.

## justify-self in block layout / justify-items too Chrome 130 · Safari TP · FF no

`justify-self` and `justify-items` have long worked on grid items and absolutely positioned boxes. CSS Align 3 also defines them for ordinary block-level boxes, so `justify-self: center` centers a fixed-width block in its container, the job `margin-inline: auto` does today. Chrome 130 shipped it in 2024. Safari Technology Preview 249 added it on 2026-07-29, which BCD 8.1.5 doesn't record yet. Firefox has an open bug (1930584). The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1360) carries over last year's, which drew 25 votes. The page has no honest probe: `justify-self: center` parses in every engine because grid accepts it, and support only shows in layout. The demo is the test: the bar sits centered if your browser supports it.

justify-self: center

On the site: the XP window is centered by `margin: 0 auto !important` on `.window` in luna.css, and auto margins center a block in every engine. `justify-self` would say the same thing in grid vocabulary, a matter of taste, so the margin stays.

## text-size-adjust / unprefixed Chrome 54 · Safari/FF -webkit- on mobile

[WebKit: no position](https://github.com/WebKit/standards-positions/issues/720) Phone browsers inflate small text on pages they judge weren't built for phones. `text-size-adjust: none` or `100%` switches that off, and other percentages scale it. Chrome parses it unprefixed since 54, and it acts on Android. iOS Safari has read only `-webkit-text-size-adjust` since the first iPhone, and Firefox for Android takes `-webkit-` (since 49) and `-moz-`; desktop Safari and Firefox have neither. So BCD's Chrome-only row hides that all three engines honor it on phones, two of them under a prefix. That's why normalize.css ships `-webkit-text-size-adjust: 100%`. WebKit and Mozilla both hold open position issues without a stance. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1353) asks for one unprefixed spelling.

On the site: no `text-size-adjust` in any spelling (grep), and all 46 HTML documents in `src/pages` declare `width=device-width, initial-scale=1`, which tells a phone the layout was built for it. The site leaves inflation to the browser and has nothing to unprefix.

Your browser doesn't parse unprefixed `text-size-adjust` (expected outside Chromium); on a phone it may still read the `-webkit-` spelling.

## flex-wrap: balance / even flex lines Chrome 150 · Safari/FF no

[WebKit: no position](https://github.com/WebKit/standards-positions/issues/660) `text-wrap: balance` for flex lines: when items wrap, the container spreads them so each line holds about the same amount, where plain `wrap` fills the first line and can leave one orphan on the last. The only workaround is guessing a `max-width` that splits well at one viewport. Chrome 150 shipped it on 2026-06-30. Mozilla's position is positive and its bug (1990050) is open; WebKit's issue has no stance. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1350) also asks for `flex-line-count`. Notepad's five status facts in a 240px box, `wrap` then `balance`:

`wrap`

Ln 1

Col 1

100%

UTF-8

CRLF

`balance`

Ln 1

Col 1

100%

UTF-8

CRLF

On the site: `.xp-statusbar` in luna.css, the band under a window, is `flex-wrap: wrap` with a middle dot between facts. In a narrow window its last fact can drop to a second row alone, and `balance` would split the facts evenly. Other engines keep today's wrap, so it's a one-line candidate once a second engine ships.

No `flex-wrap: balance` here (expected outside Chrome 150+), so the second box wraps like the first.

## :host(:has()) / shadow host by light-DOM content Safari · Firefox · Chrome no

`:host()` matches a component's own host element from inside its shadow root. With `:has()` inside, it asks what the page put in the light DOM, so `:host(:has(img))` can restyle a card that was handed an image. Today's stable WPT runs split three ways. Firefox 157 and Safari 27 pass the light-DOM test and Chrome 155 fails it, with Chromium's bug marked "on hold". Safari alone matches a complex argument such as `:host(:has(section h1))`. The mirror form, `:host:has()` over the shadow tree's own children, passes in Chrome and Safari and fails in Firefox. The `:host(:has())` tests stay tentative while [csswg-drafts#11859](https://github.com/w3c/csswg-drafts/issues/11859) asks whether it should match at all, and this [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1395) is its third run after 2023 and 2025. The left host was handed a light-DOM `<b>`; the right one wasn't:

On the site: no shadow roots ship outside this page, so nothing waits on it. The light-DOM form already works in `infotip.css`, where `#axp-infotip:has(.gh)` widens the tooltip for the GitHub activity card. The `<xp-window>` sketch on the declarative shadow DOM card is where the host form would land: a window that draws its toolbar row only when the page passes one in.

Your browser rejects `:host(:has())`, so both hosts above stay grey.

## CSS gamut mapping / out-of-gamut oklch() FF flag · Chrome/Safari no

An `oklch()` or `lab()` color can name something no screen can show. CSS Color 4 says to pull its chroma in until it fits, keeping lightness and hue. Every stable engine converts to RGB and clamps each channel on its own, which drags the hue along. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1394) lightens `#0075ff` to 97% in OKLCH: it should paint as a near-white tint, and it paints as pale cyan. The `oklch-009`, `oklch-010` and `lch-009` reftests fail in Chrome 155, Firefox 157 and Safari 27 alike. Per the proposal, Firefox has a flagged implementation, and Chrome and WebKit are looking into it. No chip: `getComputedStyle` returns the `oklch()` as written, and mapping happens at paint. Left is the color as asked; right keeps its lightness and hue at chroma 0.015:

`oklch(97% 0.2235 258.41)`

`oklch(97% 0.015 258.41)`

On the site: every color is authored in OKLCH, and `design/tokens/colors.css` pushes five tokens past sRGB under `@media (color-gamut: p3)`. Some overshoot P3 too. `--link-hover` asks for chroma 0.33 at hue 29; P3's red primary is `oklch(64.86% 0.2995 28.96)`. Its linear P3 red lands at 1.018, so every engine clamps it, and `--gold` and `--link-visited` overshoot by less. Lightning CSS gamut-maps the fallbacks it writes, and [lightningcss#1323](https://github.com/parcel-bundler/lightningcss/pull/1323), merged 2026-10-08, fixed both halves of its binary search. This build sets no targets, so the tokens ship as written.

## Missing and powerless color components / none ships everywhere · interop gap

A color component can be `none`: missing on purpose, so `color-mix()`, relative color and gradients fill it from the other color. A hue also goes powerless at chroma 0. Every engine parses `none` and mishandles it somewhere. On today's stable runs `color-computed-none.html` passes 197 of 678 subtests in Chrome 155, 291 in Firefox 157 and 410 in Safari 27, and two cases fail in opposite directions. Mixing `oklab(0.5 none none)` in OKLCH should keep the other color's chroma, and Chrome and Firefox halve it. Mixing `lch(none none 180)` should drop that powerless 180° hue, and Safari blends it in. No `@supports` test can tell right from wrong, which is the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1334)'s main argument and why this card has no chip. Each row opens with your browser's mix:

`your browser`

`spec, Safari`

`Chrome, Firefox`

`your browser`

`spec, Chrome, Firefox`

`180° blended in`

Row 1 mixes oklab(0.5 none none) into oklch(0.7 0.16 145); row 2 mixes lch(none none 180) into oklch(0.7 0.16 30).

On the site: the homepage derives its photo-frame hover with `oklch(from var(--photo-frame) calc(l - 0.19) calc(c + 0.035) h)`. `--frame` sits at chroma 0.061 and hue 253.6, so `h` is real today. Retune the frame to a neutral grey and that hue turns powerless, so the added chroma lands on whatever hue the engine reports. The build has its own copy of the bug. Lightning CSS 1.33 resolves both mixes above at build time, reading each `none` as zero, so the live swatches carry their `color-mix()` in a `style` attribute.

## Anchored container queries / @container anchored(fallback) Chrome 143 · Safari/FF no

Anchor positioning already picks a fallback through `position-try-fallbacks` when the preferred spot doesn't fit. It never told the popup which one won. Put `container-type: anchored` on the positioned element, and `@container anchored(fallback: flip-block)` restyles its contents for that case, so a tooltip's tail can move to the side that now faces the anchor. Chrome 143 shipped it. WebKit ([#501](https://github.com/WebKit/standards-positions/issues/501)) and Mozilla ([#1236](https://github.com/mozilla/standards-positions/issues/1236)) have no position yet. A developer who used it in Chromium called it "far from developer friendly" on the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1363) and filed two CSSWG issues: one asks to query an explicit fallback by name ([#14350](https://github.com/w3c/csswg-drafts/issues/14350)), the other a limit on what the query sees ([#14351](https://github.com/w3c/csswg-drafts/issues/14351)).

On the site: the shell's Explorer infotip anchors to whatever you hover, with `position-try-fallbacks: flip-block, flip-inline` in `infotip.css`, and the Run dialog's preview card flips the same way in `nav-run.css`. Both are plain boxes, and `infotip.js` picks the above-the-anchor variant by hand, adding `.up` only inside the taskbar. A fallback query would let the tip draw an XP balloon tail on whichever side the browser chose. One engine, so the boxes stay tailless.

No `container-type: anchored` here, so a flipped popup can't tell which way it flipped.

## :has-slotted / a slot that received content FF 136 · Chrome/Safari no

Matches a `<slot>` that received light-DOM content, and skips one that's showing its fallback. A component uses it to drop padding, a border or a label when the page passed nothing in; today that takes a `slotchange` listener toggling a class. Firefox 136 shipped it. WebKit backs it on paper [WebKit +](https://github.com/WebKit/standards-positions/issues/404) and Mozilla rates it positive. Safari 27 still fails two of the three basic WPT reftests. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1356) says WebKit is waiting on [csswg-drafts#14307](https://github.com/w3c/csswg-drafts/issues/14307), which reports that the spec contradicts itself. Chrome sent an Intent to Ship in January 2025, and that thread now calls it paused. The left host slotted a word; the right one shows its fallback:

slotted

On the site: the one `<slot>` anywhere is the pill on the declarative shadow DOM card, whose fallback text reads `chip`. The `<xp-window>` sketch there would want this for the status bar: hide the bar when nothing was slotted into it, with no script. Until a second engine ships it, a component that needs the check writes the listener.

No `:has-slotted` here, so the filled slot looks the same as the empty one.

## CSSPseudoElement / el.pseudo() · event.pseudoTarget Chrome 147 · FF flag · Safari no

Pseudo-elements get a JavaScript handle. `el.pseudo("::before")` returns a `CSSPseudoElement`, and UI, animation and transition events carry `pseudoTarget`, so a dialog can tell a click on its `::backdrop` from a click on itself. Chrome 147 shipped the interface and `pseudoTarget` for `::before`, `::after` and `::marker`; Chrome 152 added `::backdrop`, the case the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1347) leads with. MDN's compat data says 149, which matches neither release. Firefox has carried it behind `dom.css_pseudo_element.enabled` since 75. WebKit ([#607](https://github.com/WebKit/standards-positions/issues/607)) and Mozilla ([#1345](https://github.com/mozilla/standards-positions/issues/1345)) have no position, and adding `::backdrop` to the spec's list is an open CSSWG issue ([#13804](https://github.com/w3c/csswg-drafts/issues/13804)). Open the dialog, then click outside it:

Click anywhere: inside this box or on the dimmed backdrop.

The readout names the clicked pseudo-element.

On the site: the Run dialog in `nav-run.js` light-dismisses on a backdrop click by checking `e.target === run`. Its comment reasons that a click landing on the dialog element itself can only be its backdrop-covered margin. `pseudoTarget?.type === "::backdrop"` says the same thing directly. The heuristic works in every engine today, so it stays until a second engine ships.

No `CSSPseudoElement` here; the dialog still closes, and the readout shows `pseudoTarget` as undefined.

## CSS linked parameters / link-parameters · param() FF Nightly · Chrome/Safari no

Passes custom properties into an external SVG, so one icon file can take its color from the page the way inline SVG does. There are three spellings: `link-parameters: param(--color, blue)` on an `<img>`, a `#:~:param(--color,blue)` fragment on the URL, and `url("icon.svg", param(--color, blue))` in CSS. The SVG reads `var(--color)`. MDN's compat data marks Firefox as preview-only, meaning Nightly, with [bug 1812163](https://bugzilla.mozilla.org/show_bug.cgi?id=1812163) still open; Chrome and Safari have nothing. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1346) lists the workarounds people use instead: inlining every icon, `<svg><use>`, or painting a color through `mask-image`.

On the site: `luna.css` ships the Bliss wallpaper twice, about 1.7 KB each: a data URI with hex stops, and a P3 copy whose sky and grass stops move in chroma. Its comment says why: an SVG stop color an engine can't parse paints black, so the P3 gate has to live in CSS. With parameters the SVG would hold `var(--sky, #3f7fcc)` stops, and the P3 rule would pass new stop values to the same image. The taskbar's `<img>` refs into the `/icons.svg` sprite could take a color the same way.

No `link-parameters` property here, so an SVG image can't take colors from the page.

## Scroll snap events / scrollsnapchanging · scrollsnapchange Chrome 129 · Safari/FF no

Two events off the browser's own snapping. `scrollsnapchanging` fires mid-gesture whenever the element a scroll would land on changes, and `scrollsnapchange` fires once a new target settles; both are `SnapEvent`s naming the block and inline targets. Before these, a carousel guessed with an `IntersectionObserver` or a `scroll` listener and some arithmetic, either of which can disagree with where the browser actually snapped. Chrome 129 shipped them. WebKit ([#333](https://github.com/WebKit/standards-positions/issues/333)) and Mozilla ([#1008](https://github.com/mozilla/standards-positions/issues/1008)) list no position, Firefox's [bug 1843961](https://bugzilla.mozilla.org/show_bug.cgi?id=1843961) is open, and most of the WPT folder is still marked tentative. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1345) links no tests yet. Swipe or scroll the rail:

one · two · three

Waiting for a snap.

On the site: no shipped surface snaps. The only snap rail is this page's own CSS carousel demo, so nothing on the site waits on these events. The one `IntersectionObserver`, in `quiz.js`, watches a sentinel scroll into view, a job snapping has no part in.

No snap events here; the rail still snaps, and the readout keeps waiting.

## var() in size container queries / @container (width \>= var(--x)) Firefox · Chrome/Safari no

CSS Conditional 5 lets a size container query take its threshold from a custom property, resolved on the container: `@container (width >= var(--min-col))`. Media queries can't do this, since there's no element to read the variable from, which is why breakpoints get pasted as literals. On today's stable runs WPT's `var-evaluation.html` passes in Firefox 157 and fails in Chrome 155 and Safari 27. MDN's compat data has no entry for it yet, and Chromium tracks it as [issue 378421865](https://issues.chromium.org/issues/378421865). No chip: the honest test needs a laid-out container, which a synchronous probe in the page head doesn't have ([Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1344)). The box below is 240px wide, and the rule asks for `width >= var(--ib-cq-min)` with the variable at 200px:

The query doesn't match (yet).The query matched.

On the site: `custom-media.css` counts six narrow breakpoints in play (460, 520, 540, 560, 620 and 760px) and names one, and `@custom-media` only reaches `@media`. The one container query, Serendipity's `@container serendipity-win (max-width:560px)`, carries its number as a literal; with `var()` it could read a breakpoint token. The site's build is a second blocker: Lightning CSS 1.33 rejects the syntax with a parse error, which is why this demo's rule arrives by script.

## Responsive iframes / frame-sizing Chrome 154 · Safari/FF no

An `<iframe>` that grows to its document's height without a `postMessage` handshake. Both sides opt in. The embedder sets `frame-sizing: content-height` (or a width, block or inline variant), the embedded page adds `<meta name="responsive-embedded-sizing">`, and it calls `window.requestResize()` when its content changes. Chrome 154 shipped all three. WebKit ([#653](https://github.com/WebKit/standards-positions/issues/653)) and Mozilla ([#1394](https://github.com/mozilla/standards-positions/issues/1394)) have no position, and on today's stable runs the parsing tests pass in Chrome 155 alone. The embedded side has to agree because a cross-origin document's size is information about that document ([Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1443)).

On the site: /lens holds the one iframe, a live view of whatever third-party URL you hand it (`lens.js`, `lens.ts`), fixed at `min-height: 520px`. Each of those pages would have to carry the meta tag, and arbitrary sites won't, so the frame keeps its fixed height even in Chrome. The feature fits embeds whose authors cooperate, like a payment widget or a comment thread, and the site embeds none.

No `frame-sizing` here, so an iframe keeps whatever height its embedder gives it.

## Viewport Segments / window.viewport.segments Chrome 138 · Safari/FF no

On a foldable or dual-screen device the viewport can span a hinge, and this API reports the pieces: `@media (horizontal-viewport-segments: 2)`, six `env(viewport-segment-*)` variables for each piece's edges and size, and `window.viewport.segments` as an array of rectangles. A layout can then keep a column out of the fold. Chrome 138 shipped it, and an ordinary screen reports one segment the size of the viewport. WebKit ([#327](https://github.com/WebKit/standards-positions/issues/327)) and Mozilla ([#883](https://github.com/mozilla/standards-positions/issues/883)) have no position. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1440) says Safari 27.1 beta carries it behind a flag in the simulator, which MDN's compat data doesn't record yet. Device Posture, the next card, is its sibling:

Click to list this window's viewport segments.

On the site: every page is an XP window that `nav.js` can maximize to fill the viewport. On a two-segment screen, maximize could fill one segment and leave the hinge alone. The site has no foldable to test that on, so it stays a capability the shell could use, filed until a second engine ships.

No `window.viewport` here, so the page can't see a fold.

## Device Posture / navigator.devicePosture Chrome 132 · iOS flag · FF no

Reports whether a foldable is bent. `navigator.devicePosture.type` reads `continuous` or `folded` and fires `change`, and `@media (device-posture: folded)` does the same in CSS, so a video page could move its controls to the lower half of a half-open phone. Chrome 132 shipped it on desktop and Android; a laptop or a slab phone reads `continuous`. MDN's compat data lists it behind a `DevicePostureEnabled` flag in iOS Safari 27, and the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1439) names Safari 27.1 beta. WebKit ([#328](https://github.com/WebKit/standards-positions/issues/328)) and Mozilla ([#882](https://github.com/mozilla/standards-positions/issues/882)) have no position. Posture says how the device is held and Viewport Segments says where the fold sits, so a fold-aware layout needs both.

Click to read this device's posture.

On the site: no use. The XP desktop is a fixed fiction about a 2003 monitor, and none of its windows has a reason to care how the screen bends. A future `/garage` demo might, and that's the only path from this card to the site.

No `navigator.devicePosture` here, so the page reads every device as flat.

## Customized built-ins / is="", proposed for removal Chrome 67 · FF 63 · Safari never · removal proposed

`customElements.define("fancy-button", C, { extends: "button" })` plus `<button is="fancy-button">` upgrades a real button and keeps its built-in semantics. Chrome 67 and Firefox 63 shipped it in 2018. WebKit opposed it [WebKit -](https://github.com/WebKit/standards-positions/issues/97) and never built it. This [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1342) runs the other way from the rest. Westbrook Johnson, a past Web Components Community Group chair, asks Interop to remove it from the spec and from Chrome and Firefox, so other component work gets room. WebKit restated its no on 2026-09-17, citing two new WHATWG parser issues ([#12327](https://github.com/whatwg/html/issues/12327), [#12828](https://github.com/whatwg/html/issues/12828)). The proposal names platform-provided behaviors and customizable form controls as the paths to back instead.

On the site: nothing to lose. No script on the site defines a custom element; the one custom tag, `<xp-chip>` on the declarative shadow DOM card, is autonomous and never registered. The chip up top reads whether your browser still implements `is=`, the same way the XSLT card reads `XSLTProcessor`.

Your browser never implemented `is=`, so the removal changes nothing here.

## \<input type="week"\> / ISO week picker Chrome · iOS 18.2 · FF Android · desktop: Chrome only

A native picker for an ISO week, submitted as `2026-W41`. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1405) says only Chrome supports it, which holds on desktop. MDN's compat data has Chrome since 20, Firefox for Android since 18, and Safari since 18.2 on iOS, iPadOS and visionOS, as [WebKit's 18.2 notes](https://webkit.org/blog/16301/webkit-features-in-safari-18-2/) announced. Desktop Firefox ([bug 888320](https://bugzilla.mozilla.org/show_bug.cgi?id=888320)) and desktop Safari ([bug 200416](https://bugs.webkit.org/show_bug.cgi?id=200416)) render a plain text box that takes any string, so a `yyyy-Www` value has to be typed by hand. All three engines ship the type somewhere, and the gap is two desktop browsers. Pick a week:

On the site: no date fields at all. [/coffee](https://aadhar.sh/coffee) lists 14 days of open slots as radio buttons grouped by day, which beats a picker when there are a few dozen choices. A week field would earn a place only if the booking window grew past what one scroll shows.

Your browser treats `type="week"` as text, so the field above takes any string.

## iframe allow = "cross-origin-isolated" Chrome 87 · Safari/FF no

A top-level page earns `SharedArrayBuffer` and fine-grained timers by sending `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy`, and all three engines honor that pair. A cross-origin iframe inside the page gets the same powers only when the parent delegates them with `allow="cross-origin-isolated"`, a Permissions Policy token. Chrome has honored it since 87; MDN lists Firefox and Safari as no. The shared WPT test agrees: Chrome 154 passes 8 of 8 subtests, Firefox 157 and Safari 27 pass 3 (stable run of 2026-10-06). The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1470) asks for this one token without the rest of Permissions Policy. There's no chip: the answer only shows up as `crossOriginIsolated` inside a child of an isolated page, and this page isn't one. A probe built on `document.featurePolicy` would lean on a Chrome-only API, so it would read false in a Firefox that ships the token.

On the site: no use. Nothing in `src/client` touches `SharedArrayBuffer`, `Atomics` or wasm threads, the site sends neither COOP nor COEP, and `frame-ancestors 'none'` plus `X-Frame-Options: DENY` keep anyone from framing it. Its one iframe is /lens's live preview of somebody else's URL, and isolating that parent would break it, because `require-corp` blocks any cross-origin frame that doesn't opt in.

## Brotli in Compression Streams / new CompressionStream("brotli") Safari 18.4 · FF 147 · Chrome no

`CompressionStream` and `DecompressionStream` have spoken gzip, deflate and deflate-raw in all three engines since 2023, and the Compression Standard now names `"brotli"` too. Safari 18.4 shipped both directions first, Firefox 147 followed, and Chrome has nothing yet ([crbug 463397980](https://crbug.com/463397980)). In the 2026-10-06 WPT run Chrome 154 passes 1,020 of 1,316 compression subtests, against 1,312 for Firefox 157 and 1,314 of 1,315 for Safari 27. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1467) is mostly about deleting a dependency: a page that needs brotli in script ships a wasm build today. The constructor takes no quality argument, so the level is the engine's call. The button compresses this page's live DOM both ways:

Click to run gzip and brotli over this page's serialized markup.

On the site: brotli is already everywhere here, at the transport layer. The build writes a q11 brotli twin of every page, and the dictionary tiers send `dcb`/`dcz` deltas (the compression-dictionaries card above). The browser decodes all of it before a script sees a byte. The gap that actually bit is on the server. Probed against workerd on 2026-08-09, `new DecompressionStream("br")` throws, so /lens can't decode the site's own precompressed twin in-process and asks for the uncompressed body instead (`dispatch.ts`). Browser Interop won't move workerd.

This browser rejects `"brotli"` as a format, so the demo reports gzip alone.

## CSS module scripts / import … with { type: "css" } Chrome 123 · FF 147 · Safari no

[WebKit #77: support](https://github.com/WebKit/standards-positions/issues/77) Import a stylesheet the way you import JSON and get back a `CSSStyleSheet`, ready for `document.adoptedStyleSheets` or a shadow root. It replaces the `<link>`-plus-`onload` promise every lazy component hand-writes, and the inline-string imports bundlers invented to dodge it. Chrome 123 shipped it in 2024 and Firefox 147 followed. Safari is the gap: WebKit supports it on paper and has [a pull request open](https://github.com/WebKit/WebKit/pull/72110), and Safari 27 passes 5 of 32 css-module WPT subtests (Chrome 154: 58 of 59). This [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1466) is the fifth try, after 2023, 2024, 2025 and 2026. No chip: the only test is a dynamic `import()`, which is asynchronous and fetches a file.

On the site: `nav.js` has the exact shape this replaces. `loadStyle()` builds a `<link>`, wraps its `onload` in a promise, and pairs it with `import("/nav-run.js")` in a `Promise.all` so the Run palette never paints unstyled. The tray and the infotips load the same way. With CSS modules both halves would be imports. Until Safari ships, that means keeping both paths, so `loadStyle()` stays.

## Trusted Types / require-trusted-types-for Chrome 83 · Safari 26 · FF 148

[WebKit #186: support](https://github.com/WebKit/standards-positions/issues/186) A CSP directive, `require-trusted-types-for 'script'`, that makes the DOM's injection sinks refuse plain strings. `innerHTML`, `script.src` and their siblings then accept only a `TrustedHTML` or `TrustedScriptURL` minted by a named policy, which shrinks a DOM XSS review from every assignment to a handful of policies. Chrome has had it since 83; Safari 26 and Firefox 148 made it Baseline in February 2026. So the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1465) is cleanup: the thread counts 20 to 50 failing WPT tests per engine, and Firefox's main miss is a tentative `fromLiteral` test nobody ships. The button asks this browser which type each sink wants:

Click to query trustedTypes for four sinks.

On the site: not enforced. `lib/csp-policy.ts` stops at hashed `script-src`, and the production header on this page (curl, 2026-10-08) names 13 script hashes and no trusted-types directive. Turning it on would break 77 lines across 11 files in `src/client` that assign `innerHTML` or call `insertAdjacentHTML`. 36 of them sit in `lens.js`, which renders metadata scraped from other people's sites behind a hand-written `esc()`. That file is where a policy would pay for itself first.

No `trustedTypes` global in this browser, so the demo has nothing to ask.

## Entry state across documents / navigation.navigate(url, { state }) ships everywhere · interop gap

The Navigation API lets a page attach state to the entry it opens: `navigation.navigate("b.html", { state })`, then `navigation.currentEntry.getState()` on arrival. The API ships in all three now (Chrome 102, Safari 26.2, Firefox 147). The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1441) reports that Gecko keeps that state across a cross-document navigation, while Blink and WebKit keep it only within a document. I ran the proposal's two-file test in Chrome 152 on 2026-10-08: the new page's `getState()` came back `undefined`, while state written to the old entry with `updateCurrentEntry()` survived on `navigation.activation.from`. WPT has no test for this path, and every `navigation-api/state` test passes in all three. The proposal's title says history state; plain `history.state` starts null on any new document by design. No chip: the only test is a real navigation away from this page.

On the site: nothing crosses documents. `nav.js` reads `navigation.entries()` to decide whether the close glyph can be a `history.back()` home, and keeps per-path scroll offsets in `sessionStorage` for reloads. Entry state could hold that offset, and `updateCurrentEntry()` state already survives a reload in all three engines (WPT `location-reload.html`). So the bug isn't the blocker, and the move would buy nothing.

## JS Self-Profiling / new Profiler() Chrome 94 · Safari/FF no

[WebKit #717: no position](https://github.com/WebKit/standards-positions/issues/717) A sampling profiler a page runs on its own visitors. `new Profiler({ sampleInterval: 10, maxBufferSize: 10000 })` starts it, and `stop()` resolves to a trace of stacks, frames and script URLs to send home. Per the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1429), Excel Online, Sentry and Datadog's RUM already collect field profiles with it. Chrome shipped it in 94. Mozilla's standards-position thread is open under a security-concerns label, and WebKit hasn't taken a position. The catch is a header: the constructor throws unless the document arrived with `Document-Policy: js-profiling`, so the engine pays profiling overhead only on pages that ask. The chip tests `"Profiler" in window`, which is the API's presence. The button tries the constructor here, on a page that sends no such header:

Click to try starting a profiler on this page.

On the site: no `Document-Policy` header in `_headers` or `lib/security.ts`, and no field data to put a profile beside. The one performance series the site keeps is `perf-probe.ts`, a cron that times the homepage handlers inside a Worker and never sees a browser. The header would be one line. The real work is a collector endpoint and somewhere to read traces, and neither exists.

No `Profiler` interface in this browser, so the button has nothing to construct.

## Connection-Allowlist / response header Chrome 152 · Safari/FF no

[WebKit #583: no position](https://github.com/WebKit/standards-positions/issues/583) One response header listing every origin a document or worker may connect to, as URL patterns: `Connection-Allowlist: (response-origin "https://cdn.example")`. The browser checks it before opening any connection. The [explainer](https://github.com/WICG/connection-allowlists) scopes it wider than CSP: fetches and subresources, plus WebRTC's TURN and STUN servers, WebTransport, DNS prefetch and navigations. CSP's `connect-src` sorts requests by type and has no reach into most of those. Chrome 152 shipped it after an origin trial, with a `-Report-Only` twin. Mozilla is positive. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1424) wants the other two. No chip: script can't read a response header's enforcement, and testing it means attempting a connection, which this page won't do.

On the site: CSP already does most of this. `default-src 'self'` and `connect-src 'self'` mean no script here can reach a third-party origin, and `_headers` says there are no external script or connect origins at all. An allowlist would add DNS prefetch and WebRTC, which the site never uses. Navigations are the open question: this page alone carries over 140 external links, and `/rn` 302s to Spotify. I'd test what a clicked link does under it before trying.

## Lazy images, out-of-band loads / img.complete ships everywhere · interop gap

A `loading="lazy"` image that hasn't scrolled into view has no image data, so `complete` stays false and `naturalWidth` reads 0. Per the spec that holds even when another `<img>` loads the same URL. WPT's [`lazy-out-of-band-load.html`](https://wpt.fyi/results/html/semantics/embedded-content/the-img-element/update-the-image-data/lazy-out-of-band-load.html) tests exactly that in 8 cases (src or srcset, a real PNG or a 404, hidden or below the fold), and the 2026-10-06 stable run splits three ways. Firefox 157 passes all 8. Chrome 154 passes 4: whenever the other load succeeds, the lazy image keeps `complete` false but reports a `naturalWidth` of 100 it never fetched. Safari 27 passes none, because the lazy image fires its own `load` or `error`. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1411) links bugs in all three trackers. No chip: the bug needs a second network load to show.

On the site: the homepage grid lazy-loads its photos, but nothing reads their dimensions; `tooltip.js` uses EXIF sizes on purpose, since a thumbnail's `naturalWidth` is just 600. The one reader is /lens's execution census (`lib/agent-execution.ts`), which counts an image broken when `complete` is true and `naturalWidth` is 0. Checking `complete` first keeps Chrome's half out of the count, and the census runs in Cloudflare's headless Chromium, so Safari's half never reaches it.

## Multiple import maps / \<script type="importmap"\> × n Chrome 133 · Safari 18.4 · FF flag

Import maps launched as one per document, and the map had to arrive before any module loaded; a second map, or one after the first `import()`, was an error. The merge rules in HTML now let maps stack: a later map can add specifiers but can't override ones already mapped or resolved. The proposal points to Shopify's case, where one storefront page assembles several teams' code. Chrome 133 and Safari 18.4 ship it. Firefox has it implemented behind `dom.multiple_import_maps.enabled` since 150, off by default. In the 2026-10-06 WPT run Chrome 154 passes 11 of 12, Safari 27 10 and Firefox 157 6. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1399) asks Gecko to flip it. No chip: `HTMLScriptElement.supports("importmap")` answers for import maps in general, and the merge behavior only shows by loading modules.

On the site: no import maps. Modules import each other by path (`import("/hoist.js")` from `nav.js`), and the build's hash pass rewrites those strings to `/a/` content-hashed URLs, leaves first. So a one-byte change to `hoist.js` re-mints `nav.js` too, because its bytes now name the new hash. A map would cut that chain, and multiple maps would let the shell and a page's own islands each carry one. Each map is inline script, so it takes a CSP hash like every other block.

## requestIdleCallback() / IdleDeadline Chrome 47 · FF 55 · Safari flag

Queue work for when the main thread has nothing better to do. The callback gets a deadline whose `timeRemaining()` says how much of the idle period is left (50ms at most), and a `timeout` option forces it to run eventually. Chrome 47 and Firefox 55 have shipped it since 2015 and 2017. Safari is the gap: WebKit implemented it, then turned it off over regressions ([bug 285049](https://bugs.webkit.org/show_bug.cgi?id=285049)), and today it sits behind a flag in Technology Preview. In the 2026-10-06 WPT run Safari 27 passes 10 of 58 subtests, against 56 for Chrome 154 and 57 for Firefox 157. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1398) asks WebKit to turn it back on. Queue one and read its deadline:

Click to queue a callback with a 2000ms timeout.

On the site: three call sites, each with a fallback. `nav.js` defers the daily tips dialog (3s timeout, else a 1.5s `setTimeout`) and the WebMCP tool registration (2s, else 1.2s). `tooltip.js` defers the photo-metadata warmup (2s, else `setTimeout(run, 0)`). Every Safari visitor takes the fallback, so an iPhone gets a timer and never an idle slot. Once Safari ships, the three `else` branches can go.

No `requestIdleCallback` here; this is the browser the site's `setTimeout` fallbacks exist for.

## NotRestoredReasons / PerformanceNavigationTiming Chrome 125 · Safari/FF no

[WebKit #154: no position](https://github.com/WebKit/standards-positions/issues/154) When a Back or Forward navigation misses the back/forward cache, Chrome 125+ says why on the navigation timing entry. `notRestoredReasons` lists the blockers for the page and its same-origin frames, and reports blockers inside cross-origin frames as `masked`. It's `null` on an ordinary navigation, so it only earns anything collected from real visits. Mozilla is positive. In the 2026-10-06 WPT run Chrome 154 passes 11 of 13 subtests and Firefox 157 and Safari 27 pass none of 12. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1397) wants it in all three. The button reads the entry for how you reached this page:

Click to read performance.getEntriesByType("navigation")\[0\].

On the site: bfcache does real work here. `nav.js` sends the title-bar Back/Forward buttons and the close glyph through `history.back()`. `_headers` explains why pages carry `max-age=0` and never `no-store`, the one directive that disqualifies a page. Whether restores happen is unmeasured. A 2026-09-12 reading in an automated Chrome 152 came back `masked` on aadhar.sh/, and the control, web.dev's own bfcache article, failed the same way. Field reports would settle it, and `nav.js` already reads this entry for its prefetch beacon.

This browser's navigation entry has no `notRestoredReasons`; the button shows what it does have.

## Cross-origin redirect timing / redirectStart · redirectCount ships everywhere · interop gap

Navigation and Resource Timing hide redirect timing once a chain crosses origins, unless the hops opt in with `Timing-Allow-Origin`. The 2026-10-06 stable WPT run shows three separate bugs. `navigation-timing/redirect-tao.html`: Chrome 154 passes 6 of 6, Firefox 157 and Safari 27 pass 3, because both ignore the opt-in and hide everything. `SO-XO-SO-redirect-chain-tao`: Safari leaks `redirectStart` on a same, cross, same-origin chain it should hide. `same-origin-from-cross-origin-redirect`: Firefox gets `fetchStart` wrong. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1396) also cites [navigation-timing#160](https://github.com/w3c/navigation-timing/issues/160): redirect time can leak through `timeOrigin`, and no test covers that. No chip; the button shows this page's own entry:

Click to read this page's redirect timing.

On the site: few redirects, and only some cross origins. `*.pages.dev` 301s to aadhar.sh, `/rn` 302s to Spotify, and `/run?cmd=` can 302 off-site. Inside the origin, `/garage/horizon/` gets a 307 to the slashless URL (curl, 2026-10-08), so arriving that way should read `redirectCount` 1. No response sends `Timing-Allow-Origin` and nothing collects timing. /lens counts other sites' redirects from the DevTools protocol's Network events, which this gating never touches.

## Module scripts in XHTML and SVG / \<script type="module"\> ships everywhere · interop gap

`<script type="module">` has run in HTML documents in all three engines since 2018. The same tag in a document parsed as XML is another story. WPT's `module-in-xhtml.xhtml` and `svg/interact/scripted/module-01.svg` pass in Firefox 157 and fail in Chrome 154 and Safari 27 (2026-10-06 run), and Chromium's XHTML bug has 33 votes. The proposal's third ask runs the other way: `"type": "module"` for an extension's background service worker works in Chrome and Safari, and Firefox lacks it. A reviewer suggested splitting the proposal, with extensions the hardest part to test. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1386) is a second attempt after 2025's was passed over. No chip: the failing contexts are other document types, which this HTML page can't become.

On the site: every document is HTML served as `text/html`, so the gap never reaches it. The two inline modules, on `/dotfiles` and `/garage/pretext`, run in all three engines. The site's 16 SVG files carry no script, and there's no browser extension. Nothing here changes.

## Extended-lifetime SharedWorkers / { extendedLifetime: true } Chrome 148 · Safari/FF no

[WebKit #492: no position](https://github.com/WebKit/standards-positions/issues/492) A `SharedWorker` dies when its last page closes, so in a one-tab multi-page app every navigation tears it down and boots it again. `new SharedWorker(url, { extendedLifetime: true })` keeps it alive briefly after the last client leaves, capped at a service worker's no-client lifetime. An IndexedDB write can finish, and the next page can reattach. Chrome 148 shipped it alongside `SharedWorker` on Android, and HTML merged the spec in March 2026. Mozilla is positive. WebKit's thread is open, and Apple's first reply flagged the extended lifetime service workers already have as a problem. The TAG closed its review unsatisfied. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1382) says Firefox and Safari fail the core WPT test. No chip: the option is a constructor argument, and testing it means starting a worker from a URL.

On the site: no workers of any kind. The old service worker is a stub, `sw.js`, whose only job is unregistering installed copies. The nearest candidate is the Run palette's photo index: each page's `nav.js` fetches `/images/manifest.json` and `alt.json` and rebuilds it on first open. The HTTP cache makes that refetch cheap and bfcache covers Back, so a long-lived worker would save one JSON parse.

## Layout Instability / LayoutShift · CLS Chrome 77 · Safari/FF no

A `PerformanceObserver` entry type, `layout-shift`, for each frame where visible content moves without user input. Each entry carries a score and, since Chrome 84, the elements that moved. Summed over the worst session window it becomes Cumulative Layout Shift, the one Core Web Vital still measurable only in Chromium. An earlier Interop round took LCP and INP and left CLS out. Chrome 77 shipped the entry type. Mozilla is positive, and WebKit hasn't taken a position on the API. In the 2026-10-06 WPT run Chrome 154 passes 116 of 118 subtests and Firefox 157 and Safari 27 pass 2 of 117. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1352) is the second year in a row it's been put forward. Sum this page's shifts so far:

Click to read the buffered layout-shift entries for this page.

On the site: Chrome's view is the only one there has been. A Chromium lab script, `layout-shift-lab.ts`, found every title bar sliding 50px right on 65 of 65 pages, because `nav.js` injected Back/Forward after paint. The fix bakes the pair into the HTML, and a contract test holds it. This page has its own scar: tags injected after paint once cost CLS 0.26 on mobile. The lab left the tree with other finished probes in #1127, and Safari visitors' shifts have never been measured.

No `LayoutShift` entries in this browser, so the button can't score this page.

## OpaqueRange / textarea.createValueRange() Chrome 152 · Safari/FF no

[WebKit #541: support](https://github.com/WebKit/standards-positions/issues/541) A `Range` can't point inside a `<textarea>` or `<input>`, because their text isn't DOM nodes. Placing an autocomplete popup at the caret means cloning the field into a mirror `<div>` and measuring that. `createValueRange(start, end)` returns a live range over the control's value, with `getBoundingClientRect()`, and as an `AbstractRange` it works with the Custom Highlight API. It exposes offsets and no nodes: in Chrome 152 it has no `startContainer` property at all, so the internals stay hidden. Chrome 152 shipped it after an origin trial from 148. Mozilla is positive and WebKit supports it, but MDN has no compat entry yet, and a reviewer flagged that the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1401) has only tentative tests. Mark the typos inside a real textarea:

Click to wrap each typo in an OpaqueRange and highlight it. Edit the text after; the ranges follow.

On the site: `notepad.js` is the obvious home. /writing's Notepad view is a real `<textarea>`, and the script already reads `selectionStart` for the Ln/Col status bar. XP Notepad's Edit \> Find would be a highlight on an `OpaqueRange`. The site doesn't implement Find, and the browser's own find already searches textarea text, so this stays a nicety for one engine.

No `OpaqueRange` in this browser; the textarea stays plain and the button says so.

## Targeted SVG fixes / \<use\>, context-fill, filters, path data context-fill: Chrome 124 · FF 111 · Safari no

The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1469) bundles four SVG pain points, and each sits at a different stage:

- `<use href="icons.svg">` with no `#fragment`: Chrome 137 and Safari 27 (per WebKit's release notes); Firefox no.
- `context-fill` / `context-stroke`, so a marker or a `<use>` copy paints in its referrer's colour: Chrome 124 and Firefox 111. WebKit marks both keywords unimplemented. [WebKit: no position](https://github.com/WebKit/standards-positions/issues/331)
- SVG filters ship everywhere and disagree in detail. One concrete gap: CSS `filter` on SVG elements works in Chrome 89 and Firefox 35; Safari ignores it.
- `getPathData()` / `setPathData()`: Firefox 137 only; Chrome keeps it behind an experimental flag.

The dots below use `fill="context-stroke"`, so each should match its own line:

On the site: the [/access](https://aadhar.sh/access) graph draws its dashed rival edges with `stroke: #b8860b` and repeats `#b8860b` by hand on the `<marker>` dot, which `context-stroke` would collapse into one colour. Filters are already in use: the sound button's icon, built in `nav.js`, shades itself with an `feDropShadow` primitive.

Your browser doesn't parse `context-fill`, so both rows of dots paint in the default black.

## Pause hidden iframe media / allow="media-playback-while-not-visible" Chrome 155 · Safari/FF no

A permission policy an embedder sets on an iframe: `allow="media-playback-while-not-visible 'none'"`. While that frame is `display: none`, `visibility: hidden` or zero-sized, the browser blocks its audible `<video>`, `<audio>` and Web Audio, then lifts the block when it shows again. The workaround today is tearing the iframe down and reloading it. Chrome 155 shipped it on 2026-10-06; this card's probe reads false in Chromium 152 and true in Chrome 155. Mozilla rates it positive; WebKit hasn't said. See the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1468). [WebKit: no position](https://github.com/WebKit/standards-positions/issues/409)

On the site: [/lens](https://aadhar.sh/lens) is the one place a stranger's page plays inside this one. When the looked-up site allows framing, `lens.js` embeds it live in a sandboxed iframe with scripts on, so an autoplaying page can make noise there. One more token in that iframe's `allow` would mute it whenever the panel is hidden. Engines that don't know the token skip it, so it could go in today.

This browser doesn't list `media-playback-while-not-visible` among its policy features, so a hidden iframe keeps playing.

## Contact Picker / navigator.contacts.select() Chrome Android 80 · Safari flag · FF no

Opens the OS address book and hands the page only the contacts and fields the visitor picks: `navigator.contacts.select(["name", "email"])`. It replaces typing a friend's number into an invite form by hand. Chrome has shipped it on Android since version 80 and nowhere else; desktop Chrome doesn't expose it. WebKit has carried an implementation since iOS 14.5 behind a feature flag that has never been on by default, and Mozilla's position is "defer". The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1462) asks for that second engine. No demo here, since the only way to show it is to open the picker.

On the site: no use. The one form, the [/coffee](https://aadhar.sh/coffee) booking, asks for the visitor's own name and email, which `autocomplete="name"` and `autocomplete="email"` already fill in every engine. A contact picker is for choosing other people, and nothing here invites anyone.

No `ContactsManager` here, which is the expected answer everywhere except Chrome on Android.

## WebVTT / \<track\> + VTTCue ships everywhere · interop gap

The caption format behind `<track kind="captions">`, plus `VTTCue` for building cues in script. All three engines have shipped it for a decade (Chrome 23, Firefox 31, Safari 6), and they still parse and lay out cues differently. Safari 27's release notes alone fix cue-settings `line` parsing, `::cue()` matching the cue root, and cues larger than the viewport; Safari 26.4 fixed `enter`/`exit` events on trackless cues. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1447) points at the `/webvtt` WPT directory. The button parses one cue with `getCueAsHTML()`; compare the output across engines.

```
Click to see how this engine turns cue text into DOM.
```

On the site: no use. There's no `<video>` or `<audio>` element anywhere in `src/`. The site is still photographs, and a still image carries its words in `alt` and the captions the photo pipeline bakes.

No `VTTCue` constructor here, so the button reports that and stops.

## Media Session call actions / togglecamera · togglemicrophone · voiceactivity Chrome 93 · Safari 18.4 · FF no

Lets a video-call page hear the browser's own call controls. The mute and camera buttons on a picture-in-picture window reach the page through `setActionHandler("togglemicrophone", fn)` and its camera twin. The third action, `voiceactivity`, fires when you talk while muted. The two toggles ship in Chrome 93 and Safari 18.4. WebKit's source lists `voiceactivity` in its enum, Chromium's doesn't, and MDN has no data for it. Firefox's `MediaSessionAction` enum stops at `stop`, and its `voiceactivity` bug (2010676) is open. See the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1428). The probe registers a null handler for `togglecamera`, which throws a `TypeError` in any engine that doesn't know the action.

On the site: no use. The only audio is the XP system sounds `nav.js` plays through an `AudioContext`, behind the tray's mute button. Nothing here opens a camera or microphone, so there's no call for a platform control to toggle.

This browser rejects the `togglecamera` action, so a call page can't hear its picture-in-picture buttons.

## Pointer events on touch / pointercancel, touch-action, capture ships everywhere · touch untested

Pointer events ship in Chrome 55, Firefox 59 and Safari 13, and earlier Interop rounds aligned the mouse half. Touch never got scored, because WPT's desktop runners have no touchscreen and there were no mobile runners. The touch half adds what mouse tests never exercise: when a pan fires `pointercancel`, how `touch-action` decides it, and implicit pointer capture. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1427) rides on the new mobile-testing work. There's no capability to probe, since the API exists everywhere; the gap is in event order. Arm the pads and drag a finger across each one:

touch-action: auto

touch-action: none

```
Not armed.
```

On the site: the XP shell runs on pointer events. `nav.js` drags desktop icons and title bars with `setPointerCapture` and treats `pointercancel` like `pointerup`. Title-bar drags return early for `pointerType === "touch"` so a finger scrolls the page, while the accessory windows carry `touch-action: none` in `luna.css`. Both choices lean on engines agreeing about when a touch turns into a pan, which is the behaviour this proposal would finally score.

## WebDriver BiDi / automation over WebSocket Chrome 126 · FF 117 · Safari no

The W3C successor to classic WebDriver: one WebSocket that carries commands and pushes events back, such as console messages, network requests and new browsing contexts. Puppeteer and Playwright do that job over Chrome's DevTools Protocol today. Chrome ships the core in 126 and Firefox in 117. Safari's `safaridriver` doesn't expose it in a stable release; Selenium 4.47 (August 2026) added BiDi support against Safari Technology Preview. [WebKit +](https://github.com/WebKit/standards-positions/issues/240) It's a protocol between a test runner and the browser, so no script on this page can see it, and the card carries no probe. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1417) scopes it to the `webdriver-bidi` web-feature.

On the site: the nightly canary in `tools/canary-browsers.ts` loads this very page in Chromium, Firefox and WebKit through `playwright-core` 1.63 and diffs every chip's probe. Playwright reaches Chromium over CDP and the other two through its own bundled builds, and `tools/check-agent.ts` attaches with `connectOverCDP`. A BiDi path in all three engines would let that canary drive stock Firefox and Safari.

## WebRTC SFrame / native end-to-end media encryption Safari flag · Chrome/FF no

End-to-end encryption for calls through a media server. The browser seals each frame with SFrame (RFC 9605) before packetizing, so the server forwards ciphertext, and the keys stay in `CryptoKey` objects script can't read. The shipped alternative is `RTCRtpScriptTransform` (Chrome 141, Firefox 117, Safari 15.4), where your own worker code does the encryption. WebKit added an SFrame transform in Safari Technology Preview 117 back in 2020, and its `WebRTCSFrameTransformEnabled` setting is still off by default. No SFrame entry appears in Safari 26 or 27's release notes, and Chromium and Gecko have no implementation. MDN has no data for it, and the spec's interface names are still moving, so this card has no probe. See the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1415).

On the site: no use. Nothing here opens an `RTCPeerConnection`, and the only real-time traffic is ordinary HTTPS to one Worker.

## MediaStreamTrackProcessor + VideoTrackGenerator Safari 18 · Chrome non-standard · FF no

Turns a live camera track into a stream of `VideoFrame`s, and a stream of frames back into a track, so a background blur is a `TransformStream` in a worker. Safari 18 ships the spec shape, which lives in dedicated workers only. Chrome has had `MediaStreamTrackProcessor` since 94, but on the window, beside its own `MediaStreamTrackGenerator`; its `VideoTrackGenerator` is test-only. Firefox has neither, with a positive Mozilla position. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1414) wants the worker shape everywhere. That split is also why this card has no probe: a `window` check reads true in Chrome and false in Safari, the reverse of who ships the standard.

On the site: no use. Nothing calls `getUserMedia`; the photos arrive already finished, encoded server-side by the `bun run photos` pipeline.

## Fullscreen keyboard lock / requestFullscreen({ keyboardLock }) FF 151 · Safari 26.4, reverted · Chrome no

`el.requestFullscreen({ keyboardLock: "browser" })` stops a single Escape press from leaving fullscreen (a long press does it instead) and lets a game claim reserved shortcuts. Firefox 151 shipped it in May. Safari 26.4 shipped it in March, then WebKit commit 319103@main switched it back off until the "hold Escape" hint UI exists. I couldn't confirm which Safari release carries that revert. Chrome's older `navigator.keyboard.lock()` (Chrome 68) covers similar ground with a different API. [WebKit: no position](https://github.com/WebKit/standards-positions/issues/481) The only test is calling `requestFullscreen()`, which this page won't do uninvited, so there's no probe. See the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1409).

On the site: no use. Nothing calls `requestFullscreen()`. The Pipes screen saver in `nav-pipes.js` covers the viewport with an overlay and exits on any key, pointer drift or wheel, so there's no Escape worth trapping.

## Web Share Target / manifest share\_target Chrome 89 · Safari/FF no

A manifest member that lists an installed web app in the OS share sheet. "Share" in the phone's photo app can then send a picture straight to it as a form POST. Chrome supports it on Android since 76 and on desktop since 89; Safari and Firefox have tracking bugs and nothing shipped. Mozilla rates it positive. [WebKit neutral](https://github.com/WebKit/standards-positions/issues/11) It only acts on an installed app, so a page can't probe it from script and this card has no chip. See the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1404).

On the site: no use, by design. There's no web app manifest and no service worker, so nothing can be installed to receive a share. It also has no upload path: photos enter through `bun run photos` on a workstation, from one folder on disk.

## createImageBitmap() / resize, crop, orientation ships everywhere · output differs

`createImageBitmap(blob, { resizeWidth, resizeQuality })` decodes and scales off the main thread, which makes it the built-in thumbnailer. Engines disagree on the result: the [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1390) counts 314 WPT subtests, passing 298 in Chrome, 291 in Firefox and 259 in Safari. Chrome's `"high"` can look worse than its `"medium"`, which is why Pica, a common resizing library, keeps its own path by default. Firefox 149 added `resizeQuality` and allowed crop plus resize together. No probe, since every engine has the API; the gap is in pixels. The button shrinks a 256px zone plate to 64px at each quality; smooth grey edges mean averaging, false rings mean aliasing:

On the site: the progressive-JPEG demo on [/lwe/encoding](https://aadhar.sh/lwe/encoding) decodes truncated bytes with `createImageBitmap`, then scales them with `drawImage`; [/garage/encoding](https://aadhar.sh/garage/encoding) times decodes the same way. Thumbnails stay server-side: zenc bakes each `/i/` tier once, and content addressing needs identical bytes in every browser.

## ariaNotify() / screen reader announcements Chrome 141 · FF 150 · Safari 27

`document.ariaNotify("Saved")` asks the screen reader to speak a string, with no DOM involved. It replaces the hidden `aria-live` div that pages write text into and hope gets read. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1387) says only Firefox supports it, which is out of date: Chrome 141, Firefox 150 and Safari 27 all ship it, and Safari 27's release notes list it. MDN notes that VoiceOver may drop an announcement that collides with another event, and that ChromeOS exposes the method without speaking. [WebKit +](https://github.com/WebKit/standards-positions/issues/370) With a screen reader on, press:

```
Nothing sent yet.
```

On the site: three `aria-live` regions exist, for the quiz feedback in `quiz.js`, the tip line in `nav-tips.js` and the status bar on [/access](https://aadhar.sh/access). Each announces text that's also on screen, which live regions handle well, so they stay. `ariaNotify` is for speech with nothing visible, and the site has none today.

No `ariaNotify` on `Element` here, so the button can't reach a screen reader.

## WebCodecs / VideoEncoder, AudioEncoder, ImageDecoder Chrome 94 · FF 130 · Safari 16.4

Direct access to the browser's media codecs, one frame or chunk at a time. The video half ships in Chrome 94, Firefox 130 and Safari 16.4; Safari added the audio half in 26. `ImageDecoder` trails: Chrome 94, Firefox 133, Safari only in Technology Preview. The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1378) reports crashes and memory leaks on mobile WebKit. Safari 26.4 and 27 each fixed a `VideoDecoder` bug that emitted frames out of order. [WebKit +](https://github.com/WebKit/standards-positions/issues/341) The button asks this engine which codecs it would configure, with nothing decoded:

```
Click to list what this engine supports.
```

On the site: [/garage/encoding](https://aadhar.sh/garage/encoding) leans on `ImageDecoder` as a witness. Fed truncated prefixes of a progressive JPEG in Chromium 152, it agreed byte for byte on where painting starts. The photos themselves ship as AVIF and JPEG tiers that the image decoder handles with no script at all.

No `VideoEncoder` here, so the button finds nothing to ask.

## WebUSB / navigator.usb Chrome 61 · Safari/FF oppose

Lets a page talk to a USB device the visitor picks: flashing a microcontroller, driving a label printer, updating a keyboard's firmware without a native installer. Chrome has shipped it since 61, on desktop and Android. The other two engines say no on principle: Mozilla's position is negative, and WebKit opposes it, both over the risk of exposing arbitrary hardware to the web. [WebKit -](https://github.com/WebKit/standards-positions/issues/68) The [Interop 2027 proposal](https://github.com/web-platform-tests/interop/issues/1331) is one line long and doesn't engage with either objection. The probe only checks for `navigator.usb`; it never opens the device picker.

On the site: no use. Nothing here talks to hardware, and a personal site asking for a USB device would be a strange request to make of a visitor.

No `navigator.usb` here, which is the answer in every engine except Chromium.

## Interop 2027 proposals / all 100, indexed proposals · Oct 2026

Each year the browser vendors pick a set of focus areas and agree to fix them together, scored on shared [WPT](https://wpt.fyi/) tests. The Firefox team opened [public ranking](https://interop-rank.fxdx.dev/) of the 100 proposals for 2027, which makes the list a map of what the engines think comes next. It overlapped this page on 39 rows and missed it on 61, and those 61 now have cards of their own, just above. Each row links its proposal issue and the card that covers it.

Engine versions come from MDN's browser-compat-data 8.1.5 and web-features 3.41.0, both published 2026-10-08, read per feature rather than typed in. A version still in beta says so and doesn't count toward the row's group. "A bug list" rows already ship everywhere and the proposal is about where engines disagree. "Not in compat data" means MDN has no key for it yet, which is a different claim from no engine shipping it. The last column probes *your* browser; `n/a` means no honest synchronous probe exists, the same convention as the chips up top.

| proposal | stable engines | you |
| --- | --- | --- |
| Ships in all three engines (7) |  |  |
| [Trusted Types](https://github.com/web-platform-tests/interop/issues/1465) ↑ card<br>Ensures input has been passed through a transformation function before being passed to an API that might execute that input. | Chrome 83 Firefox 148 Safari 26 | … |
| [Improve CSS `sibling-⁠count()` and `sibling-⁠index()` functions interop.](https://github.com/web-platform-tests/interop/issues/1460) ↑ card<br>Fix remaining test failures. | Chrome 138 Firefox 154 Safari 26.2 | … |
| [WebVTT](https://github.com/web-platform-tests/interop/issues/1447) ↑ card<br>WebVTT files are loaded using the `<track>` element, and the `VTTCue` API can be used to create or update cues dynamically. | Chrome 23 Firefox 31 Safari 6 | … |
| [CSS `@counter-⁠style`](https://github.com/web-platform-tests/interop/issues/1446) ↑ card<br>Defines custom counter styles for list items, such as a sequence of symbols instead of numbers. | Chrome 91 Firefox 33 Safari 17 | … |
| [`ariaNotify()`](https://github.com/web-platform-tests/interop/issues/1387) ↑ card<br>Requests that assistive technology, such as a screen reader, announces a message to the user. | Chrome 141 Firefox 150 Safari 27 | … |
| [Improve interoperability of the WebCodecs API](https://github.com/web-platform-tests/interop/issues/1378) ↑ card<br>Low-level access to individual video frames and chunks of audio samples. | Chrome 94 Firefox 130 Safari 26 | … |
| [CSS `AccentColor` system color reflects `accent-⁠color`](https://github.com/web-platform-tests/interop/issues/1361) ↑ card<br>Provides a consistent way to style custom form controls to match native ones. | Chrome 149 Firefox 103 Safari 16.5 | … |
| Ships in all three already; the proposal is a list of bugs (8) |  |  |
| [Preserve history state after cross-document navigation](https://github.com/web-platform-tests/interop/issues/1441) ↑ card<br>Some browsers discard `history.state` after cross-document navigations, but keep it for same-document navigations. | a bug list, not one feature | … |
| [Improve interoperability of pointer events on touch devices](https://github.com/web-platform-tests/interop/issues/1427) ↑ card | Chrome 55 Firefox 59 Safari 13 | … |
| [Lazy-loaded images should not be completed by out-of-band loads](https://github.com/web-platform-tests/interop/issues/1411) ↑ card<br>Background loads of a lazy image's URL shouldn't leak into its `complete` state or metadata. | Chrome 77 Firefox 121 Safari 16.4 | … |
| [CSS `align-⁠content` for centering in buttons and inputs](https://github.com/web-platform-tests/interop/issues/1410) ↑ card<br>Allows overriding the vertical alignment of text in form controls. | a bug list, not one feature | … |
| [Timing of cross-origin redirects in the Performance API](https://github.com/web-platform-tests/interop/issues/1396) ↑ card<br>Consistent navigation timing and resource timing entries for cross-origin redirects. | a bug list, not one feature | … |
| [Improve `createImageBitmap()` resizing and cropping](https://github.com/web-platform-tests/interop/issues/1390) ↑ card<br>Better image resizing, cropping, and thumbnailing when creating an `ImageBitmap`. | Chrome 59 Firefox 98 Safari 17.2 | … |
| [JavaScript modules in more contexts](https://github.com/web-platform-tests/interop/issues/1386) ↑ card<br>Such as `<script type="module">` in XHTML and SVG documents, and module background scripts in web extensions. | Chrome 61 Firefox 60 Safari 11 | … |
| [Correct handling of powerless and missing color components](https://github.com/web-platform-tests/interop/issues/1334) ↑ card<br>Fixes `none` and powerless components in `color-⁠mix()`, relative color syntax, and gradients. | a bug list, not one feature | … |
| Ships in two engines (21) |  |  |
| [Targeted SVG improvements](https://github.com/web-platform-tests/interop/issues/1469) ↑ card<br>SVG filters, `context-⁠fill`/`context-⁠stroke`, `<use>` without a fragment, and path data APIs. | Chrome 124 Firefox 111 Safari no (status shown: context-fill/stroke) | … |
| [Brotli support in `CompressionStream` and `DecompressionStream`](https://github.com/web-platform-tests/interop/issues/1467) ↑ card | Chrome no Firefox 147 Safari 18.4 | … |
| [JavaScript CSS module imports](https://github.com/web-platform-tests/interop/issues/1466) ↑ card<br>`import … with { type: "css" }` to load CSS resources as constructable stylesheets. | Chrome 123 Firefox 147 Safari no | … |
| [CSS anchor positioning with transforms and animations](https://github.com/web-platform-tests/interop/issues/1457) ↑ card<br>Anchor-positioned elements track transforms on their anchors, and changes to the calculated `anchor()` triggers transitions. | Chrome 144 Firefox no Safari 27 | … |
| [CSS `baseline-⁠source` property](https://github.com/web-platform-tests/interop/issues/1450) ↑ card<br>Controls which baseline is used to align multi-line inline-level boxes with surrounding text. | Chrome 111 Firefox 115 Safari no | … |
| [CSS `@supports at-⁠rule()`](https://github.com/web-platform-tests/interop/issues/1434) ↑ card<br>Checks if a CSS at-rule is supported. | Chrome 148 Firefox 157 Safari preview | … |
| [CSS `symbols()` function](https://github.com/web-platform-tests/interop/issues/1432) ↑ card<br>Defines a counter style inline, such as in `list-⁠style`, without needing `@counter-⁠style`. | Chrome 155 Firefox 35 Safari no | … |
| [Media Session `togglecamera`, `togglemicrophone`, and `voiceactivity` actions](https://github.com/web-platform-tests/interop/issues/1428) ↑ card<br>Provides awareness of media actions on platform UI and hardware, such as muting from a picture-in-picture window. | Chrome 93 Firefox no Safari 18.4 | … |
| [WebDriver BiDi](https://github.com/web-platform-tests/interop/issues/1417) ↑ card<br>A new protocol for browser automation, extending 'classic' WebDriver by introducing bidirectional communication. | Chrome 126 Firefox 117 Safari no | … |
| [Scheduler API](https://github.com/web-platform-tests/interop/issues/1413) ↑ card<br>Helps chunk up main-thread work relative to other tasks. | Chrome 129 Firefox 142 Safari no | … |
| [Keyboard lock in `requestFullscreen()`](https://github.com/web-platform-tests/interop/issues/1409) ↑ card<br>Allows fullscreen pages, such as games, to prevent the Escape key from exiting fullscreen, and to handle other reserved shortcuts. | Chrome no Firefox 151 Safari 26.4 | … |
| [`103 Early Hints` preloading](https://github.com/web-platform-tests/interop/issues/1400) ↑ card<br>Browsers start preconnecting and preloading resources while the server is still generating the page, with consistent resource timing entries. | Chrome 103 Firefox 123 Safari no | … |
| [Multiple import maps](https://github.com/web-platform-tests/interop/issues/1399) ↑ card<br>Allows multiple import maps in a single document, and allows modules to load before unrelated import maps. | Chrome 133 Firefox no Safari 18.4 | … |
| [`requestIdleCallback`](https://github.com/web-platform-tests/interop/issues/1398) ↑ card<br>Queues a function that runs in idle browser time, either at the end of a frame or when the user is inactive. | Chrome 47 Firefox 55 Safari no | … |
| [Customizable/stylable `<select>`](https://github.com/web-platform-tests/interop/issues/1393) ↑ card<br>Customize the button, selected option, picker, and options with CSS. | Chrome 135 Firefox no Safari 27 | … |
| [CSS `box-⁠decoration-⁠break` without a vendor prefix](https://github.com/web-platform-tests/interop/issues/1391) ↑ card<br>Sets whether borders and backgrounds wrap each fragment of a box split across lines, columns, or pages. | Chrome 130 Firefox 32 Safari no | … |
| [Allow CSS transitioning/animating `display` and `content-⁠visibility`](https://github.com/web-platform-tests/interop/issues/1384) ↑ card<br>Useful for delaying `display: none` until an exit animation completes. | Chrome 117 Firefox no Safari 18 | … |
| [JavaScript `Temporal`](https://github.com/web-platform-tests/interop/issues/1381) ↑ card<br>A modern and extensive date/time API to replace `Date`. | Chrome 144 Firefox 139 Safari preview | … |
| [DOM `moveBefore()`](https://github.com/web-platform-tests/interop/issues/1355) ↑ card<br>Relocates a node while preserving its state like focus, animation, or move an iframe without reloading its content. | Chrome 133 Firefox 144 Safari no | … |
| [Deprecate customized built-in elements (via the `is` attribute)](https://github.com/web-platform-tests/interop/issues/1342) ↑ card<br>Remove the feature from the spec and the browsers that implement it. | Chrome 67 Firefox 63 Safari no | … |
| [Sanitizer API, via `document.parseHTML()` and `element.setHTML()`](https://github.com/web-platform-tests/interop/issues/1336) ↑ card<br>Customizes the sanitization of HTML inserted into the DOM, to prevent cross-site scripting attacks. | Chrome 146 Firefox 148 Safari no | … |
| Ships in one engine (43) |  |  |
| [HTML `<iframe allow="cross-⁠origin-⁠isolated">`](https://github.com/web-platform-tests/interop/issues/1470) ↑ card<br>Allows cross-origin isolated features, such as `SharedArrayBuffer`, within iframes. | Chrome 87 Firefox no Safari no | … |
| [CSS `filter(image, ...filters)`](https://github.com/web-platform-tests/interop/issues/1449) ↑ card<br>Takes an image and a filter, and returns a new image. | Chrome no Firefox no Safari 9.1 | … |
| [CSS grid lanes (masonry)](https://github.com/web-platform-tests/interop/issues/1445) ↑ card<br>`display: grid-⁠lanes` creates a layout where items are tightly packed into parallel lanes. | Chrome no Firefox no Safari 26.4 | … |
| [CSS `margin-⁠trim`](https://github.com/web-platform-tests/interop/issues/1444) ↑ card<br>Removes the margins of child elements when they meet the edges of the container. | Chrome no Firefox no Safari 16.4 | … |
| [Responsive iframes](https://github.com/web-platform-tests/interop/issues/1443) ↑ card<br>The `frame-⁠sizing` CSS property sizes an `<iframe>` to its content, avoiding scrolling within the iframe. | Chrome 154 Firefox no Safari no | … |
| [Viewport Segments API](https://github.com/web-platform-tests/interop/issues/1440) ↑ card<br>Adapt layouts to devices where the display is split, such as foldables. | Chrome 138 Firefox no Safari no | … |
| [Device Posture API](https://github.com/web-platform-tests/interop/issues/1439) ↑ card<br>Reflects the posture of a device, such as whether a foldable device is folded, in both CSS and script. | Chrome 132 Firefox no Safari no | … |
| [Promises from programmatic scroll methods](https://github.com/web-platform-tests/interop/issues/1435) ↑ card<br>`scroll()`, `scrollTo()`, `scrollBy()`, and `scrollIntoView()` return promises that resolve when scrolling completes. | Chrome 150 Firefox no Safari no | … |
| [CSS scroll-triggered animations](https://github.com/web-platform-tests/interop/issues/1433) ↑ card<br>`timeline-⁠trigger` and `animation-⁠trigger` play, pause, or reverse animations when the user scrolls to a particular point. | Chrome 146 Firefox no Safari no | … |
| [JavaScript self-profiling API](https://github.com/web-platform-tests/interop/issues/1429) ↑ card<br>The `Profiler` API records data about the execution of a page, which you can send to a server for later analysis. | Chrome 94 Firefox no Safari no | … |
| [`Connection-⁠Allowlist` header](https://github.com/web-platform-tests/interop/issues/1424) ↑ card<br>Limits which origins a page may connect to using fetch and other APIs. | Chrome 152 Firefox no Safari no | … |
| [Element-scoped view transitions](https://github.com/web-platform-tests/interop/issues/1419) ↑ card<br>`element.startViewTransition()` starts a view transition that only affects that element's subtree, allowing multiple transitions to run concurrently. | Chrome 147 Firefox no Safari no | … |
| [CSS `polygon()` vertex rounding](https://github.com/web-platform-tests/interop/issues/1418) ↑ card<br>Such as `polygon(round 20px, …)` to round the corners of a polygon. | Chrome 150 Firefox no Safari no | … |
| [`MediaStreamTrackProcessor` and `VideoTrackGenerator` APIs](https://github.com/web-platform-tests/interop/issues/1414) ↑ card<br>Simplifies real-time processing of video streams. | Chrome no Firefox no Safari 18 | … |
| [CSS `::scroll-⁠marker` and `::scroll-⁠marker-⁠group` pseudo-elements](https://github.com/web-platform-tests/interop/issues/1406) ↑ card<br>Generates navigation markers for scroll containers, for things like carousels, tabs, and tables of contents. | Chrome 135 Firefox no Safari no | … |
| [HTML `<input type="week">`](https://github.com/web-platform-tests/interop/issues/1405) ↑ card | Chrome 20 Firefox no Safari no | … |
| [Web app manifest `share_target`](https://github.com/web-platform-tests/interop/issues/1404) ↑ card<br>Allows an installed web app to appear in the device's share dialog as a target for shared content. | Chrome 89 Firefox no Safari no | … |
| [`NotRestoredReasons` in the Performance API](https://github.com/web-platform-tests/interop/issues/1397) ↑ card<br>Provides report data containing reasons why the current document was blocked from using the back/forward cache. | Chrome 125 Firefox no Safari no | … |
| [CSS `object-⁠view-⁠box` property](https://github.com/web-platform-tests/interop/issues/1385) ↑ card<br>Crops and zooms to an inset area of an image. | Chrome 104 Firefox no Safari preview | … |
| [Extended lifetime `SharedWorker`s](https://github.com/web-platform-tests/interop/issues/1382) ↑ card<br>Gives shared workers extra time to finish work after all pages using them have closed. | Chrome 148 Firefox no Safari no | … |
| [CSS gap decorations](https://github.com/web-platform-tests/interop/issues/1374) ↑ card<br>`column-⁠rule` and `row-⁠rule` display decorative lines between columns and rows of a flex, grid, or multi-column layout. | Chrome 149 Firefox no Safari no | … |
| [CSS `interpolate-⁠size` property and `calc-⁠size()` function](https://github.com/web-platform-tests/interop/issues/1373) ↑ card<br>Allows animating to `height: auto`, and using intrinsic sizes in calculations. | Chrome 129 Firefox no Safari no | … |
| [`PerformanceLongAnimationFrameTiming` entries in the Performance API](https://github.com/web-platform-tests/interop/issues/1372) ↑ card<br>Provides information about rendering updates that take longer than 50 milliseconds. | Chrome 123 Firefox no Safari no | … |
| [CSS `border-⁠shape` property](https://github.com/web-platform-tests/interop/issues/1371) ↑ card<br>Sets the geometry of the border box to a fully custom shape, changing the shape of borders, outlines, and shadows. | Chrome 147 Firefox no Safari no | … |
| [CSS `corner-⁠shape` property](https://github.com/web-platform-tests/interop/issues/1368) ↑ card<br>Alternative shapes for `border-⁠radius`, such as squircles and bevels. | Chrome 139 Firefox preview Safari preview | … |
| [Out-of-order streaming with `<template for>`](https://github.com/web-platform-tests/interop/issues/1366) ↑ card<br>Replaces content at a named range or insertion point, allowing partial updates to be streamed into the document out of order. | Chrome 150 Firefox no Safari no | … |
| [CSS container scroll-state queries](https://github.com/web-platform-tests/interop/issues/1364) ↑ card<br>The `@container scroll-⁠state(...)` at-rule applies styles based on the sticky positioning, snapped, and scrollable state of the container. | Chrome 133 Firefox no Safari no | … |
| [CSS anchored container queries](https://github.com/web-platform-tests/interop/issues/1363) ↑ card<br>`@container anchored(fallback: …)` applies styles based on which position fallback an anchor-positioned element is using. | Chrome 143 Firefox no Safari no | … |
| [CSS `justify-⁠items` and `justify-⁠self` in block layout](https://github.com/web-platform-tests/interop/issues/1360) ↑ card | Chrome 130 Firefox no Safari no | … |
| [Interest invokers](https://github.com/web-platform-tests/interop/issues/1358) ↑ card<br>The `interestfor` attribute triggers actions on a target element when the user shows interest, such as via hover, focus, or long-press. | Chrome 142 Firefox no Safari no | … |
| [CSS `@function`](https://github.com/web-platform-tests/interop/issues/1357) ↑ card<br>Defines custom functions that take arguments and return a CSS value. | Chrome 139 Firefox no Safari no | … |
| [CSS `:has-⁠slotted` pseudo-class](https://github.com/web-platform-tests/interop/issues/1356) ↑ card<br>Matches `<slot>` elements that have slotted content, rather than showing fallback content. | Chrome no Firefox 136 Safari no | … |
| [CSS `random()` function](https://github.com/web-platform-tests/interop/issues/1354) ↑ card<br>Chooses a random numeric value within a specified range. | Chrome 156 beta Firefox preview Safari 26.2 | … |
| [CSS `text-⁠size-⁠adjust` property](https://github.com/web-platform-tests/interop/issues/1353) ↑ card<br>Disables or modifies the browser's default text size adjustment on small screens. | Chrome 54 Firefox no Safari no | … |
| [`LayoutShift` entries in the Performance API](https://github.com/web-platform-tests/interop/issues/1352) ↑ card<br>Measures the layout stability of web pages based on movements of the elements on the page. Used to calculate cumulative layout shift (CLS). | Chrome 84 Firefox no Safari no | … |
| [CSS `flex-⁠wrap: balance`](https://github.com/web-platform-tests/interop/issues/1350) ↑ card<br>Distributes items in a multi-line flex container so each line has about the same number of items. | Chrome 150 Firefox no Safari no | … |
| [CSS `reading-⁠flow` and `reading-⁠order` properties](https://github.com/web-platform-tests/interop/issues/1349) ↑ card<br>Sets the order in which flex or grid elements are rendered to speech or reached via focus navigation. | Chrome 137 Firefox no Safari no | … |
| [`CSSPseudoElement` API](https://github.com/web-platform-tests/interop/issues/1347) ↑ card<br>Represents pseudo-elements in JavaScript, allowing things like detecting clicks on a `::backdrop`. | Chrome 149 Firefox no Safari no | … |
| [CSS scroll snap events](https://github.com/web-platform-tests/interop/issues/1345) ↑ card<br>`scrollsnapchanging` and `scrollsnapchange` events fire when a scroll gesture changes the selected scroll snap target. | Chrome 129 Firefox no Safari no | … |
| [CSS `if()` function](https://github.com/web-platform-tests/interop/issues/1343) ↑ card<br>An inline conditional value that returns a value based on a set of conditions. | Chrome 137 Firefox no Safari no | … |
| [Speculation Rules API](https://github.com/web-platform-tests/interop/issues/1335) ↑ card<br>Hints to the browser to proactively download pages in the background so they appear instantly when the user navigates to them. | Chrome 109 Firefox no Safari no | … |
| [Shadow DOM `referenceTarget`](https://github.com/web-platform-tests/interop/issues/1333) ↑ card<br>Forwards IDrefs (such as `for` and `aria-⁠labelledby`) to an element inside a shadow root. | Chrome 152 Firefox no Safari no | … |
| [WebUSB API](https://github.com/web-platform-tests/interop/issues/1331) ↑ card<br>Exposes USB-compatible devices to web pages. | Chrome 61 Firefox no Safari no | … |
| Ships in no stable engine (7) |  |  |
| [Streaming HTML insertion with sanitizing](https://github.com/web-platform-tests/interop/issues/1471) ↑ card<br>Methods such as `streamHTML()` and `streamAppendHTML()` return writable streams that incrementally insert sanitized HTML into an existing tree. | Chrome no Firefox no Safari no | … |
| [Contact Picker API](https://github.com/web-platform-tests/interop/issues/1462) ↑ card<br>`navigator.contacts` allows users to share selected entries from their contact list. | Chrome no Firefox no Safari no | … |
| [CSS `scroll-⁠snap-⁠type: pair`](https://github.com/web-platform-tests/interop/issues/1438) ↑ card<br>Snap to the same element in both axes. | Chrome 156 beta Firefox no Safari no | … |
| [Additional sanitizing HTML methods and trusted types integration](https://github.com/web-platform-tests/interop/issues/1416) ↑ card<br>Such as `beforeHTML()`, `appendHTML()`, and `replaceWithHTML()`, which insert sanitized HTML into an existing tree. | Chrome no Firefox no Safari no | … |
| [HTML `headingoffset` and `headingreset` attributes](https://github.com/web-platform-tests/interop/issues/1412) ↑ card<br>Offsets the heading level of descendant `<h1>`–`<h6>` elements. Includes the CSS `:heading()` pseudo-class. | Chrome no Firefox no Safari no | … |
| [CSS `line-⁠clamp` property](https://github.com/web-platform-tests/interop/issues/1379) ↑ card<br>Limits the text in a block container to a certain number of lines. Also allows custom end text if content overflow occurs. | Chrome no Firefox no Safari preview | … |
| [CSS linked parameters](https://github.com/web-platform-tests/interop/issues/1346) ↑ card<br>Allows passing custom properties into external SVG resources, such as colors. | Chrome no Firefox preview Safari no | … |
| Not in MDN compat data yet (14) |  |  |
| [Pause media in hidden iframes](https://github.com/web-platform-tests/interop/issues/1468) ↑ card<br>The `media-⁠playback-⁠while-⁠not-⁠visible` permission policy pauses audible media in iframes that are hidden. | not in compat data yet | … |
| [Progressive rendering of JPEG XL and AVIF images](https://github.com/web-platform-tests/interop/issues/1459) ↑ card<br>Displays a preview version of the image while the rest downloads. | not in compat data yet | … |
| [CSS `inherit()` function](https://github.com/web-platform-tests/interop/issues/1453) ↑ card<br>Like `var()`, but uses the parent element's value of the custom property. | not in compat data yet | … |
| [CSS `ident()` function](https://github.com/web-platform-tests/interop/issues/1452) ↑ card<br>Builds an identifier from strings, integers, and other identifiers, such as `view-⁠transition-⁠name: ident("item-⁠" sibling-⁠index())`. | not in compat data yet | … |
| [Single-axis scroll containers](https://github.com/web-platform-tests/interop/issues/1436) ↑ card<br>Such as `overflow: scroll clip`, which allows `position: sticky` to work relative to different scroll containers per axis. | not in compat data yet | … |
| [Web app installation via `<install>` and `navigator.install()`](https://github.com/web-platform-tests/interop/issues/1430) ↑ card<br>Prompts the user to install a progressive web app. | not in compat data yet | … |
| [WebRTC SFrame encryption](https://github.com/web-platform-tests/interop/issues/1415) ↑ card<br>Native end-to-end encryption in WebRTC, without exposing keys to JavaScript. | not in compat data yet | … |
| [CSS `random-⁠item()` function](https://github.com/web-platform-tests/interop/issues/1403) ↑ card<br>Picks a random item from a list of values. | not in compat data yet | … |
| [`OpaqueRange` for form controls](https://github.com/web-platform-tests/interop/issues/1401) ↑ card<br>A live range of text within `<textarea>` and `<input>`, for things like custom highlights and positioning popovers next to text. | not in compat data yet | … |
| [CSS `:host(:has(…))`](https://github.com/web-platform-tests/interop/issues/1395) ↑ card<br>Selects a shadow host based on its light DOM descendants. | not in compat data yet | … |
| [CSS color gamut mapping](https://github.com/web-platform-tests/interop/issues/1394) ↑ card<br>Displays out-of-gamut colors (such as from `oklch()`) closer to the intended color, rather than clipping RGB channels. | not in compat data yet | … |
| [CSS anchor positioning across shadow roots](https://github.com/web-platform-tests/interop/issues/1351) ↑ card<br>Allows elements in shadow trees to reference anchor names defined in "higher" trees. | not in compat data yet | … |
| [HTML `focusgroup` attribute](https://github.com/web-platform-tests/interop/issues/1348) ↑ card<br>Combines descendant elements into a single tab stop, with arrow-key navigation between them. | not in compat data yet | … |
| [CSS `var()` in size container queries](https://github.com/web-platform-tests/interop/issues/1344) ↑ card<br>Such as `@container (width >= var(-⁠-⁠min-⁠width))`. | not in compat data yet | … |

On the site: Interop is where a one-engine feature picks up its second and third engines, and two engines is this page's graduation bar. A card whose feature lands in the final 2027 focus areas (announced early in the year) is the strongest signal on this page that it's about to move to the homepage, so the next sweep re-reads this table first.

## The long tail: independent engines

"The platform" reaches past Chromium, Gecko, and WebKit. Two teams are building new engines largely from scratch, a useful reminder that the frontier above is the *front*, while most of the web runs well behind it. Both are pre-release and racing toward *baseline* rather than 2026 CSS, so on the features on this page they're mostly "not yet." Status as of mid-2026; check the live links, because this moves fast.

### Ladybird pre-alpha

A from-scratch engine (LibWeb) with no Chromium/WebKit/Gecko code, built by a nonprofit. It's now on the WPT dashboard (~2.07M subtests passing) and racing to render the real web.

On these features: it has the **Popover API**, and added **initial CSS anchor positioning** (`position-anchor`) in April 2026. The newer/niche ones (text-box-trim, contrast-color, scroll-driven animations, corner-shape, masonry) aren't there yet. Baseline first, frontier later.

Live: [ladybird.org/news](https://ladybird.org/news/) · [wpt.fyi (ladybird)](https://wpt.fyi/results/?product=ladybird)

### Servo alpha ~Jul 2026

A Rust engine, originally Mozilla's, now under the Linux Foundation and aimed at embedding. WPT pass rate around 95% in its focus areas; a desktop browser is still years out.

It spends its energy on layout fundamentals (flexbox, grid, tables, floats), with recent adds like `cursor` color and `content: image()`. It generally hasn't implemented the frontier CSS here yet (popover, anchor positioning, text-box-trim).

Live: [servo.org/blog](https://servo.org/blog/) · [github.com/servo](https://github.com/servo/servo)

The point: these two are *why* "ship to baseline, prototype the frontier" is the right discipline. To support the independent, indie-web engines, I lean on what's broadly shipped and leave what's newest in one browser parked here. Good deep-dive comparison: [Browser Engines 2026](https://www.youngju.dev/blog/culture/2026-05-14-browser-engines-2026-chromium-gecko-webkit-servo-ladybird-comparison-deep-dive.en).

← back to the [garage](https://aadhar.sh/garage) · [aadhar.sh](https://aadhar.sh/)

© 2026 Aadharsh Pannirselvam · built in the open · triangulated from chromestatus.com + chrome://flags (Canary) + webkit.org/standards-positions + MDN browser-compat-data · last swept 2026-10-08 against MDN browser-compat-data 8.1.5, Interop 2027 index 2026-10-08

[safari-technology-preview]: https://developer.apple.com/safari/technology-preview/ "Apple's early-release Safari for developers, updated every few weeks with web features that haven't reached regular Safari yet."
[compression-dictionaries]: https://www.rfc-editor.org/rfc/rfc9842.html "A scheme where the browser keeps an earlier copy of a file and the server sends only a compressed difference against it."
[temporal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal "A new built-in JavaScript API for dates and times, with proper time zones and calendars, meant to replace the error-prone Date object."
[gecko]: https://en.wikipedia.org/wiki/Gecko_(software) "The browser engine inside Firefox that turns HTML, CSS and JavaScript into the page you see."
[pwa]: https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps "Progressive web app: a website built to install and behave like an app, with offline support, a home-screen icon and its own window."
[chromium]: https://en.wikipedia.org/wiki/Chromium_(web_browser) "The open-source browser project that Chrome, Edge, Brave and Opera are built on."
[jpeg-xl]: https://en.wikipedia.org/wiki/JPEG_XL "A newer image format from the JPEG committee. It compresses well, decodes in parallel, and can repack an existing JPEG losslessly into a smaller file."
[browser-compat-data]: https://github.com/mdn/browser-compat-data "MDN's open dataset recording which version of each browser supports each web feature."
[leading]: https://en.wikipedia.org/wiki/Leading "The extra space between lines of text. CSS splits it into two halves, one above the letters and one below, called half-leading."
[baseline]: https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility "A label for web features that work in the current versions of all the major browsers: Chrome, Edge, Firefox and Safari."
[popover-api]: https://developer.mozilla.org/en-US/docs/Web/API/Popover_API "A browser API for showing content above the rest of the page, such as menus and tooltips, with built-in show, hide and dismiss behavior."
[anchor-positioning]: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Anchor_positioning "A CSS feature that places one element relative to another, such as a tooltip pinned to its button, without JavaScript measuring positions."
[masonry]: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout/Grid_lanes "A layout that stacks items in columns at their natural heights, each filling the gap under the one above, like bricks in a wall."
[tc39]: https://tc39.es/ "The committee that designs JavaScript. Proposals advance through numbered stages, and stage 4 means finished and added to the language."
[xslt]: https://en.wikipedia.org/wiki/XSLT "A language for transforming XML documents into other formats, such as HTML. Browsers have been able to run it natively for decades."

Source: https://aadhar.sh/garage/horizon
