Glass contrast: text on glass reaches 4.5:1 over any backdrop (static floor + adaptive density) (D365) #448
Open
opened 2026-11-09 06:53:11 +00:00 by kayg
·
7 comments
No Branch/Tag specified
main
j470b-server-mesh-rejoin
mesh-compat-382
j444b-d381-key-wins-flood
j461b-macos-utun-reader-thread
j475b-remove-host-motion
fix-reconnect-new-link
j481-d380
dev
j479-mesh-soak
fix/j476-host-facts
j475-motion-pill-split
j470-mac-node-join
root-upgrade-d376
j474-review-merge
latency-targets-120hz
nightly-suite-shards
gotatun-pin-j469
macos-utun-reader-thread
fix-flood-echo-stall
wip/j416
motion-fast-ease-out
mesh-addhost-j259
takeover-close-terminals-d368
kbd-instant-followups
fix-trust-key-double-click
nightly-suite-partial-report
appwin-icons-j207
rusqlite-slice1
swipe-content-first-d374
mesh-mtu-381
addhost-instant-open
glass-contrast-d365-mock
glass-contrast-d365
signout-followups-442
fix-startup-toast-red
research-gotatun-matrix-j317
themes-sync-j304
fix-load-flakes
fix-nix-zig-patch
fix-compute01-browser-baseline
ratchet-size-baseline-419
predict-auto-d130
revoke-coverage-d367
nightly-suite-queue-timeout
nightly-browser-suite
fix-rustsec-2026-0459
fonts-ioskeley-obel
log-retention-d375
fix-kbd-no-anim-427
fix-dev-red-specs-2
takeover-proof-305-j205
wip/j410-server-pump
ui-consistency-d372
fix-osc52-clipboard
libghostty-vt-1.0
forget-retired-host-d369
j404-merge-426
fix-setup-lock-race
fix-touch-select-426
revoke-access-report-ui
fix-predictive-echo-specs
fix-selinux-agent-exec
wip/j395-checkpoint2
wip/j395
j391-review-scratch
ui-consistency-j195-deferred-rebased
fix-rustc-paths-421
decisions-wording-341
fix-install-suite-red
fix-dev-rtt-specs
fix-pending-removal-align
fix-privileged-skip
fix-install-suite-404
review-j368
mesh-dns-j254
revoke-access-report-d356b
revoke-privilege-round5
fix-removal-confirm
review-j360
gotatun-rotation-367
deps-refresh-340
review-j354
review-J351
review-j350
addhost-takeover-d359
revoke-report-items-347
revoke-end-sessions-d362
review-j348
size-mesh-ssh-346
size-trim-337
revoke-access-diff-d356
j259-pre-rebase-b06c5a6
j305-ended-at-start-spec
addhost-sudo-consent
fix-layout-real-specs-411
fix-shell-open-411
fix-console-hosts-373
fix-shell-exit
fuzz-host-name-353
fix-host-details-410
macos-filter-j197
research-gotatun-tricks
revoke-p3-j216l
stale-state-pill
revoke-followups-j216k
revoke-progress-j216e
backup/macos01-20261106/lume/j197
backup/macos01-20261106/lume/appwin-icons-j207-stash0
backup/macos01-20261106/claude/stash-0
backup/macos01-20261106/claude/stash-1
backup/macos01-20261106/claude/J80-macos-s1
backup/macos01-20261106/claude/J80
backup/macos01-20261106/claude/J80-slice1-20261102
backup/macos01-20261106/claude/J16b-appwin-on-dev
backup/macos01-20261106/claude/appwin-launch-idle-146
backup/macos01-20261106/claude/J80-s1-run
wip/j259
trust-285-p3c
runtime-lock-j216f
mesh-ssh-p3-j175
tun-proof-p3b
trust-285-p3
terminal-polish-j271
tun-proof-p3
ssh-install-flakes-j216i
fuzz-fixes-j264
trust-285-a
trust-285-b
restart-oversized-fixture-j198
backup/macos01-20261106/lume/appwin-icons-j207
restart-bound-j216h
up-preflight-p3-j198
backup/macos01-20261106/lume/j251
up-preflight-j198
wip/up-preflight-j198-j245-p2
wip/up-preflight-j198-j245-p1
agent-fsync-offloop-j196
wip/up-preflight-j198-j245
settings-offscreen-j231
wip/up-preflight-j198-j226-inspection
wip/j103f
themes-j206
wip/up-preflight-j198-j226
flakes-j216c
backup/macos01-20261106/claude/j197
flakes-j216b
backup/macos01-20261106/claude/j197-red
wip/up-preflight-j198-review
flakes-j216
backup/macos01-20261106/claude/appwin-icons-j207
engineering-j208b
appwin-mock-j209
toast-stack-j165
engineering-j208
decisions-d351
decisions-d350
decisions-d349
appearance-presets-mock-j129
decisions-d348
docs-audit-spec
doc-coverage-baseline-raise
decisions-d347
decisions-d346b
decisions-d346
j88h-profile
ui-consistency-j195-deferred
ui-consistency-j195
engineering-practices-j194
preferred-login-ui-j166
signin-pools-263-j174
macos-node-265-s1
decisions-d345
decisions-d344
proxy-cgroup-j164
verify-quick-fuzz-329
proxy-start-io-j168
timing-flakes-j163
up-263-s1
mcp-events-j147
verify-quick-fuzz-j158
proxy-p3-j159
agent-resource-j157-detect
wip/j103d-final
remote-cancel-hang-j155
proxy-traefik-281-j62d
j149-verify-quick
proxy-traefik-281-j62b
j150-drop-unnamed
wip/j79d-red-r4
wip/j103d-d332
rootless-toast-j142
wip/j103d-head
wip/j103d
j125c-mcp
macos-build-j136
backup/macos01-20261106/claude/j136
wip/j79d-red-cli
wip/j79d-red-server
j121-link-liveness
j118c-tracer-failures
wip/j79c-head
release-desktop-j131
j124c-signed-macos
wip/j79c-red-p3
wip/j79c-red-p23
wip/j79c-red-p22
merge-j128-check
perf-deferred-j128
sidebar-toggle-260
j127-shortcut-changed-dot
j125-mcp
j118-tracer-failures
j126-account-summary
settings-sections-mock-j92
j120-pinch-lock
sidebar-292
backup/macos01-20261106/claude/j120
taste-2026-11-03
row-actions-242-215
backup/macos01-20261106/claude/stash-0-untracked
backup/macos01-20261106/claude/J80-265-s1-run
backup/macos01-20261106/claude/J80-macos-s1-20261102
j112-canvas-commands
ui-no-overshoot-scroll
fix-recovery-restart
backup/macos01-20261106/claude/stash-1-untracked
taste-2026-11-02
size-baseline-ratchet
machine-cards-structure-j108
j109-boot-copy-mockups
fix-dev-red
wip/up-263-s1
j95b-settings-rows
proxy-traefik-281
j95-option-rows
docs-human-j91
docs-keys-palette
fix-flood-1013-270-j38d
mesh-ssh-s1
mesh-ssh-s0
addhost-d291-tunnel
test-stability-load
mesh-relay-220
repro-296
shim-thp-off-24
mesh-pins-258-v2
mesh-dns
signin-d311-merged
docs-pages
ui-size-283
fix-ui-batch-specs
wip/j12b-edge-update
gate-on-arm
fix-rename-sync-293
trust-285-j60b
fix-focus-ring-clip
integ-ui-1101
edge-update-v2
signin-d311
taste-2026-11-01
layout-per-tab-250
ui-approved-splitopt-224
ui-approved-splitopt-224-on-dev
icon-text-centre-282
ui-approved-189-212-216
research-reverse-proxy
transfer-budget-320
review-mesh-pins-258-r2
sidebar-pill-259
review-mesh-relay-220
wip/j12-edge-update-v2
nix-package
status-crash-loop-237
fix-dev-test-failures
fix-fixture-socket-path
wip/mesh-relay-220-pre-dev-advance-rebase
fix-canvas-side-clip
research-head-to-head
frosted-real-256
review-mesh-pins-258
wip/mesh-relay-220-pre-final-rebase
review-layout-per-tab-250-r2
broad-suite-triage
nix-devshell
review-status-crash-loop-237-r3
wip/nix-devshell-pre-p3
taste-frosted-real
wip/code-serve-demo-2026-10-30
wip/code-serve-glass-2026-10-30
wip/code-serve-clusters-2026-10-30
wip/code-merge-release94-dev-2026-10-30
wip/rproxy-delta-scratch-2026-10-30
wip/stale-note-257-staged-2026-10-30
wip/d241-size-staged-2026-10-30
review-broad-suite-207
wip/mesh-pins-258-pre-split
wip/mesh-relay-220-pre-rebase
mesh-measure-220
plan-211-update
review-edge-autoupdate-r3
perf24-reconnect
review-stale-note-r2
release-prod-live
d241-shell-marker
review-mesh-pins-server-258
review-revocation-r3
review-setup-token-r2
review-sync-journal-r3
wip/review-mesh-pins-258-prune-review-1995fee46
frosted-scroll-256
sync-journal-r3
revocation-per-account-wip
review-tokio-asyncfd-0386
review-sync-p3s
wip/stash-8by64-stash0-2026-10-30
mesh-drag-r2
setup-token-data-wip
release-9-4
auth-busy-1013
mesh-view-overlap
sync-p3s
review-auth-busy-wake
review-readd-r8-wip
mesh-drag-bounds
audit-auth-93
app-windows-stream-start
review-authority-cap-r3
app-windows-r7-measure
readd-minimal-r6
readd-minimal-r5
app-windows-r7-soak
readd-minimal-r4
app-windows-r7
app-windows-r6
readd-minimal-r3
readd-minimal-r2
readd-minimal
merge-release8-dev
review-busy-page-liveness-r2
review-busy-page-liveness
mesh-view-trim
review-authority-read-retry-r2
review-remove-uninstall-r2
release-retry-reattach
release-retry
review-remove-uninstall
review-authority-read-retry
app-windows-ship
mesh-autoswitch-p2
mesh-ssh
mesh-relay
mesh-macos-node
screen-and-control
session-labels
review-real-logins-r5
review-real-logins-r4
review-real-logins-r3
shim-memory-r2
review-real-logins-r2
shim-memory
shadow-addhost-instant
shadow-real-logins
shadow-same-session-flood
shadow-prod-perf-fix
hosts-from-server
app-windows-real
app-windows-transport
release-prod-6w
mesh-policy-verify
quiet-close-toast
dev-merge-r4
review-mesh-agent-transport
review-app-windows-transport
settings-roundtrip
review-release-pipeline-r4
mesh-agent-transport
ended-strip-fix
review-audit-ca-update-d244
settings-rows
phone-width
signout-clear
fix-restart-tests
dev-merge-rp
signer-hardening
review-audit-ca-update
terminal-fidelity
review-audit-ca-update-wip
mesh-integrate
review-mesh-agent-transport-515d3712
review-mesh-dataplane-feec1988
mesh-dataplane
signout
research-canvas-node
wip/review-renderer-cost
wip/review-terminal-fidelity
padlock-glyph
review-release-pipeline-2e95b689
phone-polish
sidebar-polish
inbox-generic
review-mesh-dataplane-378d4055
prod-batch
ui-inbox
ui-ca-unlock
ui-machine-sections
review-mesh-dataplane-a5f744fb
tls-session-prod
session-bounds-prod
fix-dead-session
session-bounds
tls-session
palette-polish
shortcuts-d222
research-appmeta
phone-gutter
ui-centre-button
wip/ui-centre-button
wip/tls-session
review-addhost-breakit
ui-platform
renderer-cost
remote-agents
mesh-api
ssh-bootstrap
prod-hotfix2
prod-fixes
review-remote-agents
review-mesh-dataplane
session-life
research-browser
review-mesh-api
research-capture
review-mesh-api-round3
review-mesh-api-round1
gate-stability
review-gate-stability
review-relay-direct
review-mesh-api-findings
proto-sessions
font-picker
proto-addhost
proto-windows
mesh-core
latency-gate
research-bsd
research-discovery
keytray-mods
mock-core
ui-tweaks
fix-motion-sel
fix-motion
integrate2
fix-robust
t-web
t-server
vt-ghostty
fix-snap-echo
t-web-auth
review-vt2
t-agent
lane-protocol
lane-shim
lane-auth
s1-base
s1-contracts
s1-workspace
No results found.
No labels
launch-qa
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
kayg/infraternal#448
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Owner report 2026-11-09 (screenshot): in the #146 app-window mockup, the Send keys popover's glass over a light app window leaves its section labels ("Mac shortcuts", "Keys", "Function keys") and key hints ("⌘ Tab", "esc") faint grey on light grey. Owner asked whether contrast can be fixed dynamically. Related: D365 (4.5:1 for all text), D322/D338 (presets), D372, D375.
Orchestrator's approach, built in the shared glass material (packages/ui: glass-bed, overlay tokens, themes.ts):
Job J414, branch
glass-contrast-d365, base origin/dev4754b14a7. Coordination: J304c extends the audit onthemes-sync-j304; this job keeps its glass audit in its own module so the two merge cleanly.Start. Branch
glass-contrast-d365, base origin/dev4754b14a7. Plan: (1)packages/ui/src/glass-contrast.ts, an exact worst-case solver; (2)themes.tswrites per-theme glass rules; (3) red specs, then fix; (4) low-res backdrop sampler and its measured cost; (5) screenshots; (6)verify quickand the transfer budget. Estimate 4 h.Findings so far (evidence:
~/.cache/codex-scratch/j414/evidence/table-*.txton the control host, numbers below from dev-02 runs of the solver):theme-audit.ts/theme-audit.test.ts(only onthemes-sync-j304) and the?appmock=desktop&pop=keysmockup (only onappwin-mock-j209). So the glass audit lives in its own file (glass-contrast.test.ts) using its own luminance helpers and re-uses nothing from the J304 audit; they merge without touching each other. The mockup repro spec goes on a verify-only branch that mergesappwin-mock-j209; the shared-material fix has no dependency on it.data-glass-under(a remote window's picture) takes the static set; a sampler can refine it.Progress 1. Pushed
glass-contrast-d365(head239d182e9): solver + audit (122 unit cases pass on dev-02), themes.ts glass rules, sampler + cost spec. Browser before/after run is queued on dev-02 (position 8 of 8; compute01 is as long). Contrast table (exact worst case over black..white; evidencetable-before-after.txt):Cost of applying a theme: 8 solves, 2.3 ms in bun on dev-02 (once per theme change, not per frame). Note the trade: under the static floor fg2 and fg3 land within 0.05:1 of each other, so the secondary/tertiary difference is carried by size and weight, not colour, on those surfaces.
Finish. Branch
glass-contrast-d365, head56e5683545e0e1a81eb441ad9271c48454ab979f(base origin/dev4754b14a7). Verify-only branchglass-contrast-d365-mock= this + the #146 mockup (appwin-mock-j209) + the marker; that is where the pixel spec runs.Gates (evidence: gate4/quick.log, gate/budget-*.log):
verify quick: PASS on all 10 rows (scope, migrations, vendoring, fmt, doc ratchet, shape lints, doc comments on diff, test waits, ui literals, tracer typecheck).bun run transfer-budget.ts, no cap changed: exit 0. Critical gzip 318,994 -> 320,090 B (+1,096; cap 327,680), brotli 266,497 -> 267,548 (+1,051); deferred and on-demand unchanged.verify fastNOT run (task asked for quick + budget; TS-only change).Pixel table (lowest contrast in the popover, before -> after) is attached as pixel-contrast-table.txt; before/after montages attached.
Review of #448: glass contrast (D365)
Reviewed SHA:
56e5683545e0e1a81eb441ad9271c48454ab979f(glass-contrast-d365); mockup branchglass-contrast-d365-mocke96b1bed3has the samepackages/uicode (empty diff there).Verdict: REQUEST CHANGES
The pixel claim and the static maths hold. But (a) the owner's approved opacities are not kept, in the default themed set as well as over pictures, and (b) the contract that switches on the guarantee is fragile. Neither is a bug in the arithmetic.
What I verified
bun test packages/ui/src/glass-contrast.test.tson a checkout of56e5683)~/worktrees/j422~/.cache/codex-scratch/j422/spec-fixed.log,evidence/fixed/glass-contrast/ratios.jsonlspec-prefix.log,evidence/prefix/glass-contrast/ratios.jsonlevidence/break.ts. Worst underfg-8% fill: none below 4.5.core-screen.ts:441) passes table themes, so no churn. A level change does not re-solve.evidence/cost.tsglass-contrast.tsminified is about 2.4 KB, about 1.2 KB brotli, plus about 1 KB inthemes.ts.glass-adapt.tsis imported by nothing in the app, so it is tree-shaken and costs 0 today. I did not run the size gate (reviewers don't runverify).bun buildP1: the approved opacities are not kept (D160 / owner-approved levels)
The themed set, which applies to every glass card in the app, changes approved opacities on 13 of 40 theme/level pairs. It is not only over pictures. From
evidence/themed.ts:On seven dark themes, Clear and Frosted become the same opacity, and Frosted is up to 3× its approved value. The TASTE row and the PR text say the level keeps its approved opacity over the theme's own backdrop. That is true only for the four light themes and for Ink/Mocha Tinted. Dark Frosted and Clear were not kept.
Over
data-glass-under, the levels collapse. The static alpha for levels 2–4 differs by ≤0.03 on 8 of 10 themes. Ink: 0.84 for Tinted, Clear and Frosted (nominal 0.70/0.40/0.20). Paper: 0.78/0.75/0.75 (nominal 0.78/0.50/0.30). In the 1440×900 and 393×852 contact sheets the four columns (Solid, Tinted, Clear, Frosted) look the same on the Ink rows. On Paper, the light, white and black rows are also near-identical. Tinted, Clear and Frosted do not look different over a picture. That is the worst case for the owner's purpose of seeing Frosted blur.The ceiling is 0.92 and the static set sits at 0.75–0.84, which is near-opaque. D375 says "an overlay is never opaque unless Solid". I could not find the text of D365 or D375 in
DECISIONS.mdonorigin/devor on this branch (grep returns nothing), so I compared against the TASTE row and D160 only. Please confirm D365/D375 are in the decisions file.Only the owner can approve this trade-off. Recommendation: keep the approved opacity and move the work to the text. Over Frosted/Clear on a picture, adjust the label colours or give text a solid pill (the existing text-shadow halo, or a per-row backing), and only raise tint when text alone cannot reach 4.5:1. If the owner prefers more tint, they should say so with numbers on screen. Either way, get the owner to see the dark-theme Frosted change before merge.
P2
"Any backdrop" holds only over
data-glass-under. The themed set assumes the terminal behind is betweenbgandbglifted 35% tofg. Brute force with a block of an ANSI colour covering a fraction of the glass:fg3over#f9e2af);Real terminals do this (a vim status line, htop bars, a git diff). Say in the TASTE row and the module doc that the themed set is an assumption, not a bound. The pixel spec and unit audit never exercise it.
Pixel spec margin and coverage. The 4.60 floor is within quantisation of 4.5. The spec does not hover, so the 8% fill is never measured on pixels. I also found
--hoverbackgrounds widely used on glass (rows, kbd chips, tabs, settings rows). A 15% fill on top (selected row or a pressed state) drops ink Solidfg2to 3.65 in my brute force. No such fill is used in the checked popovers, but nothing stops one. Make the audit fill the maximum fill the stylesheet really uses, or deriveglassFillfrom--hover.Muddy look over a dark picture on Paper (taste). Cards 2–4 over black or a dark picture render as a flat mid-grey slab (0.75 white tint over black → about rgb 191) with grey text: it passes 4.6:1 but reads grey-on-grey, and the blurred bottom edge shows as a smear (see
fixed-1440x900-paper.pngandfixed-393x852-paper.png, rows 2 and 4). The pre-fix version was unreadable, so this is better, but it is not "native".P3
themes.ts). The memory notes say the tracer budget is nearly full on dev: run the size gate before merge and report the delta.ok:falsereturns are possible at the ceiling; the audit assertsokfor the shipped themes, but a user-added theme has no such check.Is
data-glass-undera sound contract? No: opt-in, with a silent failure modemount.on-demand.ts:159-161. The mockup already needed it on the card root and on the phone stage, which shows that the card root alone was not enough: the phone popover is not a descendant of the card. The selector is[data-glass-under] :where(...), so any popover portalled outside the marked element gets the themed set and the faint text returns, with no error.Safer default (my proposal): invert it. Make the static floor the default for glass surfaces, and opt out (
data-glass-backdrop="theme") on the few places where the backdrop is the theme's own terminal or canvas. Forgetting then costs a slightly denser card, never unreadable text. Or, better, make it automatic: in the shared overlay/popover component, on open, callelementsFromPointat the surface's box and treat anyvideo,canvasorimgbelow as "under a picture" (set the marker on the surface itself, not on an ancestor). That removes the portal problem and also letswatchBackdropstart without each caller wiring it. With P1 resolved this default costs much less.TASTE check
Text ≥4.5:1 over real pixels: passes (4.60 floor). Levels still different, opacities kept: fails (P1). No fake glass: no new borders or bevels seen. Mockup chrome unchanged. Reduced to what I measured; I did not run
verifyor the size gate.Evidence
Screenshots attached below:
fixed-1440x900-ink.png,fixed-1440x900-paper.png,fixed-393x852-paper.png(4×4 sheets: rows light / dark / white / black picture, columns Solid / Tinted / Clear / Frosted) andbefore-after-1440-paper-light-card3.png. All raw files oninfraternal-dev-02-std:~/.cache/codex-scratch/j422/evidence/{fixed,prefix,sheets}plusbreak.ts,break2.ts,cost.ts,themed.ts,spec-*.log. I ran the spec through the already-served mockups withnice -n10, 2 workers, not throughbuild-slot: it builds nothing, and the slots were full. Pixel spec needs the mockup, so I did not run it onglass-contrast-d365alone (it skips there by design).Round 2 (answer to #448 comment 88650), head
c5873a42110e4a2f0239d0868a0455f67a79f9c9. D365/D375 are in DECISIONS.md as numbered items; the review's grep missed them. All seven orchestrator decisions are in.1. Approved opacities kept. The themed set no longer touches opacity. Each text role moves in lightness as far as it must; opacity rises only where no text colour can reach 4.5:1. Of 40 theme/level pairs, one is raised: dracula Frosted 0.20 → 0.37 (Clear stays 0.40, so the levels stay distinct). Full table in
coverage-sweep.txt. Tinted/Clear/Frosted are three different opacities on every theme (asserted).2. Terminal bound. The themed range is now the theme's own 18 colours (16 ANSI, text, background): the page colour up to a block of any of them covering at most 20% of the card, ordered by what the card shows. 20% is a chosen bound, said so in the code and in a test. Sweep (pairs raised of 30 thin ones): 10–15% → 0; 20% → 1; 25% → 4; 30% → 8; 35% → 9; 50% → 16 and Clear = Frosted opacity on ink, mocha, rosepine, nord, dracula; 100% → 23. At 100% the approved levels cannot hold the line over a full bright screen, and a test records that Ink Frosted fails there. The audit holds all 30 pairs over every colour at every coverage to 20% in every fill state.
3. No marker.
glass-under.tsfinds the picture itself (hit tests at the corners and centre, below the surface:video,img≥ 64 px,canvas.aw-surface,[data-glass-picture]) and marks the surface;anchored-popovercalls it on open and each placement, and the confirmation card through its glass bed. The mockup now marks only its picture; nothing marks the window or the stage. Not covered: glass built outside those two components (palette, sheets, toasts).Red on today's head (56e568354's
packages/uiwith the new mockup and specs): 64 failed ondata-glass-undermissing on the popover (red2/run.log); the pre-fix tree failed 64/64 on contrast earlier.4. Fills. Taken from the stylesheet: hover (text 6%), chip (9%), selected (accent-soft 16%), selected+hover, each +2 points. No control fills on press (the press is a scale). The pixel spec now measures rest, hover first/last and press first/last.
Green: 68 passed (64 pixel cases × 5 states + 4 captures), lowest ratio 5.82 (was 4.60).
green3/run.log,pixel-contrast-table-r2.txt.5. Paper over a dark picture. The static floor now also holds the tint within 0.1 OKLab lightness of itself over either extreme: Paper floor 0.87 (a pale card), not 0.75 (grey slab). Dark themes 0.87–0.91. Montage
pixel-green-1440x900-paper.png.6. Precompute: no. 10 themes × 4 levels × 2 sets is about 6 KB, larger than the solver (about 1.2 KB), and grows with every scheme added. The solve is now 2.2 ms per theme (was 4.5; bisection). The sampler is a lazy on-demand chunk.
Budget (no cap changed): critical gzip 322,512 (origin/dev
cedeb4d26) → 324,709 (+2,197 B; cap 327,680, headroom now 2,971 B); brotli 269,238 → 271,399; deferred 58,492 → 58,489; on-demand largest chunk unchanged. This eats most of the critical headroom; the alternative (writing the rules from an on-demand chunk) shows unrepaired tokens for the first moments of a load. Orchestrator's call.7. Motion. None added.
Sampling cost (unchanged, headless software rendering,
r4/cost.log): 0.003–0.006 ms per sample from a live 1080p stream, a decoded VP8 video and a canvas; frame intervals p99 16.8 ms with and without the watcher. Not measured on a real GPU.Screenshots (attached):
levels2-red4-*.png(old head) andlevels2-green4-*.png: Tinted, Clear, Frosted over the terminal at 1440×900 and 393×852, Ink and Paper;pixel-green-*.png: light, dark, white, black pictures, cards 2–4.Gates:
verify quickPASS on headc5873a421(10 rows,r4/quick.log).verify faston compute01 is running (first run on685aed0e9, second queued onc5873a421); results follow.Round 2 finish. Branch
glass-contrast-d365, head431e71f5065a3c872ad6ed2e0ac3645420f6a26b(merged with origin/dev547a48885;git ls-remoteconfirms).Gates
verify quickon this head: PASS, 10 of 10 rows (q6/quick.log).verify faston compute01, head610525535(before the last dev merge;fast2/sha): the whole Rust gate PASS (25 rows incl. cargo-deny, clippy, test, static builds; privileged integration SKIP by design), excerpt attached asfast2-excerpt.txt. It then stops at one tracer unit test,startup-toast.test.ts: a closing notice cannot hide its replacement(markMotioncallsgetAttributeon the toast's fake DOM element, #427). That test fails identically on unmodified origin/dev547a48885(bun test src/canvas/sessions/startup-toast.test.tsin a clean checkout: 0 pass, 1 fail), so it is not from this branch. Because of that stop, the browser rows offastdid not run; my own browser runs are below. An earlier fast run (685aed0e9) stopped at a cargo-deny advisory that origin/dev had since fixed in Cargo.lock; merging dev cleared it.610525535,green5/run.log): 68 passed: 64 cases × 5 states (rest, hover first/last, press first/last) + 4 captures. Lowest contrast 5.82:1.r5/budget-*.log): critical gzip 322,924 (origin/devc442500c2) → 325,117 (+2,193; cap 327,680), brotli 269,657 → 271,835 (+2,178); deferred 58,543 → 58,560; largest on-demand chunk 17,438 → 17,435.r5/cost.log).glass-contrast-d365-mock,/health200).For the orchestrator: (a) the 20% terminal-coverage bound and its one raised pair (dracula Frosted 0.20 to 0.37) are choices you may want the owner to see; (b) critical-class headroom is 2,563 B now; (c) glass built outside
anchored-popoverand the confirmation card is not auto-detected over pictures.Review of #448, round 2: glass contrast (D365 / D380)
Reviewed SHA:
431e71f5065a3c872ad6ed2e0ac3645420f6a26b(glass-contrast-d365). The served mockup :47662 haspackages/uibyte-identical to it (diff -rq, empty).Verdict: REQUEST CHANGES
Reviewer scope: I ran no
verify. I measured pixels with Playwright on dev-02 against the served mockup, plus a small harness that bundles the shippedtooltip.ts,toast.tsandthemes.tsover a full-screen picture. Scripts and raw output:infraternal-dev-02-std:~/.cache/codex-scratch/j448-rev2/(term.cjs,gap3.cjs,h.cjs,h2.cjs,cap.cjs,sweep.ts,evidence/).Summary
pictureUnder). With a forced refresh the floor holds (≥6.57).startup-toast.test.tsP1
P1-a. The confirmation card never gets the picture floor on open (D380 not met).
glass-under.ts:pictureUnderassumes the surface is in the hit-test stack:stack.slice(Math.max(0, findIndex(...)) + 1). The confirm card ispointer-events:noneuntildata-open="true", andplace()→bed.sync()→refreshGlassUnderruns before that. The card is then absent fromelementsFromPoint,findIndexis -1, andslice(1)drops the first element, which is the picture. Result:data-glass-underis never set. It only appears after a laterresize/scroll(I dispatched one to confirm).Pixel evidence (shipped component over a full-screen
<img>,h.cjs confirm, anchored and centred, identical):With a forced refresh (
h2.cjs confirm resize): 12/12 anchored cases pass, lowest 6.57 (Ink 7.6 / 10.43, Paper 8.86 / 6.57). So the floor works once detection fires.Fix: when the surface is not in the stack, do not slice. Skip
surfacedescendants and.ui-glass-bedand take everything else. Also refresh once more after the open animation (setOpen(true)plus a frame). Add a confirm-card case toglass-contrast.spec.ts: the spec covers only the Send keys and gear popovers, so D380 has no pixel test.P1-b. The "20% of the card" terminal bound is wrong in the measure that matters (local, after blur). 20% by area is not 20% under a text line. Real screens, real popover (
term.cjs, true Ink/Paper via&theme=; behind the popover: dense text plus the listed blocks; pixel contrast of every run vs the lightest/darkest pixel under it):lsin a world-writable dir, green name blocks (30%)Tinted (0.70) fails too on Ink with two bright rows: a status line plus a prompt line under a popover is an everyday case. Cause: the model treats a block as an area average. A two-row (34 px) band under a 24 to 32 px blur reaches 40 to 60% locally. Anchored popovers inside a window also have no glass bed, so the card's own backdrop-filter fades at the box edge and lets sharp content through. I measured the lightest pixel behind "F9" on Ink Frosted at ~0.59 of the block colour, while a flat CSS blur of the same screen gives ~0.20.
What bound is needed? With the shipped solver (
sweep.ts,themedBackdrop(t, c)):Roughly 40 to 50% is what a status+prompt pair needs; a light dialog or pager needs 80%+. That costs Clear and Frosted their distinction on 6 to 7 of 10 themes and brings dracula to 0.80. The approved levels and a real terminal guarantee cannot both hold. Recommendation: do not claim a guarantee over arbitrary terminal content. Rewrite the TASTE row,
THEMED_COVERAGEand the module doc to say what is true: "holds for bright blocks up to about one row (≈4% of the card); two bright rows or more can drop text under 4.5:1 at all levels on dark themes." Keep the 20% constant as a documented, owner-visible assumption. The owner decides whether to buy more with opacity (table above). Add a pixel case for the themed set (see P2-b).P2
P2-a. Auto-detect covers 2 of ~20 glass surfaces. Only
anchored-popoverand the confirm card's bed callrefreshGlassUnder. Unhooked.ui-overlay-surface/.ui-panelusers that can sit over a window or picture: palette (core-screen.ts:191), sidebar (:186), toast (toast.ts:284), regular tooltip (tooltip.ts:134, only the confirm tip has a bed), settings sheet, shortcuts view, probe panel, crumb menu, viewlist, view-pointer summary,cl-float/cl-badge, share dialog, details popover, add-host wizard.Measured (mockup,
gap3.cjs; harnessh.cjs toast):data-glass-underis null.Proposal: one hook in the shared material rather than per component. Put a
watchGlassSurfaces()inglass-under.ts, started fromapplyTheme: oneMutationObserveronbody(childList, subtree, plus thehiddenanddata-openattributes) that callsrefreshGlassUnderon each.ui-overlay-surface, .ui-panelwhen it appears or becomes visible, debounced to a frame. This also covers the sidebar and palette. For surfaces that stay while the canvas moves under them (sidebar), also re-check on the canvas cameramoveend.Separate, found while measuring: the toast's filled "Undo" button is 2.73:1 on Ink regardless of backdrop (label
rgb(149,186,255)on argb(107,107,107)fill). The solver's fill model has only hover, chip and selected, so filled action buttons are not modelled.P2-b. The pixel spec's "Ink" and "Paper" are Mocha and Latte. The spec only sets
colorScheme, and the mockup then falls back tomocha/latte(core-screen.ts:385; overlay token on the "Ink" case wasrgb(44 44 59 / …), which is mocha's tint, not ink's28 28 30). The 68/68 and the 5.82 floor therefore do not test Ink or Paper, and J414's screenshots were Mocha/Latte. Add&theme=ink/&theme=paper. Unit audit and my reruns (withtheme=) cover the real themes, and the picture cases I reran at the real themes passed for popovers (the floor works there). The themed set has no pixel test at all; add the nano-bars case above.P2-c. Over a picture the three levels collapse. The floor is ~0.87 (Paper) / 0.90 (Ink): in the screenshots below the Tinted, Clear and Frosted columns over a light or dark picture are indistinguishable, and the card reads near-opaque. Same as round-1 P1.2 (D160/D375: overlays are never opaque unless Solid). The "levels stay distinct" assertion holds only for the themed set. This is an owner call; please make the numbers visible to the owner (Ink 0.90/0.90/0.90, Paper 0.87/0.87/0.87).
Check 4: critical JS headroom (2,563 B)
Keep the solver eager. A first-paint surface is a glass card: the sign-in and setup cards (
auth-card ui-overlay-surface) are the first thing a signed-out person sees, so an on-demand solver would flash unrepaired text there and then swap colours when the chunk lands. Moving only the floor (glassFloor+ drift logic) saves little because the solver code is shared. If headroom is needed later, the cheap wins are elsewhere (e.g. solve three levels, since Solid keeps alpha 1). I did not rerun the size gate; I relied on the reported +2,193 B / 2,563 B left (cap 327,680; arithmetic consistent).Check 5: TASTE look (Ink and Paper, real themes; 1440×900 and 393×852)
Sheets: rows = terminal / light picture / dark picture, columns = Tinted / Clear / Frosted. Terminal row = a dense synthetic terminal behind the real popover.
P3
MAX_SHIFT,MAX_DRIFT, the coverage sweep and the fill alphas live in code comments pluscoverage-sweep.txt; put the sweep table inTHEMED_COVERAGE's doc so the next reader sees the cost per bound.pictureUndersamples only four corners and the centre: a picture strip crossing the card between those points (a thin video letterbox) is missed.Evidence
Attached:
sheet-1440x900-ink.png,sheet-1440x900-paper.png,sheet-393x852-ink.png,sheet-393x852-paper.png,ink-card4-nano-bars.png(the 2.35:1 case),palette-ink-white.png. Raw:~/.cache/codex-scratch/j448-rev2/evidence/{term,gap,look}on dev-02 (term.jsonlhas every row of the table).