Commit Graph
17 Commits
Author SHA1 Message Date
Ruben Nogueira f10a6c1b35 test(examples): run perf budgets against a production React and add a chat profiler 2026-10-10 17:49:07 +01:00
Ruben Nogueira ef1433952d test: benchmark a list that re-renders with nothing visibly changed 2026-10-10 17:01:08 +01:00
Tommy D. Rossi db3b9b4f05 Add Solid chat example, preload, and mount memory scripts
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
2026-09-21 16:48:21 +02:00
Tommy D. Rossi cbc3de0a12 Show the mail.tax example as the README hero.
The homepage README now opens on the Superhuman-style mail client
sitting on the desktop wallpaper, not the chat window. Alt text is
`mail.tax example`. The launch snippet is `bun --hot mail.tsx`.

The committed JPEG is a window capture composited onto the Tahoe
wallpaper so the lake shows around the rounded window. Full-screen
capture cannot see other apps on this machine.

Also land the mail example itself: three panes, thread list, reading
pane, and the Framer newsletter assets.

Session: ses_f92fa1015ffeSFgm8TqqfEZnVb
2026-09-04 17:29:25 +02:00
Tommy D. Rossi 3e3249bcab examples: add a native blurred window
Add a macOS frosted-glass dashboard that demonstrates GPUI's native blurred window backdrop, transparent titlebar, and translucent React surfaces.

Keep the root tint and glass cards low-opacity so the system vibrancy remains visible. Add a dedicated example command, document it in the examples table, and include a release changeset.

Validation:
- Type-check the example with TypeScript
- Run lintcn in examples
- Launch the native window and inspect a macOS window screenshot

Session: ses_fae3bcd26ffe4af01dEaLO6DG9
2026-08-30 10:34:04 +02:00
Tommy D. Rossi 322993eecf release: @gpuix/native@0.6.0 and @gpuix/react@0.6.0
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
2026-08-29 17:14:06 +02:00
Tommy D. Rossi 50e08b9bdf feat: add bidirectional infinite chat example
Export the public host instance type for typed element refs.

Add a bounded virtual-list chat example with cursor pagination in both directions, stable prepends, variable-height Safe MDX rows, route links, loading states, and retained-page limits.

Include focused coverage for initial rendering, route navigation, paging to both ends, stable retained rows, and request termination at history boundaries.

Session: ses_fbcc7a504ffe9Ff4lP0KDJLp3B
2026-08-27 15:04:48 +02:00
Tommy D. Rossi 813ece49e0 examples: a video-editor timeline
```bash
cd examples && bun --hot timeline.tsx
```

The question was whether GPUIX can carry a real editing surface, so the
example drags clips between tracks, trims both edges with snapping,
scrubs a playhead, marquee-selects, zooms under the pointer, and pans on
both axes with a frozen ruler and a frozen track column. Data is mocked
from a seeded generator, so every run paints the same project.

```
┌──────────────────────┬─────────────────────────────────────────────┐
│ tools    00:03:50    │ Ruler   0    5    10   15   20   25         │ ◄ pans X
├──────────────────────┼─────────────────────────────────────────────┤
│ Caption              │ ▓▓▓▓  ▓▓▓      ▓▓▓▓▓▓▓                      │
│ ▸ Track 10           │ █████████████ ▎playhead                     │ ◄ pans X and Y
│ ▸ Track 9            │        ▓▓▓▓   ▓▓                            │
│ Audio 1              │ ∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿                      │
│        ▲ pans Y      │                                             │
└──────────────────────┴─────────────────────────────────────────────┘
```

Two patterns in it are worth copying.

**React owns the scroll offset.** A native `overflow: "scroll"` grid
cannot drive a frozen header: GPUI moves the grid on the wheel frame,
and the `onScroll` callback that would move the ruler lands a frame
later, so the two tear apart during a fast pan. One `onScroll` listener
on a non-scrolling parent keeps `scrollX` and `scrollY` in state, and
all three panes translate from the same numbers. Zed does the same. A
media-bin panel beside the preview stays on the native two-axis path for
comparison.

Each translating wrapper sets `pointerEvents: "none"`, because a
positioned box takes hits with no fill and would otherwise swallow every
press meant for the ruler or the empty grid behind it.

**A drag needs no overlay.** Every clip, trim handle, the ruler, the grid
and the zoom slider carry `onMouseDown`, `onMouseMove` and `onMouseUp`,
which arms GPUI's pointer capture, so a release past the window edge
still ends the gesture. A full-window overlay mounted on the press cannot
do that: capture is armed by the press, and the overlay does not exist
yet at that moment. I built the overlay first and it failed exactly that
test.

`timeline.test.tsx` drives all of it through the automation API and
asserts exact numbers read from three `<text testId>` readouts, not
screenshots. `timeline.perf.test.tsx` times mount, pan and drag on a
3,259-clip, 26-track project:

| Sample | p95 |
|---|---|
| mount | 131ms |
| pan, culling on | 1.90ms |
| pan, culling off | 0.64ms |
| drag move | 11.7ms |

Drag samples are not comparable to pan samples.
`nativeSimulateMouseMove` flushes before and after the event, so each one
contains two complete GPUI paints.

Session: ses_fc1c6df4bffewVGSCAfO0JZ4nT
2026-08-26 16:40:21 +02:00
oxura a91b54ebe7 ci: separate hardware performance checks 2026-08-24 16:08:15 +06:00
Tommy D. Rossi b6173ab6b0 Animate the final Waku sidebar
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
2026-08-23 16:44:56 +02:00
Tommy D. Rossi d5afe9282c Compile the chat example into a standalone Bun binary
Add examples/compile-chat.ts and a `compile` script. The script:

- rasterizes the OpenAI-mark SVG to PNG with a ChatGPT-green
  background via rsvg-convert
- builds a multi-size .ico for Windows and an .icns for macOS
- runs Bun.build({ compile }) on chat.tsx
- on macOS wraps the binary in GPUIX Chat.app with Info.plist
  and AppIcon.icns so Finder and Dock show the custom icon
- on Windows passes hideConsole plus the .ico into Bun's
  compile.windows metadata

Bun compile stores imported files under `/$bunfs/`. GPUI reads
<svg src> with native fs, so those virtual paths fail at runtime.

chat.tsx now imports each icon with `{ type: 'file' }` so the
compiler embeds them, then realAssetPath() copies any `/$bunfs/`
path to a real temp file before passing it to GPUI. Dev mode
(`bun --hot`) still gets a normal filesystem path and skips the copy.

Session: ses_fd20989a0ffeGUKwl4QKO65ids
2026-08-23 11:32:56 +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 7688a5db75 Stress-test safe-mdx rendering in GPUIX
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
2026-08-22 16:46:53 +02:00
Tommy D. Rossi c7e8be643b Add a ChatGPT-style example, and screenshots in the README
examples/chat.tsx is a full ChatGPT-like app in ~900 lines: sidebar with
grouped history, scrolling transcript, composer, and <markdown>, <code>
and <diff> inside assistant turns. Data is hardcoded.

    cd examples && bun run chat

Two details it exists to demonstrate:

  Chrome sets userSelect: 'none'. The sidebar, top bar and composer opt
  out, so a drag across the transcript selects message text and never a
  conversation title. A test asserts both halves.

  <diff> needs a bounded viewport, because it virtualizes internally, and
  it carries its own borderRadius. GPUI clips a scroll container to its
  bounds RECTANGLE, never to a rounded path, so a rounded wrapper with
  overflow: hidden still shows square corners underneath.

examples/native-text.tsx is the smaller demo: the three components behind
a tab switcher, with a status line for events.

Screenshots
-----------
Test output lands in gitignored screenshots/ folders and churns on every
run, so only a curated set is committed, under docs/images. They are
regenerated with:

    bun scripts/screenshots.ts

which runs the visual tests and copies eight frames through a 256-colour
palette. That is lossless in practice for flat dark UI (no gradients to
band) and cuts the committed size from 976 KB to 352 KB.

The README now opens with the chat app, gains an Examples table, and shows
each native component next to the snippet that produces it.

AGENTS.md records the two rules that were expensive to learn: all text
must go through crate::text (never div().child(String), or it is invisible
to both selection and getPaintedText), and layout numbers belong on
Theme::metrics rather than in Rust constants.
2026-08-19 22:58:37 +02:00
Tommy D. Rossi 81622ae88a feat(examples): add diff viewer with syntax highlighting and visual tests
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
2026-03-02 17:28:04 +01:00
Tommy D. Rossi 5aef30824c feat: non-blocking NodePlatform + fix React reconciler commitUpdate signature
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
2026-02-28 18:53:32 +01:00
Tommy D. Rossi f06d627974 various fixes 2026-01-29 16:48:59 +01:00