Make every native example honor GPUIX_BACKGROUND and make every current real-window automation example pass that flag. Human launches still focus normally, while agent-driven launches avoid taking the user keyboard on macOS and Windows.
Explain why live input, clicks, and screenshots do not require activation. Correct stale guidance that claimed fill and press were unavailable, and document that Linux currently ignores the focus option.
Validated with the examples and example-app test suites, type checking, lintcn, a live screenshot, and a macOS foreground-process check.
Session: ses_fb208083cffewN8NfeygDAdslz
Replace the per-example createRenderer / init / createRoot /
startFrameLoop bootstrap with one render() call. chat and diff still
guard on the entry point so tests can import the app component.
Example scripts now run under bun --hot:
- examples/package.json: counter, diff, native-text, chat
- root package.json example:counter
A save remounts React on the same window. useState resets. Do not add
createRenderer() back to these files; a second init() would open
another host.
Session: ses_fd273542bffefho3Ygye0w4LIi
Driving `renderer.tick()` from `setImmediate` spins at ~27,000 ticks per
second. Measured on an idle counter app that is 73.5% CPU.
Two causes:
1. The JS loop never yielded. It was also the pattern documented in the
README, so every user inherited it.
2. `NodePlatform::tick` passed `require_presentation: true` on every frame
request. Reading gpui's `window.rs`, that sets `min_frame_interval` to
`None`, disabling the inactive-window and thermal throttles, and makes
`needs_present` true so `window.present()` runs on every tick even when
the window is clean. gpui's own platforms pass `Default::default()` for
routine frames; MacPlatform does this from its CVDisplayLink `step`.
`require_presentation` is for compositor damage, such as X11 expose.
Pacing lives in JS, not in a blocking `tick()`. Node owns the event loop in
this architecture, so blocking inside `pump_app_events(Some(timeout))` would
stall every timer, promise and socket in the process. winit also warns that
its macOS pump stops and restarts `NSApplication` on each call. The pump was
already correct at `Some(Duration::ZERO)`; it was never the problem.
`startFrameLoop` is exported from `@gpuix/react` so users stop hand-rolling
this. It reschedules after each frame rather than using `setInterval`, so a
slow frame delays the next one instead of letting timers pile up.
The per-tick `eprintln!` is now `log::trace!`. An unconditional write to
stderr on a frame-rate hot path is a syscall plus a lock.
Results on an idle app:
| | ticks/sec | CPU |
|---------------------|-----------|-------|
| `setImmediate` loop | ~27,000 | 73.5% |
| `startFrameLoop` | ~125 | ~1% |
Rendering is unchanged. Verified with `ZED_MEASUREMENTS=1`, which logs every
real draw: an app changing state every 500ms for 8 seconds produced 15 draws
for 16 state changes, and zero draws across the ~1000 idle ticks.
Session: ses_fe4fb6420ffemBjU2wIE5PN7N7
Three fixes for the diff viewer example:
**Black-on-black text**: WordDiffTokens rendered <text> elements with
no explicit color, so GPUI defaulted to black — invisible on dark
backgrounds. Added CODE_FG (#e6edf3) to all word-diff tokens and
plain-text fallback paths. GPUI does not inherit text color from
parent elements like CSS does.
**Window width fill**: Test containers used fixed pixel widths (800,
900, 1000, 1200) smaller than the 1280x800 test canvas, leaving a
black gap on the right. Changed to width/height: '100%' so containers
fill the full viewport. Scroll test keeps height: 300 (needs overflow).
**Scroll container background**: Set backgroundColor on the scroll
container itself rather than on the DiffViewer inner div, because
minHeight: '100%' does not resolve inside GPUI scroll containers
(children get infinite available height). The remaining bottom gap
is the GPUI window background — needs a Rust-side fix for window
background color.
Also added a README note: GPUI defaults text color to black, not
white, and does not inherit color from parent elements. Every <text>
needs explicit color or it will be invisible on dark backgrounds.
Port of critique's diff.tsx from opentui to GPUIX. Renders unified or
split diff views with shiki syntax highlighting, word-level diff
highlights, line number gutters, and scrollable containers.
Key rendering differences from opentui:
- opentui <box> → GPUIX <div>
- opentui <span fg> → GPUIX <text style={{ color }}> (flex-row layout)
- opentui <span bg> → GPUIX <text style={{ backgroundColor }}>
- opentui RGBA → hex/rgba() color strings
Components:
- DiffViewer: top-level component, accepts hunks/filePath/splitView
- HighlightedTokens: renders shiki tokens as colored text in flex rows
- WordDiffTokens: word-level diff with background highlights
- UnifiedView / SplitView: two layout modes controlled by splitView prop
Visual tests (6 total) use the native GPUI test renderer with Metal
rendering and screenshot capture for before/after validation.
Also:
- Export hasNativeTestRenderer from @gpuix/react (was missing)
- Add README note about GPUI defaulting text color to black (not white,
no CSS inheritance) — every <text> needs explicit color