Commit Graph
29 Commits
Author SHA1 Message Date
Tommy D. Rossi 89b67995b2 release: @gpuix/native@0.8.0 and @gpuix/react@0.8.0
Consume the pending changesets into a minor release. Users get GPUI accessibility through role and aria-* props, Base UI Select wrapping with asChild, Finder file drops, GitHub Releases auto-update, Wayland layer-shell windows, http(s) img sources, textDecoration, textarea newlines, font-sized input rows, a runtime error overlay that keeps the macOS window alive, and several desktop and browser reliability fixes.

Bump the native Cargo crate, native npm package, React package, generated napi loader version checks, and Bun workspace lock to 0.8.0. Keep the published React dependency on @gpuix/native as workspace:^. CI is the only publish path for native platform binaries.

Fixes #37
Fixes #38
Fixes #39
Fixes #41
Fixes #46
Fixes #47
Fixes #59
Fixes #63

Session: ses_f5effe0ccffeXHPxxwP7cFDI4h
2026-09-14 19:46:44 +02:00
Tommy D. Rossi a24b4a42eb release: @gpuix/native@0.7.0 and @gpuix/react@0.7.0
Consume the seven pending changesets into a minor release. Users get native two-stop gradients, in-memory image data URLs, application-owned Tab handling, renderer-level keyboard callbacks, consistent test-renderer exports, correct Windows DPI scaling, clean last-window exit on Windows and Linux, and a frosted-glass window example.

Bump the native Cargo crate, native npm package, React package, generated napi loader version checks, and Bun workspace lock to 0.7.0. Keep the published React dependency on @gpuix/native as ^0.7.0 through workspace:^ resolution.

The release validation covers 221 Rust tests, 370 React renderer tests, 47 example tests, the starter app typecheck, CLI tests and build, package lint, release native and React builds, and both native and React tarball contents.

Fixes #30
Fixes #31
Fixes #32
Fixes #35
Fixes #36

Session: ses_fa2dcb95bffediF7Xu1SKakouC
2026-09-01 15:31:06 +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 301b834df4 release: @gpuix/native@0.5.1 and @gpuix/react@0.5.1
Fix `hasNativeTestRenderer` always reporting false for anyone who
installs the package, found by copying `example-app/` out of the
repository and installing `@gpuix/react` from npm:

    Test Files  1 skipped (1)
         Tests  6 skipped (6)

`testing.js` ships as ESM and loaded the addon with a bare
`require("@gpuix/native")`. Node has no `require` in ESM. Inside this
repository vitest inlines the workspace package and provides one, so
every suite here passed; installed from npm the package is externalized
and run by Node, the call threw `require is not defined`, and the catch
reported native as missing. Every guarded suite then skipped silently,
which is worse than failing.

It loads through `createRequire(import.meta.url)` now. Verified by
dropping the built file over the installed 0.5.0 package in that copy:
the 6 tests run, and `bun run screenshot` drives the real window.

Session: ses_fc0b7cd62ffe3aBw0pyYBOBDjf
2026-08-26 21:37:30 +02:00
Tommy D. Rossi f93e891396 native: release the test app's entity handles before the app drops
Every React test file passed on Windows and the vitest worker then died
with "Worker exited unexpectedly". The real message was underneath:

    panicked at zed/crates/gpui/src/app/entity_map.rs:1116
    Exited with leaked handles:
    Leaked handle for entity gpuix_native::...::TextEditorState

The test build turns on gpui's leak detector, which panics if an
`Entity` handle is still alive when the `App` drops. `<input>` keeps an
`Entity<TextEditorState>` in the view's custom element registry, and
that registry is only emptied when React destroys the element, so a
suite that just ends leaves handles behind. macOS never runs the
thread-local destructor at process exit, so the panic was invisible
until Windows ran the suite.

`VisualTestState` now destroys every custom element instance, and drops
the focus handles and subscriptions, from its own `Drop`. That runs
before its fields, so the `App` is still there to update the view.

Session: ses_fc0b7cd62ffe3aBw0pyYBOBDjf
2026-08-26 20:50:07 +02:00
Tommy D. Rossi ac0426c857 release: @gpuix/native@0.5.0 and @gpuix/react@0.5.0
Consume 52 pending changesets into CHANGELOG.md and bump both public
packages from 0.4.0 to 0.5.0. Minor, because users get several new
capabilities:

- GPUIX apps render in the browser through GPUI's web platform, with
  React Fast Refresh under `bun run web`
- a `highlight` prop plus `useTextSearch` and `findRanges`, so a find
  bar works across <text>, <code>, <markdown> and <diff>
- <virtual-list> can mount a window of rows (itemCount, windowStart,
  estimatedItemHeight) instead of all of them
- automation drags, hovers, wheels and holds modifiers, and reaches
  <input> and <textarea>
- a macOS menu bar, so a GPUIX app answers Cmd+Q, Cmd+H, Cmd+M, Cmd+W
- HTML-like pointer capture, full CSS cursors and colour functions,
  onAuxClick, window inset hooks, per-side borders and boxShadow
- a Windows test renderer and a sizable createTestRoot()

Two API changes need a migration note, both in the changelog:
<code> lost `showHeader` and is now a bare surface, and each React
root owns its handler map so `resetIdCounter()` is gone and
`handleGpuixEvent` takes the renderer.

Also pin the generated napi loader version checks to 0.5.0.

Local publish stays blocked. CI is the only release path because
native needs per-platform binaries.

Fixes #1
Closes #2
Fixes #20

Session: ses_fc0b7cd62ffe3aBw0pyYBOBDjf
2026-08-26 20:21:49 +02:00
Tommy D. Rossi aedc570e21 release: @gpuix/native@0.4.0 and @gpuix/react@0.4.0
Consume pending changesets into CHANGELOG.md and bump both
public packages from 0.3.0 to 0.4.0. This is a minor because
users get two new capabilities:

- motion.div native animations (Rust interpolates frames)
- Playwright-like automation API (connectTest, launch, locators)

Also pin the generated napi loader version checks to 0.4.0.

Local publish stays blocked. CI is the only release path
because native needs per-platform binaries.

Session: ses_fd0d6b222ffeQygDL9SHWVoXsE
2026-08-23 17:10:11 +02:00
Tommy D. Rossi d449d3bbd2 release: @gpuix/native@0.3.0 and @gpuix/react@0.3.0
Consume pending changesets into CHANGELOG.md and bump both
public packages from 0.2.0 to 0.3.0. This is a minor because
users get new layout, new window chrome, and new APIs:

- CSS grid on div (gridTemplateColumns, gridColumnMin)
- titlebarTransparent, windowBackground, traffic-light position
- <diff> flows by default, plus maxLines / onShowMore
- debugFrameOverlay draw-time overlay

Also ships scroll, overlay, remount, and input fixes from
the pending changesets.

Local publish stays blocked. CI is the only release path
because native needs per-platform binaries.

Session: ses_fd1303003ffeHgTR9sNvkGcWhU
2026-08-23 15:33:10 +02:00
Tommy D. Rossi 1547e53674 release: @gpuix/native@0.2.0 and @gpuix/react@0.2.0
Consume pending changesets into CHANGELOG.md and bump both
public packages from 0.1.0 to 0.2.0. This is a minor because
users get new elements, new APIs, and new capabilities:

- selectable text plus native <code>, <diff>, and <markdown>
- native <input> / <textarea>
- render() remount for bun --hot
- headless Select, Combobox, and Tooltip
- <virtual-list>, tintable <svg>, startFrameLoop()
- native GPUI platform on macOS, Windows, and Linux

CI now publishes @gpuix/react with bun publish so the
workspace:^ dependency is rewritten to ^0.2.0. The 0.1.0
react tarball on npm still has the literal workspace protocol.

Local publish stays blocked. CI is the only release path
because native needs per-platform binaries.

Session: ses_fd22a9f4fffeR2RGgyaXgLFdgW
2026-08-23 10:58:54 +02:00
Tommy D. Rossi c8d1ba3c95 Add hover/active pseudo-selector style support
GPUI natively supports hover/active pseudo-selectors via .hover() and
.active() methods on InteractiveElement. These apply a StyleRefinement
when the mouse hovers over or presses an element — entirely GPU-side,
zero JS round-trips.

This commit exposes that API through the GPUIX style system:

```tsx
<div style={{
  backgroundColor: '#313244',
  cursor: 'pointer',
  hover: { backgroundColor: '#45475a', opacity: 0.9 },
  active: { backgroundColor: '#585b70' },
}} />
```

## Rust changes

- **style.rs**: Added `hover: Option<Box<StyleDesc>>` and
  `active: Option<Box<StyleDesc>>` to StyleDesc. Box is required
  because the struct is recursive. Serde deserializes them from the
  nested JSON objects naturally.

- **renderer.rs**: In build_div(), after apply_styles(el, style),
  calls GPUI's native .hover() and .active() with the sub-styles.
  This works because apply_styles() is generic over E: Styled, and
  StyleRefinement implements Styled — so the same function that applies
  base styles also applies hover/active refinements.

## TypeScript changes

- **host.ts**: Added hover? and active? to StyleDesc typed as
  Omit<StyleDesc, 'hover' | 'active'> to prevent infinite nesting
  (GPUI only supports one level of pseudo-selectors).

## Tests (8 new)

- Hover renders without crash
- Hover with only color change
- Empty hover object is harmless
- **Visual hover transition**: captures before/after screenshots with
  nativeSimulateMouseMove — asserts PNG bytes differ, proving GPUI's
  native hover refinement actually changes rendered output
- Active renders without crash
- Combined hover + active on same element
- Hover + active alongside onClick handler
- All 111 tests pass
2026-03-02 18:35:39 +01:00
Tommy D. Rossi e136bd42d5 ci: add GitHub Actions pipeline for building and publishing to npm
Build matrix:
- macOS arm64 (macos-latest) — Metal, with test-support
- macOS x64 (macos-13) — Metal, with test-support
- Linux x64 (ubuntu-latest) — Vulkan/wgpu, no test-support
- Linux arm64 (ubuntu-24.04-arm) — Vulkan/wgpu, no test-support

Test job runs 105 tests on macOS arm64 (needs Metal for GPU-backed
TestGpuixRenderer).

Publish is version-gated: checks if the package.json version already
exists on npm, skips if so. Bump version + push to main to release.
Publishes both @gpuix/native (per-platform binaries via napi pre-publish)
and @gpuix/react (pure TypeScript).

Other changes:
- Add build:release script (no test-support) for Linux CI
- Document Cargo feature gate in Cargo.toml comments
- @gpuix/react: workspace:* → workspace:^ for publishing
- @gpuix/react: add publishConfig
2026-03-02 17:53:16 +01:00
Tommy D. Rossi 716a8756c4 feat(native): migrate napi-rs from v2 to v3
Prerequisite for CI/CD and per-platform npm publishing.

## package.json

- `@napi-rs/cli`: `^2.18.0` → `^3.1.3`
- napi config: v2 `name` + `triples` → v3 `binaryName` + `targets` array
- Added Linux targets: `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`
- `prepublishOnly`: `napi prepublish` → `napi pre-publish` (v3 hyphenated)
- Added `publishConfig` with registry + public access

## Cargo.toml

- `napi` crate: `2` → `3` (features `napi8`, `serde-json` unchanged)
- `napi-derive`: `2` → `3`
- `napi-build`: stays at `2` (stable API, no changes needed)

## Rust source (renderer.rs)

One API change required: `ThreadsafeFunction` in napi v3 is `!Clone`.
Wrapped the field in `Arc<ThreadsafeFunction<EventPayload>>` so it can
be shared into the GpuixView closure from `&self` methods. Constructor
`Arc::new`-wraps the incoming TSF, `init()` clones the Arc.

## Auto-generated files

- `index.js` now uses v3's `requireNative()` function pattern
- `index.d.ts` now includes JSDoc comments from Rust doc comments

All 105 tests pass.
2026-03-02 17:36:32 +01: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 5cc17ed499 feat(styles): add whiteSpace, textOverflow, and lineClamp support
Wire up 3 text-related style properties that exist in GPUI's Styled trait
but were not exposed through GPUIX:

**whiteSpace** — `"nowrap"` disables text wrapping (single line),
`"normal"` enables wrapping (default). Applied in both `apply_styles()`
and `build_text()` via GPUI's `.whitespace_nowrap()` /
`.whitespace_normal()`. Critical for diff viewer code lines that must
not wrap.

**textOverflow** — `"ellipsis"` truncates long text with "..." at
the end, `"ellipsis-start"` truncates from the start (showing the
end of the text). Applied via GPUI's `.text_ellipsis()` /
`.text_ellipsis_start()`. Useful for file path breadcrumbs and long
identifiers.

**lineClamp** — limits visible text to N lines. Values < 1 are ignored
(no clamping). Applied via GPUI's `.line_clamp(n)` which internally
sets overflow hidden. Useful for preview cards and summaries.

All three are applied in both `apply_styles()` (for div elements) and
`build_text()` (for text elements), so they work via inheritance from
parent divs and directly on text nodes.

**README update**: document all text style properties and add a note
about `white-space: pre` not being supported in GPUI, with a workaround
pattern (split on newlines, render each line as a separate <text> with
nowrap in a flex column).

**Tests**: 13 new tests (22 total in styles.test.tsx) with screenshots:
- whiteSpace: nowrap, normal, visual comparison, div-level inheritance
- textOverflow: ellipsis end/start, visual comparison, short text edge case
- lineClamp: 1/2/3 lines, div-level, visual comparison, zero edge case
- Composite: pre-like behavior (split lines + monospace + nowrap)

All 104 tests pass across the full suite.

Files changed:
- packages/native/src/renderer.rs — +30 lines in apply_styles() and build_text()
- packages/native/src/style.rs — +3 fields
- packages/react/src/types/host.ts — +3 TS fields
- packages/react/src/__tests__/styles.test.tsx — +581 lines (13 new tests)
- README.md — text styles docs + pre workaround note
2026-03-02 16:57:32 +01:00
Tommy D. Rossi 737d5ce0ac feat(styles): add alignSelf, flexGrow/flexShrink values, fontFamily, fontWeight, text backgroundColor, flexWrap
Wire up 6 style properties needed for building a diff viewer component:

**alignSelf** — field existed in StyleDesc but was never applied in apply_styles().
Uses direct `el.style().align_self` field access since GPUI's Styled trait has no
convenience methods for align_self. Supports center, start, end, stretch, baseline.

**flexGrow / flexShrink numeric values** — previously both always hardcoded to 1.0
regardless of the actual value passed from JS. `flexShrink: 0` and `flexGrow: 2`
now work correctly via direct `el.style().flex_grow = Some(value)` field access.

**fontFamily** — new field in StyleDesc (Rust + TS). Applied in both apply_styles()
and build_text() via GPUI's `.font_family()` method. Uses `.clone()` instead of
`.as_str()` because SharedString requires 'static lifetime.

**fontWeight** — field existed in StyleDesc but was never applied. Extracted a
`parse_font_weight()` helper that handles CSS named keywords (case-insensitive,
with hyphenated variants like extra-bold, semi-bold) and numeric strings.
Applied in both apply_styles() and build_text().

**text backgroundColor** — build_text() now applies `.bg()` on the wrapping div,
enabling word-level diff highlighting on individual <text> tokens.

**flexWrap** — field existed in StyleDesc but was never applied. Maps wrap,
wrap-reverse, nowrap to the corresponding GPUI methods.

Tests: 9 new end-to-end tests in styles.test.tsx covering all features with
Metal GPU screenshots. All 91 tests pass (82 existing + 9 new).

Files changed:
- packages/native/src/renderer.rs — apply_styles(), build_text(), parse_font_weight()
- packages/native/src/style.rs — font_family field
- packages/react/src/types/host.ts — fontFamily TS type
- packages/react/src/__tests__/styles.test.tsx — new test file
2026-03-02 16:50:33 +01:00
Tommy D. Rossi 06821f93a9 docs(scroll): add ref-based scroll API example, remove dead id prop, add ref test
Verified that React refs work through our reconciler — getPublicInstance()
returns the Instance directly, so ref.current.id gives the numeric element
ID needed for scrollTo/scrollToItem/getScrollOffset.

Changes:
- Remove id?: string from Props — it was never forwarded to Rust or used
  anywhere. Element IDs are auto-generated internally. Users access them
  via React refs (ref.current.id).
- Add test: useRef captures element, ref.current.id is a positive number,
  scrollTo via that ID works correctly.
- Add README section: overflow scroll example, per-axis scrolling, and
  full programmatic scroll pattern using refs + renderer methods.
2026-03-02 14:54:22 +01:00
Tommy D. Rossi 2917cccb79 feat(scroll): add native scrollable containers with programmatic scroll API
Add full GPUI scrolling support to GPUIX. Elements with overflow: 'scroll'
(or per-axis overflowX/overflowY) now create native GPU-accelerated scrollable
containers — GPUI handles scroll physics, offset persistence, content clipping,
and scroll wheel event interception automatically.

## How it works

```
React: <div style={{ overflow: 'scroll', height: 200 }}>
         <div style={{ height: 1000 }}>Tall content</div>
       </div>
         │
         ▼ setStyle mutation
Rust build_div():
  div().id('__gpuix_N')
       .overflow_scroll()        ← sets Overflow::Scroll on x/y
       .track_scroll(&handle)    ← persistent ScrollHandle
         │
         ▼ GPUI internals
  - Layout: Taffy computes child sizes, content overflows
  - Prepaint: retrieves scroll_offset from ScrollHandle
  - Paint: children offset by scroll_offset, scroll wheel listener installed
  - Clamp: offset clamped to [0, max_scroll]
```

## Rust changes (renderer.rs)

- GpuixView gains `scroll_handles: HashMap<u64, ScrollHandle>` — persistent
  handles keyed by element ID, same lifecycle pattern as focus_handles
- build_div() checks overflow/overflowX/overflowY style for 'scroll' and calls
  overflow_scroll() / overflow_x_scroll() / overflow_y_scroll() on the
  Stateful<Div> (these require StatefulInteractiveElement, not just Styled)
- track_scroll(&handle) wired for each scrollable element so GPUI maintains
  the scroll offset across frames
- SCROLL_HANDLES thread_local synced after each render() so napi methods can
  access ScrollHandle without an App context
- Per-axis overflow hidden added: overflowX/overflowY 'hidden' now maps to
  overflow_x_hidden() / overflow_y_hidden()

## Programmatic scroll API (napi)

Both GpuixRenderer and TestGpuixRenderer expose:
- scrollTo(elementId, x, y) — set offset directly
- scrollToItem(elementId, index) — scroll child into view by index
- getScrollOffset(elementId) → [x, y] | null — read current offset

## Tests (6 new, 80 total)

- Basic scroll: overflow scroll container, simulate wheel, verify offset changed
- Overflow-y only: per-axis scroll
- Programmatic scrollTo: set offset, verify it sticks
- Programmatic scrollToItem: scroll to 4th child by index
- Screenshot regression: before/after scroll produces different pixels
- Event + scroll combo: onScroll fires AND content scrolls simultaneously
2026-03-02 14:46:00 +01:00
Tommy D. Rossi e4a982f930 refactor(testing): remove JS shadow tree, delegate all state to Rust RetainedTree
TestRenderer previously maintained a parallel Map<number, TestElement>
in JavaScript that mirrored every mutation also sent to Rust's
RetainedTree. This was redundant — tests were reading state from the
JS copy while events went through Rust, creating a split source of truth.

Now all element state lives exclusively in Rust's RetainedTree, queried
via napi methods. The data flow is unified:

  React reconciler
         │
         └──→ Rust RetainedTree (via napi)
                      │
                      ├── mutations (createElement, setStyle, etc.)
                      ├── inspection (getAllText, getTreeJson, findByType)
                      └── simulation (simulateClick, simulateKeystrokes)

Changes:

- testing.ts: Remove ~220 lines of JS shadow tree code. Mutation methods
  are now one-line delegations to native. Inspection methods (findByType,
  getAllText, toJSON, getRoot, getElement, findByText) query Rust via
  getTreeJson() napi call and build TestElement objects on demand.
  Constructor now requires native renderer (throws if unavailable).

- test_renderer.rs: Add getRootId() napi method for root element queries.
  Add customProps to element_to_json() output so test inspection sees
  custom element props (used by img/input tests).

- index.d.ts: Auto-regenerated by napi build (adds getRootId signature).

- host.ts: Update interface comments to reflect new architecture.

Zero test file changes needed — TestElement interface and all method
signatures remain identical. All 68 tests pass.
2026-03-01 21:43:30 +01:00
Tommy D. Rossi c21786951d feat(batch): batch all React reconciler mutations into single applyBatch() FFI call
Reduces N individual napi FFI boundary crossings per React commit to 1.
A typical render with 20 elements went from ~75 separate FFI calls to 1.

## Batch format

String-named JSON tuples sent as a single applyBatch(json) call:

    [["createElement",1,"div"],["setStyle",1,"{...}"],["appendChild",0,1]]

Each tuple is ["methodName", ...args] — method names match the
NativeRenderer interface exactly.

## Rust side

- apply_batch() on both GpuixRenderer and TestGpuixRenderer
- Atomic two-phase processing via shared apply_batch_to_tree():
  Phase 1: parse_batch_ops() validates ALL ops into typed BatchOp enum
  Phase 2: apply validated ops to RetainedTree
  If any op is malformed, tree is left unchanged (no partial application)
- Single mutex lock for entire batch vs per-op locking before

## JS side — DRY via Proxy + dynamic dispatch

- BatchingRenderer (batch-renderer.ts): JS Proxy auto-captures any
  NativeRenderer method call as [name, ...args]. Adding new methods
  to NativeRenderer requires ZERO changes to the batching layer.
- TestRenderer.applyBatch: forwards to native.applyBatch() once, then
  replays locally via dynamic dispatch with _skipNative flag. Also
  requires ZERO changes when adding new methods.
- wrapWithBatching() wired into both createRoot() and createTestRoot()
- Backward compatible: individual mutation methods remain, batching
  is opt-in via applyBatch presence check

## Safety

- Queue cleared AFTER successful batch (preserved on failure)
- Event handler cleanup runs before commitMutations (coherent on throw)
- TestRenderer local replay wrapped in try/catch (no duplicate re-apply)
- setEventListener strict validation (errors on missing hasHandler arg)

All 68 existing tests pass through the batched path.
2026-03-01 21:23:10 +01:00
Tommy D. Rossi 3334adc2f9 feat(img): add native image custom element with visual regression coverage
This introduces image rendering through the custom-element pipeline and validates that image source updates produce visible GPU output in end-to-end tests.

- add native img custom element that maps src and objectFit props to gpui image rendering from local file paths\n- register image factory in default custom element registry\n- add typed ImgProps in React host types and wire img intrinsic types in jsx runtime and jsx dev runtime declarations\n- add img test suite with prop-forwarding assertion and screenshot before-after regression using deterministic SVG fixture\n- update changelog with the new image support and test coverage
2026-03-01 20:42:01 +01:00
Tommy D. Rossi ebdc105607 feat(anchored): add deferred anchored overlay primitive for popovers/dialogs
This introduces a first-class anchored overlay element so GPUIX can model command-palette/autocomplete-style UI with window-coordinate positioning and deferred top-layer rendering.

- add native <anchored> custom element backed by gpui::anchored() with optional gpui::deferred(...).with_priority(...) layering\n- support anchor/position/snap/deferred props (, , , , , , , )\n- extend CustomRenderContext to carry built child elements and update custom render trait to consume owned context\n- register AnchoredFactory in default custom element registry\n- expose  in React component map and JSX/runtime type surfaces with AnchoredProps\n- add end-to-end anchored overlay test covering open, inside click persistence, and outside-click close\n- document the capability in CHANGELOG
2026-03-01 19:57:58 +01:00
Tommy D. Rossi ea16726e75 feat(dialog): support absolute overlay positioning and add visual dialog coverage
This adds the minimum native positioning primitives needed for tooltip-like dialogs and verifies the interaction/render behavior through GPU-backed end-to-end tests.

- map style.position to GPUI relative/absolute in apply_styles\n- map top/right/bottom/left offsets so overlay elements can be placed out of flow\n- add a dialog overlay interaction test (open on button click, stay open on inside click, close on onMouseDownOutside)\n- add screenshot regression coverage that captures before/after images and asserts visual output changes when opening the dialog\n- document the change in CHANGELOG with UTC entries
2026-03-01 19:49:18 +01:00
Tommy D. Rossi f8e26c6fe1 fix(custom-elements): close lifecycle and prop-sync gaps in trait dispatch
This hardens the polymorphic custom element path so custom instances behave like built-ins across mount/update/unmount cycles.

- destroy stale custom instances when IDs disappear from the retained tree, and clear custom instances if an ID now maps to built-in div/text\n- add supported_props() and reset missing known props to null each frame to prevent stale state (for example readOnly persisting after prop removal)\n- thread retained style into CustomRenderContext and apply it in input render so style-driven layout/hit testing works for custom elements\n- filter custom event wiring to supported_events() only\n- make custom prop forwarding resilient to JSON serialization failures in host-config by falling back to null\n- add end-to-end regressions for readOnly removal and style width hit-testing\n- update changelog entry for these fixes
2026-03-01 19:11:02 +01:00
Tommy D. Rossi 8745acaa15 feat: add polymorphic custom element trait infrastructure with <input> as first implementation
## Architecture

Add a trait-based plugin system for custom native GPUI elements that can be
used as React intrinsic elements. The renderer dispatches to trait objects at
render time — each custom element lives in its own file with its own
dependencies, cleanly separated from the core renderer.

```
build_element()
  "div"  → build_div()             (built-in)
  "text" → build_text()            (built-in)
  _      → registry.render(ctx)    (trait dispatch)
```

## Rust side

- `custom_elements/mod.rs`: `CustomElement` trait, `CustomElementFactory`
  trait, `CustomElementRegistry` struct, `CustomRenderContext` (carries
  element ID, events, callback, focus handle)
- `custom_elements/input.rs`: `InputElement` + `InputFactory` — controlled
  text input with value/placeholder/readOnly props, renders as a styled div
  with border, handles keyDown/keyUp/click/focus/blur events
- `renderer.rs`: polymorphic dispatch in `build_element()` fallthrough arm,
  `setCustomProp`/`getCustomProp` napi methods, `CustomElementRegistry` on
  `GpuixView`, updated signatures for `build_div`/`build_text` to thread
  registry + window + cx through recursive calls
- `retained_tree.rs`: `custom_props: HashMap<String, Value>` on
  `RetainedElement`, with set/get methods that handle null as removal
- `test_renderer.rs`: same napi methods + registry init

## TypeScript side

- `host-config.ts`: `syncCustomProps()` / `diffCustomProps()` — auto-forward
  non-style, non-event props as JSON to Rust for any non-div/text element
- `types/host.ts`: `setCustomProp` on `NativeRenderer`, `InputProps` type
- `testing.ts`: `setCustomProp` forwarding to native + local map storage
- JSX types: `input` added to IntrinsicElements in both runtime .d.ts files
- `components/index.ts`: `input` added to component registry

## Tests (6 new, 27 total — all pass)

- Rendering: input with value prop, placeholder when empty
- Keyboard: controlled component typing via nativeSimulateKeystrokes,
  backspace handling — full React state round-trip
- Screenshots: before/after PNG capture through real Metal GPU pipeline,
  validates placeholder → typed text visual change
- Tree structure: verifies custom type appears in element tree JSON

## Bug fix

- Fix jsx-dev-runtime.js: React 19 only exports `jsxDEV` from the dev
  runtime (not `jsx`/`jsxs`). Re-export `jsxDEV` directly so vitest's JSX
  transform works. This was breaking ALL tests, not just the new ones.
2026-03-01 18:37:11 +01:00
Tommy D. Rossi 161ed6b900 docs: rewrite README to reflect mutation-based architecture
The README was describing the old JSON tree serialization approach.
The actual codebase uses a mutation-based protocol where React's
reconciler makes direct napi FFI calls (createElement, appendChild,
setStyle, commitMutations, etc.) to a Rust RetainedTree.

Changes:
- Replace 'description-based renderer / JSON element tree' language
  with 'mutation-based protocol over napi-rs FFI'
- Architecture diagram now shows individual napi calls flowing to Rust
- Add Mutation API section documenting the NativeRenderer interface
- Add Event Flow section with full pipeline diagram:
  GPUI → Rust emit_event_full → ThreadsafeFunction → JS registry → React
- Expand events from a plain list to a table with payload fields
- Add onMouseDownOutside, onScroll (delta/phase), keyboard, focus/blur
- Add Testing section covering TestGpuixRenderer (GPU-backed Metal,
  native event simulation, screenshot capture)
- Update status checklist: keyboard, focus, scroll, test renderer done
- Update usage example to use createRenderer() and tick() loop
- Mark img/svg/canvas as planned in elements table
2026-03-01 18:10:46 +01:00
Tommy D. Rossi 51a73abdcb test(visual): center screenshot probes and improve composition quality
The visual screenshots were technically valid but looked top-left anchored and visually sparse, which made them less useful for manual inspection and demos. This update centers the probe cards in full-window layouts and applies a clearer visual hierarchy so interaction diffs are easier to read at a glance.

Changes in this commit:
- move click and hover simulation coordinates to the centered card region
- redesign click, keyDown, and hover probes with rounded cards, balanced spacing, and higher-contrast text styles
- preserve strict screenshot assertions (non-empty files and before/after PNG differences)
- record the visual test polish in the changelog

Validation performed:
- bun test src/__tests__/events.test.tsx -t screenshot
- bun test src/__tests__/events.test.tsx
2026-03-01 16:38:10 +01:00
Tommy D. Rossi 23ba8e5407 fix(test-renderer): restore text glyphs in visual screenshots and broaden visual event coverage
The visual screenshot pipeline was rendering backgrounds and shapes but dropping text, which made screenshot-based validation misleading. The root cause was that the macOS visual test path could fall back to `NoopTextSystem` when `gpui_macos/font-kit` was not enabled in the native test feature set.

This change enables `gpui_macos/font-kit` under `test-support`, ensuring `VisualTestAppContext` uses `MacTextSystem` and rasterizes glyphs during `capture_screenshot()`.

It also expands screenshot assertions to cover multiple interaction classes:
- click-driven visual state changes
- keyDown-driven visual state changes
- hover (mouseEnter) visual state changes

Additional hardening included:
- forcing `window.refresh()` before screenshot capture to avoid stale frame reads
- a shared screenshot assertion helper in tests to require non-empty PNG output and before/after image differences
- changelog updates documenting the renderer and visual test improvements

Validation:
- `bun run build` (native napi module)
- `bun test src/__tests__/events.test.tsx` (21/21 passing)
- `bun test src/__tests__/events.test.tsx -t screenshot` (3/3 screenshot tests passing)
2026-03-01 16:30:09 +01:00
Tommy D. Rossi 7106344990 docs: update changelog with visual screenshot plan entry 2026-03-01 14:06:51 +01:00
Tommy D. Rossi f55c47a416 add auto-generated files notice and changelog requirement to AGENTS.md
- Document that packages/native/index.d.ts, index.js, and *.node are
  auto-generated by napi-rs during `bun run build` and must not be
  edited manually. Edit Rust #[napi] annotations instead.

- Add changelog requirement: update CHANGELOG.md at repo root after
  every change, reverse-chronological order, YYYY-MM-DD HH:MM UTC.

- Create initial CHANGELOG.md with entries for all changes in this
  session (native event test migration, Oracle fixes, AGENTS.md updates).
2026-03-01 13:26:42 +01:00