Commit Graph
12 Commits
Author SHA1 Message Date
Tommy D. Rossi b3c2823e01 feat(disktree): publish disktree as its own npm package
`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
2026-09-27 17:18:30 +02:00
Tommy D. Rossiandjuan 919a736e72 Add the first-party Solid renderer
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>
2026-09-21 10:40:12 +02:00
Tommy D. Rossi 6e75327cdc Add the GPUIX project scaffolder
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
2026-08-28 10:51:43 +02:00
Tommy D. Rossi 7d6e1f97ed Serve the browser example through Bun's dev server with React Fast Refresh
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
2026-08-26 17:59:44 +02:00
Tommy D. Rossi 7d45d3681a Make GPUIX installable: quickstart, todo example app, and a key that typechecks
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
2026-08-26 15:55:10 +02:00
Tommy D. Rossi bcfcaa92f8 Add GPUI browser rendering
Compile the retained GPUIX renderer for wasm32-unknown-unknown and expose the React mutation interface through wasm-bindgen while keeping napi-rs as the desktop host.

Publish the browser loader and Wasm asset, add the ChatGPT browser example and isolated development server, and prepare the release workflow with the required Rust and wasm-bindgen toolchains.

Use GPUI Web bundled IBM Plex Sans and Lilex fonts, add portable raw SVG source rendering, and embed the chat icons so the browser canvas matches the native layout.

Validation: 86 Rust tests, 12 example tests, 3 native image/SVG tests, React builds and package lints, Bun package dry-run, and Playwriter WebGPU visual checks.

Session: ses_fd2303a27ffeNl7JAmV7905R7z
2026-08-24 19:29:14 +02:00
Tommy D. Rossi 9c9deba259 Add the Apache-2.0 LICENSE that GPUIX already claimed in the README.
GPUI is Apache-2.0. The repo README and native Cargo.toml already said
Apache-2.0, but there was no LICENSE file. THIRD_PARTY_NOTICES pointed at
LICENSE. npm packages had no license field, so published tarballs did not
declare a license.

This commit:

- adds the official Apache-2.0 text at the repo root
- copies the same file into @gpuix/native and @gpuix/react so npm packs it
- sets license: Apache-2.0 on the root and both public packages
- points native Cargo.toml at the package LICENSE file
- links the README license section to LICENSE
- adds a patch changeset for both public packages

Copyright in the appendix is 2026 remorses.

Session: ses_fcccb94cdffeQmDC8ZoOilsTaY
2026-08-24 12:16:01 +02:00
Tommy D. Rossi 7f16f8e1ad Switch examples to render() and bun --hot
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
2026-08-23 10:44:48 +02:00
Tommy D. Rossi 1d2b470936 Add bun run dev, a watch loop for the Rust side
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.
2026-08-19 22:58:22 +02:00
Tommy D. Rossi f06d627974 various fixes 2026-01-29 16:48:59 +01:00
Tommy D. Rossi e92f161e62 agents.md 2026-01-29 16:00:00 +01:00
Tommy D. Rossi b0c7b18f3a init 2026-01-29 15:29:25 +01:00