Skip to content

Pop-out and settings fixes: a restored float stays in front, picker drags float off the desktop, menus keep off the edge, settings scroll steady, the carried view is always a window (Liquid Glass on macOS 26) - #224

Merged
foxnne merged 16 commits into
mainfrom
claude/popout-launch-fixes
Oct 5, 2026

Conversation

@foxnne

@foxnne foxnne commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Bugs you found after #223 merged, one commit each.

A float restored at launch opened behind the main window. Float windows are put back over the main window whenever it comes forward: focused (the app activating), pressed, shown or restored. That check sat only in the polled event loop. Fizzy runs on SDL's callbacks on macOS, so it never ran there, and a float window was ordered over the main window only on its first show. At launch that often comes before the main window is visible. The check now runs from both event paths (noteMainForward).

A view dragged out of the place picker couldn't leave the window. The picker's drag has no source place, and floating off the desktop only took drags that had one. A view dragged out of the picker now floats where it's let go over no window, as a corner-button drag does:

  • it comes out of whichever place holds it now;
  • that place is its home when the float closes, and it shuts if the view left it empty;
  • a view held nowhere goes home by its keywords.

Documents stay out until workspaces. Picker cards publish picker:<surface id> demo anchors.

The place picker sat flush against the window's edge. Every floating menu was placed against the bare rect of its screen, either the main window or a float's own window. Menus now stay 8 points in from its edges.

Two more, after more testing:

Settings rubber-banding. Scrolling up through the settings, the pane snapped back every few ticks. Rows out of view are skipped and keep the height they were last drawn at. A row coming back into view measured short on its first frame, because its widgets had forgotten their sizes while not drawn, then corrected itself the next frame. Everything under the row moved with it. Such a row now keeps its last height for that first frame. Scrolled back up 3000 px, the content height is identical on every frame, where before it changed every few ticks.

A carried view is a window of its own for the whole drag (macOS, float windows). Dragged off a float window, the bubble was cut off at the window's edge, then drawn behind the float once over the main window. Wherever the carry window took over, the glass also switched from refracting the app to the window's material. The carry window now shows the carried view from lift to drop, wherever it is. Over a float window it's placed by where that window sits on screen. Polled in a sandbox, it follows a drag out of a float window along its edge and on over the main window with no gap.

The carried bubble is bare material, and a float window forms out of the glass. As you asked, the carried view's window draws no base fill, so it's the OS's frosted material plus what it carries. Dialogs and float windows keep their opacity. When a float window grows out of a drop, its base fades in as it grows, reaching the window's own opacity as the real window takes over.

The carried view's content sits over the material, and a float window's content comes in as it grows. You saw the bubble's content as opaque and its fill change as invisible. Where carried things are windows, the photo is now taken without its place's background, so its content sits directly on the material. When a float grows out of a drop, the window's content arrives inside the glass as it grows (see below), not after the window appears.

A carried view is drawn by its carry window alone. A fast drag over the main window left a cutout-shaped trail. The app's own windows were still drawing a copy of the carried view under the carry window: its frosted glass, a drop's springy tail, and the drop it ran into. That copy was presented at a different moment than the window server moved the carry window, so it lagged behind. Following your rule (nothing in the app tracks the bubble unless it is all in the app), the carried view (card, tab or drop) is now drawn in a layer of its own, which the carry window takes whole (core.screens.markCarried). The main window and float windows draw nothing of it, and it has no in-app glass: the OS's material is its glass.

  • The drop zones no longer run the carried drop into their glass when floats are windows. The bubble it's aimed at lights up instead, as under a pointer.
  • The wobble now comes from the window. A drop's carry window covers its head and its tail as the tail lags on its spring. It stretches out behind a fast drag and swings back past when the drag stops.

Liquid Glass on macOS 26, as a drop of water. The carry window (the bubble, and the window a float grows into) uses Apple's NSGlassEffectView. AppKit's two public styles are both frosted, so the bubble showed no refracted edge. One of the glass's private variants (_variant 11, measured on 26.5) is a clear lens. It refracts what's behind it, even behind another window, with no blur, like a drop of water. The bubble uses it only on macOS 26 and only where its setter exists. Otherwise it keeps the public Clear style, and older macOS keeps the vibrancy material. The class is looked up at runtime, so fizzy still builds against older SDKs (CI's macos-14). The measurements and the plan are in #226. Merging with the drop zones as Liquid Glass is built in #227, stacked on this PR.

A float window opens cleanly out of the drop. From your captures:

  • A second size flashed. The growing glass overshoots the window's frame at the playful motion level, while the real window was already fading in at its final size. The window now shows only once the glass has landed exactly at its frame.
  • Text doubled near the peak. The carried photo and the window's picture crossed while each was laid out at a different size. The window's own picture now takes over in the first quarter of the growth, while the glass is small. It fills the glass with its proportions kept, so at full size it is the window's picture exactly.
  • The window flared opaque at the very end. It showed under the glass while the glass faded off it, both carrying the window's base. Now the window shows and the glass goes in the same frame. The glass grows as frost, the window's own material, rather than as the carried lens.
  • It opened centred on the pointer. The bubble rides below and right of the pointer, so the window now opens with its top-left corner at the bubble's and grows right and down, kept on screen.

No move cursor over a float window's traffic lights. The float's header is the area it's moved by, and it showed the move cursor everywhere, including over the OS's buttons. The drag area now starts past the window's own buttons, measured from the window each frame.

The carried bubble stands on a fifth of the window's base. With the glass a clear lens, the view's picture didn't read over whatever the bubble passed. It now has the window's base under it at 20% opacity.

The lifted place no longer goes see-through for a frame. Where floats are windows, the lift's photo is captured from the place's own draw without its background, and put back on screen for that frame. It was put back with a blend that writes the capture's clear pixels too, so the window showed the desktop through the place for one frame (your Plugins sidebar capture). It's now drawn on top of what's already there, as the place itself would be.

Verified

  • zig build, zig build test, zig build test-integration (28/28 steps, 278/278 tests), zig build check-web, zig build test-sdk-version (fingerprint unchanged). Cross-builds for Windows and Linux.
  • Sandboxed macOS runs:
    • Relaunched with a saved float, the main window's "shown" event now reaches the ordering check (it was dropped before), and the float stays in front.
    • A tape opens the Panel's picker and drags Output past the main window's edge. The carry window follows it out there, and a float window holding Output opens where it's let go.
    • In a frame dump, the open picker sits 8 points in from the window's right edge.
    • Holding the bubble over the main window, the main window's own picture has nothing of it. Only the carry window (window level 101) shows it.
    • Carry window bounds polled through a fast tape drag: the 104 pt bubble stretched to 142 pt behind the drag. On the stop it swung once to 137 pt and settled in about 90 ms.
    • NSGlassEffectView in a test window over a striped pattern: Clear is light with Apple's rim, Regular dark and heavily blurred. Resized every 8 ms, it stayed matched to the window. Variant 11 refracts the stripes, from a separate window too.
    • A float growing out of a drop: its window opens at alpha 0 and stays so while the glass overshoots to 386×479 and settles at 356×439. In the same poll, it shows at 356×439 and the glass window is gone. Its top edge opens at the bubble's (y 503 against 501); horizontally it was kept on screen.

Not verified:

  • The app activating with a real launch (a sandbox started from a background process isn't activated by macOS). You saw that bug in your own launches.
  • The wobble and the Liquid Glass bubble under a real pointer. The tapes drive dvui directly, so the speeds are the tape's.
  • The vibrancy fallback on macOS before 26 (no older Mac to hand).

🤖 Generated with Claude Code

foxnne and others added 5 commits October 5, 2026 09:14
…ks too

A float restored at launch opened behind the main window and stayed there.

Float windows are put back over the main window whenever it comes
forward: focused (the app activating, at launch too), pressed, shown or
restored (`order_pending`). That check sat only in the polled event loop
(`addAllEvents`). Fizzy runs on SDL's callbacks on macOS, where events
arrive through `appEvent`, so it never ran there, and a float window was
ordered over the main window only on its first show. At launch that
often comes before the main window is visible, and the main window then
showed over it.

`noteMainForward` is called from both event paths. In a sandbox launched
with a saved float, the main window's `SDL_EVENT_WINDOW_SHOWN` now reaches
it (it was dropped before), and the float stays in front.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… go, as a corner-button drag does

A view carried out of the place picker couldn't be dropped outside the
window: `floatAway` (and `floatOut` under it) took only a drag with a
source place, and the picker's is a loose drag.

A loose drag of a view now floats the view where it is let go over no
window. It comes out of whichever place holds it now (`holderOf`). That
place is its home when the float closes, and it shuts if the view leaves
it empty. A view held nowhere goes home by its keywords. Documents stay
out until workspaces (`docs/WORKSPACES_PLAN.md`).

Each picker card publishes a `picker:<surface id>` demo anchor. In a
sandbox, a tape opens the Panel's picker and drags Output past the main
window's edge: the carry window follows it out there, and a float window
holding Output opens where it is let go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The place picker opened from a corner button at the window's right edge
sat flush against that edge. Every floating menu was placed
(`dvui.placeOnScreen`) against the bare rect of the screen it opens on:
the main window's, or a float's own window's (`core.screens`).

`FloatingMenu` now insets that screen by `screen_margin` (8 points), for
menus pushed in from an edge and for centred ones. In a frame dump the
Panel's picker sits 8 points in from the window's right edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the pane no longer rubber-bands

Scrolling up through the settings (Appearance among them), the pane
snapped back every few ticks. Rows out of view are skipped and keep the
height they were last drawn at. A row coming back into view is drawn in
full, and on that first frame it measured short: what its widgets knew of
their own sizes was forgotten while they weren't drawn. The next frame it
was its own height again. The content's height dipped by 40 to 140 px for
one frame and came back, and everything under the row moved with it.

A row back in view after being skipped now keeps its last height for that
frame, at the same width. Scrolled up 3000 px in 30 px ticks in a sandbox,
with every section open, the pane's content height is now the same on
every frame (4772.4 px, 96 of 96). Before it was 4634.9 to 4772.4,
changing every few ticks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With floats as windows, a carried view was drawn in whichever window it was
over. Dragged off a float's window, it was cut off at that window's edge
until it crossed, then drawn in the main window, behind the float. Where
the carry window took it over (past the main window, under a ghost), its
glass turned from glass refracting the app under it into the window's
material, and back, at every crossing. Showing the carry window only where
the bubble wasn't wholly in one unobstructed window still popped near
edges, and the change of look stayed jarring.

On macOS, with float windows, the carry window now shows the carried view
for the whole drag, wherever it is (`Popout.carryFrame`):
- over the main window, at its place in the main window's frame;
- over a float's window, at the screen position of that window's band,
  its picture still read from the band (`Cover`, the window it overlaps
  most, since near an edge its middle is past it while the pointer is
  still on the window).

The copy left in the app's own windows under it casts no shadow of its own
beside the OS's (`core.dialogs.carried_in_window`).

Polled in a sandbox, the carry window follows both drags from lift to drop
with no gap: Explorer across the main window and into the float it opens,
and a view out of a float's window along its edge and on over the main
window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne foxnne changed the title Pop-out fixes: a restored float stays in front, picker drags float off the desktop, menus keep off the window's edge Pop-out and settings fixes: a restored float stays in front, picker drags float off the desktop, menus keep off the edge, settings scroll steady, the carried view is always a window Oct 5, 2026
foxnne and others added 4 commits October 5, 2026 10:14
…ms out of the glass as it opens

As asked: the carried view's window draws no base fill, so while dragged it
is the OS's frosted material and what it carries: lighter, more
glass-like. Dialogs and float windows keep their opacity.

As a float's window grows out of the drop (`Popout.growFrame`), its base
now fades in with the growth, from the carried glass's none to the
window's own opacity, which it reaches as the real window takes over under
it. The window forms out of the glass, as the in-window glass used to,
instead of arriving with its fill already on. `carryBegin` takes the share
of the base to lay down (`fill`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…coming in as it grows

You saw the bubble's middle as opaque, with only its rim showing the
material. The fill fading in as a float's window opened couldn't be seen,
and the window seemed to appear first and fill in after.

**The photograph.** It was taken from the frame as drawn, the place's
background and the window's under it. It was then laid over an opaque
fill (`backed`), so text never floated in the frost. In a window of its
own that picture covered the window's material. With carry windows
(`core.dialogs.carry_windows`, set by `Popout`), the view is now captured
alone (`photographFromFrame` declines, and `keepShot` doesn't back it).
Its content sits over the material, which shows between, and the base
fading in under it as the window opens can be seen. A frosted pane inside
a view draws dark in that capture.

**The overlap.** Over the second half of the growth (`landingFraction`
past ½), the window comes in under the growing glass while the glass fades
off it, one into the other. Before, the window came in only after the
growth ended. Polled in a sandbox, the window's opacity rises from 0 to 1
as the glass's falls from 1 to 0, both finishing as the glass settles on
the window's frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hes toward its tail

The app's own windows drew a copy of the carried view under the carry window — its glass, a
drop's tail, the drop it ran into — presented at another moment than the window server moved the
carry window, so a fast drag left it trailing behind. Where carried things are windows of their
own, what is carried (card, tab or drop) is now drawn in a layer of its own
(`core.screens.markCarried`) that the carry window takes whole; the main window and float windows
draw none of it, and no in-app glass is drawn for it: the OS's material is its glass. The drop
zones no longer run the carried drop into their glass there; the bubble it is aimed at lights.

A drop's carry window is the union of its head and its tail as that lags on its spring, so it
stretches behind a fast drag and swings back when it stops — the wobble, from the window itself.
`core.dialogs.carried_in_window` is gone with the copy it kept from casting a second shadow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The carried bubble's window, and the window a float grows into as it opens, use Apple's Liquid
Glass (`NSGlassEffectView`, Clear style) from macOS 26: its rim, its continuous corners, set from
the window's shape. Before macOS 26 they keep the vibrancy material. The class is looked up by name
and its properties set by key, so fizzy still builds against older SDKs (CI's macos-14).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne foxnne changed the title Pop-out and settings fixes: a restored float stays in front, picker drags float off the desktop, menus keep off the edge, settings scroll steady, the carried view is always a window Pop-out and settings fixes: a restored float stays in front, picker drags float off the desktop, menus keep off the edge, settings scroll steady, the carried view is always a window (Liquid Glass on macOS 26) Oct 5, 2026
foxnne and others added 2 commits October 5, 2026 11:15
…size

The window a float grows into faded in over the second half of the growth while the carried
glass still grew — past the window's frame and back, at the playful motion level — so two sizes
of one window showed at once, and the photograph scaled to the overshoot doubled what the window
showed (the user's capture). The window now shows nothing until the glass has landed at its frame;
what the glass carries comes in as it grows instead: the carried view's photograph goes, and the
float's own picture (last frame's) arrives in its place, scaled to the glass, its base with it.
Landed, the glass is the window's size and shows what the window does: the window shows under it
at once, and it fades off it. Polled: the float window opens at alpha 0, stays so while the glass
overshoots to 386×479 and settles at 356×439, and shows at 356×439 as the glass fades.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The user saw the bubble's glass as a frost, without the refracted edges of Control Center's. Both
of AppKit's public glass styles are frosted, in a window and behind one. One of the glass's private
variants (`_variant` 11, measured on macOS 26.5) is a clear lens: unblurred, bending and drawing in
what is behind it toward its rim — what is behind another window included, the window server doing
the lensing — as a drop of water, as the app's own glass is. The carry window's glass takes it,
through its typed setter, only on macOS 26 and only where the setter is there: a later macOS that
renumbered the variants could leave the bubble with no glass (13 draws none), and key-value coding
an unexpected value into the glass's Swift enums kills the process. Elsewhere it keeps the public
Clear style.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
foxnne and others added 5 commits October 5, 2026 11:50
…cture uncropped, and hands over in one frame

Two things the user still saw as a float's window opened out of a drop. The text doubled near the
growth's peak: the carried photograph (cropped to keep its proportions) crossed with the float's
picture (stretched to the glass), each laid out at a different size. The picture now takes over in
the first quarter of the growth, while the glass is small, and fills the glass with its
proportions kept, so at the window's size it is the window's picture exactly. And the window flared
opaque at the very end: it showed under the glass as the glass faded off it over 120 ms, both with
the window's base. Now the window shows and the glass goes in the same frame (polled: the float
window goes to alpha 1 at 356×439 in the poll the glass, at 356×439, is gone). The glass grows as
frost (Liquid Glass's own variant), as the window's material is, not as the carried lens
(`viewports.carryLens`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he carried glass's was

The carried glass rides below and right of the pointer, but a float let go over no window opened
centred on the pointer, so the glass grew out round it rather than down from where it was. The
float now opens with its top left at the carried glass's (the drop's head, or its card), and the
glass grows into it right and down; the float's picture fills the growing glass from its top left
too. With nothing carried (the picker, a test) it still opens round where it was let go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e frame it is photographed

Where carried things are windows, the lift's photograph is captured from the place's own draw
without its background (6105a2f), and that capture is put back on the screen for the frame. It was
put back with `blit`, a dissolve that writes the capture's pixels exactly, clear ones included, so
for the frame a view was lifted on the window was see-through under the place — the user saw the
desktop through the sidebar for one frame. It is put back source-over (`blitOpaque`) now, over what
the frame already has there, as the place's draw would have gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A float's header is the area it is moved by, and dvui shows the move cursor over all of it — over
its OS window's traffic lights too, which lie on the header where the OS frames the window. The
header's drag area now starts past them: the window's own buttons measured each frame
(`viewports.buttonsWidth`, `fizzy_macos_window_buttons_width`: the zoom button's right edge and
as much again as the close button stands in), handed to the float (`Floats.Viewport.buttons_w`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the carried bubble's glass a clear lens, the view's picture in it, over whatever it passed,
did not read (the user). The bubble now has the window's base under its picture at 20% opacity —
lighter than a window still. `carryBegin`'s `fill` is that opacity outright now, no longer a
share of the window's (nothing else passed one).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne
foxnne merged commit 0253742 into main Oct 5, 2026
6 checks passed
@foxnne
foxnne deleted the claude/popout-launch-fixes branch October 5, 2026 20:45
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