react-native-gtkx 0.4.0-alpha.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/apis/host.gtkx.js.map +1 -1
  2. package/dist/components/modal.d.ts +1 -1
  3. package/dist/components/modal.js +16 -8
  4. package/dist/components/modal.js.map +1 -1
  5. package/dist/gtkx/bridge/adw.js.map +1 -1
  6. package/dist/gtkx/bridge/core.d.ts +1 -2
  7. package/dist/gtkx/bridge/core.js +2 -5
  8. package/dist/gtkx/bridge/core.js.map +1 -1
  9. package/dist/gtkx/bridge/geometry.js +1 -1
  10. package/dist/gtkx/bridge/geometry.js.map +1 -1
  11. package/dist/gtkx/bridge/layout-manager.js +18 -5
  12. package/dist/gtkx/bridge/layout-manager.js.map +1 -1
  13. package/dist/gtkx/bridge/slot-portal.js.map +1 -1
  14. package/dist/gtkx/bridge/svg-node.js +13 -6
  15. package/dist/gtkx/bridge/svg-node.js.map +1 -1
  16. package/dist/gtkx/bridge/view-box.js +18 -9
  17. package/dist/gtkx/bridge/view-box.js.map +1 -1
  18. package/dist/gtkx/bridge/widgets.generated.adw.js +1 -1
  19. package/dist/gtkx/bridge/widgets.generated.adw.js.map +1 -1
  20. package/dist/mcp/data/generated.d.ts +58 -42
  21. package/dist/mcp/data/generated.js +63 -43
  22. package/dist/mcp/data/generated.js.map +1 -1
  23. package/dist/runner/host-dev.js +5 -0
  24. package/dist/runner/host-dev.js.map +1 -1
  25. package/dist/runner/host.js +15 -0
  26. package/dist/runner/host.js.map +1 -1
  27. package/dist/runner/index.d.ts +27 -0
  28. package/dist/runner/index.js +206 -1
  29. package/dist/runner/index.js.map +1 -1
  30. package/dist/sea/assemble.d.ts +9 -0
  31. package/dist/sea/assemble.js +16 -3
  32. package/dist/sea/assemble.js.map +1 -1
  33. package/dist/sea/bundle.d.ts +37 -2
  34. package/dist/sea/bundle.js +76 -4
  35. package/dist/sea/bundle.js.map +1 -1
  36. package/dist/sea/native-shim.js +1 -1
  37. package/dist/sea/native-shim.js.map +1 -1
  38. package/dist/testing/index.js +28 -0
  39. package/dist/testing/index.js.map +1 -1
  40. package/dist/vite/index.js +3 -3
  41. package/dist/vite/index.js.map +1 -1
  42. package/dist/vitest/index.d.ts +1 -1
  43. package/dist/vitest/index.js.map +1 -1
  44. package/package.json +9 -9
  45. package/dist/gtkx/bridge/use-signal.d.ts +0 -2
  46. package/dist/gtkx/bridge/use-signal.js +0 -41
  47. package/dist/gtkx/bridge/use-signal.js.map +0 -1
@@ -1451,7 +1451,7 @@ export declare const DOC_CHUNKS: readonly [{
1451
1451
  }, {
1452
1452
  readonly doc: "docs/architecture/overview.md";
1453
1453
  readonly heading: "The escape hatch";
1454
- readonly text: "If something is still missing, reach the widget directly — every wrapper\nhere forwards its `ref` to the real GObject:\n\n```tsx\nconst viewRef = useRef<Adw.NavigationView | null>(null)\n<NavigationStack ref={viewRef} stack={stack}>…</NavigationStack>\n// viewRef.current is the real Adw.NavigationView\n```\n\nThere is deliberately no wall. A missing convenience should cost one line,\nnot a fork.\n\n---\n\nMeasured numbers behind these decisions (the Yoga/GTK feasibility spike,\nframe-budget studies, the navigation research) live in `docs/research/` —\nrepo-only working notes, not published here. The standing gtkx upstream\nagenda is `docs/upstream-gtkx.md`, and every RC-stage workaround the bridge\ncarries is cataloged in `docs/gtkx-rc4-notes.md`.";
1454
+ readonly text: "If something is still missing, reach the widget directly — every wrapper\nhere forwards its `ref` to the real GObject:\n\n```tsx\nconst viewRef = useRef<Adw.NavigationView | null>(null)\n<NavigationStack ref={viewRef} stack={stack}>…</NavigationStack>\n// viewRef.current is the real Adw.NavigationView\n```\n\nThere is deliberately no wall. A missing convenience should cost one line,\nnot a fork.\n\n---\n\nMeasured numbers behind these decisions (the Yoga/GTK feasibility spike,\nframe-budget studies, the navigation research) live in `docs/research/` —\nrepo-only working notes, not published here. The standing gtkx upstream\nagenda is `docs/upstream-gtkx.md`, and every workaround the bridge carries\nis cataloged in `docs/gtkx-1.2-notes.md`.";
1455
1455
  }, {
1456
1456
  readonly doc: "docs/architecture/overview.md";
1457
1457
  readonly heading: "Related";
@@ -1559,7 +1559,7 @@ export declare const DOC_CHUNKS: readonly [{
1559
1559
  }, {
1560
1560
  readonly doc: "docs/architecture/glossary.md";
1561
1561
  readonly heading: "Bridge";
1562
- readonly text: "`src/gtkx/bridge/` — the one place in this package's source that imports\n`@gtkx/*` (eslint-enforced). gtkx is a release-candidate dependency, so its\nAPI drift is absorbed here instead of at every call site; each workaround\nthis forces carries an `RC4-WORKAROUND` tag, cataloged in\n`docs/gtkx-rc4-notes.md`. See [Overview](overview).";
1562
+ readonly text: "`src/gtkx/bridge/` — the one place in this package's source that imports\n`@gtkx/*` (eslint-enforced). gtkx is a young dependency, so its API drift\nis absorbed here instead of at every call site; each workaround this forces\ncarries a `1.2-WORKAROUND` tag, cataloged in `docs/gtkx-1.2-notes.md`. See\n[Overview](overview).";
1563
1563
  }, {
1564
1564
  readonly doc: "docs/architecture/glossary.md";
1565
1565
  readonly heading: "Profile";
@@ -1587,7 +1587,7 @@ export declare const DOC_CHUNKS: readonly [{
1587
1587
  }, {
1588
1588
  readonly doc: "docs/guide/installation.md";
1589
1589
  readonly heading: "New project from the template";
1590
- readonly text: "```bash\nnpx degit itsmepetrov/react-native-gtkx/template my-app\ncd my-app\nnpm install\nnpm run dev # a window opens, Fast Refresh applies edits live\n```\n\nProduction build:\n\n```bash\nnpm run build # single bundle: dist/bundle.js\nnpm start # node dist/bundle.js\n```\n\nMeasured in a clean Ubuntu 26.04 container, system dependencies\npreinstalled: 63 seconds from `npm install` to a window on screen.\n\nThe template's `vite.config.ts` wires the `react-native-gtkx/vite` preset\n(the `react-native` → `react-native-gtkx` alias, Metro-style platform\nextensions); `gtkx dev`/`gtkx build` pick it up automatically. Its\n`tsconfig.json` maps the `\"react-native\"` specifier through `paths` so\neditor types resolve too. The default entry is `src/index.tsx`, and\n`Comp.tsx` next to `Comp.linux.tsx` builds the Linux variant for an\nextensionless `import { Comp } from \"./Comp\"` — `Platform.select({ linux:\n…, native: …, default: … })` works exactly as it does in React Native and\nis tree-shaken out of the production build.";
1590
+ readonly text: "```bash\nnpx degit itsmepetrov/react-native-gtkx/template my-app\ncd my-app\nnpm install\nnpm run dev # a window opens, Fast Refresh applies edits live\n```\n\nProduction build:\n\n```bash\nnpm run build # single bundle: dist/bundle.mjs\nnpm start # node dist/bundle.mjs\n```\n\nMeasured in a clean Ubuntu 26.04 container, system dependencies\npreinstalled: 63 seconds from `npm install` to a window on screen.\n\nThe template's `vite.config.ts` wires the `react-native-gtkx/vite` preset\n(the `react-native` → `react-native-gtkx` alias, Metro-style platform\nextensions); `gtkx dev`/`gtkx build` pick it up automatically. Its\n`tsconfig.json` maps the `\"react-native\"` specifier through `paths` so\neditor types resolve too. The default entry is `src/index.tsx`, and\n`Comp.tsx` next to `Comp.linux.tsx` builds the Linux variant for an\nextensionless `import { Comp } from \"./Comp\"` — `Platform.select({ linux:\n…, native: …, default: … })` works exactly as it does in React Native and\nis tree-shaken out of the production build.";
1591
1591
  }, {
1592
1592
  readonly doc: "docs/guide/installation.md";
1593
1593
  readonly heading: "Add Linux to an existing React Native app";
@@ -1623,7 +1623,7 @@ export declare const DOC_CHUNKS: readonly [{
1623
1623
  }, {
1624
1624
  readonly doc: "docs/guide/toolchains.md";
1625
1625
  readonly heading: "The Metro path";
1626
- readonly text: "`run-linux` and `build-linux` are contributed to the React Native CLI by\n`react-native-gtkx`'s own `react-native.config.js` — no separate install,\nthey come with the package.\n\n```bash\nnpx react-native run-linux [--entry-file <path>] [--bundle-output <path>]\n [--skip-bundling] [--dev] [--port <number>]\n\nnpx react-native build-linux [--entry-file <path>] [--bundle-output <path>]\n [--standalone] [--sea] [--sea-output <path>]\n```\n\n`run-linux` bundles with Metro and opens the window; `--dev` starts (or\nreuses) the Metro dev server on `--port` (default `8081`) instead, for\nFast Refresh. `build-linux` bundles for distribution and stops short of\nrunning it — see [Packaging](packaging.md) for `--standalone`/`--sea`,\nthe two flags that turn the Metro bundle into something shippable.";
1626
+ readonly text: "`run-linux`, `build-linux` and `deploy-linux` are contributed to the React\nNative CLI by `react-native-gtkx`'s own `react-native.config.js` — no\nseparate install, they come with the package.\n\n```bash\nnpx react-native run-linux [--entry-file <path>] [--bundle-output <path>]\n [--skip-bundling] [--dev] [--port <number>]\n\nnpx react-native build-linux [--entry-file <path>] [--bundle-output <path>]\n [--standalone] [--sea] [--sea-output <path>]\n\nnpx react-native deploy-linux [--entry-file <path>] [--target <formats>]\n [--out <path>] [--print-manifests] [--skip-build]\n```\n\n`run-linux` bundles with Metro and opens the window; `--dev` starts (or\nreuses) the Metro dev server on `--port` (default `8081`) instead, for\nFast Refresh. `build-linux` bundles for distribution and stops short of\nrunning it — see [Packaging](packaging.md) for `--standalone`/`--sea`,\nthe two flags that turn the Metro bundle into something shippable.\n`deploy-linux` goes one step further and builds an installable `.deb`/\n`.rpm`/`.AppImage` from that same Metro build — also covered on the\n[Packaging](packaging.md) page, which is where its vite-path counterpart\nlives too (the same command name works for either toolchain).";
1627
1627
  }, {
1628
1628
  readonly doc: "docs/guide/toolchains.md";
1629
1629
  readonly heading: "The React Compiler (vite path only)";
@@ -1659,7 +1659,7 @@ export declare const DOC_CHUNKS: readonly [{
1659
1659
  }, {
1660
1660
  readonly doc: "docs/guide/packaging.md";
1661
1661
  readonly heading: "The vite path: one bundle";
1662
- readonly text: "```bash\ngtkx build\n```\n\nproduces `dist/bundle.js` — everything except the native GTK addon\ninlined into one file — plus `dist/gtkx.node` (and\n`dist/gschemas.compiled` alongside it, if the app declares a GSettings\nschema; the bundle's own banner points `GSETTINGS_SCHEMA_DIR` at its own\ndirectory). That pair is the whole runtime: copy it anywhere with Node\n≥ 24, GTK4 ≥ 4.20 and libadwaita ≥ 1.8 (or just GTK4, on the [plain-GTK\nprofile](plain-gtk.md)) and `node bundle.js` runs it — no `node_modules`\ninvolved.";
1662
+ readonly text: "```bash\ngtkx build\n```\n\nproduces `dist/bundle.mjs` — everything except the native GTK addon\ninlined into one file — plus `dist/gtkx.node` (and\n`dist/gschemas.compiled` alongside it, if the app declares a GSettings\nschema; the bundle's own banner points `GSETTINGS_SCHEMA_DIR` at its own\ndirectory). That pair is the whole runtime: copy it anywhere with Node\n≥ 24, GTK4 ≥ 4.20 and libadwaita ≥ 1.8 (or just GTK4, on the [plain-GTK\nprofile](plain-gtk.md)) and `node bundle.mjs` runs it — no `node_modules`\ninvolved.";
1663
1663
  }, {
1664
1664
  readonly doc: "docs/guide/packaging.md";
1665
1665
  readonly heading: "The Metro path: `build-linux`";
@@ -1667,55 +1667,71 @@ export declare const DOC_CHUNKS: readonly [{
1667
1667
  }, {
1668
1668
  readonly doc: "docs/guide/packaging.md";
1669
1669
  readonly heading: "Choosing an artifact";
1670
- readonly text: "`build-linux` produces three shapes from the same Metro step; the choice\nbetween them is a distribution question, not a different build:\n\n| Flag | Artifact | Needs installed | Size (`hn-app`, linux-arm64) |\n| -------------- | -------------------------- | ---------------------------------- | ---------------------------- |\n| _(none)_ | `dist/main.jsbundle` | a `node_modules` tree **and** Node | 0.4 MB + the tree |\n| `--standalone` | `dist/<name>.cjs` | Node only | 6.9 MB |\n| `--sea` | `dist/<name>` (executable) | nothing at all | 104 MB (30 MB compressed) |\n\n```bash\nnpx react-native build-linux --standalone # in the app root\nnode ./dist/<your-package-name>.cjs # one script, system node\n\nnpx react-native build-linux --sea\n./dist/<your-package-name> # one executable, nothing else\n```\n\nBoth flags produce the jsbundle exactly as before, then one additional\nfile next to it. `--sea-output <path>` overrides where that file goes;\nthe default is `dist/<package name>` with any npm scope stripped (plus\n`.cjs` for `--standalone`).\n\nPick **`--standalone`** for anything installed through a package\nmanager — it's the same shape this project's own `.deb`s ship (a bundle\nplus a `nodejs` dependency), and the lightest of the three by any\nmeasure that counts: the plain jsbundle only looks smaller because its\n`node_modules` tree isn't weighed. Pick **`--sea`** for \"download this\none file and run it,\" where nothing can be assumed to be installed —\nit's `--standalone` with a copy of Node wrapped around it, and that copy\nis the entire ~97 MB difference between the two.\n\nThe vite path has no `--sea` equivalent: its bundle loads the native\naddon through a dynamically obtained `require` a bundler can't intercept\nthe way it intercepts a static import, and the bundle currently needs\ntop-level await, which the single-file SEA format can't run. Ship a\nvite-path app as its `bundle.js`/`gtkx.node` pair, or as a `.deb`.";
1670
+ readonly text: "`build-linux` produces three shapes from the same Metro step; the choice\nbetween them is a distribution question, not a different build:\n\n| Flag | Artifact | Needs installed | Size (`hn-app`, linux-arm64) |\n| -------------- | -------------------------- | ---------------------------------- | ---------------------------- |\n| _(none)_ | `dist/main.jsbundle` | a `node_modules` tree **and** Node | 0.4 MB + the tree |\n| `--standalone` | `dist/<name>.cjs` | Node only | 6.9 MB |\n| `--sea` | `dist/<name>` (executable) | nothing at all | 104 MB (30 MB compressed) |\n\n```bash\nnpx react-native build-linux --standalone # in the app root\nnode ./dist/<your-package-name>.cjs # one script, system node\n\nnpx react-native build-linux --sea\n./dist/<your-package-name> # one executable, nothing else\n```\n\nBoth flags produce the jsbundle exactly as before, then one additional\nfile next to it. `--sea-output <path>` overrides where that file goes;\nthe default is `dist/<package name>` with any npm scope stripped (plus\n`.cjs` for `--standalone`).\n\n`--standalone` is what `deploy-linux` (below) builds a `.deb`/`.rpm`/\n`.AppImage` from — it is not really a competing choice, it is that\npackaging step's own input. Reach for it directly only when you want the\nbundle without any packaging around it (a container image, a CI artifact\npassed to another step). `--sea` stays the odd one out: \"download this\none file and run it,\" where nothing not even a package manager — can\nbe assumed. It's `--standalone` with a copy of Node wrapped around it,\nand that copy is the entire ~97 MB difference between the two.";
1671
1671
  }, {
1672
1672
  readonly doc: "docs/guide/packaging.md";
1673
1673
  readonly heading: "What `--sea`/`--standalone` need that a plain build doesn't";
1674
1674
  readonly text: "Both flags inline `virtual:gtkx-config` (which re-exports codegen\noutput), so — unlike a plain `build-linux`, which needs neither — they\nneed the gtkx codegen store already generated, and therefore GTK\ndevelopment headers on the build machine. The `--sea` build also fetches\n`postject` through `npx` the first time it runs, so that first build\nneeds network access.\n\nThe native addon (a real `dlopen`ed library) can't be plain bundled JS,\nso both artifacts carry it as bytes instead — a SEA asset in the\nexecutable, a base64 literal in the `.cjs` — and extract it to\n`$XDG_CACHE_HOME/react-native-gtkx-sea` on first run, keyed by content\nhash (falling back to a temp directory if `$HOME` is read-only). Repeat\nlaunches reuse the extracted file.\n\nThe `--sea` executable is large mostly because it carries a full copy of\nNode: on linux-arm64, `hn-app` measures 104 MB (30 MB zstd-compressed),\nof which roughly 98 MB is Node itself — the app code and native addon\ntogether are under 7 MB. The build strips Node's own debug symbols as\npart of assembling the executable (best-effort: a build machine without\n`binutils` gets a larger executable and a warning, not a failed build),\nwhich is most of what keeps that number from being worse — an\nunstripped `node` binary carries about 19 MB of debug information\nnothing in a shipped app can use.";
1675
1675
  }, {
1676
1676
  readonly doc: "docs/guide/packaging.md";
1677
- readonly heading: "What a release actually ships";
1678
- readonly text: "A tagged release builds `.deb` packages for each example app (both\ntoolchain shapes, per the [Choosing an artifact](#choosing-an-artifact)\nnote above) plus one `--sea` executable, zstd-compressed, uploaded\nalongside them. The `.deb`s remain how these apps are installed; the\nloose executable is there for a machine with no Node to depend on at\nalldownload it, `zstd -d` it, and run it.\n\nA `.deb` built this way stages either the vite pair (`bundle.js` +\n`gtkx.node`, launched with `exec node \"/opt/<pkg>/bundle.js\"`) or the\n`--standalone` script (`exec node \"/opt/<pkg>/<name>.cjs\"`) under `/opt`,\nalongside a `.desktop` entry and an icon, and declares:\n\n```\nDepends: nodejs (>= 24), libgtk-4-1 (>= 4.20), libadwaita-1-0 (>= 1.8), gir1.2-gtk-4.0, gir1.2-adw-1\n```\n\nPackage a [plain-GTK](plain-gtk.md) app the same way and drop\n`libadwaita-1-0`/`gir1.2-adw-1` from that lineevery example this\nproject ships today uses the Adw profile, so its own release\npipeline always declares both.";
1677
+ readonly heading: "Packaging: `npx react-native deploy-linux`";
1678
+ readonly text: "One command builds real `.deb`/`.rpm`/`.AppImage`/`.flatpak` packages —\nvalidated desktop entry and AppStream metadata, correct per-format\ndependencies, an icon installed into the system theme from either\ntoolchain. It works from any react-native-gtkx app's own project\ndirectory; which half of it runs is decided for you:\n\n- a project with a `vite.config.*` at its root (the vite path gallery,\n monitor and every app built with `gtkx dev`/`gtkx build`) proxies\n straight to `npx gtkx deploy`, since that project already looks\n exactly like what `gtkx deploy` expects;\n- everything else (the Metro path — an app with `ios`/`android` and\n `run-linux`/`build-linux`) runs a real `--standalone` build first (the\n same step described above), reshapes that output into the same shape a\n vite build would have produced, then hands off to `gtkx deploy --skip-build`\n over it. hn-app is this repo's own example of that path.\n\n```bash\nnpx react-native deploy-linux --target deb,rpm,appimage\nnpx react-native deploy-linux --target deb --print-manifests # metadata only, no packages built\nnpx react-native deploy-linux --skip-build --target rpm # package what was already built\n```\n\n| Flag | Meaning |\n| --------------------- | ---------------------------------------------------------------------- |\n| `--entry-file <path>` | Metro path only — same meaning as `build-linux`'s (default `index.js`) |\n| `--target <formats>` | Comma-separated: `deb`, `rpm`, `appimage`, `flatpak` |\n| `--out <path>` | Output directory, relative to the project root (default `build`) |\n| `--print-manifests` | Write the desktop-entry/AppStream files, then stop — no packages built |\n| `--skip-build` | Package the already-staged `dist/` instead of rebuilding (see below) |\n\n`--skip-build` means the same thing on both paths package what's\nalready in `dist/`, don't rebuild — but on the Metro path it also skips\nthis command's own build/staging step, not just `gtkx build`'s; running\nit before anything has ever been staged is a clear error, not a\nconfusing one three layers down.";
1679
1679
  }, {
1680
- readonly doc: "docs/gtkx-rc4-notes.md";
1680
+ readonly doc: "docs/guide/packaging.md";
1681
+ readonly heading: "The `deploy` config";
1682
+ readonly text: "Both toolchains read the same block from the project's `gtkx.config.ts`:\n\n```ts\nexport default defineConfig({\n applicationId: \"com.example.myapp\",\n libraries: [\"Gtk-4.0\", \"Adw-1\"],\n deploy: {\n name: \"My App\",\n summary: \"One line describing the app\",\n description: \"A longer paragraph or two.\",\n categories: [\"Utility\"],\n developer: { name: \"Your Name\", email: \"you@example.com\" },\n license: \"MIT\",\n icons: \"icon.svg\",\n },\n})\n```\n\nThere is no default: deploying without a `deploy` block is a thrown\nerror naming exactly which fields to add (derived from `package.json`\nwhere it can guess). `name`/`summary`/`categories` feed a real\n`.desktop` entry and AppStream `.metainfo.xml`, validated by\n`desktop-file-validate`/`appstreamcli` before anything is packaged — a\ncategory that isn't a real freedesktop one, or two main categories where\nonly one is allowed, fails the build rather than shipping quietly wrong\nmetadata. The full schema (screenshots, MIME types, per-target\ndependencies, signing, `targets`/`binaryName`/`applicationId` overrides)\nis [gtkx's own reference](https://gtkx.dev/guide/deploying); this page\ncovers what's specific to running it through `deploy-linux`.";
1683
+ }, {
1684
+ readonly doc: "docs/guide/packaging.md";
1685
+ readonly heading: "What ships";
1686
+ readonly text: "Every target bundles its own private copy of Node by default\n(`deploy.node.source: \"download\"`, verified against `SHASUMS256.txt`,\ncached under `~/.cache/gtkx/node/`) — no `Depends: nodejs` line, at the\ncost of roughly 97 MB per package. Measured on linux-arm64, both\ntoolchains land in the same range because both are dominated by that\nbundled runtime: `monitor` (vite) is a 40.3 MB `.deb`; `hn-app` (Metro)\nis a 40.7 MB `.deb`, a 40.5 MB `.rpm` and a 36.5 MB `.AppImage`. A `.deb`\ndeclares:\n\n```\nDepends: libgtk-4-1, libadwaita-1-0, hicolor-icon-theme, adwaita-icon-theme, gsettings-desktop-schemas, libc6 (>= 2.28)\n```\n\n— the glibc floor read directly off the bundled Node's own ELF notes.\nPackage a [plain-GTK](plain-gtk.md) app the same way and `libadwaita-1-0`\ndrops out on its own (it comes from the app's declared `libraries`, not\na fixed list). Prefer the old thin-package shape, where the system's own\n`nodejs` is a dependency instead of a bundled copy? `deploy.node.source:\n\"host\"` (embed the machine's own Node) or `\"path\"` (embed a specific\none) are the escape hatch — see the reference linked above.\n\n`deb`/`rpm`/`appimage` need nothing preinstalled beyond a handful of\nsmall system tools (`desktop-file-validate`, `appstreamcli`, `tar`,\n`file`, `binutils` optionally for a smaller bundled Node) — the actual\npackagers, [nfpm](https://github.com/goreleaser/nfpm) for deb/rpm and\n[appimagetool](https://github.com/AppImage/appimagetool), download and\ncache themselves under `~/.cache/gtkx` on first use. A missing tool\nfails with the exact `apt`/`dnf`/`pacman`/`zypper` command to install it,\ndetected from `/etc/os-release`. `flatpak` is the heavier target: it\nneeds `flatpak`/`flatpak-builder` installed and the GNOME `Platform`/\n`Sdk` runtime pulled from Flathub before it can build anything (a\none-time, network-bound, multi-hundred-MB fetch) — every example this\nproject ships deliberately leaves `flatpak` out of its own\n`deploy.targets`, proven separately as a build-only artifact instead of\nbeing part of the regular `deb,rpm,appimage` set.";
1687
+ }, {
1688
+ readonly doc: "docs/guide/packaging.md";
1689
+ readonly heading: "The vite path's own `--sea`";
1690
+ readonly text: "There isn't one, and `deploy-linux` is why that's no longer the gap it\nused to be: the vite bundle loads the native addon through a dynamically\nobtained `require` a bundler can't intercept the way it intercepts a\nstatic import, and the bundle needs top-level await, which the\nsingle-file SEA format can't run — both investigated, neither a drop-in\nfix. Ship a vite-path app as its `bundle.mjs`/`gtkx.node` pair for\nrunning from a checkout, or package it with `deploy-linux` for\neverything else; the gap this used to be (no way to turn a vite app into\nsomething a package manager installs) is what `deploy-linux` closes.";
1691
+ }, {
1692
+ readonly doc: "docs/gtkx-1.2-notes.md";
1681
1693
  readonly heading: "Live workarounds";
1682
- readonly text: "| Name | What rc.4 does | Our workaround | Removal condition |\n| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |\n| `use-signal-stale-handler` | `useSignal` still routes the handler through React's `useEffectEvent`; `react-reconciler@0.33.0` only refreshes it in `commitBeforeMutationEffects` for `case 0` (FunctionComponent) `case 11` (ForwardRef) and `case 15` (SimpleMemoComponent) fall through unrefreshed, so any `useEffectEvent` in a `memo`/`forwardRef` component is pinned to its mount closure forever (our `ScrollView` is a `forwardRef` with the `useSignal` calls inside it) a fetch-fed FlatList empties itself on the first scroll. **rc.4 did not bump React and did not change the path** — see below | `gtkx/bridge/use-signal.ts` re-pins the latest handler (insertion effect) and hands gtkx a stable wrapper; the bridge exports that hook, not gtkx's | A stable React 19.3 (React fixed the refresh on the 19.3 line) |\n| `runtime-dedupe` | Two bundled copies of the gtkx runtime still double-init GLib and abort; nothing guards against it. Reproduced on rc.4: `g_log_set_writer_func() called multiple times`, SIGABRT | `src/vite/index.ts` puts `resolve.dedupe` over `@gtkx/*` + `react` (+ `@react-navigation/*` for its context) into the preset every app inherits | Idempotent runtime init upstream, or an error that names the duplicate |\n| `prop-portal` | `createPortal(children, container, key?)` is byte-for-byte the rc.3 signature and can still only target a container's DEFAULT slot (\"children\"). Every other slot an object exposes declaratively a window's `Gio.ActionMap` (`actions`), a widget's `controllers`, an `AdwApplicationWindow`'s `breakpoints` is reached only by passing an element-valued PROP, which the reconciler routes through an internal `\"gtkx:prop\"` element. rc.4 exports that element from neither `@gtkx/react` nor `/internal`, and its `exports` map now refuses the deep path outright | `gtkx/bridge/slot-portal.ts` restates the `\"gtkx:prop\"` element name and wraps it in `createSlotPortal(children, target, slot)` — the one line that would move if gtkx renames it. `WindowActions`/`ApplicationActions`/`WindowControllers` are built on it | gtkx exports a slot-aware portal (or the prop element) from a public entry point |\n| `renderhook-no-window` | `renderHook` still mounts into a bare `Gtk.Box` — rc.4's `render-hook.js` is byte-identical to rc.3's, which was byte-identical to rc.2's — so window-dependent APIs have no toplevel to read | Hook tests create a window with `render()` first (`tests/gtk/apis/dimensions.test.tsx`); packaged for consumers as `renderHookWithWindow` (`react-native-gtkx/testing`) | `renderHook` mounts into the same harness window `render` uses |";
1694
+ readonly text: "| Name | What 1.2.2 does | Our workaround | Removal condition |\n| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `runtime-dedupe` | Two bundled copies of the gtkx runtime still double-init GLib and abort; nothing guards against it across two DISTINCT `.node` files. Reproduced on 1.2.2 by actually building the duplicate: `gtkx: GLib-ERROR: g_log_set_writer_func() called multiple times`. The FAILURE MODE changed since 1.0 (see upstream-gtkx.md ask #2) it is now a catchable, named Node `uncaughtException` instead of a SIGABRT/core dump but the underlying defect has not: the init is still not idempotent across distinct binaries, and the error still does not name the duplicate package | `src/vite/index.ts` puts `resolve.dedupe` over `@gtkx/*` + `react` (+ `@react-navigation/*` for its context) into the preset every app inherits | Idempotent runtime init upstream across distinct native-addon files, or an error that names the duplicate |\n| `prop-portal` | `createPortal(children, container, key?)` is still byte-for-byte the rc.3/rc.4 signature and can still only target a container's DEFAULT slot (\"children\"). `@gtkx/react`'s public entry and its `/internal` subpath both still withhold the `\"gtkx:prop\"` element `/internal` still re-exports `createElementComponent` (the function that BUILDS an intrinsic element by GType name) but not the `Prop` literal itself, which stays a sibling export inside `element.js` that no public or `/internal` path reaches | `gtkx/bridge/slot-portal.ts` restates the `\"gtkx:prop\"` element name and wraps it in `createSlotPortal(children, target, slot)` — the one line that would move if gtkx renames it. `WindowActions`/`ApplicationActions`/`WindowControllers` are built on it | gtkx exports a slot-aware portal (or the prop element) from a public entry point |\n| `renderhook-no-window` | `renderHook` still mounts into a bare `Gtk.Box` — 1.2.2's `render-hook.js` still passes `container: new Gtk.Box()` unconditionally, and `RenderHookOptions` still carries only `wrapper` and `initialProps` — so window-dependent APIs have no toplevel to read | Hook tests create a window with `render()` first (`tests/gtk/apis/dimensions.test.tsx`); packaged for consumers as `renderHookWithWindow` (`react-native-gtkx/testing`) | `renderHook` mounts into the same harness window `render` uses |\n| `gtk-application-argv` | `@gtkx/react`'s `<GtkApplication>` bootstrap still builds the GApplication's own command line as `[applicationId.split(\".\").at(-1), ...process.argv.slice(2)]` and hands it to `runApplication(application, commandLine)` — confirmed unchanged by reading the installed 1.2.2 source (`components/application.tsx`) directly, no `argv` prop or options object anywhere on `ApplicationComponentProps`. Our runner hosts are invoked as `node dist/runner/host.js <bundle-path>` / `node dist/runner/host-dev.js <bundle-url>`, so `process.argv[2]` is our OWN internal plumbing, not a user-facing argument — left in place it reaches GLib's local command-line handling as a stray positional it tries to open as a file (`GLib-GIO-CRITICAL: This application can not open files`), and the window never activates | `process.argv.length = 2` right after each host reads its own positional, before the bundle runs (`src/runner/host.ts`, `host-dev.ts`) — both mount `<GtkApplication>` from a `useLayoutEffect`, which always fires after this line. Not needed on the vite path (`gtkx build`/`gtkx dev` bundles run with no extra positional) or the SEA build (which never reads `process.argv`) | `@gtkx/react` exposes a way to pass `runApplication`'s argv explicitly (an `argv` prop on `<GtkApplication>`, or an options object) instead of always reading `process.argv` itself |";
1695
+ }, {
1696
+ readonly doc: "docs/gtkx-1.2-notes.md";
1697
+ readonly heading: "How each was checked against 1.2.2";
1698
+ readonly text: "The rule is that a changelog entry (or, for a stable release, a release page)\nis a claim and the removal condition is the test, so each row was re-run on\nthe real runtime rather than read about.\n\n- **`runtime-dedupe`** — reproduced the exact shape the condition names —\n two DISTINCT `.node` files in one process — by copying the installed\n `@gtkx/native` + `@gtkx/native-linux-arm64-gnu` package trees (1.2.2) to\n two separate absolute paths, each under its own `node_modules/@gtkx/`\n (Node caches a native addon by the resolved path of the `.node` FILE, so\n two copies at different paths are two distinct files even with identical\n bytes) and importing both `main.js` entry points in one script. First\n import succeeds; the second raises `gtkx: GLib-ERROR:\ng_log_set_writer_func() called multiple times` as an uncaught Node\n exception, exit code 1, no core file — reproduced twice for consistency.\n This is the SAME defect as 1.0 (`log_writer.rs`'s `install()` still uses a\n `OnceLock` — a WITHIN-one-binary guarantee only — confirmed unchanged\n since rc.4 by diffing the file across GitHub tags) but a DIFFERENT\n failure mode: 1.0's identical probe produced a bare SIGABRT/core dump\n (`exit 134`). The change traces to `node_env.rs`'s `raise_fatal`, which\n gained an explicit `napi_open_handle_scope`/`napi_close_handle_scope`\n pair around the raw `napi_fatal_exception` call in the 1.x line (absent\n at 1.0.0, confirmed by diffing `node_env.rs` at both tags) — see\n upstream-gtkx.md ask #2 for the full mechanism. Neither half of the\n removal condition is met: the error still does not name the duplicate\n package.\n- **`prop-portal`** — enumerated the real module exports by reading the\n compiled `dist/index.js` and `dist/internal.js` directly (same method as\n before): `@gtkx/react`'s `dist/index.js` exports exactly `useApplication`,\n `useBindSetting`, `useParentWindow`, `useProperty`, `useSetting`,\n `useSignal`, `rootElement`, `createPortal`, `createRoot`, `quit`;\n `dist/internal.js` exports `createApplicationWindowComponent`,\n `createApplicationComponent`, `createElementComponent`,\n `createPortaledComponent`, `createWindowComponent`, `useLatestRef`,\n `useMergedRef`, `settleAccessible`, `isRootElement`,\n `createReconcilerRoot`, `setReconcilerErrorHandler`. No value in either is\n `\"gtkx:prop\"` — `components/element.js` itself still exports both `Prop`\n and `createElementComponent`, but `internal.js` re-exports only the\n latter. `createPortal`'s own `.d.ts` signature is still `(children,\ncontainer, key?)`, byte-identical to rc.3/rc.4/1.0. A deep import of\n `@gtkx/react/dist/components/element.js` still throws `Package subpath\n'./dist/components/element.js' is not defined by \"exports\"` — reproduced\n fresh against 1.2.2. Functional confirmation:\n `tests/gtk/components/app-registry.gtk.test.tsx` (exercises\n `WindowActions`/`WindowControllers` built on `createSlotPortal`) passes\n clean, 6/6, on 1.2.2.\n- **`renderhook-no-window`** — reproduced the rc.4-era check verbatim, fresh\n on 1.2.2: in one throwaway test file, `Gtk.Window.getToplevels().getNItems()`\n before and after a bare `renderHook(() => null)` reads 0 → 0, then\n `render(null)` in the same file reads 0 → 1. `RenderHookOptions` in\n `dist/render-hook.d.ts` still carries only `wrapper` and `initialProps`,\n and `render-hook.tsx` still constructs `new Gtk.Box()` unconditionally as\n its container. Functional confirmation: `tests/gtk/apis/dimensions.test.tsx`\n (3/3) still passes using the `render()`-first pattern.\n- **`gtk-application-argv`** — read `@gtkx/react@1.2.2`'s installed\n `components/application.tsx` source directly: `commandLine` is still\n `[applicationId?.split(\".\").at(-1) ?? \"gtkx\", ...process.argv.slice(2)]`,\n and `ApplicationComponentProps` still only carries `applicationId`,\n `children` and `ref` — no `argv` field, no options object, grepped the\n whole `@gtkx/react` and `@gtkx/runtime` trees for `argv` and found nothing\n else relevant. Functional confirmation: this task's own\n `npm run typecheck`/`build:dist`/`gallery-smoke.ts` runs on the merged\n worktree state (which mounts `<GtkApplication>` through the workaround)\n stay green.";
1683
1699
  }, {
1684
- readonly doc: "docs/gtkx-rc4-notes.md";
1685
- readonly heading: "`use-signal-stale-handler` is a decision upstream made, not an oversight";
1686
- readonly text: "We shipped the fix as a PR (gtkx-org/gtkx#469) and it was **closed unmerged\non purpose**. @eugeniodepalo: _\"closing this in favour of waiting for\nupstream… Since React fixes this properly on 19.3 for all fiber tags, I'd\nrather take the version bump than carry a workaround I'd revert.\"_ So this\nrow does not move on any gtkx release only a stable React 19.3 retires it,\nand the hazard is wider than `useSignal`: any hook built on `useEffectEvent`\ninherits it until then.\n\nrc.4 was checked against that condition anyway, because the condition names a\nReact version and a release could satisfy it by bumping one: it does not.\n`@gtkx/react@1.0.0-rc.4` peers `react: ^19.2` and depends on\n`react-reconciler: ^0.33.0` character-identical to rc.3 — and React's own\n`latest` is still 19.2.8, with 19.3.0 published only as canaries. rc.4 did\ntouch `useSignal`, but only to rename its options (`after`/`immediate` →\n`isAfter`/`isImmediate`); the body still calls `useEffectEvent`, and its\ndoc comment still says React fixes this on the 19.3 line.";
1700
+ readonly doc: "docs/gtkx-1.2-notes.md";
1701
+ readonly heading: "What 1.2 changed under us";
1702
+ readonly text: "Unlike 1.0, 1.2 introduced **no new workaround**: the two breaks task 001\n(\"first light\") absorbed strict signal typing, the `bundle.mjs` rename —\nwere both plain fixes to our own code (a looser-than-actual type signature,\na hardcoded output filename), not gtkx behavior we have to route around, so\nneither has a row here or a removal condition to track.\n\n**1.2.1's criticals-raise-uncaught-exceptions change** (task 001's own\n\"criticals-throw\" probes already confirmed clean at the application level —\nscroll adjustment lore, modal open/close cycles, teardown) turned out to\nmatter for this task too: it is the same change that altered\n`runtime-dedupe`'s failure mode (see the row above and upstream-gtkx.md\nask #2), and re-probing it with two FRESH, more aggressive stress tests\n(a synchronous 5,000-call burst of invalid `Gtk.Adjustment.configure()`\ncalls with an `uncaughtException` handler installed, and a real\n`GtkWidget.addTickCallback`-driven burst of 2,000 identical calls under the\nactual `@gtkx/vitest` forks pool — both well past the 1,699-call burst that\noriginally triggered ask #2's crash) produced zero crashes on 1.2.2, where\nthe same infrastructure crashed on 2026-08-04. See upstream-gtkx.md ask #2\nfor the full writeup and the mechanism found.\n\n`registerClass` subclassing's chain-up capability (1.0's candidate answer to\nupstream-gtkx.md ask #3 — every generated `vfunc*` method `protected` and\nchainable via `super.vfuncX()`) is unchanged on 1.2.2, confirmed by reading\nthe installed `@gtkx/gi/gtk` typings directly; still not adopted into\n`layout-manager.ts`, per this epic's scope (adopting it is a follow-up, not\na migration task).\n\n`createDialogComponent` (upstream ask #10) is unchanged on 1.2.2: the\ninstalled `dist/adw/dialog.d.ts` still types its returned component as\n`(props: PresentedProps<Adw.Dialog>) => ReactNode` with `Adw.Dialog`\nresolved against `@gtkx/react`'s own store — not generic, not\nstore-agnostic. See \"Resolved by configuration\" below; nothing to revert.";
1687
1703
  }, {
1688
- readonly doc: "docs/gtkx-rc4-notes.md";
1689
- readonly heading: "How each was checked against rc.4";
1690
- readonly text: "The rule is that a changelog entry is a claim and the removal condition is\nthe test, so each row was re-run on the real runtime rather than read about.\n\n- **`use-signal-stale-handler`**`tests/gtk/bridge/use-signal-upstream.gtk.test.tsx`\n calls gtkx's own hook directly on a `memo` component; it is an `it.fails`\n guard that starts passing the day the defect is gone. On rc.4 it still\n fails, and it is the \"1 expected fail\" the whole suite reports.\n- **`runtime-dedupe`** — the first two attempts at a probe both said \"no\n abort\", and both were wrong, which is worth recording: Node caches a native\n addon by the resolved path of the `.node` FILE, so a second copy of the thin\n `@gtkx/native` JS wrapper shares one addon instance and one Rust static, and\n a second `init()` on it returns normally — on rc.3 exactly as on rc.4. The\n failure needs two DISTINCT `.node` files in one process (an app with its own\n `@gtkx/native-linux-*-gnu` plus a nested one under the library), each\n carrying its own `glib::log::WRITER_FUNC`. Built that way, rc.4 dies:\n `gtkx: GLib-ERROR: g_log_set_writer_func() called multiple times`, exit 134,\n core dumped. Neither half of the condition is met — the init is not\n idempotent, and while the error names the symbol it does not name the\n duplicate package, which is the part that would make it debuggable.\n\n **Not the only SIGABRT out of this subsystem.** A second, separate crash —\n a Rust panic inside the `writer_trampoline` `log_set_writer_func` installs\n (registered fine, once) rather than a double-registration — twice took\n down a CI worker fork under `tests/gtk/dnd/collision-thresholds.gtk.test.tsx`\n (2026-08-04, runs 30903167960 and 30904467362). Not a new\n `RC4-WORKAROUND` row: the trigger was our own bug (`scroll-view.tsx`'s\n `syncAdjustmentRange` calling `Gtk.Adjustment.configure()` with an invalid\n range, retried every frame), now fixed, not gtkx drift to absorb in the\n bridge. Full backtrace and the upstream ask (harden `writer_trampoline`\n with `catch_unwind`) are in\n [docs/upstream-gtkx.md](upstream-gtkx.md#2-a-panic-inside-the-glib-log-writer-trampoline-aborts-the-whole-process-not-just-the-offending-log-call).\n\n- **`prop-portal`**enumerated the real module objects on the runtime rather\n than reading the `.d.ts`. `@gtkx/react` exports exactly `createPortal`,\n `createRoot`, `quit`, `rootElement`, `useApplication`, `useBindSetting`,\n `useParentWindow`, `useProperty`, `useSetting`, `useSignal`;\n `@gtkx/react/internal` exports `applyWrite`, `createApplicationComponent`,\n `createElementComponent`, `createReconcilerRoot`, `createWindowComponent`,\n `getAccessibleMetadata`, `isRootElement`, `setReconcilerErrorHandler`,\n `useMergedRef`. No value in either is `\"gtkx:prop\"`, and there is no\n slot-aware portal. rc.4 also made the fallback worse rather than better:\n importing `@gtkx/react/dist/components/element.js` now fails with _\"not\n exported under the conditions [node, development, import]\"_, so restating\n the literal is the only route left. The literal itself did not move —\n `const Prop = \"gtkx:prop\"` is unchanged in rc.4's `components/element.tsx`\n (only a doc comment above it was deleted), which the passing\n `WindowActions`/`WindowControllers`/breakpoint suites confirm functionally.\n- **`renderhook-no-window`** `RenderHookOptions` still carries only\n `wrapper` and `initialProps`, no `container`, and on the runtime\n `renderHook` took the toplevel count from 0 to 0 while `render` took it\n from 0 to 1 in the same file.";
1704
+ readonly doc: "docs/gtkx-1.2-notes.md";
1705
+ readonly heading: "Resolved by configuration";
1706
+ readonly text: "- `dialog-component-ref-widen`@gtkx/react's `createDialogComponent` pins\n one store's `Adw.Dialog` in its returned props type, which breaks codegen\n typechecking for every workspace app with its own store (upstream ask #10\n in docs/upstream-gtkx.md, **still open on 1.2.2** the installed\n `dist/adw/dialog.d.ts` is unchanged, confirmed by reading it directly). We\n no longer carry a workaround: the examples set `codegen: false` and share\n the root-generated store — the sanctioned workspace shape\n (`removeSharedStoreShadow` in @gtkx/cli exists exactly for it), which\n makes the app and @gtkx/react resolve the same store by construction.\n This shape stays required, not optional, on 1.2.2 the factory has not\n become generic. A patch-package patch briefly filled this slot during the\n 1.0 migration and was removed the same day: the installed `.d.ts` proved\n platform-specific for one published version, making any single patch\n unappliable on the other OS.";
1691
1707
  }, {
1692
- readonly doc: "docs/gtkx-rc4-notes.md";
1693
- readonly heading: "What rc.4 renamed under us";
1694
- readonly text: "rc.4 is a naming-convention sweep. Nothing below changed behaviour, but each\none is a compile error or a silent runtime miss for a consumer of the RC.\n\n- **`@gtkx/react` moved the settings types off its public entry point.**\n `SettingsSchema`, `SettingsSchemaKeys` and `SettingValue` are now exported\n from `/internal` only, while the hooks they type (`useSetting`,\n `useBindSetting`) stay public so an app that wants to name the type of a\n setting has no supported import for it. The bridge re-exports them from\n `/internal`; the ask to put them back is in\n [docs/upstream-gtkx.md](upstream-gtkx.md). (`MenuItem` and `VflConstraints`\n left the public entry too; nothing here used them.)\n- **`@gtkx/codegen`'s `runCodegen` result renamed `regenerated` →\n `isRegenerated`** (`src/runner/index.ts`), and the package dropped its\n `./gi` and `./jsx` subpath exports in favour of a new `./internal`.\n- **`@gtkx/vitest` renamed `GtkxPluginOptions` `PluginOptions`**\n (`src/vitest/index.ts`).\n- **The element config renamed `lazy` `isLazy` and `omitProps` →\n `omittedProps`.** This is the one with no compiler behind it: our three\n hosts synthesize `virtual:gtkx-config` as SOURCE TEXT\n (`src/runner/host.ts`, `src/runner/host-dev.ts`,\n `src/sea/gtkx-config-module.ts`), so a stale key typechecks perfectly and\n simply stops marking elements lazy at runtime. Caught by diffing rc.4's own\n `renderConfigModule` against ours and proven by the headless `run-linux`\n proof, not by a gate.\n- **`virtual:gtkx-config`'s metadata constants** went `SIGNALS` `signals`,\n `CONSTRUCT_PROPS` `constructProps`, `CONSTRUCT_ONLY_PROPS` →\n `constructOnlyProps`, `DEFAULT_PROPS` `defaultProps`. Free for us: all\n three hosts re-export the module wholesale\n (`export * from \"@gtkx/jsx/metadata\"`) rather than naming its members.\n- **`@gtkx/testing` renamed `GtkxElementError` `ElementError`,\n `render`'s `animations`/`reactStrictMode` `areAnimationsEnabled`/\n `isReactStrictMode`, and `prettyWidget`'s `highlight` → `shouldHighlight`.**\n We use none of them, but a consumer's test suite will.\n- **`defineBehavior`'s `createContext` `initialize`**, and\n `@gtkx/utils` dropped its `./function` subpath. Neither reaches us.";
1708
+ readonly doc: "docs/gtkx-1.2-notes.md";
1709
+ readonly heading: "Fixed in 1.2.1 (history)";
1710
+ readonly text: "- **`use-signal-stale-handler`** — gtkx's oldest workaround, alive since\n rc.3. `useSignal` routed the handler through React's `useEffectEvent`,\n and `react-reconciler@0.33.0` only refreshed it in\n `commitBeforeMutationEffects` for `case 0` (FunctionComponent) —\n `case 11` (ForwardRef) and `case 15` (SimpleMemoComponent) fell through\n unrefreshed, so any `useEffectEvent` inside a `memo`/`forwardRef`\n component was pinned to its mount closure forever (our `ScrollView` is a\n `forwardRef` with the `useSignal` calls inside it — a fetch-fed FlatList\n emptied itself on the first scroll). Upstream had ruled on this before\n (gtkx-org/gtkx#467, our own fix offered as #469 and **closed unmerged on\n purpose** @eugeniodepalo: \"closing this in favour of waiting for\n upstream… Since React fixes this properly on 19.3 for all fiber tags, I'd\n rather take the version bump than carry a workaround I'd revert\") — and\n it was re-checked and reconfirmed unfixed on both rc.4 and 1.0.0, since\n the removal condition names a React version a release could satisfy by\n bumping.\n **gtkx fixed it directly instead, without waiting for React 19.3.** The\n 1.2.1 changelog's own bugfix entry: \"Fixed `useSignal` running the handler\n captured on the first render for every emission inside a component\n wrapped in `memo` or `forwardRef`... The hook built on React 19.2's\n `useEffectEvent`, which does not pick up the updated function through\n those wrappers; the handler is now held in a ref written from\n `useInsertionEffect`.\" Confirmed by reading the installed 1.2.2 source\n directly: `@gtkx/react`'s `hooks/use-signal.ts` no longer imports\n `useEffectEvent` at all — it calls the package's own new `useLatestRef`\n (`hooks/use-latest-ref.ts`), a `useRef` refreshed by a `useInsertionEffect`,\n exactly the pattern our bridge wrapper used to restore by hand.\n `react-reconciler` stayed at `^0.33.0` and `react` stayed on `^19.2` in\n the installed 1.2.2 tree this is gtkx's own fix, not a React bump.\n Probed by flipping `tests/gtk/bridge/use-signal-upstream.gtk.test.tsx`'s\n `it.fails` guard into a plain `it` and running it targeted on 1.2.2: it\n now passes (previously, running the unflipped `it.fails` guard on 1.2.2\n first, per the \"reproduce the original failure first\" discipline, reports\n `Error: Expect test to fail` the wrapped assertion itself succeeds,\n meaning `it.fails`'s own expectation of failure is what fails). Deleted\n the workaround: `src/gtkx/bridge/use-signal.ts` (the ref-plus-\n `useInsertionEffect` wrapper) is gone, and `src/gtkx/bridge/core.ts`\n re-exports `useSignal` straight from `@gtkx/react` again, alongside the\n package's other hooks.";
1695
1711
  }, {
1696
- readonly doc: "docs/gtkx-rc4-notes.md";
1712
+ readonly doc: "docs/gtkx-1.2-notes.md";
1697
1713
  readonly heading: "Fixed in rc.3 (history, one line each)";
1698
1714
  readonly text: "- **`gsk-colorstop-boxed-write`** — constructing a `Gsk.ColorStop` threw in\n the native addon, so SVG gradients had zero constructible stops and painted\n nothing. **Fixed upstream by us** (gtkx-org/gtkx#473, closing #472): a\n record field write converts through `toNative` now.\n- **`graphene-rect-nested-boxed-props`** — the same native bug reached through\n `new Graphene.Rect({ origin, size })`; same upstream fix, so `svg-node.ts`\n uses the plain constructor again.\n- **The codegen freshness lie** — rc.2's `@gtkx/cli` could report \"bindings up\n to date\" over a store `npm install` had pruned. Fixed upstream in\n gtkx-org/gtkx#470 (also ours); separately `src/runner` calls the\n programmatic `@gtkx/codegen` API rather than the CLI.";
1699
1715
  }, {
1700
- readonly doc: "docs/gtkx-rc4-notes.md";
1716
+ readonly doc: "docs/gtkx-1.2-notes.md";
1701
1717
  readonly heading: "Fixed in rc.2 (history, one line each)";
1702
1718
  readonly text: "- **`vitest-compositor`** — rc.1 defaulted the headless display to weston;\n rc.2's default IS sway, so `vitest.config.ts` calls the plugin with no\n arguments.\n- **`no-virtual-seat`** — rc.1 had no input seat under sway, so windows never\n activated and `userEvent` was impossible; rc.2 starts a virtual seat.\n- **`fixed-layout-child`** — rc.1's declarative `<GtkFixedLayoutChild>`\n created a detached object; moot since containers moved to our own\n `RnGtkxLayout` manager and GtkFixed left the codebase.\n- **`controllers-as-children`** — rc.1 silently ignored controllers passed as\n JSX; rc.2 has a `controllers` slot on `GtkWidget`. Pressable and TextInput\n still attach theirs imperatively on purpose — a choice now, not a\n workaround.";
1703
1719
  }, {
1704
- readonly doc: "docs/gtkx-rc4-notes.md";
1705
- readonly heading: "Behaviour rc.4 changed under us";
1706
- readonly text: "Nothing measurable. The suite is **166 files, 1601 passed + 1 expected fail**\non rc.4 identical to main's own CI run on rc.3 (251c353), file for file and\ntest for test. The renames above are the whole of the release as far as this\nrepo can observe it: the reconciler's commit-time signal handling, the\nharness window, the accessibility tree and the codegen output all behave as\nthey did on rc.3, and the regenerated bindings typecheck clean.\n\nTwo things worth knowing before debugging something odd on rc.4:\n\n- **A first codegen after a version bump is slow enough to look like a hang.**\n The store fingerprint includes the app's own config, so each example\n regenerates once on top of the root's run — ~45 s for the gallery on the\n VM. `scripts/gtkx-dev-headless.ts` sleeps 25 s before its first shot and\n will report `FAST-REFRESH-FAIL` on a cold store; run `npx gtkx codegen` in\n the example first.\n- **`gtkx dev` still binds vite's HMR websocket on the fixed port 24678**, and\n the CLI exposes no way to move it. A second `gtkx dev` anywhere on the\n machine logs `WebSocket server error: Port 24678 is already in use` and the\n edit never reaches the app, while the supervisor still prints \"Fast Refresh\n complete\" — so the log marker alone is not proof the refresh applied.\n Verified identical on rc.3, so this is not new, but it makes the dev-path\n proof unreliable when another app is running.";
1720
+ readonly doc: "docs/gtkx-1.2-notes.md";
1721
+ readonly heading: "Status of this migration";
1722
+ readonly text: "The full suite (186+ files) has not run yet that is deliberately deferred\nto the `gtkx-1-2-migration` epic's task 003, which runs it exactly once for\nthe whole migration. What is confirmed on 1.2.2 as of this task: `npm run\ntypecheck` and `npm run build:dist` green (both before and after deleting\n`use-signal-stale-handler`), the flipped `use-signal-upstream.gtk.test.tsx`\nregression test green on a targeted run, and every probe in the tables above\n(four re-audited workarounds, two fresh upstream-ask #2 stress probes, one\nretired workaround) behaves as expected. Nothing here should be read as \"the\nsuite passes\" that claim belongs to task 003 alone.\n\nTwo environmental notes carried forward from the rc.3/rc.4/1.0 era, neither\ngtkx-version-specific and both unexamined by this task:\n\n- **A first codegen after a version bump is slow enough to look like a hang.**\n The store fingerprint includes the app's own config, so each example\n regenerates once on top of the root's run — ~45 s for the gallery on the\n VM. `scripts/gtkx-dev-headless.ts` sleeps 25 s before its first shot and\n will report `FAST-REFRESH-FAIL` on a cold store; run `npx gtkx codegen` in\n the example first.\n- **`gtkx dev` still binds vite's HMR websocket on the fixed port 24678**, and\n the CLI exposes no way to move it. A second `gtkx dev` anywhere on the\n machine logs `WebSocket server error: Port 24678 is already in use` and the\n edit never reaches the app, while the supervisor still prints \"Fast Refresh\n complete\" — so the log marker alone is not proof the refresh applied.";
1707
1723
  }, {
1708
- readonly doc: "docs/gtkx-rc4-notes.md";
1724
+ readonly doc: "docs/gtkx-1.2-notes.md";
1709
1725
  readonly heading: "Behaviour rc.3 changed under us (still true)";
1710
- readonly text: "- **Blockable signals are no longer suppressed for a whole React commit** —\n rc.3 wraps each framework write individually, so an emission the framework\n did not cause (one raised from a `useLayoutEffect`, or aimed at another\n `createRoot` tree) reaches its handler. Our navigators lean on this.\n- **`render`'s harness window is undecorated**, so role queries see only what\n the test rendered.\n- **A widget with `accessibleLabelledBy` reports the relation as its\n accessible name**, ahead of its own text — the precedence ARIA defines.\n- **`toHaveTextContent` no longer falls back to the accessible name**;\n **`toHaveDisplayValue` throws** on a widget without one; **checked state is\n tri-state**.\n- **Records are constructible only when their bytes can be copied.** Neither\n `Gsk.ColorStop` nor `Graphene.Rect` is caught by it.\n- **Single-child widgets have no `content`/`child` props** (pass the widget as\n a child instead).";
1726
+ readonly text: "Carried forward from the rc.3/rc.4/1.0 notes; none of this is re-verified by\nthis task (it is `@gtkx/testing`-adjacent runtime behavior, out of this\nepic's scope — see the `gtkx-1-0-migration` epic's task 004 for the last\nfull re-check):\n\n- **Blockable signals are no longer suppressed for a whole React commit** —\n rc.3 wraps each framework write individually, so an emission the framework\n did not cause (one raised from a `useLayoutEffect`, or aimed at another\n `createRoot` tree) reaches its handler. Our navigators lean on this.\n- **`render`'s harness window is undecorated**, so role queries see only what\n the test rendered.\n- **A widget with `accessibleLabelledBy` reports the relation as its\n accessible name**, ahead of its own text — the precedence ARIA defines.\n- **`toHaveTextContent` no longer falls back to the accessible name**;\n **`toHaveDisplayValue` throws** on a widget without one; **checked state is\n tri-state**.\n- **Records are constructible only when their bytes can be copied.** Neither\n `Gsk.ColorStop` nor `Graphene.Rect` is caught by it.\n- **Single-child widgets have no `content`/`child` props** (pass the widget as\n a child instead).";
1711
1727
  }, {
1712
- readonly doc: "docs/gtkx-rc4-notes.md";
1728
+ readonly doc: "docs/gtkx-1.2-notes.md";
1713
1729
  readonly heading: "Non-workarounds (quirks that stay)";
1714
- readonly text: "- 64-bit FFI values arrive as BigInt → `toNumber()` at the boundary\n (`gtkx/bridge/measure.ts`);\n- signal names are kebab-case (\"value-changed\"); signals do not pass the\n emitter (get the widget from a ref);\n- role queries in tests use the `Gtk.AccessibleRole` enum, not strings;\n- `npm install` prunes the codegen store (`node_modules/.gtkx` is not in the\n lockfile) → run `npm run codegen` after installing — npm behavior, not gtkx;\n- measuring unmapped widgets yields 0 (offscreen Label probes are the\n exception) → re-measure on the `map` signal + re-commit measured leaves on\n every flush (`layout/node.ts`);\n- mixed-session setups only: running an app on a bare compositor (headless\n sway) while `XDG_RUNTIME_DIR` points at a full GNOME session can segfault in\n a GTK signal handler when the GNOME settings portal pushes updates into the\n app (`g_cclosure_marshal_VOID__OBJECTv` via the FFI emit path); cutting\n `DBUS_SESSION_BUS_ADDRESS` avoids it, which is why the headless scripts do.\n Normal desktop and container runs are unaffected. The portal-push crash\n needs a live settings change to trigger and stays on the list unconfirmed.";
1730
+ readonly text: "- 64-bit FFI values arrive as BigInt → `toNumber()` at the boundary\n (`gtkx/bridge/measure.ts`);\n- signal names are kebab-case (\"value-changed\"); signals do not pass the\n emitter (get the widget from a ref);\n- role queries in tests use the `Gtk.AccessibleRole` enum, not strings;\n- `npm install` prunes the codegen store (`node_modules/.gtkx` is not in the\n lockfile) → run `npm run codegen` after installing — npm behavior, not gtkx;\n- measuring unmapped widgets yields 0 (offscreen Label probes are the\n exception) → re-measure on the `map` signal + re-commit measured leaves on\n every flush (`layout/node.ts`);\n- mixed-session setups only: running an app on a bare compositor (headless\n sway) while `XDG_RUNTIME_DIR` points at a full GNOME session can segfault in\n a GTK signal handler when the GNOME settings portal pushes updates into the\n app (`g_cclosure_marshal_VOID__OBJECTv` via the FFI emit path); cutting\n `DBUS_SESSION_BUS_ADDRESS` avoids it, which is why the headless scripts do.\n Normal desktop and container runs are unaffected. The portal-push crash\n needs a live settings change to trigger and stays on the list unconfirmed;\n- GLib criticals and errors now raise as a Node `uncaughtException` (1.2.1,\n see upstream-gtkx.md ask #2) — a `g_return_if_fail` violation that used to\n scroll past in a log now ends an app with no `process.on(\"uncaughtException\")`\n handler. `gtkx dev` already installs one; so does every headless probe\n script this repo uses.";
1715
1731
  }, {
1716
- readonly doc: "docs/gtkx-rc4-notes.md";
1732
+ readonly doc: "docs/gtkx-1.2-notes.md";
1717
1733
  readonly heading: "Procedure when the next release ships";
1718
- readonly text: "1. Update the `@gtkx/*` pins (root, spike, examples, template), then\n `npm install && rm -rf node_modules/.gtkx && npm run codegen`;\n2. Run everything on Linux: `npm run typecheck && npm test`, `build:dist`,\n `check:package`, plus the headless example proofs;\n3. Walk the live-workaround table: for each row check the removal condition,\n delete the tag and the row together when it is met, and move the entry into\n the history section above — **with a probe that proves the fix on the real\n runtime**, not just the release notes claiming it. And make the probe\n reproduce the ORIGINAL failure first: two of the three `runtime-dedupe`\n probes written for rc.4 reported a fix that was not there, because they\n were not actually building the duplicate;\n4. Re-tag whatever survives (`RC4-WORKAROUND` → the new release), rename this\n file to match the new pin, and update `docs/upstream-gtkx.md` if an ask was\n answered.";
1734
+ readonly text: "1. Update the `@gtkx/*` pins (root, spike, examples, template), then\n `npm install && rm -rf node_modules/.gtkx && npm run codegen`;\n2. Run everything on Linux: `npm run typecheck && npm test`, `build:dist`,\n `check:package`, plus the headless example proofs;\n3. Walk the live-workaround table: for each row check the removal condition,\n delete the tag and the row together when it is met, and move the entry into\n the history section above — **with a probe that proves the fix on the real\n runtime**, not just the release notes claiming it. And make the probe\n reproduce the ORIGINAL failure first: two of the three `runtime-dedupe`\n probes written for rc.4 reported a fix that was not there, because they\n were not actually building the duplicate; the `use-signal-stale-handler`\n guard the 1.2 audit flipped is the same discipline the other direction —\n running the UNFLIPPED `it.fails` guard on the new runtime first, so the\n \"now fails to fail\" observation is the actual probe, not an assumption.\n The same discipline applies to every future audit, this one included;\n4. Re-tag whatever survives (`1.2-WORKAROUND` → the new release), rename this\n file to match the new pin, and update `docs/upstream-gtkx.md` if an ask was\n answered.";
1719
1735
  }, {
1720
1736
  readonly doc: "docs/research/navigation-extensibility.md";
1721
1737
  readonly heading: "1. The two layers";
@@ -1737,21 +1753,21 @@ export declare const DOC_CHUNKS: readonly [{
1737
1753
  readonly heading: "5. The desktop-RN landscape";
1738
1754
  readonly text: "No other desktop React Native platform has native navigation integration:\n\n- **react-native-screens** lists Windows support, but it is a thin\n old-architecture module; native-stack on modern react-native-windows\n fails, because screens has no new-architecture Windows implementation and\n RNW 0.82 removed the old one. Microsoft's own react-native-gallery falls\n back to the JS drawer.\n- **react-native-macos** is not supported by react-native-screens at all —\n AppKit has no navigation-stack primitive to bind to.\n\nOur path — a real `Adw.NavigationView` driven from a custom navigator, with\nreact-navigation state as the source of truth and native pops reported back\ninto it — is structurally the iOS native-stack / `UINavigationController`\nmodel, which neither desktop platform reached. GTK's advantage is that the\nprimitive exists at all: back button, Escape, back gesture, history menu and\ntransitions ship with the widget.\n\nThe trade: a JS stack can render anything into its fake header, while our\nchrome is real and had to be opened up deliberately — which is what the\nintrinsic-size root does.\n\nSources: react-native-screens README and discussions #1575 / #2541, RNW\ndiscussions #14273 / issue #4152 / new-architecture docs / 0.82 release\npost, microsoft/react-native-gallery, reactnavigation.org native-stack docs.";
1739
1755
  }, {
1740
- readonly doc: "docs/gtkx-rc4-notes.md";
1741
- readonly heading: "RC4-WORKAROUND(use-signal-stale-handler)";
1742
- readonly text: "`useSignal` still routes the handler through React's `useEffectEvent`; `react-reconciler@0.33.0` only refreshes it in `commitBeforeMutationEffects` for `case 0` (FunctionComponent) `case 11` (ForwardRef) and `case 15` (SimpleMemoComponent) fall through unrefreshed, so any `useEffectEvent` in a `memo`/`forwardRef` component is pinned to its mount closure forever (our `ScrollView` is a `forwardRef` with the `useSignal` calls inside it) a fetch-fed FlatList empties itself on the first scroll. **rc.4 did not bump React and did not change the path** see below — our workaround: `gtkx/bridge/use-signal.ts` re-pins the latest handler (insertion effect) and hands gtkx a stable wrapper; the bridge exports that hook, not gtkx's — removed when: A stable React 19.3 (React fixed the refresh on the 19.3 line)";
1756
+ readonly doc: "docs/gtkx-1.2-notes.md";
1757
+ readonly heading: "1.2-WORKAROUND(runtime-dedupe)";
1758
+ readonly text: "Two bundled copies of the gtkx runtime still double-init GLib and abort; nothing guards against it across two DISTINCT `.node` files. Reproduced on 1.2.2 by actually building the duplicate: `gtkx: GLib-ERROR: g_log_set_writer_func() called multiple times`. The FAILURE MODE changed since 1.0 (see upstream-gtkx.md ask #2) it is now a catchable, named Node `uncaughtException` instead of a SIGABRT/core dumpbut the underlying defect has not: the init is still not idempotent across distinct binaries, and the error still does not name the duplicate package — our workaround: `src/vite/index.ts` puts `resolve.dedupe` over `@gtkx/*` + `react` (+ `@react-navigation/*` for its context) into the preset every app inherits — removed when: Idempotent runtime init upstream across distinct native-addon files, or an error that names the duplicate";
1743
1759
  }, {
1744
- readonly doc: "docs/gtkx-rc4-notes.md";
1745
- readonly heading: "RC4-WORKAROUND(runtime-dedupe)";
1746
- readonly text: "Two bundled copies of the gtkx runtime still double-init GLib and abort; nothing guards against it. Reproduced on rc.4: `g_log_set_writer_func() called multiple times`, SIGABRT — our workaround: `src/vite/index.ts` puts `resolve.dedupe` over `@gtkx/*` + `react` (+ `@react-navigation/*` for its context) into the preset every app inherits — removed when: Idempotent runtime init upstream, or an error that names the duplicate";
1760
+ readonly doc: "docs/gtkx-1.2-notes.md";
1761
+ readonly heading: "1.2-WORKAROUND(prop-portal)";
1762
+ readonly text: "`createPortal(children, container, key?)` is still byte-for-byte the rc.3/rc.4 signature and can still only target a container's DEFAULT slot (\"children\"). `@gtkx/react`'s public entry and its `/internal` subpath both still withhold the `\"gtkx:prop\"` element — `/internal` still re-exports `createElementComponent` (the function that BUILDS an intrinsic element by GType name) but not the `Prop` literal itself, which stays a sibling export inside `element.js` that no public or `/internal` path reaches — our workaround: `gtkx/bridge/slot-portal.ts` restates the `\"gtkx:prop\"` element name and wraps it in `createSlotPortal(children, target, slot)` the one line that would move if gtkx renames it. `WindowActions`/`ApplicationActions`/`WindowControllers` are built on it — removed when: gtkx exports a slot-aware portal (or the prop element) from a public entry point";
1747
1763
  }, {
1748
- readonly doc: "docs/gtkx-rc4-notes.md";
1749
- readonly heading: "RC4-WORKAROUND(prop-portal)";
1750
- readonly text: "`createPortal(children, container, key?)` is byte-for-byte the rc.3 signature and can still only target a container's DEFAULT slot (\"children\"). Every other slot an object exposes declaratively a window's `Gio.ActionMap` (`actions`), a widget's `controllers`, an `AdwApplicationWindow`'s `breakpoints` — is reached only by passing an element-valued PROP, which the reconciler routes through an internal `\"gtkx:prop\"` element. rc.4 exports that element from neither `@gtkx/react` nor `/internal`, and its `exports` map now refuses the deep path outright — our workaround: `gtkx/bridge/slot-portal.ts` restates the `\"gtkx:prop\"` element name and wraps it in `createSlotPortal(children, target, slot)` the one line that would move if gtkx renames it. `WindowActions`/`ApplicationActions`/`WindowControllers` are built on it — removed when: gtkx exports a slot-aware portal (or the prop element) from a public entry point";
1764
+ readonly doc: "docs/gtkx-1.2-notes.md";
1765
+ readonly heading: "1.2-WORKAROUND(renderhook-no-window)";
1766
+ readonly text: "`renderHook` still mounts into a bare `Gtk.Box`1.2.2's `render-hook.js` still passes `container: new Gtk.Box()` unconditionally, and `RenderHookOptions` still carries only `wrapper` and `initialProps` so window-dependent APIs have no toplevel to read — our workaround: Hook tests create a window with `render()` first (`tests/gtk/apis/dimensions.test.tsx`); packaged for consumers as `renderHookWithWindow` (`react-native-gtkx/testing`) — removed when: `renderHook` mounts into the same harness window `render` uses";
1751
1767
  }, {
1752
- readonly doc: "docs/gtkx-rc4-notes.md";
1753
- readonly heading: "RC4-WORKAROUND(renderhook-no-window)";
1754
- readonly text: "`renderHook` still mounts into a bare `Gtk.Box` — rc.4's `render-hook.js` is byte-identical to rc.3's, which was byte-identical to rc.2's so window-dependent APIs have no toplevel to read our workaround: Hook tests create a window with `render()` first (`tests/gtk/apis/dimensions.test.tsx`); packaged for consumers as `renderHookWithWindow` (`react-native-gtkx/testing`) — removed when: `renderHook` mounts into the same harness window `render` uses";
1768
+ readonly doc: "docs/gtkx-1.2-notes.md";
1769
+ readonly heading: "1.2-WORKAROUND(gtk-application-argv)";
1770
+ readonly text: "`@gtkx/react`'s `<GtkApplication>` bootstrap still builds the GApplication's own command line as `[applicationId.split(\".\").at(-1), ...process.argv.slice(2)]` and hands it to `runApplication(application, commandLine)` confirmed unchanged by reading the installed 1.2.2 source (`components/application.tsx`) directly, no `argv` prop or options object anywhere on `ApplicationComponentProps`. Our runner hosts are invoked as `node dist/runner/host.js <bundle-path>` / `node dist/runner/host-dev.js <bundle-url>`, so `process.argv[2]` is our OWN internal plumbing, not a user-facing argument — left in place it reaches GLib's local command-line handling as a stray positional it tries to open as a file (`GLib-GIO-CRITICAL: This application can not open files`), and the window never activates — our workaround: `process.argv.length = 2` right after each host reads its own positional, before the bundle runs (`src/runner/host.ts`, `host-dev.ts`) both mount `<GtkApplication>` from a `useLayoutEffect`, which always fires after this line. Not needed on the vite path (`gtkx build`/`gtkx dev` bundles run with no extra positional) or the SEA build (which never reads `process.argv`) — removed when: `@gtkx/react` exposes a way to pass `runApplication`'s argv explicitly (an `argv` prop on `<GtkApplication>`, or an options object) instead of always reading `process.argv` itself";
1755
1771
  }, {
1756
1772
  readonly doc: "docs/reference/components/view.md";
1757
1773
  readonly heading: "View";