Repository navigation
app: every glass draws in the window material - #549
Merged
Merged
Conversation
Part of fizzyedit/plans#5 (`plans/ZIG_0_17_PLAN.md`, step 2; the spike's findings are in fizzyedit/plans#12) The first PR into the `zig/0.17` integration branch. It opens the branch **green on 0.17**, so each PR after it is a reviewable migration with CI on every platform, not a long red stretch. ## What changes - **Toolchain:** `minimum_zig_version = "0.17.0"` everywhere; `ZIG_VERSION` 0.17.0 in `ci.yml`, `web.yml`, `release.yml`, `plugin-loop.yml`. - **Forks, pinned by commit on a `zig-0.17` branch in each** (tagged when `zig/0.17` merges to `main`, per `docs/DEPENDENCIES.md` rule 2): - `foxnne/dvui-dev` `2c899d18`: `fizzy-dev`'s 8 patches rebased onto dvui's `main` (`41f6703`), one-line conflict; passes dvui's tests on 0.17. - `foxnne/zig-objc` `f90394b`: `@typeInfo`, `**`; passes its tests. - `fizzyedit/md4zig` `eb5241c`: `@cImport` → translate-c module; passes its tests. - `foxnne/assetpack` `91eb632`: one line. - `fizzyedit/sdl_zig` `404f499`: five config values 0.17 rejects (still SDL 3.4.16). - zf and nightwatch repinned to their 0.17 commits. - **`scripts/zig-0.17/rewrite.sh`**: the mechanical half, re-run after every merge from `main`, deleted at the end. `zig fmt`'s upgrades, the optimize-mode, `dupeZ`/`bufPrintZ`, `EnumSet`, `std.meta.Int` renames, the `@typeInfo` field loops, and the zon stand-in below. - **Hand edits** from the spike: the build API renames, `**`, `@bitCast` to extern structs, `fingerprint.zig` translated loop for loop, Zoir/`Ast.parse` options, dvui's per-pointer capture (dvui#1014), and others. - **51 `ZIG017` stand-ins in 33 files**, each holding the place of a migration a later PR does properly: - the build system (custom `makeFn` steps, configure-time install paths, `--libc`, the MSVC shim, Velopack's `addPathDir`); - `std.zon.parse`: results now come from the caller's arena. The stand-in parses into a short-lived arena and deep-copies into the old allocator (`zig017ZonDupe`), with a file-local `zig017ZonFree`, so ownership is unchanged and nothing leaks; - the SDK's settings and command schemas reading field records (`sdk/src/zig017_fields.zig`); - Velopack's `@cImport`. - `recorded_sdk_shape_fingerprint` moved (dvui's shape since 2026-10-02). It is released with the merge to `main`. Diff size: 156 files, +2165/−870. Most of it is the script's output and the stand-ins' helpers. Read the script, not its output. ## SDK impact - [ ] None - [ ] Core-only or additive: reaches plugins at the next SDK release - [x] Fingerprint moved: recorded in `sdk/src/version.zig`, `sdk_version` untouched, PR labelled `sdk` - [ ] SDK release: bumps `sdk_version`, lists the `sdk` PRs since the last `sdk-v*` tag ## Verified - [x] macOS (arm64, 0.17.0): `scripts/check-fmt.sh`; `zig build`; `zig build test` (91/91 steps); `zig build test-integration` (77/77 steps, 523/523 tests); `zig build check-web`; `zig build test-sdk-version`; `zig build check-tests-reached`. The spike also launched the app (window 1.1 s after `main`). - [ ] Windows: CI's cross-compile (not run locally). - [ ] Linux: CI (not run locally). - [x] Web: `zig build check-web`. **Example repos:** `scripts/check-examples.sh` now builds the examples' `zig-0.17` branches (`fizzyedit/example-app` `533c38b`, `example-plugin` `34c94ec`; back to `main` at the merge, marked `ZIG017`). It also now gives `example-app` this tree's SDK everywhere it pins one: its nested `plugins/shader`, and its bundled `example-plugin` (`hello`), whose pinned commit carried SDK 0.2.18. It stops macOS `tar` from adding AppleDouble `._*` files, which the backend took for SDL patches when the script ran on a Mac. All 8 example builds pass locally on macOS. **The plugin dev install is done for real here** (moved down from #438), since a stand-in that skipped it broke the plugin rebuild benchmark: `sdk/plugin_sdk_install.zig` is a program the plugin's build runs, reading `FIZZY_PROFILE`/`HOME` itself at make time, and `FIZZY_PLUGIN_USE_LLVM` becomes `-Dplugin-use-llvm` (0.17 caches the configuration without regard to the environment), which `scripts/plugin-loop/bench.py` passes. ## Follow-ups The next PRs into `zig/0.17`: the build system (next), zon arenas, `@typeInfo` with a fingerprint-equality test, Velopack translate-c, the example repos. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of fizzyedit/plans#5 (`plans/ZIG_0_17_PLAN.md`, step 2: the build system). **Stacked on #437**: based on `zig/017-scaffold`, so this diff is only the app's build-system work. The plugin dev install and `-Dplugin-use-llvm` moved down into #437. It goes to `zig/0.17` once #437 merges. ## What changes Zig 0.17 runs `build.zig` in a configurer and the graph in a separate maker. There are no custom steps (`makeFn`), no install paths at configure time, and the configuration is cached. - **"Set aside a running executable"** (Windows hosts) becomes `build/set_aside.zig`, run before the install. Its directory comes from a new `common.installDirPath`: a `LazyPath` relative to the install prefix, resolved when the build runs, in place of `b.getInstallPath`. The packaging script's `--packDir` uses the same helper. - **The `update` step and root `update.zig` are deleted.** They pulled dvui-dev `main` and a zigwin32 0.16 branch, neither of which fizzy pins any more. - **Configure-time reads are declared** (`dependOnFileContents`/`Metadata`/`DirectoryContents`): `VERSION`, each plugin's `plugin.zig.zon`, `web/index.html`, the web plugin checkouts and their `web/` dirs, `release.yml`, `patches/sdl`. Without that, 0.17 reuses a stale configuration (checked: a changed `VERSION` now reaches the binary). Existence checks and directory opens go through `b.root`'s own `Path` API. - **A path that may not exist** (a web plugin checkout and its `web/` directory; #437 does the same for a plugin's `release.yml`) is declared by `dependOnPathExistence`: the file when it exists, otherwise the nearest directory that does. 0.17's maker cannot load a configuration that depends on a path inside a missing directory ("configuration failed: FileSystemFailure"); a plugin without `.github/` hit it. Checked by building `plugins/image` copied without its `.github/`. **Left for the release-packaging PR**, each still marked `ZIG017`, since they run only in release builds and need the release workflow to verify: - the global `--libc` (→ `-Dwindows-libc`) and the MSVC translate-c shim; - Velopack's `addPathDir` for `mksquashfs`; - the `FIZZY_MACOS_*` signing fallbacks and `SDKROOT`, which are environment reads at configure. ## SDK impact - [x] None: no `sdk/` file changes here (the plugin install moved to #437). - [ ] Core-only or additive: reaches plugins at the next SDK release - [ ] Fingerprint moved - [ ] SDK release ## Verified - [x] macOS (arm64, 0.17.0): - `scripts/check-fmt.sh`, `zig build`, `zig build test` (91/91 steps), `zig build test-integration` (77/77 steps), `zig build check-web`, `zig build test-sdk-version`, `zig build check-tests-reached`. - `scripts/check-examples.sh` against the examples' `zig-0.17` branches: all 8 builds pass. - Standalone `cd plugins/image && FIZZY_PROFILE=<sandbox> zig build` installs into `<sandbox>/plugins/image/`, again on a rebuild with no change. A clean `-Doptimize=ReleaseFast` plugin build fetches no Velopack (AGENTS.md's acceptance test). - The set-aside step, forced on for a macOS run: nothing to do on a first build (install dir absent), checks the installed exe on the next. - [x] Windows: `zig build -Dtarget=x86_64-windows-gnu -Dnative-backend=fizzy -Dno-emit` configures and compiles. Not run on a Windows host. - [ ] Linux: CI. - [x] Web: `zig build check-web`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of fizzyedit/plans#5 (`plans/ZIG_0_17_PLAN.md`, step 2: the build system). **Stacked on #438** (and #437): based on `zig/017-build-steps`, so this diff is only release packaging. ## What changes **Velopack 1.2.161.** `velopack_zig` moves to graphl-tech/velopack-zig#9 (`a80b037`, its Zig 0.17 port), and with it Velopack's runtime goes from 0.0.1589 to 1.2.161. `vpk` in `.config/dotnet-tools.json` goes from 0.0.1298 to 1.2.161; `vpk` 1.2 targets .NET 10, so `release.yml` installs 10.0, not 8.0. What users gain is in velopack-zig#9: a code signature that survives updates on macOS, atomic macOS updates, the Linux locator fix, delta fixes, and 64-bit Windows bootstrappers. `Velopack.h` only gained declarations; fizzy's updater is unchanged. **The rest of the build system's 0.17 stand-ins:** - **`Velopack.h` as a module.** `@cImport` is gone. `velopack.linkVelopack(.c_modules = …)` gives each module that calls the C API a `velopack_c` import (translate-c), once per module: the integration, demo and bench tests share one `app` module. The updater imports it only when Velopack is enabled. - **`vpk pack` runs through velopack-zig's `tools/run_env.zig`** (`velopack.addVpkRun`), which sets `DOTNET_ROLL_FORWARD` and puts the bundled `mksquashfs` on `PATH` when vpk runs. So does the MSVC setup script. In 0.17, `Run.setEnvironmentVariable` freezes the configure-time environment into the cached configuration, and `Run.addPathDir` is gone. `MksquashfsBuild.bin_dir` is now `mksquashfs`'s own output directory, with no install step. - **Signing and the macOS SDK are options only.** `-Dmacos-sign-app`, `-Dmacos-sign-installer` and `-Dmacos-notary-profile` lose their `FIZZY_MACOS_*` environment fallbacks, and `SDKROOT` becomes `-Dmacos-sdk`: 0.17 caches the configuration without regard to the environment. `scripts/release.sh` and `release.yml`'s macOS job turn the same variables into the options, and `packageall` forwards them to its macOS children. - **No global `--libc`:** `-Dwindows-msvc-libc` or the managed `.velopack-msvc/` only. The managed manifest's existence and an explicit manifest's contents are declared to the configure cache. - **The MSVC translate-c shim works again.** It finds each module's translate-c step through the generated-file index and `Step.cast`, as `std.Build` does, in place of the removed `Step.id`. - **`docs/PITFALLS.md`, "The build (Zig 0.17)":** the four traps (environment reads, `setEnvironmentVariable`, undeclared configure-time reads, declared paths in missing directories), each with what fizzy does instead. After this, 36 `ZIG017` markers remain on the branch, none in the build system: the zon stand-ins, the `@typeInfo` helpers, the example-branch switch. ## SDK impact - [x] None: no `sdk/` file changes here. - [ ] Core-only or additive: reaches plugins at the next SDK release - [ ] Fingerprint moved - [ ] SDK release ## Verified - [x] macOS (arm64, 0.17.0): - `zig build package` packs a release with vpk 1.2.161: `fizzy-arm64-macos-Setup.pkg`, `-Portable.zip`, `fizzy-0.2.0-arm64-macos-full.nupkg`. The bundle carries `Contents/Resources/sq.version`, 1.x's signature-preserving layout. Unsigned: no identities on this machine. - `zig build -Dvelopack=true` links Velopack (36 `vpkc_` symbols); `zig build test-integration -Dvelopack=true` (79/79 steps). - `scripts/check-fmt.sh`, `zig build`, `zig build test`, `zig build test-integration` (77/77 steps), `zig build check-web`, `zig build test-sdk-version`. - `scripts/check-examples.sh` against the examples' `zig-0.17` branches: all 8 builds pass. - `zig build -Dtarget=x86_64-windows-msvc` configures; `-Dtarget=x86_64-windows-gnu -Dno-emit` builds. - [ ] Windows: not run. The MSVC path (`.velopack-msvc/`, the shim) needs a packaging run. - [ ] Linux: not run. The AppImage path is velopack-zig#9's `run_env --path`, which its CI passes on `ubuntu-latest`. - [ ] **A test release.** `release.yml` packaging all six targets, signed, is the real check for this PR. It has no dry-run mode: it releases an existing tag and stops at a *draft* GitHub release, which nothing auto-updates to until it is published. So the check is a test tag on this branch (e.g. `v0.2.0-zig017.1`), the workflow's draft release, then deleting both. Tagging is the maintainer's call. - [ ] An update round trip, from an installed 0.0.1589-runtime release to one packed with this, per OS. It waits for that dry run's artifacts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of fizzyedit/plans#9 ## What changes The backend stops following the main window through Spaces (`macos_monitor.unwatch`) before it destroys it. Quitting a dvui app on macOS crashed in `objc_msgSend` from `window_in_fullscreen_space`, under `SDL_VideoQuit`: the full-screen monitor's display-link pump, and a block it queued as a way out of full screen ended, touched the window after SDL had destroyed it. Floats' windows were already unwatched as they closed (`viewportClose`); the main window never was. fizzy's own quit goes through the same `deinit`, so it had the same exposure. ## SDK impact - [x] None ## Verified - [x] macOS: two crash reports (`dvui-app`, quitting after full-screen use) before; none in the many quits since. - [ ] Windows: not affected (macOS only). - [ ] Linux: not affected (macOS only). - [ ] Web: not affected. ## Follow-ups None. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Part of fizzyedit/plans#9 ## What changes On fizzy's backend, every dvui floating window that is not modal, `dvui.osWindow`'s included, is shown in an OS window of its own: the OS's title bar and buttons, moving, resizing, maximize, full screen and close. Its content sits below a title bar in the theme's colour. Modal dialogs stay in the window they are over. An app writes plain dvui; on a backend without OS windows, or past the backend's limit, the same floating window stays in the main window with dvui's own header. fizzy opts out (`floating_os_windows = false`, set in `Entry`): its floats keep `Popout`. How: - **dvui asks** (dvui-dev `bd7736d`, pinned here): `Backend.floatingWindowOs(id, open_flag, min, max)` as a floating window begins, and `floatingWindowTitle(id, title)` from `windowHeader`. True: dvui draws no frame of its own and the header draws nothing; the title goes on the OS window. - **The OS owns each window's place and size** (`os_windows.zig`). Each frame the floating window takes its window's frame, and is never grown to fit its content (`_auto_size`). Its `min_size_content` / `max_size_content` become the OS window's limits: dvui's demo, fixed at 600 wide, can't be stretched or maximized. Following the content's own minimum instead fought the person as a pane collapsed. - **The keyboard follows the OS's key window**: a new window opens key, and dvui focuses whichever window the OS makes key. - **Windows**: owned windows, the DWM caption in the theme's colour; no maximize button where a window has a maximum size (Windows ignores the maximum when maximizing). - **Linux**: the non-callback frame loop runs the same steps as `appIterate` (it ran none of them: no OS windows at all). - **Shared with Popout**: `viewports.keptTarget` (Popout's `keepTarget`, moved), `viewports.publishScreens`. - **dvui pin**: as `git+https`. Zig 0.17's build-time fetch left this commit's GitHub tarball unstripped (`docs/PITFALLS.md`). ## SDK impact - [x] Core-only or additive: reaches plugins at the next SDK release (the dvui pin; the fingerprint is unchanged) ## Verified - [x] macOS: `fizzyedit/example-app`'s `run-dvui` (zig-0.17 branch) on this tree: the demo and two child windows as OS windows, combo boxes and dialogs in their own window, theme changes, size limits. Screenshots and a pixel measurement of the title bars. - [x] Windows: Windows 11 ARM VM: owned windows with the system caption; children stay above the main window after a click on it; the demo has no maximize button, the others do. - [x] Linux: Ubuntu 26.04 VM, XWayland: each child its own window, transient for the main window, GNOME's decorations; the demo can't be maximized (fixed width), the others can. On Wayland floating windows stay in the main window (a window can't be placed there), as before. - [x] Web: `zig build check-web`. ## Follow-ups - macOS window shape and full screen: the next PR in this stack. - Popout's float ↔ OS window loop on the same core as `os_windows`, leaving Popout fizzy's own parts (drag, drop zones, glass): its own PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Part of fizzyedit/plans#9 ## What changes A plain dvui app's main window and its floating windows' OS windows take the shape fizzy's own windows have on macOS, so they go into and out of full screen the way fizzy's do: smoothly, the window's own content growing, not AppKit's pictures. - **Content under a clear title bar, with the title showing.** The backend keeps a band under the title bar clear and gives dvui only what is below it (`main_picture`, `macos_monitor.titlebarStrip`, the same strip fizzy's header follows), so the band shrinks to nothing in full screen and comes back on the way out. - **A backdrop in the theme's colour** (`fizzy_macos_window_plain_backdrop`) under the drawing, with the window's corners, in an otherwise clear window. On the way into or out of full screen it follows the picture as fizzy's glass does (`glass_follow`), and draws the shadow and outline the OS draws for no full-screen window. It's refreshed however a window gets into or out of full screen (Split View too). - **The full-screen monitor** is installed by the backend for an app that hasn't installed it itself (fizzy has), with the window standing still at its full-screen size while its picture grows or shrinks (`still`). The renderer lays the main window's frame down at the picture (`window_offset`), which fizzy's `FrameTarget` does for fizzy. - **Shadow and outline**: taken again once a picture is up and as the size changes, as for fizzy's app-framed floats; SDL turns the shadow off on a transparent window. - **One frame rate fix that helps fizzy's floats too**: while one float's window moves itself into or out of full screen, the others are held as the main window is (`mainHeld`). - `titlebarStrip` serves fizzy's header and the plain band (`backend_native.titlebarStripHeight` calls it). Tried and dropped: keeping a plain window's own titled style through the monitor (the content landed a title bar off), and stepping the window's size through the animation (the demo laid out at each size in a new drawable and ran choppy). ## SDK impact - [x] None ## Verified - [x] macOS: `run-dvui` (example-app, zig-0.17): full screen in and out on the main window and children, smooth by eye; title band, main content and child bands measured at the same colour (`#2b2b2b`); shadow and outline back in a window capture. Child windows' transitions measured at 18 ms a frame against the main window's 5.5 (work under 1.5 ms): still to explain. - [ ] Windows: not affected (macOS only). - [ ] Linux: not affected (macOS only). - [x] Web: `zig build check-web`. ## Follow-ups - Why a child window's full-screen transition runs at ~55 fps (sample it while it runs). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Part of fizzyedit/plans#9 ## What changes On macOS a plain dvui app's floating windows' OS windows stay above the main window, as owned windows do on Windows and transient windows on X11, by being child windows of it (`addChildWindow:`). The window server then never puts the main window over them: clicking or raising the main window no longer flashes it over them. A child becomes key and comes in front of the other children. Child windows otherwise move with their parent; owned windows don't. So while the person moves or live-resizes the main window, its children let go (put back where they were as the press began, and kept above it), and attach again the moment the press is let go. A plain click on the title bar lets nothing go: AppKit posts will-move for a click too, and letting go and attaching around it let the main window's raise land between. Full screen: children stay on the desktop. A window going into or out of full screen lets go of its children and its parent before AppKit takes child windows along, and nothing attaches to it until it has landed. Attaching only happens where the main window is on the Space being looked at and not in full screen. Tried and dropped, each with a log of what AppKit did: children following the main window into its full-screen Space. A window can't be moved between Spaces through public API; attached from another Space, a child stayed the child of a window it couldn't reach, and both were lost. Inside the main window's Space, every way a child could take part in it (a Space of its own, tiling, Split View) left the Space black or out of reach, even as an auxiliary window. ## SDK impact - [x] None ## Verified - [x] macOS: `run-dvui` (example-app, zig-0.17), with a temporary attach log read after each run: clicks and drags on the main window, full screen in and out on the main window and children, clicking between children. - [ ] Windows: not affected (macOS only). - [ ] Linux: not affected (macOS only). - [x] Web: `zig build check-web`. ## Follow-ups None. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Conflicts were main's builtin.target rename (#435) against the same rename on zig/0.17, taken from zig/0.17; check-examples.sh, plugins/text's selection hash and the tape reader taken from main. rewrite.sh re-run; by hand: a multi-character string repeat (`**`) in app/state_delta.zig's test as a comptime loop, the tape reader's new field loop in 0.17's shape, and the recorded fingerprint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of fizzyedit/plans#5 (`plans/ZIG_0_17_PLAN.md`, step 2): catching `zig/0.17` up with `main`. It was 40 commits behind, up to #469. ## Please merge this one by fast-forward, not squash This PR is a merge commit with `main` as its second parent. Squashing it would drop that parent. The next catch-up would then find the old merge base, and every line `rewrite.sh` changed in `main`'s code would conflict again. Once CI is green, the clean way is a fast-forward of `zig/0.17` to this commit: `jj bookmark set zig/0.17 -r <this commit>` then `jj git push --bookmark zig/0.17`. That's a push to the integration branch, not to `main`. Your call: squash also works, at the cost of re-resolving these lines next time. ## What changed beyond the merge - **Conflicts (11 files).** Nearly all were `main`'s `builtin.target` rename (#435) against the same rename already on `zig/0.17`; those take `zig/0.17`'s side. These files take `main`'s side, then `rewrite.sh`: - `scripts/check-examples.sh`: `main`'s fuller bsdtar fix. - `plugins/text/src/Document.zig`: the selection hash. - `sdk/tape/binary.zig`: the header now written early, and the versioned field read. - `sdk/src/version.zig`: re-recorded (below). - **`scripts/zig-0.17/rewrite.sh` re-run** over the code `main` brought in. - **By hand:** - `app/state_delta.zig`: a test's multi-character string repeat (`"…" ** 100`, gone in 0.17) is now a comptime loop. - `sdk/tape/binary.zig`: the tape reader's new indexed field loop, in 0.17's `field_names` shape. - `recorded_sdk_shape_fingerprint` is `0x0b0e62b7eac39f6d`: `main`'s SDK changes since the switch, on 0.17. It is released with the merge to `main`, as before. ## SDK impact - [x] Fingerprint moved: recorded in `sdk/src/version.zig`, `sdk_version` untouched ## Verified - [x] macOS (arm64, Zig 0.17.0): - `scripts/check-fmt.sh` - `zig build` - `zig build test`: 95/95 steps, 232/232 tests - `zig build check-tests-reached`: 120/120 - `zig build check-web`: 41/41 - `zig build test-sdk-version`: 35/35 - `zig build test-integration`: 77/77 steps, 559/559 tests - [ ] Windows, Linux, the example repos: CI. `check-examples.sh` wasn't run locally because the machine is under load from other sessions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of fizzyedit/plans#5 (`plans/ZIG_0_17_PLAN.md`, step 2, follow-up (c): `@typeInfo`, with the fingerprint-equality test). Stacked on #486. ## What changes - **The settings and command schema builders read 0.17's `@typeInfo` directly.** `sdk/src/settings.zig` and `sdk/src/Command.zig` loop over `field_names`, `field_types` and `field_attrs`, and take a cell's default with `FieldAttributes.defaultValue`. - This replaces the `ZIG017` stand-in that rebuilt 0.16-shaped field records. `sdk/src/zig017_fields.zig` is deleted. - The bare-payload structs (`Plain`) pass `@typeInfo`'s own name slice to `@Struct` instead of copying it. - **The proof the plan asked for.** `fingerprint.zig` was translated loop for loop in #437, and this adds the check: a test hashing a fixed set of types, one of each kind the hashes look into, at a depth that reaches them all. Both hashes must equal the values **Zig 0.16** computed for the same types, with the file as it was on `main` (7b538aa): - `hashAllShape`, the target-invariant hash that's recorded: `0x2ae9f3848d7d3d9f`. - `hashAll`, on fixed-width types only, since it folds in sizes: `0xc0b59f8f6303fb84`. - The 0.16 run used `main`'s file with Zig 0.16.0 and the 0.17 run uses this branch's with Zig 0.17.0; the values match. `ZIG017` markers left on the branch: the two per-pointer-capture notes (dvui#1014) and `check-examples.sh`'s branch pins, which go back to `main` at the merge. ## SDK impact - [x] None. `zig build test-sdk-version` passes with the fingerprint #486 recorded: the schema shapes plugins see are unchanged. ## Verified - [x] macOS (arm64, Zig 0.17.0): - `scripts/check-fmt.sh` - `zig build` - `zig build test`: 95/95 steps - `zig build check-tests-reached`: 120/120 - `zig build check-web`: 41/41 - `zig build test-sdk-version`: 35/35 - `zig build test-integration`: 77/77 steps - [ ] Windows, Linux: CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of fizzyedit/plans#5 (`plans/ZIG_0_17_PLAN.md`, step 2, follow-up (f): dvui API drift). Stacked on #488. Two places that hand a lifted view's drag on to the mouse's capture read dvui's internal `Window.captures[0]`. dvui 0.17 made capture per-pointer (dvui#1014), and the switch PR left these as `ZIG017` stand-ins that assumed the mouse: - the view-drag lift in `app/layout/Chooser.zig`; - the workbench tab drag in `plugins/workbench/src/Workspace.zig`. Both now call `dvui.captureMouseGet()`. That's dvui's public call (CONTRIBUTING: dvui's public API over its state), and it returns the capture of the pointer that last moved, so a touch drag keeps its own capture instead of the mouse's. ## SDK impact - [x] None ## Verified - [x] macOS (arm64, Zig 0.17.0): - `scripts/check-fmt.sh` - `zig build` - `zig build test`: 95/95 steps - `zig build check-web`: 41/41 - `zig build test-integration`: 77/77 steps - [ ] A view or tab dragged by hand: not done, because the screen is locked. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of fizzyedit/plans#5 (`plans/ZIG_0_17_PLAN.md`, step 2). Writes down the three Windows traps the 0.17 release work (#448) cost time on, so they cost the next person a minute. ## What changes `docs/PITFALLS.md`, under "The build (Zig 0.17)", gets a "Windows, on 0.17" subsection: - **An MSVC exe must link `oldnames` itself.** 0.16 did it for every `*-windows-msvc` exe and 0.17 doesn't, so `getcwd`/`chdir` stop resolving. Now in `build/exe.zig`. - **`translate-c` takes `--libc=<ini>` before its `--`.** After the `--`, the flag goes to the C front end and is lost. Now in `build/msvc.zig`. - **sdl_zig must filter SDL's `core/linux/` for Windows.** `SDL_dbus.c` includes `<unistd.h>` before its guard. Done in fizzyedit/sdl_zig `ef7eb36`; a move to upstream sdl_zig must keep the filter. ## SDK impact - [x] None. Docs only. ## Verified - [x] Docs only: checked each entry against the code it names (`build/exe.zig`, `build/msvc.zig`, sdl_zig `build.zig` at `ef7eb36`). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of fizzyedit/plans#5 (`plans/ZIG_0_17_PLAN.md`, step 3: pixi on 0.17). Found by building pixi against `zig/0.17`'s `sdk/`. ## What changes - **`core.image.blitData` compiles on 0.17.** Its transparent path converted a `dvui.Color.PMA` to `[4]u8` and back with `@bitCast`, which 0.17 refuses for an `extern struct` (the plan's `@bitCast` audit). It now uses `std.mem.toBytes` and `std.mem.bytesToValue`. - **A test compiles it.** Nothing in fizzy calls `blitData`; pixi does, so the switch PR's build never reached it. The new test blits a transparent pixel over an opaque one, and is reached from `core/sizing_tests.zig`, the one root carrying dvui. Other `@bitCast`s that fizzy itself never instantiates may be hiding the same way. The store-plugin builds are what find them, and that's this step. ## SDK impact - [x] None for the shape: `blitData`'s signature is unchanged. Plugins build this `core/` file in, and with it pixi builds on 0.17. ## Verified - [x] macOS (arm64, Zig 0.17.0): `scripts/check-fmt.sh`, `zig build test`, `zig build check-tests-reached` (914 tests in 113 files), `zig build test-integration` (77/77 steps, 561/561; the sizing tests' 115 include the new one). - [x] pixi builds against this tree's `sdk/` by path, and installs into a sandbox `FIZZY_PROFILE`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of fizzyedit/plans#5 (`plans/ZIG_0_17_PLAN.md`, step 3: store plugins on 0.17). The agent plugin's port needs it (fizzyedit/agent#23). ## What changes - **`fizzy.plugin.installBeside(b, exe, .{ .plugin = "<id>" })`.** On `zig build install`, it copies a second artifact into the plugin's own `{plugins}/{id}/` directory, beside the library. The agent plugin ships `fizzy-mcp` there, and the chat plugin looks for it. On 0.16 agent did this with a custom `makeFn` step; 0.17 has no custom steps. - **The install helper keeps the source's permissions,** so a program stays executable. It's the same helper `install` already runs (`sdk/plugin_sdk_install.zig`), now shared through one `addDevInstall` function. - `docs/PLUGINS.md` §2.4 says how to use it. ## SDK impact - [x] Build surface only, shape unchanged: `zig build test-sdk-version` passes with the recorded fingerprint. Plugin authors get the new function with the 0.17 SDK release. ## Verified - [x] macOS (arm64, Zig 0.17.0): `scripts/check-fmt.sh`, `zig build`, `zig build test-sdk-version`, and `plugins/image`'s standalone `zig build` (installs through the reworked helper). - [x] fizzyedit/agent#23 builds against this `sdk/` by path, and `fizzy-mcp` lands in `<profile>/plugins/agent/` as `-rwxr-xr-x`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#486 was squash-merged, so zig/0.17 lost main as an ancestor and the next merge would have gone back to #434 as its base. This commit takes #486's original merge commit (46ddcd8) as a second parent and keeps zig/0.17's tree as it is, so main at #469 is the base again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A clean merge; rewrite.sh re-run over the new code. By hand in #490's app/control: a test's string repeat becomes a splatted array, Ast.parse takes .{ .mode = .zon }, Zoir is read through a pointer, and a test's zon parse takes 0.17's options. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of fizzyedit/plans#5 (`plans/ZIG_0_17_PLAN.md`, step 2): the last stand-ins before `zig/0.17` goes to `main`. Stacked on #503; retarget to `zig/0.17` once it lands. ## What changes - **The tape reader drops its 0.16 workaround.** `sdk/tape/binary.zig` fed std's inflate an `Endless` input, a stream's bytes followed by zeros without end. That kept 0.16's inflate off a bug: `tossBitsShort` crashed on a stream that ended inside its last four bytes. 0.17's `tossBitsShort` checks the bits it has before tossing them, so the reader is a plain `std.Io.Reader.fixed` again, and a stream cut short comes back as `Corrupt`. The Adler-32 check stays: 0.17 still leaves the checksum unchecked. - **The readme and `docs/PLUGINS.md` say Zig 0.17.0.** That covers the plugin template's `minimum_zig_version` and the release workflow's `zig-version`. Left for later, on purpose: - `scripts/zig-0.17/` stays until the switch, in case `main` moves again and needs another catch-up. It's deleted in the last PR before the switch. - `check-examples.sh` keeps pointing at the examples' `zig-0.17` branches through the switch. It flips to `main` in a one-line PR right after their 0.17 PRs land, so `main`'s CI is never red in between. ## SDK impact - [x] None: no shape change, and the tape format is unchanged. ## Verified - [x] macOS (arm64, Zig 0.17.0): `zig build test`, 99/99 steps, 583/584 (1 skipped). That includes `fizzy-tape-tests` 62/62, whose "not a whole one" test reads a tape cut short at every byte, deflated and not. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
) Part of fizzyedit/plans#9 (`plans/DVUI_UPSTREAM_PLAN.md`), the fizzy-backend side. This replaces #483's child-window model, after you reported on `run-dvui` that: - moving the main window sometimes moved the floats; - a window going full screen took the others into its Space; - the main window came in front of them afterwards. ## Why #483 made each OS-framed float (`Viewport.native`) a child window of the main window, so it would stay above it. **macOS has no window that both stays above another and goes full screen on its own:** | | Stays above the main window | Own full screen | |---|---|---| | Child window | yes, but it moves with its parent and goes into full screen with it | no | | Above the normal level | yes, but it's taken into the app's full-screen Space as a palette, on the way there and when you swipe to it | no | | Normal window | no, it comes forward when clicked | yes | This PR tried two of those in turn: a window level, then the level dropped around full screen, then reordering windows when the main window came forward. Each patch covered one moment, and the next test found another. ## What it does now The floats become **ordinary document windows**. That's how macOS apps' torn-out windows work (VS Code, Xcode), and it's what an OS window of dvui's is. - Each goes full screen alone. - Moving the main window moves only the main window. - You decide which window is in front. **Gone:** - `attachNatives` and its per-frame checks; - the press monitor, the frame restore and the reattach timer; - the key-raise observer; - the let-go/way-done marks; - `macos_monitor.inTransition`. 2 lines added, 265 removed. **Given up:** the floats are no longer held above the main window on macOS. On Windows and Linux they still are, as owned and transient windows, where that's the standard behaviour. **A pitfall noted** in `backend/AGENTS.md`, under secondary windows: no macOS window both stays above another and has a full screen of its own. **A named follow-up:** a palette kind of window, an `NSPanel` that floats over the app's windows and follows its full screen but can't go full screen itself, offered through the backend's floating-window hook for an app that wants tool windows. ## Not in this PR A float's full screen is choppier than the main window's, and an outline shows 30–50 px outside it on the way. Both need measuring on screen. ## SDK impact - [x] None ## Verified - [x] macOS (arm64, Zig 0.17.0): - `scripts/check-fmt.sh` - `zig build` - `zig build test`: 91/91 steps - `zig build check-web`: 41/41 - `zig build test-sdk-version`: 35/35 - `zig build test-integration`: 77/77 steps - `run-dvui` built and launched. - [ ] By hand on `run-dvui`: a float and the main window into and out of full screen, and moving the main window. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ce (#501) Part of fizzyedit/plans#9 and the multi-window plan (fizzyedit/plans#26, step 3). Stacked on #492. **What changes.** A plain dvui app on the fizzy backend now gets an OS window only for a floating window that asks for one. By default that's `dvui.osWindow`'s. Everything else, dvui's demo included, stays in the main window. That's what David wants for dvui's demo: in-app floating windows, plus an "I'm an OS window" demo. - **dvui-dev `zig-0.17` (98a0e07):** - `FloatingWindowWidget.InitOptions.os_window`, set by `dvui.osWindow`'s fallback floating window. - `Backend.floatingWindowOs` gains `asked: bool`. - This is a commit on top of the one fizzy pinned (bd7736d), so the old pin stays reachable. - **Backend:** `floating_os_windows` becomes `.asked` (the default), `.all` (every non-modal floating window, as before) or `.none`. - **Fizzy:** sets `.none`, since it takes its floats out through Popout. - **`sdk/build.zig.zon`:** dvui pinned at 98a0e07, as `git+https` like before. `docs/DEPENDENCIES.md` now says what `zig/0.17` pins from dvui-dev: patches 9 (the hook reshaped to `floatingWindowOs`/`floatingWindowTitle`) and 10 (this one), on top of the eight rebased onto upstream `main`. ## SDK impact - [x] None. `test-sdk-version` passes with the recorded fingerprint. ## Verified - [x] macOS (arm64, Zig 0.17.0): - `scripts/check-fmt.sh` - `zig build` - `zig build test`: 91/91 steps - `zig build check-web`: 41/41 - `zig build test-sdk-version`: 35/35 - `zig build test-integration`: 77/77 steps, 523/523 tests - dvui-dev: `zig build -Dbackend=testing test`, 37/37. - `run-dvui` built and launched. Not looked at on screen. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…#504) Part of the multi-window plan (fizzyedit/plans#26, step 2: a float's full screen as smooth as the main window's). Stacked on #501. On its way into or out of full screen, a float's window jumps to the screen's size and stands still there, while its picture grows or shrinks inside it. The main window does the same and runs smoothly. You reported two problems on `run-dvui`: a float's animation is choppier, and the space beyond it fills with black, with a band outside the window on the way back. **The frame rate, measured on `run-dvui`:** a float's animation ran at **≈ 56 fps**, a steady 18 ms a frame, while the main window's presented at ≈ 230 per second. - The present itself took 0.03 ms, and turning vsync off didn't help, so the wait was elsewhere. - The frame loop sleeps each frame out to 1/60 s while the app is unseen (`unseen()`, `hidden_frame_ns`). That stops a hidden app spinning a core. - But it counted only the main window. While a float goes into a full-screen Space of its own, the main window is on another Space, so it counted as unseen, and every frame of the float's animation slept: 16.7 ms of sleep plus about 1.3 ms of work. - Now a frame presented into a viewport window that is itself visible counts as seen (`viewport_seen`). **Hangs mid-animation**, where the animation sticks, then jumps to its end. A temporary frame trace (not committed) caught frames of exactly 1006–1009 ms, four in about 700 frames. - That's Metal's one-second drawable timeout: a frame asked for the main window's drawable while the main window sat on a Space nobody was looking at. - The main window was held back only while a float was mid-animation, and SDL's occlusion flag lags AppKit's. - Now it's also held whenever it sits on a Space nobody is looking at (`fizzy_macos_window_off_active_space`). The next traced session had no such frames in 1,288. - On purpose, not "every way of being unseen" (`windowUnseen`): fizzy shows its main window undrawn for its first frame (`showUndrawn`), and held then, it would never draw that frame. Checked: fizzy starts and reveals its window as before (on screen, opaque, 1130 ms after `main`). - Left: one gap of 915 ms in that session. Only 105 ms of it was our loop waiting for an event; the rest was on the main thread outside our frame, so most likely macOS's Space switch. **Four more problems, also fixed here:** 1. **The shadow was redone every frame.** A float re-asked the window server for its shadow whenever its *picture* changed size, which during the animation is every frame. The window server works a shadow out from the window's pixels, here those of a full-screen window. The trigger is now the *window's* size, as for the main window, so it happens once, at the jump. 2. **The background filled the screen around the picture.** A float's backdrop view supplies its colour and follows the picture, and the window behind it should be clear. But `skin` set the window's background to the theme colour, and `setBackground` applies `skin` to every float's window again. That painted over the clear background the backdrop had set, filling the full-screen-size window around the picture: dark in a dark theme. Now a window with the backdrop gets its colour on the backdrop and stays clear (`fizzy_macos_window_has_plain_backdrop`). 3. **Square corners inside rounded ones.** In your screenshot the picture's square bottom corners stuck out past the backdrop's rounded ones. The OS rounds a window's content to its corners, but during the animation the window is full-screen size and the picture only part of it, so nothing rounded the picture. Now SDL's view is clipped to the picture's rounded rect for the whole animation (`clip_content`). The corner radius eases from the window's radius to square as the picture grows, and back on the way out, instead of switching at one end. 4. **A band around the float when frames are slow.** Your screenshot, taken while recording the screen, shows a second rounded outline about 40 px outside the picture. The picture moves only in the app's frames, but the monitor's pump (a display-link tick) also stepped the animation between frames, moving the backdrop with it. With frames slowed down, the backdrop ran ahead of the picture. Now a still window's animation steps only in the app's frames (`fizzy_macos_window_space_step`), so the backdrop and the picture always move together. Also in `backend/AGENTS.md`: step a still window's way only in the app's frames. ## SDK impact - [x] None ## Verified - [x] macOS (arm64, Zig 0.17.0): - `scripts/check-fmt.sh` - `zig build` - `zig build test`: 91/91 steps - `zig build check-web`: 41/41 - `zig build test-sdk-version`: 35/35 - `zig build test-integration`: 77/77 steps - `run-dvui` built and launched. - [x] Measured on `run-dvui` with a temporary trace (not committed): ≈ 56 fps for a float, ≈ 230 presents/s for the main window, the present itself 0.03 ms. - [ ] By hand: a float into and out of full screen, as smooth as the main window, with no black around it and no band on the way back. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…510) Part of the multi-window plan (fizzyedit/plans#26). Stacked on #504. **The bug** (you, on `run-dvui`): hovering a float's window lit up the main window's widgets underneath it. **Why:** AppKit sends mouse-moved events to the *key* window, wherever the pointer is. SDL's `mouseMoved:` reports them as that window's, with no check of what's under the pointer. So with the main window key, motion over a float's window in front of it arrived as the main window's, at a spot that window covers. Clicks weren't affected, because AppKit sends those to the window under the pointer. **The fix** extends `heldAcrossWindows`, which already handled a held pointer: - With a viewport open, a motion event is placed by where the pointer actually is (`heldPoint`) when the app's window in front under the pointer belongs to a viewport and isn't the window the event came from. That window is found with `windowNumberAtPoint`, through `fizzy_macos_window_at_pointer`. - Over another app's window, nothing changes, so hover doesn't leak through someone else's window either. - The cost is one window-server query per mouse move, only while a viewport is open and no button is held. **A second bug, the same kind** (you, on `run-dvui`): with the main window full screen, dragging dvui's demo window around made it vanish over certain zones. - While a button is held, `heldPoint` decides which of the app's windows the pointer is over by comparing it with each window's rect on the desktop. It never asked whether that window is on the Space being shown. - The floats left on the desktop still matched where they lie there, so the drag was read as over one of them. The demo was dragged into that window's band, out of sight. - Now a window nobody sees (`windowUnseen`, which covers being on another Space) is skipped, both for the window that took the press and in the scan. ## SDK impact - [x] None ## Verified - [x] macOS (arm64, Zig 0.17.0): - `scripts/check-fmt.sh` - `zig build` - `zig build test`: 91/91 steps - `zig build check-web`: 41/41 - `zig build test-sdk-version`: 35/35 - `zig build test-integration`: 77/77 steps - [ ] By hand on `run-dvui`: - hover a float's window over the demo: nothing lights up beneath it; - main window full screen, drag the demo window over where the floats lie on the desktop: it stays. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 18, 2026
foxnne
force-pushed
the
backend/window-materials
branch
2 times, most recently
from
October 18, 2026 16:17
9eb49c9 to
e5763b9
Compare
foxnne
force-pushed
the
app/glass-material
branch
from
October 18, 2026 16:18
bac5e0a to
4a4f6b0
Compare
core.material: what the app's windows are made of (none, transparent, blur, frost, liquid, and an opacity), the kinds a backend offers (only a kind every surface it puts glass on can show), and the fallback from the kind asked to the best one offered (liquid > frost > blur > transparent > none). std-only, with a unit-test root, but for publish/get: the app publishes the effective material each frame before anything draws, in dvui's data as native_glass is, so a plugin's glass reads the same value. The backends say what they show, each in a plain struct of their own the app copies field for field (the backend does not depend on core): macOS vibrancy (frost), a plain blur and Liquid Glass from macOS 26; Windows Acrylic (frost); Linux the compositor's blur behind a window where it has one (wayland_blur.offered, a read-only twin of behind); every one a clear window too. The web shows every kind, all drawn in its canvas. Kept out of dvui (the user): dvui keeps working on stock backends, and nothing changes until the app asks for a material. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app asks for liquid (FIZZY_GLASS asks for less until the setting lands) and publishes what the backend can show. Editor.tick maps it onto the dialog style, refraction and the glass program, which core and every plugin already read; Popout offers the OS's Liquid Glass only for liquid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
foxnne
added a commit
that referenced
this pull request
Oct 18, 2026
An SDL fetch on #549 failed with `bad HTTP response code: '500 Internal Server Error'`, which the script did not count as a fetch failure. A 4xx still fails at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
foxnne
force-pushed
the
backend/window-materials
branch
from
October 18, 2026 16:38
e5763b9 to
4eca7f6
Compare
foxnne
force-pushed
the
app/glass-material
branch
from
October 18, 2026 16:38
4a4f6b0 to
d2128cb
Compare
…548) Part of the multi-window plan (fizzyedit/plans#26, step 3a: tearing off a floating window). Stacked on #546. **What it does** in a plain dvui app on the fizzy backend (`run-dvui`): 1. **Drag a floating window by its header past the main window** (or any of the app's windows). It becomes a small snapshot card at the pointer: half its size, at most 360 pt wide, in a carry window with the OS's shadow, held at the point the pointer grabbed. It's taken out of the window it was in. 2. **Let go over the desktop:** the card grows to the window's full size where it opens (200 ms, eased out). The floating window is then in an OS window of its own there, and the card goes once that window has a frame on screen. `floatingWindowOs` answers true for it even though it didn't ask (`os_windows.torn`). **Why a card** (your call): a full-size live picture made to look like an OS window never quite matches the window it becomes, so there was always a jump. A card that grows into the window shows the change of state instead. 3. **Close that window:** it's an ordinary in-app floating window again the next time it shows. 4. **Move a torn-off window wholly inside the main window and let go:** it goes back in as an in-app floating window, where you let it go. *Wholly* inside, the model Dear ImGui uses, so a window that only overlaps the main one stays a window. Only torn-off windows do this; a `dvui.osWindow` asked for its OS window and keeps it. **The title bars change smoothly** (your second try). In the app the floating window has dvui's header; as an OS window it has the OS's title bar instead, a different height. Each switch used to jump the content. - **Going out:** from the frame after it passes the app's windows, the floating window is drawn without dvui's header, as an OS window's is, though no window is opened yet (`os_windows.heldFrameless`; `named` holds the window back). The card shows its content live, and a blank band of the header's height closes up as the card shrinks. - **Dropped on the desktop:** the card grows to the window's frame, and a title bar's band opens at its top. The window's content then lands under the OS's title bar exactly where the card's content was (`viewports.titleBand`). - **Merged back:** the window's last picture shrinks from its frame to its in-app place, and the OS's title-bar band turns into dvui's header band (`headerHeight`: the heading font's line height plus padding, computed from the theme). The in-app floating window takes over as the card goes. **Nothing clipped** (your third and fourth tries): the card showed only part of the window, both just past the main window's edge and while growing into its window. dvui clips a floating window to the screen nearest its middle, and the main window wins every tie. While a floating window is shown only as a card (torn off, or growing until its window opens), the backend now parks it far left in the frame, as far left of the main window as the bands are right of it, on a screen of its own (`park`). dvui clips it to exactly itself there. The card is placed by the pointer, so where it sits in the frame doesn't matter. Back over the app it's re-anchored under the pointer. **Over a window, when the card is under the pointer** (your fifth try, traced): dragged out and then over a child window, the card never left and the drop tore it off over the child. Once the card existed, "is the pointer over no window of the app's?" answered yes on every frame (244 of them in the trace), because the window in front under the pointer was the card itself, or the glass the OS puts with it. It's now asked of the app's own windows that take the pointer: shown, on the Space being shown, and not click-through (`fizzy_macos_pointer_over_app_window`). **The window it's over comes forward** (your sixth try): during a drag, each app window the pointer comes over (the main window or a child) is brought in front of the others, once as it comes over it. It's ordered front without being made key, so the keyboard stays where it was. It shows where the dragged window would land (`viewports.bringForward`). **Into a small window** (your seventh try): dragging the demo into a freshly made `dvui.osWindow` made it vanish. Each OS window's picture took the subwindows whose *middle* was in its part of the frame. dvui keeps a floating window larger than its screen from that screen's top left, with the rest past the edge, so the demo's middle fell outside the small window's part. No window's picture took it, and the main window doesn't show that far into the frame. Now a window takes every subwindow that overlaps its part of the frame. Parts are a band apart, so a subwindow overlaps one at most. **Smoothing, from your first try:** - **The pop as it leaves:** the card now shrinks from the floating window's own size into card size around the pointer (150 ms) instead of jumping. - **The shimmer while moving:** the snapshot is sampled smoothly, not by nearest pixel, and the card is placed on whole pixels. - **Landing lower than the pointer when dragged back in:** the floating window is re-anchored so the point you grabbed is under the pointer again, rather than where dvui's clamp left it. Over another of the app's windows, the held pointer already carries it into that window (`heldPoint`, made Space-aware in #510). **Backend only, no dvui change.** dvui says nothing about drags, so the backend reads one from what dvui shows: - the floating window that holds the mouse capture (`dvui.captureMouseGet`); - its rect moving at a constant size, which means a move by its header, not an edge resize; - the button let go (`pointerHeld`). An earlier design added a dvui hook (`floatingWindowDragged`, dvui-dev `tear-off-0.17`). It's dropped, following your rule that this stays in the backend. **Scope:** - Only where the app leaves its floating windows to the backend (`floating_os_windows != .none`). Fizzy carries its own out (Popout), so nothing changes for fizzy. - macOS only, because it needs to know whether the pointer is over any of the app's windows (`pointerOverNoWindow`, `windowNumberAtPoint`). Windows and Linux need the same query, and that's the seam (`viewports.tears`). **Known rough edges, to judge by hand:** - The card is a snapshot taken as it leaves the app's windows, not live. That's fine for a drag, but say if you'd rather it stayed live. **From review:** a grow could never end. If the floating window stopped being drawn during it (the app closed it), or its OS window never opened (no window to spare, past eight, or no `open_flag` to close one by), the card stayed on screen and every frame asked for the next. Now the grow gives up (`endGrow`): at once if the window isn't drawn, or a second past the card's growing if no window has opened. The card goes, the window isn't marked torn off, and it stays an ordinary in-app floating window. ## SDK impact - [x] None ## Verified - [x] macOS (arm64, Zig 0.17.0): - `scripts/check-fmt.sh` - `zig build` - `zig build test`: 93/93 steps - `zig build check-web`: 41/41 - `zig build test-sdk-version`: 35/35 - `zig build test-integration`: 77/77 steps - `run-dvui` built and launched. - [x] By hand on `run-dvui` (you): drag out with the card; drop on the desktop and grow into a window; drop into a child window and into a newly made one; merge back; windows coming forward under the drag; the title bars changing without jumps. These were the items to check: - drag the demo window out: the card shrinks smoothly and doesn't shimmer; - drag it back in: it stays under the pointer; - drop it on the desktop: it grows into the window; - move that window wholly into the main window and let go: it merges back in. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Part of fizzyedit/plans#5 (`plans/ZIG_0_17_PLAN.md`, step 2): catching `zig/0.17` up with `main` again, through #509. ## Please merge by fast-forward, not squash This is one merge commit, with `main` (#509) as its second parent. Squashing it would drop that parent again, and the next catch-up would re-conflict. To fast-forward: ``` jj bookmark set zig/0.17 -r 44849f2 && jj git push --bookmark zig/0.17 ``` ## What changed beyond the merge - **`build/package.zig`** takes `main`'s restructure (#537), with the 0.17 port reapplied by hand: - `vpk` runs through `velopack.addVpkRun`. - The pack directory comes from `installDirPath`, and paths from `b.root` (no `getInstallPath`, `getPath3` or `pathFromRoot`). - `release_mode` is read from `b.graph`. - User options are read as 0.17's map, whose values are the union itself. - The signing options reach the per-target child builds through #537's forwarding of every `-D` option. - **`build/app.zig`:** the per-app `VERSION` read is declared to the configure cache. - **`app/control/`** (#499, #509): three tests' string repeats (`"x" ** n`, removed in 0.17) are splatted arrays. - **dvui-dev** moves to its `zig-0.17` branch at `8fa9b9c`. `main` repinned dvui-dev for #540 and #544, adding five commits to the 0.16 `fizzy-dev` stack. Four of them are ported onto `zig-0.17`. The fifth (the snapshot port) is already in upstream's 0.17 base. dvui's tests on 0.17: 269/269, and the SDL3 build passes. - **Fingerprint re-recorded:** `0xf20b8afc2f374648`, from #536's SDK change plus the ported dvui. - `rewrite.sh` re-run over the new code. The merge first reached #554, where `main` itself was red: #508's openapi test clashed with #536. So it goes through #509, which fixed that test. ## SDK impact - [x] Fingerprint moved: recorded in `sdk/src/version.zig`, `sdk_version` untouched. ## Verified - [x] macOS (arm64, Zig 0.17.0): - `scripts/check-fmt.sh` - `zig build`: 71/71 - `zig build test`: 105/105 steps (629/630 tests on the first full run, 1 skipped) - `zig build check-tests-reached`: 981 tests in 119 files - `zig build check-web`: 42/42 - `zig build test-sdk-version`: 35/35 - `zig build test-integration`: 78/78 steps - `plugins/image` standalone build: 43/43 - [ ] Windows, Linux, the example repos: CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
4 of 5 tasks
foxnne
added a commit
that referenced
this pull request
Oct 18, 2026
## What changes `scripts/ci-retry.sh` now retries a dependency fetch whose DNS lookup failed. On #549 (Zig 0.17), `Tests (Linux)` failed twice, about 15 minutes apart, fetching codeberg's `translate-c` (a `git+https` dependency under dvui): ``` error: unable to discover remote git server capabilities: NameServerFailure ``` The script matched only `TemporaryNameServerFailure`, so it printed "not a fetch failure — not retrying" and failed on the first attempt. Codeberg resolved fine from a laptop at the same time, and other runs with a warm cache never fetched it. The pattern is now `NameServerFailure`, which covers both, plus the git fetch's own wording, `unable to discover remote git server capabilities`. It also retries a server error (`bad HTTP response code: '5xx`): an SDL fetch on #549 failed with `'500 Internal Server Error'` and was not retried. A 4xx, a failing test or a compile error still fails on its first attempt. ## SDK impact - [x] None - [ ] Core-only or additive: reaches plugins at the next SDK release - [ ] Fingerprint moved: recorded in `sdk/src/version.zig`, `sdk_version` untouched, PR labelled `sdk` - [ ] SDK release: bumps `sdk_version`, lists the `sdk` PRs since the last `sdk-v*` tag ## Verified - [x] `bash -n scripts/ci-retry.sh` passes. - [x] The new patterns match the exact lines from both failed logs, and a `404` does not match. - [ ] Not seen retrying in CI: that needs the DNS failure to happen again. ## Follow-ups - `zig/0.17` needs this too once `main` is merged forward into it; #549's runs go through that branch's copy of the script. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
zig/0.17 brought main's dvui port (8fa9b9c, "deferred queues replayed
in one place"), which took viewports.Picture.subwindow away for
Window.renderSubwindow(sw, .{ .take = true }). The tear-off (#548) is
ported to it: its live picture and an OS window's picture use
renderSubwindow, an OS window still taking every subwindow over its part
of the frame; Picture.drop empties render_cmds_front too, as take does.
build/app.zig keeps both new test roots (material, menu order).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Part of fizzyedit/plans#5 (`plans/ZIG_0_17_PLAN.md`, step 2). Fixes findings 1–3 of the high-effort review of `main`…`zig/0.17`. Stacked on #558; retarget to `zig/0.17` once that's fast-forwarded. ## What changes - **A settings payload holding pointers is a compile error.** Zig 0.17's zon parse allocates into an arena, so `applyZon` and `setZonText` (`sdk/src/settings.zig`) parse into one and copy strings out. Any other pointer would be left pointing into the arena after it's freed: a `[]const []const u8`, or a struct with a slice. On `main` such a payload parsed into `gpa` and was never freed either, because the schema owns strings only. So `Schema` now refuses one with a message saying so (`holdsPointers`). No setting in fizzy or the seven store plugins uses one; all are bools, numbers, enums and strings. Command arguments are untouched: they live in the call's arena, so a list argument is still fine. - **A failed copy frees the copies before it.** If `applyZon`'s copy-out runs out of memory partway, it frees the strings it already copied and leaves the live value as it was. Before, the first copies leaked. - **Build-time reads respect the build root's `sub_path`.** The 0.17 port read `VERSION` (`build/app.zig`), `web/index.html` (`build/web.zig`) and plugin manifests (`sdk/plugin_sdk.zig`, `plugins/shared/build/helpers.zig`) through `b.root.root_dir.handle`, and found `../core` from `root_dir.path`. Both ignore `b.root.sub_path`. They now go through `b.root.joinString`, which keeps it. That's the same result for every build today, and correct for a root that sits in a subdirectory. Findings 4 and 5 (one translate-c of `Velopack.h` per module, one dev-install helper per artifact) are efficiency only and left for later. ## SDK impact - [x] Shape unchanged (`test-sdk-version` passes as recorded). A plugin author who declares a pointer-holding setting now gets a compile error instead of a dangling value. ## Verified - [x] macOS (arm64, Zig 0.17.0): - `scripts/check-fmt.sh` - `zig build`: 71/71 - `zig build test`: 105/105 steps, 630/631 (1 skipped), including the new `holdsPointers` test - `zig build check-tests-reached`: 982 tests - `zig build check-web`: 42/42 - `zig build test-sdk-version`: 35/35 - `zig build test-integration`: 78/78 - `plugins/image` standalone: 43/43 - [ ] Windows, Linux: CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Part of fizzy's glass material (fizzyedit/plans#27) and the multi-window plan (fizzyedit/plans#26). Stacked on #510. The shape was agreed with the glass session (fizzy-1d), which stacks core's glass shapes on this. **Kept out of dvui**, by your decision: dvui keeps working on stock backends, and the material is fizzy's. The OS windows' material is the backend's job, and in-app glass is core's. The earlier version of this PR added the type to dvui (dvui-dev `window-material-0.17`). That's dropped: no dvui repin. **`core/material.zig`:** - `Material { kind: Kind = .none, opacity: f32 = 1 }`. - `Kind { none, transparent, blur, frost, liquid }`, plainest first. - `Kinds`, a struct of bools with `has(kind)`. The offer rule: a backend offers a kind only if every surface it puts glass on can show it. - `Material.effective(offered)`: the kind asked for, or the best one offered below it, in the order liquid → frost → blur → transparent → none. It has a unit test and a std-only root in `build/app.zig` (`fizzy-material-tests`). - `publish` and `get`: the effective material this frame, kept in dvui's data as `native_glass` does, so a plugin's glass reads the same value as fizzy's. Only these two reach dvui. **The backends** answer in a plain `MaterialKinds` struct of their own, because `backend/` doesn't depend on `core`. The app copies it field by field (`Entry.materialOffered`): | Platform | Kinds it offers | |---|---| | macOS | transparent, blur, frost (vibrancy); liquid from macOS 26 (`liquidGlassAvailable`) | | Windows | transparent, frost (Acrylic) | | Linux | transparent; blur where the compositor blurs behind windows (`wayland_blur.offered`, a read-only twin of `behind`) | | Web | every kind, all drawn in the canvas | **Each frame**, before anything draws, `Entry` publishes `Material{}.effective(offered)`. That's `.none` until fizzy has a setting for it, which is fizzy-1d's part. **No behaviour changes.** **Next:** applying the material to the OS windows (backend), and a plain dvui app's main window no longer getting Liquid Glass by default. ## SDK impact - [x] None. `test-sdk-version` passes with the recorded fingerprint. For plugin authors there's a new `core.material` namespace. ## Verified - [x] macOS (arm64, Zig 0.17.0): - `scripts/check-fmt.sh` - `zig build` - `zig build test`: 93/93 steps, `fizzy-material-tests` among them - `zig build check-web`: 41/41 - `zig build test-sdk-version`: 35/35 - `zig build test-integration`: 77/77 steps, 501/501 tests - `zig build check-tests-reached`: 118/118 - [ ] Windows, Linux: CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
foxnne
marked this pull request as ready for review
October 18, 2026 18:39
Part of the window material (fizzyedit/plans#27, "backend: apply the effective material to every OS window") and the multi-window plan (fizzyedit/plans#26). Stacked on #549. **One decision, one applier** (agreed with fizzy-1d): - `core.material` decides, and fizzy's Entry publishes the effective value. - The backend applies it. Entry hands the same value to the backend each frame, right after the publish (`setWindowMaterial`). - `backend/` doesn't depend on `core`, so the backend has a plain-typed twin: `MaterialKind`, field for field, and `window_material`. **What the backend does,** for the windows it dresses itself (`dressPlainWindows`: a plain dvui app's main window and its OS-framed floats): - Falls back from the kind asked to what it can show (`effectiveMaterial`, same order as `core.material`). - Keeps the theme's window colour as the backdrop, at the opacity asked (opaque for `none`). - Under it, the OS's vibrancy: the window's frosted material for frost, the full-screen-UI one for blur. - **Liquid is fizzy's own window glass**, not Apple's bare glass: the same parts fizzy's windows stand on (`windowGlassInstall`: lens, frost body, plain blur, colour) and the same look each frame (`glass_look.window` at the material's opacity, the clearing bevel by the window's size), over a clear backdrop. Choosing another material removes the parts. The first version, one `NSGlassEffectView` clipped to the corners (`masksToBounds`), looked unlike fizzy and cost a lot. - The material follows the backdrop exactly, including through the full-screen animation (`glass_follow`). - A float's window opened later takes the same material. **`glass_look` moves into the backend** so it can use it: `core/gfx/glass_look.zig` → `backend/src/glass_look.zig`, a std-only module of its own. - The backend's build makes one instance per build (`glassLookModule`). `core` takes that same instance where it is linked beside the backend (`core_module.addImports`' new argument), since a file may belong to only one module. - Web, the tests and the plugin SDK make their own instance from the file. `repoPath` moves to `core_module` for that. - `scripts/pack-sdk.sh` vendors the file into the SDK tarball beside `core/`. **Not yet:** - **Fizzy's own windows** (it dresses them itself: `app_dresses_windows`) don't take the material yet. That's the next step on plans#27. - **In-app floating windows** in the matching material: the next step after this one. - **Windows and Linux:** `platform.window.plainMaterial` is macOS only. ## SDK impact - [x] None. The plugin boundary is unchanged. The SDK tarball now carries `backend/src/glass_look.zig` (checked: `scripts/pack-sdk.sh` output lists it in `.paths`). ## Verified - [x] macOS (arm64, Zig 0.17.0): `scripts/check-fmt.sh`, `zig build`, `zig build test`, `zig build check-tests-reached`, `zig build check-web`, `zig build test-sdk-version`, `zig build test-integration` all pass. - [x] `plugins/image` builds against `sdk/` (a sandbox `FIZZY_PROFILE`). - [x] By hand on `run-dvui` (fizzyedit/example-app#7): each material and the transparency slider, on the main window and an OS window. - [ ] By hand: Liquid Glass's look and cost against the first version, side by side. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
foxnne
added a commit
that referenced
this pull request
Oct 18, 2026
Part of fizzyedit/plans#27 (option A, the corner). ## What changes A report from the phone web build: a focused chip in a place's top-right corner sat under the place's corner button. The draw-order half was #540. This is the trigger half: the button came up whenever the pointer was within 64pt of the corner. On touch the last touch *is* the pointer, so tapping a control in that corner put the button on top of it. - **A filled place always shows its corner:** a piece of glass filling the card's top-right corner (option A in the prototypes). Its top and right edges are the card's, its outer corner is the card's own curve, and its inner corner is rounded. It carries the button's grid as a small glyph. Its frost is read once (`witness`), since what is under it is the card's own padding. - A glass shape's corner is held to half its side, so the piece is at least twice the card's radius wide. That way its outer curve is always the card's, at any corner roundness. - **Only the corner brings the button up, and the corner turns into it.** - The piece of glass becomes the button: one glass id, its rect and corners eased from the corner's to the button's circle by the button's fade, and back as it goes. - With a mouse: the pointer on the corner, then anywhere on the button while it is up. It stays down under a modal, keeping #535's rule. - With a finger: a tap on the corner brings the button up and goes no further, so the place never sees it. A second tap opens the picker, and holding the button lifts the view as before. It goes after 3s (a `dvui.timer` wakes the frame), or at the next touch anywhere else. - The finger's target is 44pt, centred on the card's outer corner, so half of it lies in the gutter. - **Any material, not only glass.** The piece and the morph draw through `core.dialogs.glassBubbles`, which now takes per-corner `radii` (each held to half the shorter side; the frosted-pane and plain-fill fallbacks take the same corners). Where there is no glass program or no blur, it is frosted, a transparent tint or an opaque fill, in the same shape, as plans#27's material says once that lands. - **Empty places are unchanged:** the hatch and the button, always. Prototypes: https://claude.ai/artifact/MotGphK5UyeH9sQ6NHeYZk (option A). The piece's inner corner is rounded to half its side (the most a shape's corner can be), not a full quarter-circle, so it reads as the corner filled with glass rather than a quarter disc. ## SDK impact - [x] None - [ ] Core-only or additive: reaches plugins at the next SDK release - [ ] Fingerprint moved: recorded in `sdk/src/version.zig`, `sdk_version` untouched, PR labelled `sdk` - [ ] SDK release: bumps `sdk_version`, lists the `sdk` PRs since the last `sdk-v*` tag ## Verified - [x] macOS 26: - `scripts/check-fmt.sh`, `zig build` and `zig build check-web` all pass. - `zig build test`: 105/105 steps. - `zig build test-integration`: 54/54 steps, 594/594 tests. - On screen: a sandbox fizzy (`--profile`), captured at rest. The corner piece shows in the explorer, the main view and the panel, its outer curve on each card's, with the glyph. - Not seen: the morph into the button on hover, a tap, the 3s hide, or the non-glass materials (those need plans#27's material, #549). The hover and tap need input I didn't drive, since no tape here hovers a corner. - [ ] Windows: - [ ] Linux: - [ ] Web: `check-web` only. **The phone flow (tap, tap, hide) is what this is for, and needs seeing on a phone.** ## Follow-ups - Once #563 merges, the corner reads `dvui.pointerKind() == .touch` in place of its own `_chooser_touch` flag, in a small PR of its own. - Cost not measured: every filled place now draws its corner glass each frame it draws, with its frost read once (`witness`). That is one small pane draw per place. - The hint beside the button on a first tap ("Tap to arrange · hold to move", in the prototypes) is not drawn here. - Option C, the button docked in a tab strip. - A demo tape that hovers and taps the corner, so this is covered by a test. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2 of 3 tasks
Part of the window material (fizzyedit/plans#27). Stacked on #549 (`app/glass-material`), where #565 put fizzy's window glass on a plain dvui app's windows. **The bug** (the user, on `run-dvui` with Liquid Glass): a blurred patch in the window's top-left corner, with a diagonal edge, that appears and disappears while the window is resized. **Why:** the frost and colour of a window of Liquid Glass are masked by the clearing bevel, a mask layer that each frame's look sizes (`windowFeatherMask`). A live resize grows the parts straight away (autoresizing), but the mask stayed at its old size until the next frame. AppKit's layers are anchored at the bottom-left, so as the window grew, the frost along its top fell outside the old mask for that moment, and the old mask's rounded corner showed as a diagonal. **The fix:** each masked part (frost and colour) sizes its mask as AppKit resizes it. One shared observer listens for each part's frame changes (`FizzyGlassMaskFollower`), and `windowGlassRemove` unregisters a part as the glass is taken away. Fizzy's own windows install the same parts, so they get it too. ## SDK impact - [x] None ## Verified - [x] macOS (arm64, Zig 0.17.0): `scripts/check-fmt.sh`, `zig build`. - [ ] By hand: live-resize a `run-dvui` window on Liquid Glass (and a fizzy window): no patch in the corner. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
3 tasks done
2 of 5 tasks
foxnne
added a commit
that referenced
this pull request
Oct 18, 2026
) Part of the window material (fizzyedit/plans#27): step 1 of in-app floating windows in the material, for a plain dvui app on fizzy's backend. Stacked on #549. **What changes:** fizzy's in-app glass moves from `core` into the backend, as a module of its own (`backend/src/glass/`, `glassModule`): - `FrameTarget` (the frame drawn into a target, so what's under a surface can be read back); - `BlurBackdrop` (the frosted and blurred backdrops); - `LiquidField`, `liquid_glass`, `liquid_blob` and the glass shader; - `programs` (fragment programs); - `motion` (the motion setting). `core` re-exports every name under the one it had (`core.FrameTarget`, `core.widgets.BlurBackdrop`, `core.motion`, …). Fizzy, plugins and plugin authors see no change. **Why:** a plain dvui app on fizzy's backend links dvui and the backend, not `core`. To draw an in-app floating window in the window's material (frost, blur, Liquid Glass), the backend needs the same glass fizzy draws with. Moving it, not copying it, keeps one glass. **How:** - **Dependencies:** the glass depends on dvui and `glass_look` only. It must not import `core` (`core/AGENTS.md`). - **The profiler** stays in `core`. The glass's six scopes reach it through `glass/prof.zig`: - the host's profiler hands its scopes to the host's copy of the glass each frame; - it publishes them through dvui's store, the same way it publishes itself, so a plugin's copy finds them once; - nothing is spent while no profiler hands any over. - **One module per dvui,** as the backend is: - the app's `core` takes the backend's instance (`core_module.addImports`' new `glass` argument); - web, the tests and the plugin SDK make their own from the files; - `scripts/pack-sdk.sh` vendors `backend/src/glass/` into the SDK tarball; - `backend/build.zig.zon`'s `src` already ships it. - **Tests:** the glass's five tests (frost capture sizes, the blobs' smooth union) get a test root of their own, `fizzy-glass-tests`, instead of `core/sizing_tests.zig`. - **Docs:** paths updated in `AGENTS.md`, `core/AGENTS.md`, `backend/AGENTS.md` and `docs/DEPENDENCIES.md`. **Next on this stack:** the backend draws a plain app's frame through `FrameTarget`; a dvui-dev hook letting the backend draw a floating window's background; in-app windows in the material. ## SDK impact - [x] None. The plugin boundary's fingerprint doesn't move (`test-sdk-version`). Plugins build `core` with the glass vendored beside it, the same names. ## Verified - [x] macOS (arm64, Zig 0.17.0): `scripts/check-fmt.sh`, `zig build`, `zig build test`, `zig build check-tests-reached`, `zig build check-web`, `zig build test-sdk-version`, `zig build test-integration` all pass. - [x] `plugins/image` builds against `sdk/` (sandbox `FIZZY_PROFILE`). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
foxnne
added a commit
that referenced
this pull request
Oct 18, 2026
## What changes
`fizzy.screen` and tapes see a float's OS window by its real title
again, so an agent or a demo can aim at it by name. Before, a float
window was listed as e.g. `.name="\x01\x00\x00\x00\x04…"`,
`.tag="fizzy.window:float:\x00\x00…"` (seen 2026-10-18 on macOS,
`FIZZY_WORKSPACES=1`, a floated document).
The cause: `Popout.windowsShown` walked the float slots by value (`for
(outs) |slot| { const o = slot orelse continue; … }`), so `.title =
o.title_buf[0..o.title_len]` was a slice into a stack copy that died
with each iteration, and `Demo.frameEnd` read it after. The loop now
walks the slots by pointer, so the title is a slice of the float's own
buffer, which lives as long as the float does.
The menus/dialogs loop below it copies the same way but takes nothing by
reference from its copy (a dialog's title comes from
`core.dialogs.dialogTitle`, `m.id` is a value), so it stays as it is.
## SDK impact
- [x] None
## Verified
- [x] macOS: `scripts/check-fmt.sh`, `zig build`, `zig build test`, `zig
build test-integration` (78/78 steps), `zig build check-web`, `zig build
test-sdk-version`. Live, in a fizzy of its own (`--profile`,
`FIZZY_WORKSPACES=1`), with the Output view floated by a live tape and
`fizzy.screen` read over the control socket. Without this change:
`.name="\x01\x00\x00\x00\x04\x00"`,
`.tag="fizzy.window:float:\x00\x00\x00\x00XI"`, and the JSON route
refuses the whole screen (`the command's result holds text that is not
UTF-8`). With it: `.name="Output"`, `.tag="fizzy.window:float:Output"`.
For that check `SDLBackend.zig`'s `viewports` had to be `pub` again:
#549 dropped the `pub`, so `backend_native.zig`'s
`@hasDecl(@import("backend"), "viewports")` is false and on `main`
floats never get OS windows. That fix is not in this PR.
- [ ] Windows:
- [ ] Linux:
- [ ] Web: n/a (no popouts on web)
## Follow-ups
A headless reach for `Popout.windowsShown` would let a test pin float
window titles; it needs a fake `viewports` to stand in for the OS
windows.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
foxnne
added a commit
that referenced
this pull request
Oct 18, 2026
On macOS, a float (and a menu, a dialog) opens in an OS window of its own again. ## What broke #549 inserted `pub const glass_look = @import("glass_look");` between the doc comment and `pub const viewports = @import("viewports.zig");` in `backend/src/SDLBackend.zig`, and dropped the `pub` from `viewports`. `src/backend/backend_native.zig` chose its viewports with `@hasDecl(@import("backend"), "viewports")`; a non-pub declaration is invisible to `@hasDecl` from another file, so fizzy silently took `viewports_none` (`supported = false`) and `Popout.enabled()` returned false at comptime. ## The fix - `SDLBackend.zig`: `viewports` is `pub` again, with its comment. `glass_look` goes private, above it — the same shape #589 gives those lines (nothing reaches `backend.glass_look`; everyone uses `core.glass_look`). - The silent fallback is gone: `build/exe.zig` hands the executable a `native_backend` options module (`fizzy = native_backend == .fizzy`), and `backend_native.viewports` takes `@import("backend").viewports` directly when it is set. A `viewports` that loses its `pub` is now `error: 'viewports' is not marked 'pub'` instead of popouts turned off. - `viewports_none.zig`'s doc no longer recommends the `@hasDecl` pattern, and says why. ## Verified - With `pub` removed again, `zig build` fails at `src/backend/backend_native.zig:155: 'viewports' is not marked 'pub'`. - Live on macOS (2026-10-18, before this PR, with the `pub` restored by hand): floating the Output view opens an OS window that `fizzy.screen` lists as `fizzy.window:float:Output`; without it the float stays inside the main window. - Gates, macOS: `scripts/check-fmt.sh`, `zig build`, `zig build test`, `zig build test-integration` (80/80 steps), `zig build check-web`, `zig build test-sdk-version`, and `zig build -Dnative-backend=sdl3` (dvui's own backend still takes `viewports_none`). `scripts/check-examples.sh` left to CI. ## Overlap #589 rewrites the same three lines of `SDLBackend.zig`; whichever lands second has a one-line conflict (keep `pub` on `viewports`). #590 touches a different part of the file. ## SDK No boundary change; fingerprint and `sdk_version` untouched. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Part of fizzyedit/plans#27 (step 2). Now on
main: #546 landed withzig/0.17(#575).This PR also carries #565, merged into its branch: plain dvui apps' windows take the window material in the backend, and
glass_look.zigmoves tobackend/src/as a shared module. So the diff is this PR's three files (Entry,Editor,Popout) plus #565's.What changes
Every glass surface in the app and in plugins (dialogs, menus, the command palette, tooltips, glass buttons, the dragged view) now draws in one window material. That material is #546's
core.material: whatever the backend can show of what the app asks for.liquidnative_glassis onfrostblurtransparentdialogFill)noneLiquidField.publishEnabled).Editor.tickmaps the material onto those.frosttakes the same code path fizzy took with the glass shader switched off, so it is a path that has already been exercised.src/Entry.zigasks forliquiduntil there is a setting (plans#27, step 6), and publishes it beforePopout.beginFrame, so the drag's OS glass sees this frame's value.FIZZY_GLASS=frost|blur|transparent|noneasks for less, for testing.SDK impact
sdk/src/version.zig,sdk_versionuntouched, PR labelledsdksdk_version, lists thesdkPRs since the lastsdk-v*tagVerified
scripts/check-fmt.sh,zig build,zig build check-webandzig build test-sdk-versionall pass.zig build test: 93/93 steps.zig build test-integration: 77/77 steps, 523/523 tests.--profile) launched with eachFIZZY_GLASS, the command palette opened with ⇧⌘P, and the window captured.liquidshows the bent edge;frostthe same blur flat-edged;transparentthe welcome logo sharp behind the tint;noneopaque.blurwas launched and captured, but its screenshot wasn't compared side by side withfrost.check-webonly.Follow-ups
BlurBackdropreads onlyrender_cmds, so glass inside a front-to-back block (a place's corner button) blurs nothing of that block (plans#27).glass_look's forDrops/forText split goes in its own PR.transparentover busy content is hard to read (the palette over the welcome screen). It is what the kind is; a later look at a minimum opacity undertransparentmay be worth it.🤖 Generated with Claude Code