Skip to content

Float the chrome over the list and let the platform set the type - #23

Merged
mlsimon734 merged 5 commits into
mainfrom
style/glassy
Aug 8, 2026
Merged

Float the chrome over the list and let the platform set the type#23
mlsimon734 merged 5 commits into
mainfrom
style/glassy

Conversation

@mlsimon734

@mlsimon734 mlsimon734 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

What it looks like

Chrome 151, both themes, captured with the CDP harness (scratch-chrome/shoot-pr.ts) — exact pixels, no window chrome. Committed under docs/media/pr/ and linked by commit SHA, so they survive this branch being deleted.

The cockpit, scrolled — the chrome floating over the list.

Light Dark
Cockpit, light theme, scrolled Cockpit, dark theme, scrolled

At rest vs. scrolled — the part a single still cannot show. The header carries no material at the top of the list, because the queue's padding means there is genuinely nothing behind it; the glass appears only once rows are underneath. Same page, same theme, 260px of scroll between them.

At rest — no material Scrolled — glass
Cockpit at rest, header carries no material Cockpit scrolled, glass active behind the header

Inspector open, rows selected. The inspector and the rows stay opaque paper — glass is a navigation-layer material only. This is also the frame where the action capsules are live rather than all-disabled.

Light Dark
Cockpit inspector, light theme Cockpit inspector, dark theme

The popup keeps full-bleed bars, at its real 600px width.

Light Dark
Popup, light theme Popup, dark theme

What changes

The cockpit stops being three stacked slabs. The list now runs the full height of the viewport and the chrome floats over it as rounded capsules inset from every edge, with the queue running past them on all sides. The header carries no material at rest — nothing is behind it then — and the glass fades in once rows are actually underneath it. That reactivity is the most characteristic part and is not visible in a still.

Glass is a navigation-layer material only. backdrop-filter samples only what is behind an element in the same document (an extension popup cannot blur the page beneath it), so without content passing under a bar the effect is decoration — which PRODUCT.md rejects. Rows, the inspector, and every options control stay opaque paper. That anti-reference is now worded to separate decorative glassmorphism from a navigation layer.

Typography drops from two bundled faces to one, 143 KB → 10 KB. The UI face is the platform's own (SF / Segoe UI Variable), so the app renders in the same type as the browser around it. Young Serif survives for the wordmark alone, subsetted. It replaced Vollkorn — a text serif doing display work that had spread into section titles, empty states, inspector headings, and the favicon letter fallback, where it was 10px serif capitals inside an 18px circle.

The popup deliberately keeps full-bleed bars. At 600×560, capsule gaps cost rows it has not got. The asymmetry is recorded in DESIGN.md so it does not later read as drift.

Three things that were measured, not assumed

  • --glass has to sit a step deeper than the content it floats over. Tinted with --paper it was paper blurred over paper, and a build with 28px blur and a 55% fill was indistinguishable from no glass at all in light theme. The constraint was the missing value boundary, not the blur radius.
  • The chrome had to detach. Full-bleed translucent bars welded to the viewport edge are a vibrant toolbar — something macOS has had since Yosemite — and read as almost no change.
  • Small capsules need a denser fill than a band. 13px row text crossing a narrow capsule at an arbitrary offset turned to mud at the 75% a full-width bar got away with, hence --glass-dense.

Bugs fixed along the way

  • .action-bar was swallowing clicks on every row behind the empty space beside its capsules. It is pointer-events: none with the capsules auto — detaching chrome means its box stops matching what you can see of it.
  • .tab-title no longer unclamps to a full wrap on hover, which grew the row and shoved every row below it out from under the cursor.
  • The capped queue is centred. Left-aligned under a full-width header it put a centred search bar over a hard-left list with ~600px of void.
  • The keyboard legend groups each key with its label. A run of text in a flex container is its own anonymous item and takes the gap like any element, so a flat 6px spaced keycaps and words identically — space was genuinely equidistant from move and toggle.
  • Deepening the bar cost two contrast ratios, both restored: the search well (--well, which steps away from the bar in whichever direction the theme requires) and the keyboard legend (--muted, from 3.8:1).

Verification

bun run check — typecheck, 458 tests, format, oxlint, web-ext lint. All pass; the 3 web-ext warnings are pre-existing (Defuddle's innerHTML).

The glass and the floating layout are not reachable from bun test. The screenshots above are a live Chrome 151 pass over a real 15-tab backlog — the layout, both themes, the scroll-lift transition, and the inspector all behave. Still wants a Firefox pass for backdrop-filter cost while scrolling a large backlog — that is the main open risk, and it is the one thing Chrome cannot answer.

Notes

  • No manifest changes, no permission changes.
  • docs/media/cockpit-{light,dark}.png are regenerated here — they were the old three-slab layout in Vollkorn and they feed docs/STORE.md. The docs/media/store/ set (the five 1280×800 listing images) is still stale and wants a pass before the next store submission, along with the inspector and popup variants.
  • AGENTS.md now requires screenshots on any PR that changes a visible surface, and records the capture recipe and why the images are committed rather than dragged into the web editor.

The source-URL check tells two clips of different pages apart, but not two
clips of the SAME page racing from two Gullet processes: same source, and
the claim maps are per-process. tab_clip now returns contentHash — SHA-256
of exactly what it handed over — and a note that does not hash to it was
written by some other clip. That identifies the invocation rather than the
destination, so it needs no shared state and no marker in the user's notes.

Measured before built, in both clip modes and both of the user's real
vaults as well as a bare one, at 238 B and 50 kB: Obsidian writes back
byte-identical text — no trailing newline added, no line endings rewritten,
no truncation. That measurement is what makes a mismatch trustworthy enough
to disqualify a note, and if it ever stops holding the symptom is every
clip reporting mismatched, which is a verdict of its own precisely so the
message can name that possibility.

Also from the live run: Obsidian creates a note and fills it a beat later,
and an empty file has no parseable source, so the fallback read it as ours.
An empty note now never vouches for a clip.

The hash is never required — an older extension sends none and attribution
falls back to source. The extension hashes with WebCrypto and Gullet with
Bun's hasher, so tests/clip-source.test.ts pins them together; nothing else
would catch an encoding or digest change.

ClipVerifier takes an evidence object rather than four positional
arguments, which is the simplification pass deferred from #19.

Verified: bun run check (456 tests).
The content fingerprint was taken over the LF text the extension
composed, but in the default clipboard mode that text reaches Obsidian
through the OS clipboard, and Windows carries plain text as
CF_UNICODETEXT with CRLF. The note on disk therefore has endings the
extension never produced, so every landed clip on Windows would hash
differently, report `mismatched`, leave the requested close undone, and
send the agent back to re-clip a page Obsidian had already filed.

Both hashers now fold CRLF to LF first. Line endings are transport, not
content; the only thing given up is telling apart two clips that differ
in nothing else, and there is no such pair.

The cross-boundary test was proving less than it claimed: it hashed with
its own WebCrypto copy, so it only ever showed that Bun agrees with the
test. It imports the real extension helper now — which is why that
helper moves out of bridge-methods.ts, whose import graph reaches
`browser`, into its own pure module — and covers both the CRLF route and
a pair that must still hash apart.

`tab_clip`'s MCP description still told agents a verified clip is no
proof of the extraction and that concurrent same-URL sessions can share
evidence. With a contentHash present that has not been true since
c6a6a70; it now splits the guarantee by whether the result carries one.
The cockpit was three stacked opaque slabs — header, list, footer — that
read as separate blocks rather than as one tool. Both list surfaces now
run the list the full height of the viewport with the chrome floating
over it, in two fixed stacks whose heights `trackChromeHeights` publishes
as `--chrome-top` / `--chrome-bottom`: the warning banner and the
Devour-failures panel appear and disappear inside them, so a CSS-fixed
inset would either clip the first row or leave a gap. The scroll region
feeds the same values into `scroll-padding-block`, because keyboard nav
arrives with `block: "nearest"` and would otherwise park the focused row
under the bar it just travelled beneath.

Glass belongs to that navigation layer and nowhere else. `backdrop-filter`
samples only what is behind an element in the same document — an extension
popup cannot blur the page beneath it — so without content passing under a
bar the effect is decoration, which is what the anti-references reject.
Rows, the inspector and every options control stay opaque paper.

Three things were measured rather than assumed:

- `--glass` must sit a step deeper than the content it floats over. Tinted
  with `--paper` it was paper blurred over paper, and a build with 28px
  blur and a 55% fill was indistinguishable from no glass at all in light
  theme. The constraint was the missing value boundary, not the radius.
- In the cockpit the chrome had to *detach*. Full-bleed translucent bars
  welded to the viewport edge are a vibrant toolbar, which macOS has had
  since Yosemite; capsules inset from every edge, with the queue running
  past them on all sides, are the thing that actually reads as new. The
  header also carries no material at rest — nothing is behind it then —
  and gains it once rows are underneath (`body.chrome-lifted`).
- Small capsules need a denser fill than a band. 13px row text crossing a
  narrow capsule at an arbitrary offset turned to mud at the 75% a
  full-width bar got away with, hence `--glass-dense`.

`.action-bar` is `pointer-events: none` with its capsules `auto`. It still
spans the full width while only two capsules are visible in it, and was
swallowing every click on the rows behind that empty space — detaching
chrome means its box stops matching what you can see of it. `.queue`
carries a `mask-image` fade at both ends for the same reason: content is
now visible in the gaps, and a half-drawn row there reads as a fault.

The popup keeps full-bleed bars. At 600x560 capsule gaps cost rows it has
not got. The asymmetry is deliberate and recorded, not drift.

Typography drops from two bundled faces to one, 143 KB to 10 KB. The UI
face is the platform's own, which renders the app in the same type as the
browser around it. Young Serif survives for the wordmark alone, subsetted;
it replaced Vollkorn, a text serif doing display work that had also spread
into section titles, empty states, inspector headings and the favicon
letter fallback — 10px serif capitals inside an 18px circle. The global
`font-feature-settings` went with Geist: it existed for that face's
ss01/ss03/cv11, which is what forced every display element to reset it,
and the `tnum` it also carried is declared at each of the ten sites that
hold an updating number.

Deepening the bar cost two contrast ratios, both restored. The search well
sat on `--bone`, lighter than the new bar, so an inset control read as
raised; a substrate-agnostic `--hover-strong` darkening looked right and
put the placeholder at 3.3:1. The well steps *away* from the bar instead,
and which direction that is flips with the theme (`--well`). The cockpit's
keyboard legend fell to 3.8:1 on `--muted-soft` and uses `--muted`.

Three fixes found along the way:

- The capped queue is centred. Left-aligned under a full-width header it
  put a centred search bar over a hard-left list with ~600px of void.
- `.tab-title` no longer unclamps to a full wrap on hover, which grew the
  row and shoved every row below it out from under the cursor.
- The keyboard legend groups each key with its label. A run of text in a
  flex container is its own anonymous item and takes the `gap` like any
  element, so the flat 6px spaced keycaps and words identically and the
  ` ` meant to bind them did nothing — `space` was genuinely
  equidistant from `move` and `toggle`.

Verified with `bun run check`. The glass and the floating layout are not
reachable from `bun test`; they were checked against a static render
harness in both themes and at the narrow breakpoint, and still want a live
pass for Firefox's `backdrop-filter` cost while scrolling a large backlog.
`docs/media/cockpit-{light,dark}.png` are now stale and feed docs/STORE.md.
@mlsimon734
mlsimon734 marked this pull request as ready for review August 8, 2026 01:50
A PR that rebuilds a surface cannot be reviewed from prose. Captured the
cockpit and the popup in both themes with the CDP harness, including the
pair the header's material depends on: at rest there is nothing behind it
and it carries none, and only the scrolled frame shows the glass at all.

`docs/media/cockpit-{light,dark}.png` were still the three-slab layout in
Vollkorn and feed `docs/STORE.md`; they are the scrolled frames now. The
`store/` variants are still stale and want the 0.2.x submission pass.

AGENTS.md promotes the screenshot rule out of a trailing clause, and
records why the shots get committed rather than dragged in: GitHub's
upload endpoint is session-authenticated and unreachable from `gh`.
@mlsimon734
mlsimon734 merged commit 975a887 into main Aug 8, 2026
1 check passed
@mlsimon734
mlsimon734 deleted the style/glassy branch August 8, 2026 02:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant