Consume the pending changesets into a minor release. Users get the
terminal renderer, a headless Dialog with an Escape layer stack, default
Tab focus navigation, focus-visible styles and a headless Button,
createPortal, onLayout and onScrollChange, native scrollbars, new CSS
styles, registerFont, BGRA image uploads, getElementsAt, idle GC,
display-link frame pacing on macOS and Windows ARM64 builds.
Bump the native Cargo crate, napi loader version checks, native, React
and Solid packages to 0.11.0, @gpuix/cli to 0.1.2 and disktree to 0.2.1.
Fixes#73Fixes#74Fixes#75Fixes#76Fixes#77Fixes#78Fixes#84Fixes#91Fixes#93Fixes#95Fixes#96Fixes#97Fixes#98Fixes#99
Discord: https://discord.com/channels/1422625037164351591/1562091690376962111
Session: ses_ea68cd52affex3PxfjqF4PhYyY
Consume the pending changesets into a minor release. Users get the official Solid 1 renderer, AnimatePresence exit animations, live image uploads and scrollIntoView on host refs, native window controls, and an OS file picker.
Bump the native Cargo crate, native npm package, React package, Solid package, generated napi loader version checks, and Bun workspace lock to 0.10.0. Keep published adapter dependencies on @gpuix/native as workspace:^. CI is the only publish path for native platform binaries.
Fixes#68
Session: ses_f3b7fae0dffeZjEBH48TZSQVBq
Consume the pending changesets into a minor release. Users get a window-level onSelectionChange callback for text selection, a live-window click panic fix, and docs to pin @gpuix/react and @gpuix/native to the same exact version.
Bump the native Cargo crate, native npm package, React package, generated napi loader version checks, and Bun workspace lock to 0.9.0. Keep the published React dependency on @gpuix/native as workspace:^. CI is the only publish path for native platform binaries.
Fixes#38
Session: ses_f56207a65ffeFBg1JxfiwGIPqH
Consume the pending changesets into a minor release. Users get GPUI accessibility through role and aria-* props, Base UI Select wrapping with asChild, Finder file drops, GitHub Releases auto-update, Wayland layer-shell windows, http(s) img sources, textDecoration, textarea newlines, font-sized input rows, a runtime error overlay that keeps the macOS window alive, and several desktop and browser reliability fixes.
Bump the native Cargo crate, native npm package, React package, generated napi loader version checks, and Bun workspace lock to 0.8.0. Keep the published React dependency on @gpuix/native as workspace:^. CI is the only publish path for native platform binaries.
Fixes#37Fixes#38Fixes#39Fixes#41Fixes#46Fixes#47Fixes#59Fixes#63
Session: ses_f5effe0ccffeXHPxxwP7cFDI4h
Consume the seven pending changesets into a minor release. Users get native two-stop gradients, in-memory image data URLs, application-owned Tab handling, renderer-level keyboard callbacks, consistent test-renderer exports, correct Windows DPI scaling, clean last-window exit on Windows and Linux, and a frosted-glass window example.
Bump the native Cargo crate, native npm package, React package, generated napi loader version checks, and Bun workspace lock to 0.7.0. Keep the published React dependency on @gpuix/native as ^0.7.0 through workspace:^ resolution.
The release validation covers 221 Rust tests, 370 React renderer tests, 47 example tests, the starter app typecheck, CLI tests and build, package lint, release native and React builds, and both native and React tarball contents.
Fixes#30Fixes#31Fixes#32Fixes#35Fixes#36
Session: ses_fa2dcb95bffediF7Xu1SKakouC
Consume the pending changesets into a minor release. Users get
stable GPUI identities on every host surface, background window
launch, live desktop fill/press, pixel-stable virtual-list
anchors, a single applyBatch transport, one prebuilt target per
OS, and CRLF-safe <code> rows.
CI publishes with bun, not npm, so workspace:^ never ships as a
literal string. React still rewrites @gpuix/native to the current
native version before publish, because bun pack otherwise keeps
the previous lockfile caret (^0.5.1 does not include 0.6.0).
The infinite-chat Windows assertion no longer requires the
viewport to be empty of message ids. On Windows the short window
still showed three ids after wheeling into the void; the test now
checks that the list anchor is on the trailing edge instead.
Fixes#25
Session: ses_fb1f5cd3fffeAe9VxtTeynTdjA
Fix `hasNativeTestRenderer` always reporting false for anyone who
installs the package, found by copying `example-app/` out of the
repository and installing `@gpuix/react` from npm:
Test Files 1 skipped (1)
Tests 6 skipped (6)
`testing.js` ships as ESM and loaded the addon with a bare
`require("@gpuix/native")`. Node has no `require` in ESM. Inside this
repository vitest inlines the workspace package and provides one, so
every suite here passed; installed from npm the package is externalized
and run by Node, the call threw `require is not defined`, and the catch
reported native as missing. Every guarded suite then skipped silently,
which is worse than failing.
It loads through `createRequire(import.meta.url)` now. Verified by
dropping the built file over the installed 0.5.0 package in that copy:
the 6 tests run, and `bun run screenshot` drives the real window.
Session: ses_fc0b7cd62ffe3aBw0pyYBOBDjf
Every React test file passed on Windows and the vitest worker then died
with "Worker exited unexpectedly". The real message was underneath:
panicked at zed/crates/gpui/src/app/entity_map.rs:1116
Exited with leaked handles:
Leaked handle for entity gpuix_native::...::TextEditorState
The test build turns on gpui's leak detector, which panics if an
`Entity` handle is still alive when the `App` drops. `<input>` keeps an
`Entity<TextEditorState>` in the view's custom element registry, and
that registry is only emptied when React destroys the element, so a
suite that just ends leaves handles behind. macOS never runs the
thread-local destructor at process exit, so the panic was invisible
until Windows ran the suite.
`VisualTestState` now destroys every custom element instance, and drops
the focus handles and subscriptions, from its own `Drop`. That runs
before its fields, so the `App` is still there to update the view.
Session: ses_fc0b7cd62ffe3aBw0pyYBOBDjf
Consume 52 pending changesets into CHANGELOG.md and bump both public
packages from 0.4.0 to 0.5.0. Minor, because users get several new
capabilities:
- GPUIX apps render in the browser through GPUI's web platform, with
React Fast Refresh under `bun run web`
- a `highlight` prop plus `useTextSearch` and `findRanges`, so a find
bar works across <text>, <code>, <markdown> and <diff>
- <virtual-list> can mount a window of rows (itemCount, windowStart,
estimatedItemHeight) instead of all of them
- automation drags, hovers, wheels and holds modifiers, and reaches
<input> and <textarea>
- a macOS menu bar, so a GPUIX app answers Cmd+Q, Cmd+H, Cmd+M, Cmd+W
- HTML-like pointer capture, full CSS cursors and colour functions,
onAuxClick, window inset hooks, per-side borders and boxShadow
- a Windows test renderer and a sizable createTestRoot()
Two API changes need a migration note, both in the changelog:
<code> lost `showHeader` and is now a bare surface, and each React
root owns its handler map so `resetIdCounter()` is gone and
`handleGpuixEvent` takes the renderer.
Also pin the generated napi loader version checks to 0.5.0.
Local publish stays blocked. CI is the only release path because
native needs per-platform binaries.
Fixes#1Closes#2Fixes#20
Session: ses_fc0b7cd62ffe3aBw0pyYBOBDjf
Consume pending changesets into CHANGELOG.md and bump both
public packages from 0.3.0 to 0.4.0. This is a minor because
users get two new capabilities:
- motion.div native animations (Rust interpolates frames)
- Playwright-like automation API (connectTest, launch, locators)
Also pin the generated napi loader version checks to 0.4.0.
Local publish stays blocked. CI is the only release path
because native needs per-platform binaries.
Session: ses_fd0d6b222ffeQygDL9SHWVoXsE
Consume pending changesets into CHANGELOG.md and bump both
public packages from 0.2.0 to 0.3.0. This is a minor because
users get new layout, new window chrome, and new APIs:
- CSS grid on div (gridTemplateColumns, gridColumnMin)
- titlebarTransparent, windowBackground, traffic-light position
- <diff> flows by default, plus maxLines / onShowMore
- debugFrameOverlay draw-time overlay
Also ships scroll, overlay, remount, and input fixes from
the pending changesets.
Local publish stays blocked. CI is the only release path
because native needs per-platform binaries.
Session: ses_fd1303003ffeHgTR9sNvkGcWhU
Consume pending changesets into CHANGELOG.md and bump both
public packages from 0.1.0 to 0.2.0. This is a minor because
users get new elements, new APIs, and new capabilities:
- selectable text plus native <code>, <diff>, and <markdown>
- native <input> / <textarea>
- render() remount for bun --hot
- headless Select, Combobox, and Tooltip
- <virtual-list>, tintable <svg>, startFrameLoop()
- native GPUI platform on macOS, Windows, and Linux
CI now publishes @gpuix/react with bun publish so the
workspace:^ dependency is rewritten to ^0.2.0. The 0.1.0
react tarball on npm still has the literal workspace protocol.
Local publish stays blocked. CI is the only release path
because native needs per-platform binaries.
Session: ses_fd22a9f4fffeR2RGgyaXgLFdgW
GPUI natively supports hover/active pseudo-selectors via .hover() and
.active() methods on InteractiveElement. These apply a StyleRefinement
when the mouse hovers over or presses an element — entirely GPU-side,
zero JS round-trips.
This commit exposes that API through the GPUIX style system:
```tsx
<div style={{
backgroundColor: '#313244',
cursor: 'pointer',
hover: { backgroundColor: '#45475a', opacity: 0.9 },
active: { backgroundColor: '#585b70' },
}} />
```
## Rust changes
- **style.rs**: Added `hover: Option<Box<StyleDesc>>` and
`active: Option<Box<StyleDesc>>` to StyleDesc. Box is required
because the struct is recursive. Serde deserializes them from the
nested JSON objects naturally.
- **renderer.rs**: In build_div(), after apply_styles(el, style),
calls GPUI's native .hover() and .active() with the sub-styles.
This works because apply_styles() is generic over E: Styled, and
StyleRefinement implements Styled — so the same function that applies
base styles also applies hover/active refinements.
## TypeScript changes
- **host.ts**: Added hover? and active? to StyleDesc typed as
Omit<StyleDesc, 'hover' | 'active'> to prevent infinite nesting
(GPUI only supports one level of pseudo-selectors).
## Tests (8 new)
- Hover renders without crash
- Hover with only color change
- Empty hover object is harmless
- **Visual hover transition**: captures before/after screenshots with
nativeSimulateMouseMove — asserts PNG bytes differ, proving GPUI's
native hover refinement actually changes rendered output
- Active renders without crash
- Combined hover + active on same element
- Hover + active alongside onClick handler
- All 111 tests pass
Build matrix:
- macOS arm64 (macos-latest) — Metal, with test-support
- macOS x64 (macos-13) — Metal, with test-support
- Linux x64 (ubuntu-latest) — Vulkan/wgpu, no test-support
- Linux arm64 (ubuntu-24.04-arm) — Vulkan/wgpu, no test-support
Test job runs 105 tests on macOS arm64 (needs Metal for GPU-backed
TestGpuixRenderer).
Publish is version-gated: checks if the package.json version already
exists on npm, skips if so. Bump version + push to main to release.
Publishes both @gpuix/native (per-platform binaries via napi pre-publish)
and @gpuix/react (pure TypeScript).
Other changes:
- Add build:release script (no test-support) for Linux CI
- Document Cargo feature gate in Cargo.toml comments
- @gpuix/react: workspace:* → workspace:^ for publishing
- @gpuix/react: add publishConfig
Prerequisite for CI/CD and per-platform npm publishing.
## package.json
- `@napi-rs/cli`: `^2.18.0` → `^3.1.3`
- napi config: v2 `name` + `triples` → v3 `binaryName` + `targets` array
- Added Linux targets: `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`
- `prepublishOnly`: `napi prepublish` → `napi pre-publish` (v3 hyphenated)
- Added `publishConfig` with registry + public access
## Cargo.toml
- `napi` crate: `2` → `3` (features `napi8`, `serde-json` unchanged)
- `napi-derive`: `2` → `3`
- `napi-build`: stays at `2` (stable API, no changes needed)
## Rust source (renderer.rs)
One API change required: `ThreadsafeFunction` in napi v3 is `!Clone`.
Wrapped the field in `Arc<ThreadsafeFunction<EventPayload>>` so it can
be shared into the GpuixView closure from `&self` methods. Constructor
`Arc::new`-wraps the incoming TSF, `init()` clones the Arc.
## Auto-generated files
- `index.js` now uses v3's `requireNative()` function pattern
- `index.d.ts` now includes JSDoc comments from Rust doc comments
All 105 tests pass.
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
Wire up 3 text-related style properties that exist in GPUI's Styled trait
but were not exposed through GPUIX:
**whiteSpace** — `"nowrap"` disables text wrapping (single line),
`"normal"` enables wrapping (default). Applied in both `apply_styles()`
and `build_text()` via GPUI's `.whitespace_nowrap()` /
`.whitespace_normal()`. Critical for diff viewer code lines that must
not wrap.
**textOverflow** — `"ellipsis"` truncates long text with "..." at
the end, `"ellipsis-start"` truncates from the start (showing the
end of the text). Applied via GPUI's `.text_ellipsis()` /
`.text_ellipsis_start()`. Useful for file path breadcrumbs and long
identifiers.
**lineClamp** — limits visible text to N lines. Values < 1 are ignored
(no clamping). Applied via GPUI's `.line_clamp(n)` which internally
sets overflow hidden. Useful for preview cards and summaries.
All three are applied in both `apply_styles()` (for div elements) and
`build_text()` (for text elements), so they work via inheritance from
parent divs and directly on text nodes.
**README update**: document all text style properties and add a note
about `white-space: pre` not being supported in GPUI, with a workaround
pattern (split on newlines, render each line as a separate <text> with
nowrap in a flex column).
**Tests**: 13 new tests (22 total in styles.test.tsx) with screenshots:
- whiteSpace: nowrap, normal, visual comparison, div-level inheritance
- textOverflow: ellipsis end/start, visual comparison, short text edge case
- lineClamp: 1/2/3 lines, div-level, visual comparison, zero edge case
- Composite: pre-like behavior (split lines + monospace + nowrap)
All 104 tests pass across the full suite.
Files changed:
- packages/native/src/renderer.rs — +30 lines in apply_styles() and build_text()
- packages/native/src/style.rs — +3 fields
- packages/react/src/types/host.ts — +3 TS fields
- packages/react/src/__tests__/styles.test.tsx — +581 lines (13 new tests)
- README.md — text styles docs + pre workaround note
Wire up 6 style properties needed for building a diff viewer component:
**alignSelf** — field existed in StyleDesc but was never applied in apply_styles().
Uses direct `el.style().align_self` field access since GPUI's Styled trait has no
convenience methods for align_self. Supports center, start, end, stretch, baseline.
**flexGrow / flexShrink numeric values** — previously both always hardcoded to 1.0
regardless of the actual value passed from JS. `flexShrink: 0` and `flexGrow: 2`
now work correctly via direct `el.style().flex_grow = Some(value)` field access.
**fontFamily** — new field in StyleDesc (Rust + TS). Applied in both apply_styles()
and build_text() via GPUI's `.font_family()` method. Uses `.clone()` instead of
`.as_str()` because SharedString requires 'static lifetime.
**fontWeight** — field existed in StyleDesc but was never applied. Extracted a
`parse_font_weight()` helper that handles CSS named keywords (case-insensitive,
with hyphenated variants like extra-bold, semi-bold) and numeric strings.
Applied in both apply_styles() and build_text().
**text backgroundColor** — build_text() now applies `.bg()` on the wrapping div,
enabling word-level diff highlighting on individual <text> tokens.
**flexWrap** — field existed in StyleDesc but was never applied. Maps wrap,
wrap-reverse, nowrap to the corresponding GPUI methods.
Tests: 9 new end-to-end tests in styles.test.tsx covering all features with
Metal GPU screenshots. All 91 tests pass (82 existing + 9 new).
Files changed:
- packages/native/src/renderer.rs — apply_styles(), build_text(), parse_font_weight()
- packages/native/src/style.rs — font_family field
- packages/react/src/types/host.ts — fontFamily TS type
- packages/react/src/__tests__/styles.test.tsx — new test file
Verified that React refs work through our reconciler — getPublicInstance()
returns the Instance directly, so ref.current.id gives the numeric element
ID needed for scrollTo/scrollToItem/getScrollOffset.
Changes:
- Remove id?: string from Props — it was never forwarded to Rust or used
anywhere. Element IDs are auto-generated internally. Users access them
via React refs (ref.current.id).
- Add test: useRef captures element, ref.current.id is a positive number,
scrollTo via that ID works correctly.
- Add README section: overflow scroll example, per-axis scrolling, and
full programmatic scroll pattern using refs + renderer methods.
TestRenderer previously maintained a parallel Map<number, TestElement>
in JavaScript that mirrored every mutation also sent to Rust's
RetainedTree. This was redundant — tests were reading state from the
JS copy while events went through Rust, creating a split source of truth.
Now all element state lives exclusively in Rust's RetainedTree, queried
via napi methods. The data flow is unified:
React reconciler
│
└──→ Rust RetainedTree (via napi)
│
├── mutations (createElement, setStyle, etc.)
├── inspection (getAllText, getTreeJson, findByType)
└── simulation (simulateClick, simulateKeystrokes)
Changes:
- testing.ts: Remove ~220 lines of JS shadow tree code. Mutation methods
are now one-line delegations to native. Inspection methods (findByType,
getAllText, toJSON, getRoot, getElement, findByText) query Rust via
getTreeJson() napi call and build TestElement objects on demand.
Constructor now requires native renderer (throws if unavailable).
- test_renderer.rs: Add getRootId() napi method for root element queries.
Add customProps to element_to_json() output so test inspection sees
custom element props (used by img/input tests).
- index.d.ts: Auto-regenerated by napi build (adds getRootId signature).
- host.ts: Update interface comments to reflect new architecture.
Zero test file changes needed — TestElement interface and all method
signatures remain identical. All 68 tests pass.
Reduces N individual napi FFI boundary crossings per React commit to 1.
A typical render with 20 elements went from ~75 separate FFI calls to 1.
## Batch format
String-named JSON tuples sent as a single applyBatch(json) call:
[["createElement",1,"div"],["setStyle",1,"{...}"],["appendChild",0,1]]
Each tuple is ["methodName", ...args] — method names match the
NativeRenderer interface exactly.
## Rust side
- apply_batch() on both GpuixRenderer and TestGpuixRenderer
- Atomic two-phase processing via shared apply_batch_to_tree():
Phase 1: parse_batch_ops() validates ALL ops into typed BatchOp enum
Phase 2: apply validated ops to RetainedTree
If any op is malformed, tree is left unchanged (no partial application)
- Single mutex lock for entire batch vs per-op locking before
## JS side — DRY via Proxy + dynamic dispatch
- BatchingRenderer (batch-renderer.ts): JS Proxy auto-captures any
NativeRenderer method call as [name, ...args]. Adding new methods
to NativeRenderer requires ZERO changes to the batching layer.
- TestRenderer.applyBatch: forwards to native.applyBatch() once, then
replays locally via dynamic dispatch with _skipNative flag. Also
requires ZERO changes when adding new methods.
- wrapWithBatching() wired into both createRoot() and createTestRoot()
- Backward compatible: individual mutation methods remain, batching
is opt-in via applyBatch presence check
## Safety
- Queue cleared AFTER successful batch (preserved on failure)
- Event handler cleanup runs before commitMutations (coherent on throw)
- TestRenderer local replay wrapped in try/catch (no duplicate re-apply)
- setEventListener strict validation (errors on missing hasHandler arg)
All 68 existing tests pass through the batched path.
This introduces image rendering through the custom-element pipeline and validates that image source updates produce visible GPU output in end-to-end tests.
- add native img custom element that maps src and objectFit props to gpui image rendering from local file paths\n- register image factory in default custom element registry\n- add typed ImgProps in React host types and wire img intrinsic types in jsx runtime and jsx dev runtime declarations\n- add img test suite with prop-forwarding assertion and screenshot before-after regression using deterministic SVG fixture\n- update changelog with the new image support and test coverage
This introduces a first-class anchored overlay element so GPUIX can model command-palette/autocomplete-style UI with window-coordinate positioning and deferred top-layer rendering.
- add native <anchored> custom element backed by gpui::anchored() with optional gpui::deferred(...).with_priority(...) layering\n- support anchor/position/snap/deferred props (, , , , , , , )\n- extend CustomRenderContext to carry built child elements and update custom render trait to consume owned context\n- register AnchoredFactory in default custom element registry\n- expose in React component map and JSX/runtime type surfaces with AnchoredProps\n- add end-to-end anchored overlay test covering open, inside click persistence, and outside-click close\n- document the capability in CHANGELOG
This adds the minimum native positioning primitives needed for tooltip-like dialogs and verifies the interaction/render behavior through GPU-backed end-to-end tests.
- map style.position to GPUI relative/absolute in apply_styles\n- map top/right/bottom/left offsets so overlay elements can be placed out of flow\n- add a dialog overlay interaction test (open on button click, stay open on inside click, close on onMouseDownOutside)\n- add screenshot regression coverage that captures before/after images and asserts visual output changes when opening the dialog\n- document the change in CHANGELOG with UTC entries
This hardens the polymorphic custom element path so custom instances behave like built-ins across mount/update/unmount cycles.
- destroy stale custom instances when IDs disappear from the retained tree, and clear custom instances if an ID now maps to built-in div/text\n- add supported_props() and reset missing known props to null each frame to prevent stale state (for example readOnly persisting after prop removal)\n- thread retained style into CustomRenderContext and apply it in input render so style-driven layout/hit testing works for custom elements\n- filter custom event wiring to supported_events() only\n- make custom prop forwarding resilient to JSON serialization failures in host-config by falling back to null\n- add end-to-end regressions for readOnly removal and style width hit-testing\n- update changelog entry for these fixes
## Architecture
Add a trait-based plugin system for custom native GPUI elements that can be
used as React intrinsic elements. The renderer dispatches to trait objects at
render time — each custom element lives in its own file with its own
dependencies, cleanly separated from the core renderer.
```
build_element()
"div" → build_div() (built-in)
"text" → build_text() (built-in)
_ → registry.render(ctx) (trait dispatch)
```
## Rust side
- `custom_elements/mod.rs`: `CustomElement` trait, `CustomElementFactory`
trait, `CustomElementRegistry` struct, `CustomRenderContext` (carries
element ID, events, callback, focus handle)
- `custom_elements/input.rs`: `InputElement` + `InputFactory` — controlled
text input with value/placeholder/readOnly props, renders as a styled div
with border, handles keyDown/keyUp/click/focus/blur events
- `renderer.rs`: polymorphic dispatch in `build_element()` fallthrough arm,
`setCustomProp`/`getCustomProp` napi methods, `CustomElementRegistry` on
`GpuixView`, updated signatures for `build_div`/`build_text` to thread
registry + window + cx through recursive calls
- `retained_tree.rs`: `custom_props: HashMap<String, Value>` on
`RetainedElement`, with set/get methods that handle null as removal
- `test_renderer.rs`: same napi methods + registry init
## TypeScript side
- `host-config.ts`: `syncCustomProps()` / `diffCustomProps()` — auto-forward
non-style, non-event props as JSON to Rust for any non-div/text element
- `types/host.ts`: `setCustomProp` on `NativeRenderer`, `InputProps` type
- `testing.ts`: `setCustomProp` forwarding to native + local map storage
- JSX types: `input` added to IntrinsicElements in both runtime .d.ts files
- `components/index.ts`: `input` added to component registry
## Tests (6 new, 27 total — all pass)
- Rendering: input with value prop, placeholder when empty
- Keyboard: controlled component typing via nativeSimulateKeystrokes,
backspace handling — full React state round-trip
- Screenshots: before/after PNG capture through real Metal GPU pipeline,
validates placeholder → typed text visual change
- Tree structure: verifies custom type appears in element tree JSON
## Bug fix
- Fix jsx-dev-runtime.js: React 19 only exports `jsxDEV` from the dev
runtime (not `jsx`/`jsxs`). Re-export `jsxDEV` directly so vitest's JSX
transform works. This was breaking ALL tests, not just the new ones.
The README was describing the old JSON tree serialization approach.
The actual codebase uses a mutation-based protocol where React's
reconciler makes direct napi FFI calls (createElement, appendChild,
setStyle, commitMutations, etc.) to a Rust RetainedTree.
Changes:
- Replace 'description-based renderer / JSON element tree' language
with 'mutation-based protocol over napi-rs FFI'
- Architecture diagram now shows individual napi calls flowing to Rust
- Add Mutation API section documenting the NativeRenderer interface
- Add Event Flow section with full pipeline diagram:
GPUI → Rust emit_event_full → ThreadsafeFunction → JS registry → React
- Expand events from a plain list to a table with payload fields
- Add onMouseDownOutside, onScroll (delta/phase), keyboard, focus/blur
- Add Testing section covering TestGpuixRenderer (GPU-backed Metal,
native event simulation, screenshot capture)
- Update status checklist: keyboard, focus, scroll, test renderer done
- Update usage example to use createRenderer() and tick() loop
- Mark img/svg/canvas as planned in elements table
The visual screenshots were technically valid but looked top-left anchored and visually sparse, which made them less useful for manual inspection and demos. This update centers the probe cards in full-window layouts and applies a clearer visual hierarchy so interaction diffs are easier to read at a glance.
Changes in this commit:
- move click and hover simulation coordinates to the centered card region
- redesign click, keyDown, and hover probes with rounded cards, balanced spacing, and higher-contrast text styles
- preserve strict screenshot assertions (non-empty files and before/after PNG differences)
- record the visual test polish in the changelog
Validation performed:
- bun test src/__tests__/events.test.tsx -t screenshot
- bun test src/__tests__/events.test.tsx
The visual screenshot pipeline was rendering backgrounds and shapes but dropping text, which made screenshot-based validation misleading. The root cause was that the macOS visual test path could fall back to `NoopTextSystem` when `gpui_macos/font-kit` was not enabled in the native test feature set.
This change enables `gpui_macos/font-kit` under `test-support`, ensuring `VisualTestAppContext` uses `MacTextSystem` and rasterizes glyphs during `capture_screenshot()`.
It also expands screenshot assertions to cover multiple interaction classes:
- click-driven visual state changes
- keyDown-driven visual state changes
- hover (mouseEnter) visual state changes
Additional hardening included:
- forcing `window.refresh()` before screenshot capture to avoid stale frame reads
- a shared screenshot assertion helper in tests to require non-empty PNG output and before/after image differences
- changelog updates documenting the renderer and visual test improvements
Validation:
- `bun run build` (native napi module)
- `bun test src/__tests__/events.test.tsx` (21/21 passing)
- `bun test src/__tests__/events.test.tsx -t screenshot` (3/3 screenshot tests passing)
- Document that packages/native/index.d.ts, index.js, and *.node are
auto-generated by napi-rs during `bun run build` and must not be
edited manually. Edit Rust #[napi] annotations instead.
- Add changelog requirement: update CHANGELOG.md at repo root after
every change, reverse-chronological order, YYYY-MM-DD HH:MM UTC.
- Create initial CHANGELOG.md with entries for all changes in this
session (native event test migration, Oracle fixes, AGENTS.md updates).