mirror of
https://github.com/remorses/gpuix.git
synced 2026-10-29 03:04:52 +08:00
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
73 lines
3.0 KiB
TypeScript
73 lines
3.0 KiB
TypeScript
/**
|
|
* Regenerate the screenshots the README links to.
|
|
*
|
|
* Test output lands in gitignored `screenshots/` folders and churns on every
|
|
* run. Only the curated set below is committed, so the README never shows a
|
|
* stale frame and the repo never carries a binary diff per test run.
|
|
*
|
|
* bun scripts/screenshots.ts
|
|
*/
|
|
|
|
import { spawnSync } from 'node:child_process'
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
const OUT = path.join(ROOT, 'docs', 'images')
|
|
|
|
/** `[source screenshot, committed name]`, relative to the repo root. */
|
|
const CURATED: [string, string][] = [
|
|
['example-app/screenshots/todo.png', 'todo-app.png'],
|
|
['examples/screenshots/chat-top.png', 'chat-app.png'],
|
|
['packages/react/screenshots/showcase.png', 'showcase.png'],
|
|
['packages/react/screenshots/markdown-document.png', 'markdown.png'],
|
|
['packages/react/screenshots/code-typescript.png', 'code.png'],
|
|
['packages/react/screenshots/diff-multi-file.png', 'diff.png'],
|
|
['packages/react/screenshots/selection-after.png', 'selection.png'],
|
|
['packages/react/screenshots/metrics-roomy.png', 'metrics.png'],
|
|
]
|
|
|
|
function run(command: string, args: string[], cwd: string) {
|
|
console.log(`[screenshots] ${command} ${args.join(' ')}`)
|
|
const result = spawnSync(command, args, { cwd, stdio: 'inherit' })
|
|
if (result.status !== 0) {
|
|
console.error(`[screenshots] failed: ${command} ${args.join(' ')}`)
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
console.log('[screenshots] rendering visual tests')
|
|
run('bun', ['run', 'test', 'showcase'], path.join(ROOT, 'packages', 'react'))
|
|
run('bun', ['run', 'test', 'markdown'], path.join(ROOT, 'packages', 'react'))
|
|
run('bun', ['run', 'test', 'code'], path.join(ROOT, 'packages', 'react'))
|
|
run('bun', ['run', 'test', 'diff-native'], path.join(ROOT, 'packages', 'react'))
|
|
run('bun', ['run', 'test', 'chat'], path.join(ROOT, 'examples'))
|
|
// The todo example has no test suite. It captures itself through the
|
|
// automation client, with the motion clock paused.
|
|
run('bun', ['run', 'screenshot'], path.join(ROOT, 'example-app'))
|
|
|
|
fs.mkdirSync(OUT, { recursive: true })
|
|
for (const [from, name] of CURATED) {
|
|
const source = path.join(ROOT, from)
|
|
if (!fs.existsSync(source)) {
|
|
console.error(`[screenshots] missing ${from} — did its test run?`)
|
|
process.exit(1)
|
|
}
|
|
const target = path.join(OUT, name)
|
|
// A 256-colour palette is lossless in practice for flat dark UI (no
|
|
// gradients to band) and cuts the committed size by about two thirds.
|
|
const shrunk = spawnSync(
|
|
'magick',
|
|
[source, '-strip', '-colors', '256', '-define', 'png:compression-level=9', target],
|
|
{ stdio: 'inherit' }
|
|
)
|
|
if (shrunk.status !== 0) {
|
|
console.warn('[screenshots] magick unavailable, copying uncompressed')
|
|
fs.copyFileSync(source, target)
|
|
}
|
|
const kb = Math.round(fs.statSync(target).size / 1024)
|
|
console.log(`[screenshots] ${name} (${kb} KB)`)
|
|
}
|
|
console.log(`[screenshots] wrote ${CURATED.length} files to docs/images`)
|