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
disktree now runs on plain Node (>=20). Bun is no longer required.
- ship tsc output in dist/, bin is dist/cli.js with a node shebang
- tsconfig follows the npm-package template (nodenext, rootDir src)
- worker URL follows the running file extension (.ts in src, .js in dist)
- prepublishOnly wipes dist and rebuilds
- README and AGENTS use npx disktree
Session: ses_f1b9d5d03ffep38dyOKhtgIJSt
- remove the SF_DATALESS check in scan-worker.ts. It called libc lstat
through bun:ffi with a hardcoded arm64 struct offset
- remove the `cloud` field from DirRecord and its use in core.ts
- iCloud folders are now scanned like any other folder
- disktree still runs on Bun (raw .tsx bin)
- bump disktree to 0.1.2 so CI publishes it
Session: ses_f1b9d5d03ffep38dyOKhtgIJSt
Limit scan workers to four, reducing peak physical footprint by about 60 MiB on a 350k-file scan. Publish disktree independently after its GPU-backed test, so unrelated GPUIX CI failures cannot block the release.
Session: ses_f1be8a987ffeslJ1dooQ4pgDnx
`bunx disktree [dir]` opens the treemap on a folder, the current directory,
or `/`. Published as disktree@0.1.0.
- moved from examples/ to a `disktree` workspace package: src/app.tsx
(store + view), src/cli.tsx (bin), src/core.ts, src/scan-worker.ts
- scan runs on a pool of worker threads with sync lstat, like the Rust
original on rayon; the frontier is handed back after 400 dirs so deep
trees spread across workers. Full disk here: 24.5M files in 150 s, no
stall
- skips iCloud dataless folders (SF_DATALESS via bun:ffi lstat), like the
Rust scanner; listing them made macOS download them and stalled the scan
- keeps the 12 largest files per directory, folds the rest into one
"N smaller files" tile
- store lives on globalThis so `bun --hot` remounts instead of starting a
second full scan
- scan progress bar uses used disk space as the total when scanning a
volume root
- lighter pastel tiles at 90% opacity
Session: ses_f1cc78bbbffeiqA1qhn3vB0pzN
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
Port the chat example to @gpuix/solid under examples/solid with jsx
preserve, the Solid bunfig preload, and a matching automation driver.
examples/package.json now depends on @gpuix/solid and solid-js and
adds chat-solid. bun.lock follows that.
mem-mount.tsx (React) and examples/solid/mem-mount.tsx print mount
time plus RSS/heap for the same ChatApp so the two adapters can be
compared.
Session: ses_f3c65bfd5ffegJLklT8domWQ7B
Introduce @gpuix/solid for stable Solid 1 with a small universal host tree, shared native mutation and event infrastructure, native-backed testing and automation, motion, AnimatePresence, Select, Combobox, and Tooltip.
Compile reactive JSX through an OpenTUI-style Bun preload and exported Bun.build plugin, with no Vite or wrapper CLI. Add direct workspace, real native renderer, imported TSX, and installed-tarball end-to-end coverage.
Automatically flush signal mutations outside event boundaries while keeping failed native batches available for retry.
Session: ses_f4102f732ffesSfYYRRHlXA2j9
Co-authored-by: juan <47119916+jhomra21@users.noreply.github.com>
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
Resolve the executable path before comparing it with import.meta.url. Package managers launch the gpuix binary through a symlink, so the previous direct comparison made @gpuix/cli@0.1.0 exit without parsing any command.
Add a symlink regression test and release @gpuix/cli@0.1.1. Consume the original feature changeset because 0.1.0 is already published.
Session: ses_fb87b7e28fferSvDT71z3OadR1
Publish a new @gpuix/cli package with a goke-powered `gpuix new` command. The command downloads the official example app, replaces its workspace React dependency with the latest npm release, and installs the generated project with Bun.
Update the quickstart, example documentation, workspace metadata, release notes, and CI publish order. Add a real GitHub-backed integration test and shell completion support.
Session: ses_fb87b7e28fferSvDT71z3OadR1
`bun scripts/web.ts` now registers one bundled document per example:
`/` keeps the chat and `/infinite` serves the infinite history. The
shared cross-origin isolation headers wrap each document the same way.
The inline page styles move from web.html into a shared web.css that
both documents import, and web-infinite-chat.{html,tsx} are the browser
entry pair for the example. bun.lock picks up string-dedent for the
example's seeded message generator.
Session: ses_fbc66676dffeMDwZ3jJhimdkw6
An edit to `examples/chat.tsx` now hot-updates the running page instead of
reloading it. Components swap in place and `useState` survives, so the GPUI
canvas, the composer text and the scroll position all stay where they were.
A reload re-downloads a ~19 MB Wasm module; this avoids that entirely.
`scripts/web.ts` drops the `Bun.build` step and the hand-written static file
map. It now registers `examples/web.html` as a Bun HTML route with
`development: { hmr: true }`, so Bun bundles the page, watches the graph and
applies the React Fast Refresh transform.
Fast Refresh never talks to a renderer directly. It goes through the React
DevTools global hook:
Bun HMR runtime
-> refreshRuntime.injectIntoGlobalHook(window)
@gpuix/react reconciler.ts
-> reconciler.injectIntoDevTools()
-> hands the hook scheduleRefresh + setRefreshHandler
react-refresh
-> performReactRefresh() -> scheduleRefresh(root)
That injection already existed. What was missing was a bundler that runs the
transform, and a test proving the wiring holds.
`examples/web-chat.tsx` must NOT call `import.meta.hot.accept("./chat", ...)`.
Bun runs an importer's dependency-accept callback even when the imported module
already self-accepted for Fast Refresh, so the callback remounted the tree on
top of a successful refresh and wiped every hook. That looks exactly like Fast
Refresh being broken, and it is not.
The Wasm half is a singleton: `WebGpuixRenderer::init` fails with "GPUIX web is
already running" once its thread-local app exists, and gpui_web appends its own
canvas to <body>. Living in node_modules is not what protects it, because Bun
bundles it into the same client registry. What protects it is that Bun re-runs
only the changed module and then walks upward through its importers, so an
unchanged dependency stays evaluated and cached.
packages/native/.cargo/config.toml links with `--shared-memory`, so the
generated `WebAssembly.Memory` is `shared: true` and the document must be
cross-origin isolated. `Bun.serve` has no way to add headers to an HTML route
(oven-sh/bun#16873), so the bundled document is registered on a private path and
re-sent from `/` with COOP + COEP. Only the top-level document needs them:
`require-corp` constrains cross-origin subresources, and every asset the dev
server emits is same-origin.
`packages/react/src/__tests__/fast-refresh.test.tsx` mounts through the GPU test
renderer, clicks to set state, swaps the component family, and asserts the new
output keeps the old state. It fails when `injectIntoDevTools()` is removed.
This matters because the failure mode is silent. Without the injection there is
no error and no page reload: Bun still accepts the update and still calls
`performReactRefresh()`, which iterates zero mounted roots and schedules
nothing. The bundle changes and the painted UI stays stale.
Do not assert on the return value of `injectIntoDevTools()`. It ends in
`hook.checkDCE ? true : false` and react-refresh installs no `checkDCE`, so a
working injection still reports `false`.
- `--build-only` / `--rebuild` / auto-build-if-missing in scripts/web.ts, and
the missing check tests both wasm-bindgen outputs, not just the `.wasm`
- `examples/tsconfig.json` gets `noEmit: true`; nothing there is ever compiled
by tsc and a stray `tsc -p .` littered a `.js` next to every `.tsx`
- `@types/bun` as a root devDependency plus a `typeRoots` entry so
`scripts/tsconfig.json` types `Bun.serve` and the `*.html` bundler import
- README documents that automation locator bounds are canvas pixels, not CSS
pixels. Do not use `window.devicePixelRatio`; a tool can override the viewport
scale after GPUI has sized its canvas. Use `canvas.width / canvas.clientWidth`
Validated in Chrome with playwriter: text edits and a module-level colour
constant edit both repaint the canvas with state preserved, and a revert
restores a byte-identical screenshot.
Session: ses_fc233909fffeHxAGjrf2jkWSet
`example-app` had a `screenshot.ts` and nothing that asserted behaviour.
`app.test.tsx` mounts `TodoApp` on the GPU test renderer and drives it
with the same locator API, in-process, with no child window:
const { render, renderer } = createTestRoot({ width: 940, height: 660 })
render(<TodoApp />)
const app = await connectTest(renderer)
await app.getByTestId('view-inbox').click()
await app.getByTestId('row-t5').hover()
await app.getByTestId('delete-t5').click()
Five tests: the active view paints every row, delete removes the hovered
row, the composer prepends, a completed task moves between views, and a
task added past the viewport height stays on screen.
**`app.tsx` changes**
`render()` now sits behind an entry-point check, the same one `chat.tsx`
uses, so importing the file does not open a window.
Rows carry `row-<id>`, `toggle-<id>`, `star-<id>` and `delete-<id>`. The
trash button only exists while the row is hovered, so a test must
`hover()` before it can `click()`.
**Why `getPaintedText()` and not `getAllText()`**
Both would work here, since every row is a `<text>`. `getPaintedText()`
is the one that proves the row is actually on screen, which is the whole
point of the fifth test.
Session: ses_fc16226ffffeOn4QTVIbw4K7Nz
Installing the published packages and writing an app was not possible by
following the README. Three things blocked it.
**1. `key` failed on every GPUIX element**
error TS2322: Type '{ key: string; ... }' is not assignable to type 'Props'.
Property 'key' does not exist on type 'Props'.
Any list built with `.map()` broke on the first `tsc` run. `key` was expected
to arrive through `JSX.IntrinsicAttributes`, but TypeScript 5 ignores that
member for intrinsic elements. React's DOM types work only because
`DetailedHTMLProps` already carries `key`.
`key` now sits on `Props`, next to `ref`, and on `VirtualListShared`, the one
element prop type that does not extend `Props`. Verified with a probe over all
12 intrinsic elements plus `motion.div`, `VirtualList`, Select, Combobox and
Tooltip.
`jsx-dev-runtime.d.ts` also re-exported `jsx` and `jsxs` from
`react/jsx-dev-runtime`, which exports only `jsxDEV`. The declarations now
match `jsx-dev-runtime.js`.
Nothing caught this because Bun never typechecks and the build tsconfig
excludes `src/__tests__`. CI now runs `bun run typecheck` in `example-app`,
whose `app.tsx` maps a keyed list inside a `<virtual-list>`.
**2. A hello world installed 254 MB**
`@gpuix/native` packed all six platform binaries into the main tarball through
a `*.node` glob, on top of the six per-platform packages that
`optionalDependencies` already resolves.
node_modules 254M
|- @gpuix/native 185M <- all six, never loaded
|- @gpuix/native-darwin-arm64 23M <- the one that loads
|- @gpuix/react 544K
The glob is gone. `index.js` still resolves the platform package at runtime.
**3. The README never said how to install**
It opened with the architecture and the mutation protocol. There was no
`bun add` anywhere, and no mention of `jsxImportSource`, which is required:
without it TypeScript uses DOM types and `<virtual-list>`, `<markdown>`,
`<code>` and `style.hover` all fail.
A **Quickstart** now sits between the hero and Examples: install, tsconfig,
entry file, `bun --hot`, `bun build --compile`. Nothing was removed. The
Building section says up front that it is for working on GPUIX itself.
**example-app/**
A todo app to copy, in one file, using the Waku palette from `chat.tsx`.
| Script | What it does |
|---|---|
| `bun run dev` | `bun --hot app.tsx` |
| `bun run build` | standalone binary in `dist/todo` |
| `bun run web:build` | browser renderer, needs nightly Rust |
| `bun run web:dev` | browser bundle served with COOP/COEP |
| `bun run screenshot` | drives the app through the automation client |
| `bun run typecheck` | `tsc --noEmit` |
It shows `<virtual-list>`, a native `<input>`, `motion.div`, tinted `<svg>`,
native `hover` and `active`, and `testId` hooks. `scripts/screenshots.ts`
regenerates `docs/images/todo-app.png` from it.
Verified on all four paths: `bun --hot`, the 77 MB binary, the browser build at
`localhost:4188`, and a clean `typecheck`.
Two things worth remembering, both now in comments:
- the browser build embeds only IBM Plex Sans and Lilex, so `Helvetica` falls
back to a monospace and the family has to differ per target
- `maxWidth` on a `<virtual-list>` row does nothing; the list lays each row out
at list width, so the column cap goes one level in
`bun.lock` carries a few entries from unrelated in-flight work in `examples/`.
They are inert and prune on the next install.
Session: ses_fc1c928b0ffeeHIfdRpxtE0QaV
Wrap the rewritten 252px sidebar and its 1px divider in one native motion.div clipping row. Keep the content mounted, expose stable collapse and expand locators, and preserve the current Waku layout while the main pane expands.
Drive the real collapse through the automation locator and freeze the native clock to capture exact 100ms and 200ms frames without timer sleeps. Keep composer and send locators aligned with the automation guide.
Use the direct mdast Root type for the safe-mdx cache and declare @types/mdast in the examples package. Update the README sidebar pattern to the exact 253px animated width.
Session: ses_fd5faf7f1ffezwFhkdKQa42frN
Tests and live apps now share one typed command catalog. Locators query
the retained tree. Input, screenshots, and the motion clock go through
the same host.
TypeScript client
- import { connectTest, launch } from @gpuix/react/automation
- getByTestId / getByText / getByType, click, waitFor, screenshot
- clock.pause / set / fastForward for deterministic review frames
- captureFrames(dir, [0, 150, 300]) writes one PNG per timestamp
Wire protocol
- One Zod schema per method. Request and result types stay aligned
- SSE data: lines parsed with eventsource-parser
- Lines without a data: prefix are ignored, so console.log cannot
break a message
- A live app listens on stdin only when stdin is a pipe, not a TTY
- launch() pipes the child and speaks that protocol. No env var
Native host
- testId is a universal React prop stored on the retained tree
- Per-frame paint bounds, recorded during paint so the frame reset
cannot wipe them
- AutomationClock freezes Instant::now() for motion
- TestGpuixRenderer and GpuixRenderer both expose tree, bounds,
click, clock, and screenshot
```
controller --stdin data: {...}--> child app
vitest --napi--------------> AutomationHost --> GpuixView
```
Session: ses_fd5a69e69ffeM8kfT2AHR6vZIN
Add a second chat transcript path that parses MDX with safe-mdx and maps its AST to ordinary GPUIX React host elements. The stress document covers inline formatting, lists, task items, tables, links, fenced code, and a custom MDX component. Fenced blocks continue to use the native code element for syntax highlighting.
Capture the composed transcript through the native GPU test renderer and snapshot every painted text run. The test also verifies that this path creates no native markdown element.
Expose React 19 JSX ElementType from both GPUIX JSX runtimes and align React type packages so libraries whose components return ReactNode type-check correctly. Add the public patch release note and lock safe-mdx 1.14.0 with its parser dependencies.
Session: ses_fd63e9acdffexwTU6lDfivWem3
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
The test file events.test.tsx used // @ts-nocheck because JSX types
resolved to DOM's HTMLDivElement instead of GPUIX's Props. This meant
<text> was unknown, event handlers expected React.MouseEvent instead of
EventPayload, and onMouseDownOutside didn't exist in DOM types.
Fix: declare a JSX namespace in jsx-runtime.d.ts and jsx-dev-runtime.d.ts
that maps GPUIX element types (div, text, img, svg, canvas) to the
existing Props type. Add jsxImportSource: '@gpuix/react' to the package
tsconfig so TypeScript resolves JSX types from GPUIX instead of DOM.
Also add @types/node to fix a pre-existing require() error in testing.ts.
tsc --noEmit now passes with zero errors in packages/react.
Implement a custom GPUI platform (NodePlatform) that runs inside Node.js
without blocking the event loop. Instead of the old renderer.run() which
called [NSApp run] and blocked forever, the new API is:
renderer.init({ title, width, height }) // non-blocking
renderer.render(jsonTree) // update element tree
renderer.tick() // pump events + render frame
The JS side drives rendering via a setImmediate loop, keeping Node.js alive
for React state updates and event handling.
## NodePlatform architecture
Node.js event loop tick
├── JS: React reconciler produces tree → renderer.render(json)
└── JS: renderer.tick()
└── Rust: pump winit events + drain GPUI tasks + render frame
Key components:
- NodePlatform: implements gpui::Platform, run() returns immediately
- NodeDispatcher: main-thread queue + delayed queue, drained during tick()
- NodeWindow: winit window + wgpu surface, maps winit events to GPUI input
- NodeDisplay: minimal display info for GPUI layout
## React reconciler fix (critical bug)
react-reconciler 0.31+ (React 19) removed the updatePayload parameter from
commitUpdate. The old 6-param signature shifted all args by one, causing
newProps to receive the internal fiber handle instead of actual props. This
silently wiped all instance styles on every state update.
// OLD (broken): commitUpdate(instance, updatePayload, type, oldProps, newProps, handle)
// NEW (fixed): commitUpdate(instance, type, oldProps, newProps, handle)
Also removed dead prepareUpdate method — no longer called by react-reconciler
0.31+. The reconciler now uses oldProps !== newProps reference equality.
## GPUI dependency update
Updated from rev 83ca310 to 14f37ed (post gpui_web merge) which includes:
- gpui_wgpu crate with WgpuRenderer + CosmicTextSystem
- Platform trait changes for non-blocking run()
- Application::with_platform() constructor