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
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
The suite covered adding, deleting and starring a row, but nothing
proved a sidebar tab actually changes the screen. A view filter that
silently returned the same rows, or a header that kept the previous
title and count, would have passed.
The new case clicks Inbox, Starred, Done and Today and snapshots the
whole painted screen for each, so the header title, the header count
and the row set are all locked. It also returns to the tab the app
opened on and asserts the screen matches the first Today capture, so a
tab cannot be one-way.
Session: ses_fc0b7cd62ffe3aBw0pyYBOBDjf
`EmptyState` was the single child of the `<virtual-list>`, so it could only
pad itself down from the top: a list row is sized to its content and never
fills the viewport. It is now rendered in place of the list, as a
`flexGrow: 1` box that centres on both axes.
{visible.length === 0 ? (
<EmptyState view={view} />
) : (
<virtual-list estimatedItemHeight={48}>…</virtual-list>
)}
A starred row also painted a static star next to the hover strip, which
already carries a star button tinted with the accent. The static marker now
steps aside while the row is hovered.
Verified with live-app screenshots through the automation client. The five
`example-app` tests and `tsc` still pass.
Session: ses_fc149768bffeA9Ig3eeIhN2APN
`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
Oracle review of 7d45d36 plus a bug the review turned up.
**Remove `VirtualList`**
The windowed wrapper is gone from `@gpuix/react`. Windowing on the React side
is application state: pass `itemCount`, `estimatedItemHeight` and `windowStart`
to the host `<virtual-list>` and render only that slice.
A generic wrapper cannot know when to widen its own window. It moved `range`
only when the native list reported a new visible range, so growing `itemCount`
without a scroll left the extra rows unmounted forever. A filter does exactly
that. The todo example hit it immediately: switching from a 2-row view to a
6-row view painted 2 rows.
view-done ok
view-starred ok
view-inbox FAILED: "Ship it" never mounted
view-today ok
`VirtualList` was never published, so the pending
`.changeset/windowed-virtual-list.md` now describes the native `itemCount` plus
`windowStart` path instead. README and AGENTS.md carry a worked example and the
rule that no wrapper should come back.
**Review fixes**
`web:build` ran `bun run --cwd ../packages/native build:web`, a path that does
not exist once the folder is copied, while the README claimed nothing depended
on the repository. The script is gone. The browser renderer ships inside
`@gpuix/native`; `web.ts` explains the repository-only build in its failure
message.
`example-app/README.md` claimed `<virtual-list>` builds only visible rows. GPUI
builds only visible native rows, but React still mounts every child. Corrected.
`noImplicitAny: false` is dropped from the Quickstart tsconfig and from
`example-app/tsconfig.json`. The example passes under full `strict`, so the
starter should not hand out weaker checking.
The lockfile finding resolved itself: `examples/package.json` on main now
declares `cbor-x` and `msgpackr`, and `bun install` in a clean checkout of HEAD
produces no diff.
**Verified**
`packages/react` 302 tests pass. `example-app` typechecks under full strict,
lints clean, and all four filter views paint through the automation client.
Session: ses_fc1c928b0ffeeHIfdRpxtE0QaV
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