Working on gpuix-svelte

To build an app with the package, start from the README. The renderer's design, and the constraints any change has to respect, are written down in CLAUDE.md.

Setup

You need Node 24 or later (26.1 for demo:glass-ffi, and .nvmrc pins 26) or Bun 1.4 or later, on macOS (arm64), Windows (x64) or Linux (x64). Dependencies install with Bun, and its bun.lock is the lockfile, so bun install is the whole setup. @gpuix/native comes prebuilt from npm, and Svelte comes from vendor/svelte-<version>-<sha>.tgz, a build of the unreleased custom-renderer PR. Scripts run through npm run on either runtime.

git clone https://github.com/khromov/gpuix-svelte
cd gpuix-svelte
bun install

Demos and tests

npm run demo              # four demos at once: counter, tic-tac-toe, Hacker News, liquid glass
npm run demo:counter      # counter; edit examples/counter/Counter.svelte and save to hot-reload
npm run demo:tictactoe    # tic-tac-toe with score tracking
npm run demo:hn           # Hacker News reader (live data, scrollable list)
npm run demo:glass        # liquid-glass control center (GPUI's blurred translucent window)
npm run demo:glass-ffi    # the same app on real Liquid Glass: NSGlassEffectView via FFI, macOS 26+
                          # (older macOS gets the window blur, other platforms plain transparency)
npm run demo:styling      # styling playground: which CSS text reaches GPUI and which is dropped
npm run demo:desktop      # desktop APIs: window controls, file picker and drops, live image
                          # pixels, selection, focus walking, accessibility props
npm run demo:web          # tic-tac-toe in a browser, on GPUI's WebAssembly/WebGPU build (Bun only)
npm run tutorial          # interactive onboarding guide: 12 steps with live samples and quizzes
npm test                  # typecheck, then the headless renderer tests
npm run lint              # ESLint, which CI runs as a separate step

New here? npm run tutorial (or bun run tutorial) opens a guided walkthrough of the renderer that is itself a gpuix-svelte app. Each step pairs an explanation and a diagram with the source of a small component and that component running live, and ends with a quiz.

Every test script, and every demo apart from the Bun-only demo:web, has a Bun twin under a bun: prefix: npm run bun:test, npm run bun:demo, npm run bun:demo:counter, and so on. The twins run the same entry points on Bun, which takes the .svelte loader as a --preload. Dependencies come from bun install either way, and CI runs both runtimes.

Substrate, the "second brain" app that used to live in examples/second-brain, moved to its own repository on 2026-10-12. It consumes the published package there like any other app.

A demo that starts cleanly prints

[gpuix-svelte] created native window
[gpuix-svelte] mount complete

and GPUIX_SCREENSHOT=/tmp/x.png npm run demo:counter writes a PNG after every mount and remount. Use a single demo for that, since several would all write to the same file.

The demos run on all three platforms. The tests run on macOS and Windows, where gpuix builds TestGpuixRenderer. On Linux, CI typechecks, lints and checks that the binding loads.

npm run test:coverage mounts Svelte's own custom-renderer samples when SVELTE_SAMPLES_DIR points at a svelte checkout's packages/svelte/tests/custom-renderers/samples. Without it, the script skips.

Moving the Svelte build

npm run vendor looks up the custom-renderer PR's head commit, downloads that commit's build from pkg.svelte.dev into vendor/, repoints package.json and reinstalls. Run npm test and npm run bun:test afterwards. The PR moves on its own schedule, which is why each release of the package ships exactly the build it was tested against.

The consumer path

npm run consume packs the tarball the way a release would and scaffolds an app with packages/create-gpuix-svelte (whose template is starter/). It then installs the tarball into that app, typechecks it and runs its headless test through the installed bin. npm run bun:consume does the same with Bun and also builds the app's standalone binary. consume:open and bun:consume:open skip the test and start the app, so you can look at its window. CI runs both passes.

Releasing

PR titles must be conventional commits (feat:, fix:, docs:, chore: …), because a squash merge makes the title the commit. release-please keeps a release PR open on main. Merging it tags the release, runs the tests and the consumer path on macOS, and publishes gpuix-svelte and create-gpuix-svelte to npm through npm trusted publishing (OIDC, with provenance). npm run pack -- <dir> builds the same tarball locally.

Docs site

npm run docs renders the README and docs/*.md to static HTML in dist/docs, along with a markdown copy of each page, an llms.txt index and an llms-full.txt for coding agents. The docs workflow publishes it to GitHub Pages on every push to main that touches them. A new page needs an entry in PAGES in scripts/docs.ts.