Composer submit used to only clear the draft. Enter and the send
button now append a user turn and scroll the virtual-list to that
row. The test types hello, submits, and asserts the last row.
Markdown turns now go through SafeMdxContent. Parse results are
cached by source so a 5k remount does not re-parse the same
blocks. The live window starts at 5,000 turns instead of 10,000.
Also add a wheel-flush profiler for bun --cpu-prof, retitle the
compiled binary as Waku-style, refresh chat-app.png, and drop the
stale chat-diff curated screenshot.
Session: ses_fd1333811ffefCKd7t9qN7SLgI
Run GPUI through its native platform architecture on macOS, Windows, Linux, and FreeBSD. Keep the embedded AppKit pump on macOS and route non-macOS commands through a dedicated GPUI UI thread.
Replace the painted input label with a platform-backed editor model. Add multiline layout, IME ranges, grapheme-safe navigation, caret and selection painting, clipboard actions, undo and redo, horizontal and vertical scrolling, native focus control, and textarea registration.
Update the native test renderer, generated declarations, platform dependencies, submodule target, and development-loop documentation to match the new runtime.
Session: ses_fd70de910ffe1g2yG1yAkCjeLu
Replace the custom NodePlatform, window, dispatcher, display, clipboard, and winit event translation with the real gpui_macos MacPlatform and MacWindow implementations.
Add remorses/zed as the zed submodule on its gpuix branch. The fork adds a main-thread embedded AppKit mode with non-blocking event pumps, including the after-wait run-loop phase required to start GPUI frame sources and present Metal drawables.
Point Cargo at the submodule GPUI crates, remove the custom-platform dependency stack, invalidate the retained GPUI window through ApplicationHandle after React commits, and keep the paced JavaScript frame loop responsible only for pumping AppKit.
Remove obsolete platform plans, update build and development guidance, and add the native package changeset.
Validated with 82 Rust tests, 145 React tests, 11 example tests, live counter rendering and click updates, native transcript scrolling, and live text selection.
Session: ses_fdb06bf3affe9nMT6DamndI4zV
examples/chat.tsx is a full ChatGPT-like app in ~900 lines: sidebar with
grouped history, scrolling transcript, composer, and <markdown>, <code>
and <diff> inside assistant turns. Data is hardcoded.
cd examples && bun run chat
Two details it exists to demonstrate:
Chrome sets userSelect: 'none'. The sidebar, top bar and composer opt
out, so a drag across the transcript selects message text and never a
conversation title. A test asserts both halves.
<diff> needs a bounded viewport, because it virtualizes internally, and
it carries its own borderRadius. GPUI clips a scroll container to its
bounds RECTANGLE, never to a rounded path, so a rounded wrapper with
overflow: hidden still shows square corners underneath.
examples/native-text.tsx is the smaller demo: the three components behind
a tab switcher, with a status line for events.
Screenshots
-----------
Test output lands in gitignored screenshots/ folders and churns on every
run, so only a curated set is committed, under docs/images. They are
regenerated with:
bun scripts/screenshots.ts
which runs the visual tests and copies eight frames through a 256-colour
palette. That is lossless in practice for flat dark UI (no gradients to
band) and cuts the committed size from 976 KB to 352 KB.
The README now opens with the chat app, gains an Examples table, and shows
each native component next to the snippet that produces it.
AGENTS.md records the two rules that were expensive to learn: all text
must go through crate::text (never div().child(String), or it is invisible
to both selection and getPaintedText), and layout numbers belong on
Theme::metrics rather than in Rust constants.
There is no hot reload for the native half and there cannot be. require()
of a .node file calls process.dlopen, Node has no matching unload, and the
live state (the winit event loop, the wgpu device, the open window, the
selection registry) lives in thread-locals of the loaded library. A second
load would get empty thread-locals and a dead window, and napi registers
the module once per process.
The rebuild is fast enough that it does not matter. Measured after
touching one file:
cargo check --lib 1.5s
cargo build --lib 4.9s
bun run build:debug (napi) ~2s
one vitest screenshot file ~2s
scripts/dev.ts watches packages/native/src, rebuilds, and refreshes a
target. A Rust edit reaches fresh PNGs in about four seconds.
bun run dev rebuild, re-render screenshots
bun scripts/dev.ts --shots diff only tests matching "diff"
bun scripts/dev.ts --app native-text rebuild, restart an example app
Screenshot mode is the default because Preview.app reloads a PNG on write
and, unlike a live window, the PNG can be read by an agent.
Builds are serialized: a burst of saves otherwise starts overlapping cargo
runs that then block on cargo's own lock and report confusing timings.
scripts/ gets its own package.json and tsconfig.json so the watcher is
type-checked (`bun x tsc -p scripts`) rather than only being run.