Float the chrome over the list and let the platform set the type - #23
Merged
Conversation
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
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`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What it looks like
Chrome 151, both themes, captured with the CDP harness (
scratch-chrome/shoot-pr.ts) — exact pixels, no window chrome. Committed underdocs/media/pr/and linked by commit SHA, so they survive this branch being deleted.The cockpit, scrolled — the chrome floating over the list.
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.
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.
The popup keeps full-bleed bars, at its real 600px width.
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-filtersamples 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 — whichPRODUCT.mdrejects. 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.mdso it does not later read as drift.Three things that were measured, not assumed
--glasshas to sit a step deeper than the content it floats over. Tinted with--paperit 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.--glass-dense.Bugs fixed along the way
.action-barwas swallowing clicks on every row behind the empty space beside its capsules. It ispointer-events: nonewith the capsulesauto— detaching chrome means its box stops matching what you can see of it..tab-titleno longer unclamps to a full wrap on hover, which grew the row and shoved every row below it out from under the cursor.gaplike any element, so a flat 6px spaced keycaps and words identically —spacewas genuinely equidistant frommoveandtoggle.--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'sinnerHTML).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 forbackdrop-filtercost while scrolling a large backlog — that is the main open risk, and it is the one thing Chrome cannot answer.Notes
docs/media/cockpit-{light,dark}.pngare regenerated here — they were the old three-slab layout in Vollkorn and they feeddocs/STORE.md. Thedocs/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.mdnow 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.