← Design · c130

research/platform.md

Platform: what the kit leaves to us

Use this file to plan the OS work that neither GPUI nor GPUI Kit does: the tray on three OSes, notifications, menus, packaging and signing, auto-update, login items, single instance, deep links, fonts, minimum OS versions, and how the footprint is measured. Each section says what is known, which crate or OS API does it, and what the c131 spike must test before the plan promises it. It answers Tyler's Q4, Q5, Q7 and Q9 as far as reading can. Every source was read on 2026-11-09. The c131 spike appends its measured numbers at the end of this file under "From the spike".

1. The tray on three OSes

The question that decides D7 (does the tray move into the app?): GPUI has no tray API, and nobody has published tray-icon running inside a GPUI app.

  • Upstream GPUI refused it twice. GPUI 1 had a macOS status item in 2022; it is gone. PR 13098 (Linux tray, 2024) was closed: "I don't think we want to add large APIs to GPUI without a direct usage in Zed itself first." PR 44047 (macOS and Windows tray, by the GPUI Kit author, December 2025) was closed: "without a Zed use case, I can't validate this code". — https://github.com/zed-industries/zed/pull/13098, https://github.com/zed-industries/zed/pull/44047
  • What GPUI does give a tray app, on Zed main: QuitMode::Explicit (do not quit when the last window closes), ActivationPolicy::Accessory ("for an accessory app that shows windows, such as a menu bar utility"), on_reopen, activate, hide. These are not in crates.io gpui 0.2.2; whether the kit's pinned gpui-pre snapshot has them is unverified. — https://raw.githubusercontent.com/zed-industries/zed/main/crates/gpui/src/app.rs
  • The working pattern, used by every third-party integration found: the native callback (AppKit, Win32, D-Bus) must not touch GPUI state ("the App RefCell is whatever the interrupted turn left it as"); it fills a channel, and a GPUI foreground task drains it and dispatches actions. — https://github.com/remorses/gpuix/issues/111, https://github.com/remorses/zed/pull/15
  • Third-party crates. gpui-tray by domenkozar, 0.3.0 (2026-10-21, 19 stars): AppKit through objc2, Shell_NotifyIconW on Windows, StatusNotifierItem over zbus on Linux without GTK; "The crate uses GPUI's existing event loop"; feature flags for gpui, gpui-kit or gpui-ce, but its kit dependency is pinned at 0.6 against today's 0.9.1, and its author wrote "macOS can't be built from crates at the moment" in August. A second gpui-tray (kkkqkx123, 2 stars, no release) targets gpui-kit 0.8 and exposes Tray::is_available() so an app starts without a tray host. — https://github.com/domenkozar/gpui-tray, https://github.com/kkkqkx123/gpui-tray
  • tray-icon (tauri-apps, 0.26.1, 2026-10-07): Windows, macOS, Linux via AppIndicator or ksni. Its rules: on macOS "an event loop must be running on the main thread" and the icon is created after the loop has started; on Linux the default backend needs a GTK 3 main loop, which GPUI does not run, so only the ksni backend ("manages its own worker thread") fits; events arrive through TrayIconEvent::receiver() or a handler you bridge yourself. Application::run(|cx| …) fires on the main thread after AppKit has launched, which is the point where a macOS tray may be created. Untested anywhere public. — https://docs.rs/tray-icon/latest/tray_icon/, https://github.com/tauri-apps/tray-icon
  • qtray today (../qcontrol/crates/qtray): tray-icon 0.24 on a tao 0.35 event loop with ActivationPolicy::Accessory, objc2-app-kit for alerts and progress, menu rebuilds debounced at 100 ms; macOS only. GPUI uses neither tao nor winit, so qtray's loop cannot run inside a GPUI process; its model and menu port, its loop does not.
  • Linux variance. GNOME dropped status icons by default in 3.26 (2017); the "AppIndicator and KStatusNotifierItem Support" extension restores them (versions for Shell 45 to 51); KDE hosts StatusNotifierItem natively; on Wayland it is pure D-Bus. The app must run tray-less on a stock GNOME. — https://blogs.gnome.org/aday/2017/08/31/status-icons-and-gnome/, https://extensions.gnome.org/extension/615/appindicator-support/
Path for D7CostRisk
A. One process: tray through AppKit / Win32 / zbus on GPUI's loop (the gpui-tray shape), window on demandPort qtray's model and menu; write or adopt the three backendsThird-party crates lag the kit by two or three minors; macOS build gap reported
B. One process: tray-icon created inside Application::run, ksni on Linux, events bridged by channelSmallest code; the crate qtray already usesUntested with GPUI; Windows message-loop sharing unverified
C. Two processes: qtray stays (macOS) and opens the app; Windows and Linux trays come laterZero new tray code for V1Two binaries, two lifecycles; Tyler's Q4 stays open

The spike tries B, then A, on macOS first, and records which one the window and the tray survive together.

2. System notifications

3. Menus

4. Packaging and signing

OSArtifactSigningWhat Zed doesCost and gates
macOS.app in a .dmg; Info.plist with bundle id, URL schemes, iconsDeveloper ID certificate, codesign --options runtime (hardened runtime), notarytool submit --wait, stapler staple. "all software… distributed with Developer ID must be notarized"Zed's fork of cargo-bundle, MACOSX_DEPLOYMENT_TARGET=12.0, signs binary, helpers and bundle, DMG via hdiutil; ad hoc --sign - without certificatesApple Developer Program $99 a year. Since Sequoia no Control-click override: unsigned apps are blocked until the person visits Privacy & Security. — https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution, https://developer.apple.com/news/?id=saqachfa, https://raw.githubusercontent.com/zed-industries/zed/main/script/bundle-mac
WindowsInno Setup .exe (per-user, PrivilegesRequired=lowest, no UAC), or WiX MSI, or MSIX (identity, needed for Store and some platform APIs)Authenticode: Azure Artifact Signing "~$9.99/month" (org in US, Canada, EU, UK and others; 1 to 20 business days to validate), OV certificate $150 to 300 a year on an HSM, EV $400+ with no SmartScreen bypass since 2024Inno Setup with an AUMID shortcut, HKCU registry, Azure Artifact SigningSmartScreen reputation "can take several weeks and hundreds of clean installs" whatever the certificate; enterprises can whitelist intranet sources. winget by PR to microsoft/winget-pkgs. — https://learn.microsoft.com/en-us/windows/apps/package-and-deploy/code-signing-options, https://learn.microsoft.com/en-us/windows/apps/package-and-deploy/smartscreen-reputation
Linux.tar.gz with bin/, libexec/, bundled libs minus GLib, .desktop and icons; install script into ~/.local; .deb / AppImage via cargo-bundle, plus .rpm / pacman via cargo-packagernone requiredThe tarball above; community packages for Arch, Fedora, Nix, Flathub are "not official"Runtime: Vulkan 1.3, portals for file chooser, OpenURI and secrets, glibc 2.31+. — https://raw.githubusercontent.com/zed-industries/zed/main/script/bundle-linux, https://zed.dev/docs/linux

Tools: cargo-bundle 0.12 (.app, .deb, AppImage, MSI experimental; signs but "does not notarize"), cargo-packager 0.11 (DMG, .deb, AppImage, pacman, NSIS, WiX, with a matching updater), cargo-dist 0.34 (tarballs, shell and MSI installers, GitHub Releases pipeline; CLIs, not bundles). — https://github.com/burtonageo/cargo-bundle, https://github.com/crabnebula-dev/cargo-packager, https://axodotdev.github.io/cargo-dist/book/introduction.html

5. Auto-update

  • How Zed does it (crates/auto_update): poll hourly (POLL_INTERVAL = 60 * 60), ask the release endpoint for the asset by channel, OS and arch; macOS mounts the DMG and rsync --deletes into the running bundle's path; Linux untars and rsyncs into the prefix; Windows runs the Inno installer /verysilent /update=true and hands off to auto_update_helper.exe because a running exe cannot be replaced; then set_restart_path + restart(). Status machine Idle → Checking → Downloading → Installing → Updated | Errored. No checksum or signature check was found in that file; trust is HTTPS plus Gatekeeper. — https://raw.githubusercontent.com/zed-industries/zed/main/crates/auto_update/src/auto_update.rs
  • Alternatives: self_update 1.3 (GitHub, S3 or a static manifest; zipsign signatures; single binaries, not bundles); cargo-packager-updater (JSON endpoint, minisign, formats app, appimage, nsis, wix); Tauri's updater (signatures "cannot be disabled"); Sparkle 2.10 (macOS, EdDSA, an Objective-C framework, no Rust binding found). — https://github.com/jaemk/self_update, https://raw.githubusercontent.com/crabnebula-dev/cargo-packager/main/crates/updater/README.md, https://github.com/sparkle-project/Sparkle
  • What the OS requires: a replaced macOS bundle must be signed and notarized again or Gatekeeper stops it; the person needs write access to the install path; Windows needs a per-user install to avoid elevation and a helper to swap the running exe.
  • qupdate (../qcontrol/crates/qupdate, "Fetch and apply CI-published qcontrol binaries") already does the sidecar check (latest JSON with version, git SHA, artifact, SHA-256), the tarball download with integrity, and the in-place replace, with an elevation rule. What a GUI app adds: the visible status machine, a non-interruptible install phase, a "restart to update" affordance, a channel-aware cadence, a kill switch for package-managed installs (Zed's ZED_UPDATE_EXPLANATION), and a bundle-aware replace on macOS.

6. Startup at login

7. Single instance

9. Fonts and licensing

FontLicenceReserved Font NameConsequence
Inter 4.1SIL OFL 1.1"Inter"Ship the unmodified file under its own name; a subset or format change is a Modified Version and must be renamed. — https://github.com/rsms/inter
Geist Mono 1.9.2SIL OFL 1.1none in the licence headerSimpler to subset or rename; confirm the embedded licence strings first. — https://github.com/vercel/geist-font
IBM Plex MonoSIL OFL 1.1"Plex"Zed's "Zed Plex Mono" had to be withdrawn: it "conflicts with the open font license due to the use of 'Plex' in the name". — https://raw.githubusercontent.com/IBM/plex/master/LICENSE.txt, https://github.com/zed-industries/zed-fonts
  • The OFL permits bundling in sold software; "At a minimum you must include the copyright statement, the license notice and the license text." — https://openfontlicense.org/ofl-faq/
  • Ruled (c131 D14, 2026-11-09): Inter for the UI and Geist Mono for code, both bundled, not the system font. The kit's path is one call at startup, before any window opens: cx.text_system().add_fonts(vec![Cow::Borrowed(include_bytes!("../fonts/Inter-Regular.ttf").as_slice())]), then Theme::update(cx, |theme| theme.font_family = "Inter".into()) or "font.family": "Inter" and "mono_font.family": "Geist Mono" in the theme JSON. Register every face the UI uses ("a single regular file is not a promise of all styles"); refer to the family by the name inside the file, which for the variable Inter-Regular.ttf the kit bundles in its web gallery is "Inter Variable", not "Inter"; the docs add "Keep the font's redistribution license with the app". If the family fails to load, GPUI falls back to its default stack; if nothing loads, layout panics, so the spike checks the load on all three OSes. Zed does the same at a larger scale: every .ttf under assets/fonts on the background executor, named through aliases. — https://gpui-kit.com/docs/fonts.md, https://raw.githubusercontent.com/zed-industries/zed/main/crates/zed/src/main.rs
  • The OFL side of that ruling: ship the unmodified Inter file under its own name (Reserved Font Name "Inter"); a subset or a renamed build is a Modified Version. c131 Q5 is therefore closed as a product choice; what remains is the licence text in the bundle.

10. Minimum OS versions and hardware

OSZed's runtime floorThe kit's requirements pageNotes
macOS12.0 Monterey, Intel and Apple silicon (MACOSX_DEPLOYMENT_TARGET=12.0)"macOS 15 or later" (reads as the build machine; unverified as a runtime floor)Metal is always present. If nsh is ever embedded, its floor is macOS 14. — https://zed.dev/docs/installation, https://gpui-kit.com/docs/installation
WindowsWindows 10 1903+ and 11 22H2+, 64-bit, x64 and Arm64; "a GPU supporting DirectX 11" with a vendor driver, "not the Microsoft Basic Display Adapter"Windows 10 or later, MSVC toolchainVMs and RDP sessions on the basic adapter are outside the stated support. — https://zed.dev/docs/windows
Linuxx86_64 and aarch64; "a Vulkan 1.3 driver is required"; glibc 2.31+; Wayland or X11; portalsUbuntu 24.04 verified; Vulkan, Wayland or X11, fontconfig, xkbcommon"Zed failed to open a window" is the symptom of no Vulkan 1.3 device; software rendering is an opt-in, slow path at best. — https://zed.dev/docs/linux, https://github.com/zed-industries/zed/issues/16428

For employee laptops: any Mac on macOS 12 qualifies; Windows needs a 2012-or-later GPU with a real driver; Linux VMs without Vulkan get no window. The app needs a "GPU unavailable" state that still shows the tray or a plain message rather than nothing (c131 R5).

11. Measuring the footprint

What the spike must test (c131 Phase 0)

  1. Tray path B, then A, on macOS: does the window and the tray survive together on GPUI's loop; do QuitMode::Explicit and ActivationPolicy::Accessory exist in the pinned snapshot.
  2. A notification from an unbundled cargo run (expected: nothing on macOS), then from a bundled .app.
  3. A qpoint://connect?... link on macOS through on_open_urls, and a loopback redirect, both against the fixture plane.
  4. Idle memory.app.private, CPU and wakes with strobe for the hello app with a table bound to the fixture world, on Mark's Mac; Windows and Linux in CI, numbers recorded even when the build fails.
  5. Whether the Linux build opens a window in a VM without Vulkan 1.3, and what the app says when it cannot.

Open questions for Tyler's session

  • Q4: tray path A, B or C; and whether Windows and Linux trays are V1 or later.
  • Q5: notifications in or out; if in, the installed-and-signed prerequisite applies on macOS and Windows.
  • Q7: who holds the Apple Developer ID and the Windows signing account; per-user Windows install (no UAC) as the default; which Linux formats; does qupdate grow bundle-awareness or does the app get its own updater.
  • Q9: the macOS floor (12 as Zed, or higher), DirectX 11 and Vulkan 1.3 as stated hardware requirements, and the GPU-unavailable fallback.
  • Not answerable by reading: whether gpui-tray builds against gpui-kit 0.9.x; which GPUI features the kit's gpui-pre pin exposes; Windows toasts from an unpackaged app on current Windows 11; whether an in-place macOS update keeps TCC permissions; whether the sensor sees loopback OAuth traffic.

Sources

Tray: https://docs.rs/tray-icon/latest/tray_icon/ · https://github.com/tauri-apps/tray-icon · https://crates.io/api/v1/crates/ksni · https://github.com/zed-industries/zed/pull/13098 · https://github.com/zed-industries/zed/pull/44047 · https://github.com/domenkozar/gpui-tray · https://github.com/kkkqkx123/gpui-tray · https://github.com/remorses/zed/pull/15 · https://github.com/remorses/gpuix/issues/111 · https://raw.githubusercontent.com/zed-industries/zed/main/crates/gpui/src/app.rs · https://blogs.gnome.org/aday/2017/08/31/status-icons-and-gnome/ · https://extensions.gnome.org/extension/615/appindicator-support/ · ../qcontrol/crates/qtray/Cargo.toml and src/tray.rs Notifications and menus: https://gpui-kit.com/docs/system-notification · https://developer.apple.com/documentation/usernotifications/asking-permission-to-use-notifications · https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/hh802762(v=vs.85) · https://learn.microsoft.com/en-us/windows/apps/develop/notifications/app-notifications/app-notifications-quickstart · http://specifications.freedesktop.org/notification/latest/protocol.html · https://github.com/hoodie/notify-rust · https://raw.githubusercontent.com/zed-industries/zed/main/crates/title_bar/src/application_menu.rs · https://gpui-kit.com/docs/native-extension · https://github.com/tauri-apps/muda Packaging and updates: https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution · https://developer.apple.com/documentation/security/hardened-runtime · https://developer.apple.com/news/?id=saqachfa · https://developer.apple.com/programs/ · https://raw.githubusercontent.com/zed-industries/zed/main/script/bundle-mac · https://raw.githubusercontent.com/zed-industries/zed/main/crates/zed/resources/windows/zed.iss · https://learn.microsoft.com/en-us/windows/apps/package-and-deploy/code-signing-options · https://learn.microsoft.com/en-us/windows/apps/package-and-deploy/smartscreen-reputation · https://learn.microsoft.com/en-us/azure/artifact-signing/quickstart · https://learn.microsoft.com/en-us/windows/package-manager/package/repository · https://raw.githubusercontent.com/zed-industries/zed/main/script/bundle-linux · https://raw.githubusercontent.com/zed-industries/zed/main/script/install.sh · https://github.com/burtonageo/cargo-bundle · https://github.com/crabnebula-dev/cargo-packager · https://axodotdev.github.io/cargo-dist/book/introduction.html · https://raw.githubusercontent.com/zed-industries/zed/main/crates/auto_update/src/auto_update.rs · https://github.com/jaemk/self_update · https://v2.tauri.app/plugin/updater/ · https://github.com/sparkle-project/Sparkle · ../qcontrol/crates/qupdate/src/lib.rs Login, instance, links: https://developer.apple.com/documentation/servicemanagement/smappservice · https://learn.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys · http://specifications.freedesktop.org/autostart/latest/ · https://github.com/zzzgydi/auto-launch · https://raw.githubusercontent.com/zed-industries/zed/main/crates/zed/src/zed/mac_only_instance.rs · https://raw.githubusercontent.com/zed-industries/zed/main/crates/zed/src/zed/windows_only_instance.rs · https://raw.githubusercontent.com/zed-industries/zed/main/crates/zed/src/zed/open_listener.rs · https://v2.tauri.app/plugin/single-instance/ · https://v2.tauri.app/plugin/deep-linking/ · https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app · https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85) · https://raw.githubusercontent.com/zed-industries/zed/main/crates/gpui_macos/src/platform.rs · https://datatracker.ietf.org/doc/html/rfc8252 Fonts, OS floors, measurement: https://github.com/rsms/inter · https://github.com/vercel/geist-font · https://raw.githubusercontent.com/IBM/plex/master/LICENSE.txt · https://openfontlicense.org/ofl-faq/ · https://github.com/zed-industries/zed-fonts · https://gpui-kit.com/docs/fonts · https://zed.dev/docs/installation · https://zed.dev/docs/windows · https://zed.dev/docs/linux · https://gpui-kit.com/docs/installation · https://github.com/zed-industries/zed/issues/16428 · https://raw.githubusercontent.com/anomalyco/strobe/main/README.md · https://raw.githubusercontent.com/anomalyco/strobe/main/skill/SKILL.md · https://raw.githubusercontent.com/anomalyco/strobe/main/SPEC.md · https://registry.npmjs.org/@hona/strobe · https://manp.gs/mac/1/footprint · https://man7.org/linux/man-pages/man5/proc_pid_status.5.html · https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-process_memory_counters_ex

From the spike (c131 Phase 0, 2026-11-09 and 2026-11-10)

Use this section to replace the "unverified" and "untested anywhere public" qualifiers above with what a running app showed. The spike lives in ../../../../qnative (provisional name), cycle c1.spike; every claim below has a commit there. Machine: Apple M2 Max, 32 GB, macOS 15.6, Homebrew Rust 1.99.1; GPUI Kit 0.9.1 on gpui-pre 0.3.12; strobe 0.26.0.

Tray coexistence: yes, on macOS, in one process (D7 path B)

  • tray-icon 0.26.1 (qtray's crate, two minors on) created inside Application::run's callback works: AppKit's loop is running there (the callback is applicationDidFinishLaunching), which is the crate's one requirement. No second loop exists; NSStatusItem rides GPUI's.
  • Menu clicks: AppKit → MenuEvent::set_event_handler → an unbounded futures channel → a GPUI foreground task with &mut App. The AppKit side never touches GPUI; the pattern in §1 holds and costs twelve lines.
  • Proof without a screenshot: CoreGraphics lists two windows for the process, the main one (layer 0, 1120×721) and the status item (layer 25, 38×24, top right). Closing the window leaves the process and the status item; the tray's Open command reopens the window; cx.quit() removes both. The AppKit click itself was not exercised (no pointer in the session); the code path from the channel on was.
  • Every lifecycle call §1 listed as unverified is in gpui-pre 0.3.12: QuitMode::{Default, LastWindowClosed, Explicit} (Application::with_quit_mode, App::set_quit_mode; the default already means Explicit on macOS), App::set_activation_policy(Regular | Accessory), Application::on_reopen, App::activate, App::hide, App::set_menus, App::set_dock_menu, Application::on_open_urls, App::register_url_scheme, App::set_app_identity, App::show_system_notification and dismiss_system_notification. Tray-only mode is QuitMode::Explicit plus set_activation_policy(Accessory) when the last window closes and Regular plus activate(true) on Open; the spike does exactly that.
  • Path C (qtray stays) is not needed on macOS. Windows (default backend) and Linux (ksni, no GTK loop) are wired per target and build only in CI; neither was run.

What the kit gives and does not give

NeedThe kitFound
Traynothingtray-icon beside GPUI's loop, as above
Menu barGPUI App::set_menus (native NSMenu on macOS; drawn elsewhere), set_dock_menupresent in 0.3.12; not used by the spike
Context and popup menusPopupMenu (drawn, actions and key bindings) and NativeMenu (NSMenu / TrackPopupMenuEx / drawn fallback on Linux)documented; not used
Title barTitleBar with TitleBar::window_options(): transparent title bar with traffic lights at (9, 9) on macOS, drawn controls on Windows and Linux, app_owns_titlebar_dragused; works on macOS
NotificationsGPUI show_system_notification and the kit's Notification::system() / in_app_and_system() on Root; one app-wide response handler that gpui_kit::init installspresent; not tested: the docs say a cargo run binary cannot post on macOS and a bundled .app is required; a bundle was out of the spike's scope
Bundled fontscx.text_system().add_fonts(vec![Cow::Borrowed(bytes)]) before the window, then Theme::update(cx, |t| t.font_family = "...".into())works: all_font_names() lists "Inter Variable" (the name inside InterVariable.ttf, not the file name); two calls, no asset plumbing; Inter 4.1 upright and italic add 1.8 MB to the binary
Data tableDataTable + TableState<D: TableDelegate>; Column::new(key, name); render_td draws, cell_text is what copy, dump and the default accessibility label readworks; bind a Vec<Row> in a delegate, nothing else
SidebarSidebar::new(id) (the docs show no id), SidebarGroup, SidebarMenu, SidebarMenuItemworks
Honest statesEmpty (header, title, description), StatusBar for the footerused for the empty table and the footer
Preferences on disknothingan unbundled binary named app made AppKit write ~/Library/Preferences/app.plist on first run; a bare executable's identity is its file name until it is bundled (strobe refused to measure it for that reason)

Footprint (strobe 0.26.0, 2026-11-09 and 2026-11-10; the recipe is in the repo's README)

BuildModePrivate memory (memory.app.private = physical footprint)RSSCPU, idle, 60 sWakes/sStartup, main → first renderBinary
releasewindow, Inventory page over the bundled world52 MB (11-09) · 80 MB (11-10)66–68 MB0.005% of one core (2.9 ms)0.9264 ms (median of 3)17.9 MB
releasetray only16 MB39 MB0.002% (1.2 ms)0.3—17.9 MB
debugwindow80 MB83 MB0.005% (2.8 ms)1.2270 ms122 MB
debugtray only16 MB51 MB0.002% (1.1 ms)0.3—122 MB

Method: the app launched by hand with QPOINT_DATA=synthetic, 5 s to settle, then strobe record pid:<n> --for 60s (attached from outside: frames refused without Screen Recording for the session's host app; CPU, wakes, energy and memory.app.private recorded); footprint(1) and ps at 60 s as the cross-check. Startup is the app's own clock: main to the first render of the shell, median of three. The release profile is opt-level = 3, lto = "thin", codegen-units = 1, strip = true. strobe refused a launch-controlled recording of the windowed app twice: once because of the generic executable name (above), once because it will not measure a window that is not frontmost unless the Mac has been idle for a minute and strobe has the Accessibility permission; attaching by PID has neither rule. --windowless works for the tray-only mode.

  • Read against §11's rule ("memory.app.private, idle, after 60 s, plus idle CPU and wakes, binary size, time to first frame"): every column is there except frames, which strobe could not capture without Screen Recording; the startup figure is the app's own clock to the first render, not the first presented frame.
  • The two windowed release readings are 28 MB apart while RSS moved 2 MB: the physical footprint counts GPU-backed surfaces (GPUI's Metal layer), which follow the display the window is on (an external 5K display on 11-09, the next day's display set on 11-10). State the budget as a range per display until Phase 1 measures it per display. Tray-only has no Metal layer and is 16 MB in both builds, both days.
  • Idle is quiet: no steady wake-up source, package idle wake-ups 0.00–0.02/s, insights empty in all four recordings. A 10 s look taken 2 s after launch showed 6% of a core and 181 wakes/s; that is the start-up tail, gone by the 5 s settle. The shape's "idle app under a stated RSS, nothing resident when closed, the tray alone minimal" is met in kind; the numbers to put in it are these.
  • Binary: 17.9 MB release with strip = true and thin LTO, of which 1.8 MB is Inter; ureq with rustls and tray-icon are in it. 122 MB debug, unstripped.
  • Recordings on Mark's Mac, for strobe query <rec> threads|memory|insights: rec_cdfcb0, rec_2b2d45, rec_79dba9, rec_deabd6.

Build times (M2 Max, Rust 1.99.1)

BuildTimeNotes
Debug, from a clean target dirabout 3 min424 crates; cargo build -p app with the kit's default features
Release, from a clean target dir1 min 42 sopt-level = 3, lto = "thin", codegen-units = 1, strip = true
Release, after a dependency-feature change1 min 12 sadding tray-icon, futures, png and ureq re-unified features and rebuilt gpui-pre-platform and gpui-component
Debug, app crate only2–5 sthe edit loop
Release, app crate only30 sthin LTO and one codegen unit on the final link

Cargo's lock on the target directory serialises a release build and the debug edit loop; the spike gave the release build its own CARGO_TARGET_DIR to keep iterating.

API surprises in 0.9.1 (against the docs)

  1. gpui-pre 0.3.12 does not build on Rust 1.94. profiler.rs calls std::hint::cold_path, stable since 1.95; the Installation page says 1.92. Homebrew's Rust was 1.94.1 on this Mac; 1.99.1 builds it.
  2. Sidebar::new takes an ElementId; the Sidebar page shows Sidebar::new().
  3. TableDelegate::render_empty takes &mut self; cell_text, not cell_label, is the seam that copy, dump and the default row label read.
  4. gpui_kit::open_window returns (AnyWindowHandle, Entity<V>), not a WindowHandle<V>; a later update goes through the handle.
  5. Application::on_reopen (and on_open_urls) borrow the builder and return &Self; they cannot be chained after with_quit_mode.
  6. IconName comes in two: gpui_kit::component::IconName (the component set) and gpui_kit::assets::IconName (the Lucide catalog); only the default set in gpui-kit-assets/default-icons.txt is embedded without icon_assets!.
  7. The /skills page for 0.9.1 lists no skills; llms-full.txt is 4.7 MB and 100 831 lines, of which the second half is the zh-CN mirror.
  8. Outside the kit: tray-icon's with_icon_as_template is deprecated for with_icon_templated; png 0.18's output_buffer_size returns Option<usize>.

Still open after the spike

  • The AppKit click on the status item, a Dock reopen, and whether the Dock icon actually disappears on Accessory: a person with a pointer confirms them in a minute (the app prints tray: Open requested from the menu and app: reopen requested).
  • Notifications from a bundled .app (§2), qpoint:// through on_open_urls (§8), the Linux window without Vulkan (§10): not attempted.
  • Windows and Linux builds: the workflow exists in .github/workflows/build.yml; the repository has no remote, so it has not run.

Qpoint Brand Style Guide