Skip to content

build: bump @gjsify/* to 0.48.0 - #181

Merged
JumpLink merged 8 commits into
mainfrom
chore/bump-gjsify-0.48.0
Sep 5, 2026
Merged

build: bump @gjsify/* to 0.48.0#181
JumpLink merged 8 commits into
mainfrom
chore/bump-gjsify-0.48.0

Conversation

@JumpLink

@JumpLink JumpLink commented Sep 5, 2026

Copy link
Copy Markdown
Owner

The toolchain was 38 minors behind, and it was behind in four different places at
once. @gjsify/* was declared as ^0.10.0, ^0.14.0 and ^0.16.3 across ten
manifests; CI bootstrapped ^0.8.0 globally and then called bare gjsify for
every step; and the Flatpak build ran a vendored bundle last cut at 0.10.0. One
green badge, four toolchains, and none of them the one a developer runs.

All of it now says 0.48.0.

What the bump broke, and what fixes it

Adwaita namespaced its widget classes. AdwButton is Gtk.Button,
AdwClamp is Adw.Clamp, and so on — the GNOME names the web and Android twins
already used in their own comments. 18 classes in app-web, 17 in app-android.
AdwCard, AdwBottomSheetContent, AdwBottomSheetSheet and AdwSourceView
stay flat.

Three renames the compiler cannot see. These are the ones worth reviewing:

Was Is How it would have failed
AdwComboRow attribute items model Silently. An unknown attribute is ignored, so the hex monitor's region list would render EMPTY.
GtkMenuButton.menuItems menuModel Silently. Same shape, no menu.
AdwViewSwitcherBar.revealed = .reveal = Loudly, at runtime. revealed is now {get: true, set: false}; assigning it from a module throws TypeError: Cannot set property revealed. app-web drives that line on every mobile layout switch.

@girs had moved to flat versioning. app-gnome pinned the old
1.10.0-4.0.4 scheme while @gjsify/storybook@0.48.0 depends on 4.6.0, so a
second copy of every ambient .d.ts landed under storybook's node_modules
119 duplicate global declarations. All 12 pins are now 4.6.0.

--app lost its hard default in gjsify 0.19.0 and follows the host runtime
instead. app-gnome's build:gjs passes no --app, so a Node-hosted CLI would
write a Node bundle carrying a GJS shebang, with no error. Fixed by declaring
gjsify.app: "gjs".

The lockfile format moved 2 → 4. An older CLI cannot read v4, which is why
build-aux/gjsify.gjs.mjs is re-vendored and gjsify-sources.json regenerated
in the same commit — its 168 @gjsify tarballs across three versions are now 136
at 0.48.0. Without that the Flatpak leg of this very PR could not build.

The CI pin

ci.yml and deploy-pages.yml bootstrapped @gjsify/cli@^0.8.0 and never
switched to the workspace copy, so format, lint, the builds and the type
checks all ran the bootstrap. They now prepend the workspace bin to
GITHUB_PATH after install — the step issue-to-pr.yml already had — and every
bootstrap pin is exact.

A PATH step is silent when it fails to take effect, so both jobs also assert
gjsify --version equals the version in node_modules/@gjsify/cli. Drift
becomes an error message rather than a number nobody reads.

This mattered more than a version skew: on the pinned 0.16.3, gjsify format --check and gjsify lint crash with a yargs "unknown arguments"
error, and lint then hangs forever (killed after 632 s). Upstream fixed it in
0.16.4 — the next patch after the pin. Nobody noticed because CI ran 0.8.x.

Three failures only CI could show

The first CI run on this branch went red in both container jobs, and neither was
reproducible from a local checkout. Both are fixed here.

The Fedora image is missing two libraries the new native bundler needs.
@gjsify/rolldown-native 0.48.0's typelib pulls libjson-glib-1.0.so.0 and
libsoup-3.0.so.0; bare fedora:43 plus the workflow's own
git tar xz findutils gjs has neither. Measured by running that prereq line in a
container: of the seven libraries the native prebuilds need, exactly those two
are absent. GJS surfaces it as a GIRepository warning followed by
Unsupported type void, attributed to whichever step first loads the bundler —
here "Validate translation catalogs", which has nothing to do with it. A GNOME
desktop already has both, which is why every local run was green.

--with-dependencies silently builds nothing in this repo. The Flatpak build
asked meson for gjsify workspace @learn6502/app-gnome build --with-dependencies,
but collectTransitiveClosure only follows deps whose spec starts with
workspace:, and this monorepo declares its internal deps as plain ^x.y.z
ranges deliberately (pnpm-workspace.yaml says why). The flag matched nothing,
built nothing, printed nothing and exited 0 — then gjsify build --app gjs
aborted on @learn6502/core and learn's two generated .ui files. Reproduced
locally from a clean tree with both flag spellings and --include-dev. meson now
spells out core -> examples -> learn, the order the workflows already use;
from clean that chain exits 0 and produces the same 784530-byte executable.

The learn build now needs GTK typelibs in a container that had none. The
CLI auto-bundles the by-name @mdx-js/rollup plugin, and bundlePluginForGjs
passes no globals policy — that bundle is always --globals auto, which since
0.48.0 sees HTMLCanvasElement/document/navigator in the plugin's graph and
injects GTK-backed registers. The result demands Pango, PangoCairo, Gdk and
GdkPixbuf at import. learn's own --globals node scopes learn's bundle, not the
plugin's, and nothing in package.json can reach it. Measured in a container:
all four typelibs absent after the prereq line, all four supplied by
pango gtk4 gdk-pixbuf2. This gives up a property the type-check container had
on purpose (#143's follow-up scoped learn's globals precisely to keep it
GTK-less) — getting it back is a gjsify fix, and the workflow comment says so.

A related sharp edge, recorded but not fixed here: app-web's build hits the same
missing-dependency condition and exits 0 anyway, emitting a bundle 91 KB
smaller than the correct one, because only --app gjs treats an unresolved bare
specifier as fatal.

Measured

Every command run with node_modules/.bin/gjsify 0.48.0, never the one on
PATH. Type-error counts are re-derived, not carried over.

Gate Before After
format --check crash + exit 1 0 (294 files)
lint crash, then hung 0 (190 warnings, 0 errors)
workspace core build 0 0
workspace examples build 0 0
workspace learn build 0 0
workspace translations check 0 0
workspace learn check 0 0
workspace core check 0 0
workspace common-ui check 0 0
workspace app-web check 1 — 31 errors 0
workspace app-gnome check:typescript 1 — 119 errors 0
workspace app-android check 1 — 25 errors 0
workspace cli check 0 0
workspace app-web build:app 1 — 28 bundler errors 0

gjsify install --immutable succeeds and leaves the lockfile untouched, which is
what CI runs.

And CI agrees, on its own hardware: Code Quality 39s, Type check (GJS 1.86)
3m9s, Flatpak Build 3m18s — all green.
Both container jobs print the line the
new assert exists for:

gjsify: /__w/Learn6502/Learn6502/node_modules/.bin/gjsify -> 0.48.0 (lockfile: 0.48.0)

That is the workspace copy, not the global bootstrap — the first time this
repository's CI can show which toolchain it measured. The Flatpak leg installs
the executable, gresource, metainfo, schemas and icons, exports the repo and
emits a real eu.jumplink.Learn6502-x86_64.flatpak.zip.

Exercised, not just compiled

A green typecheck cannot see a bundle that builds and does nothing.

  • Headless CLI — assembles stack-example to the expected 24 bytes, emits
    the correct disassembly, and simulates snake to completion in 15473
    steps
    .
  • Web bundle in headless Chrome — app.js (1.04 MB) loads with every custom
    element upgraded (toolbar view, view stack, source view ×32, combo row, split
    view, 5 clamps), zero console errors and zero uncaught exceptions. The
    combo row carries its 7 real options, which is the itemsmodel fix
    proving itself. At a 420×780 viewport the mobile branch runs and the switcher
    bar reports reveal: true.
  • GNOME app — builds to a 784 KB GJS executable plus gresource and compiled
    schemas, and starts on Wayland through all four views with no exceptions. The
    gettext plugin still emits 928 translated entries across 14 languages.

Deliberately left open

  • Two upstream defects in @gjsify/adwaita-nativescript@0.48.0. AdwClamp
    declares private _measuredWidth, which ViewCommon already declares
    private; AdwNavigationView narrows the public Observable._emit to private.
    Either makes the widget non-assignable to View. Both are type-only — the
    runtime objects are real NativeScript views. Five call sites go through a
    documented asView helper (packages/app-android/app/utils/as-view.ts) that
    names both and is meant to be deleted once the fix ships. This belongs in a
    gjsify PR and is not opened here.
  • The translation catalogs are untouched. Building @learn6502/translations
    does regenerate them (the POT grows 457 → 461 msgids, no catalog shrinks), but
    that is Translations: catalogs and Android i18n lag the sources #179's lag closing and it wants translator review, not a toolchain
    bump. Reverted here.
  • The type-check container is no longer GTK-less. The plugin bundler should
    either stop injecting DOM globals or take a policy from the consumer; until
    then pango gtk4 gdk-pixbuf2 stay in the workflow.
  • gjsify workspace --with-dependencies is effectively dead for this repo
    until it stops requiring the workspace: protocol, or the repo adopts that
    protocol. The meson call site no longer depends on it; nothing else used it.
  • gjsify lint needs a real node on PATH under GJS — oxlint has no
    native bridge. CI's lint job is on ubuntu with Node, so this is fine today.
  • Build: package scripts that swallow their exit status #180 is largely fixed upstream. The in-process dispatch that swallowed exit
    status was closed in gjsify 0.23.0. Re-measured on 0.48.0: a single-command
    script body wrapping a throwing bundle exits 1, and app-web's build:app
    propagated a failing bundle as exit 1. AGENTS.md records the re-measurement
    and keeps the chains — the residual hole (a command that neither throws nor
    sets process.exitCode) is narrower, not closed.

AGENTS.md also drops two vite plugins that stopped being local packages, adds
packages/cli, and stops crediting app-web to Jekyll.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YQ87zT8bbwAhB1Hn264Krx

The tree declared three ranges of one toolchain — ^0.10.0, ^0.14.0 and ^0.16.3
across ten manifests — which gjsify's installer resolves into a single hoisted
slot, so every workspace built against whichever one happened to win. All 20
declarations are now ^0.48.0 and the lockfile holds one version: 142 top-level
@gjsify packages, all 0.48.0. The lockfile also moves from format 2 to 4, which
0.48.0 writes; an older CLI cannot read it, which is why the vendored Flatpak
bundle and gjsify-sources.json are re-cut in the same commit.

Adwaita moved its widget classes into Adw/Gtk namespaces, matching the GNOME
names the web and Android twins already mirror in their own comments. That is 18
classes in app-web and 17 in app-android, plus three renames the compiler cannot
see: AdwComboRow's `items` attribute became `model` (an unknown attribute is
ignored, so the hex monitor's region list rendered EMPTY), GtkMenuButton's
`menuItems` became `menuModel`, and AdwViewSwitcherBar's `revealed` lost its
setter in favour of `reveal` — measured in a browser, `revealed` is now
{get: true, set: false} and assigning it from a module throws
"TypeError: Cannot set property revealed". app-web drives that line on every
mobile layout switch.

app-gnome's @Girs pins moved to 4.6.0, the flat generator versioning that
@gjsify/storybook 0.48.0 depends on. The old 1.10.0-4.0.4 scheme left a second
copy of every ambient .d.ts under storybook's node_modules and 119 duplicate
global declarations with it. It also gains `gjsify.app: "gjs"`: `--app` lost its
hard default in gjsify 0.19.0 and now follows the host runtime, so `build:gjs`
(which passes no --app) produced a Node bundle carrying a GJS shebang whenever a
Node-hosted CLI ran it — silently, since nothing errors.

Measured with the project's own node_modules/.bin/gjsify 0.48.0. Type errors:
app-web 31 -> 0, app-gnome 119 -> 0, app-android 25 -> 0. All 14 gates green.
Exercised, not just compiled: the headless CLI assembles stack-example to the
expected 24 bytes and simulates snake to completion in 15473 steps; the web
bundle loads in headless Chrome with every custom element upgraded, the combo
row carrying its 7 real options, and zero console errors or uncaught exceptions;
the GNOME app builds and starts on Wayland through all four views.

Five app-android call sites go through a documented `asView` helper. Two
@gjsify/adwaita-nativescript widgets redeclare a member NativeScript's View
already owns — AdwClamp's private `_measuredWidth` and AdwNavigationView's
narrowing of the public `Observable._emit` — so they are not assignable to View.
Both are type-only and belong upstream; the helper names them and is meant to be
deleted on the next bump.

Claude-Session: https://claude.ai/code/session_01YQ87zT8bbwAhB1Hn264Krx
Every workflow bootstrapped @gjsify/cli@^0.8.0 globally and then called bare
`gjsify` for all of format, lint, the builds and the type checks. Nothing after
the install step used the CLI the tree resolves, so CI measured 0.8.x while a
developer measured 0.16.3 and the Flatpak build measured the vendored 0.10.0 —
three toolchains, one green badge. The badge was not wrong about 0.8.x; it was
silent about everything else, and `gjsify format --check` and `gjsify lint` in
fact CRASHED on the pinned CLI (a yargs "unknown arguments" error, with lint
then hanging forever) from 0.16.3 until the fix in 0.16.4.

ci.yml and deploy-pages.yml now prepend the workspace bin to GITHUB_PATH after
install, the step issue-to-pr.yml already had, and every bootstrap pin is exact
0.48.0 rather than a floating caret. A PATH step is silent when it fails to take
effect and a CLI that merely runs proves nothing about which one it is, so both
jobs also assert that `gjsify --version` equals the version in
node_modules/@gjsify/cli — the drift becomes an error message instead of a
number nobody reads.

Claude-Session: https://claude.ai/code/session_01YQ87zT8bbwAhB1Hn264Krx
The "package scripts must be a chain" rule cited a measurement on the CLI this
repo pinned at the time (0.16.3), and that pin has just moved. The defect it
describes — a single-command script body dispatched in-process, then exiting
with a `process.exitCode` its commands never set — was fixed upstream in gjsify
0.23.0. Re-measured here on 0.48.0: a script whose whole body is
`gjsify run <throwing bundle>` exits 1, and app-web's single-command `build:app`
propagated a failing bundle as exit 1.

The rule stays, and so does the incident behind it (issue #180, the catalog
check that reported "All translations passed" over catalogs it had emptied) — a
rule stripped of its reason gets simplified back into the bug, and the residual
hole is narrower rather than closed: a dispatched command that neither throws
nor sets process.exitCode still yields 0.

Also: bare `gjsify format` writes since 0.29.0 (`--write` defaults to true), so
the pre-commit line said "check" for a step that edits files. And the package
table listed two vite plugins as local packages long after they moved to
@gjsify/*, omitted packages/cli entirely, and still credited app-web to Jekyll,
which its own section says was removed.

Claude-Session: https://claude.ai/code/session_01YQ87zT8bbwAhB1Hn264Krx
Both failures came out of the first CI run on this branch, and neither was
visible from a local checkout.

The Fedora jobs run a bare `fedora:43` that installs `git tar xz findutils gjs`.
@gjsify/rolldown-native 0.48.0's typelib now pulls libjson-glib-1.0.so.0 and
libsoup-3.0.so.0, and neither is in that image — measured by running the
workflow's own prereq line in a container: of the seven libraries the native
prebuilds need, exactly those two are missing. GJS reports it as a
GIRepository warning and then "Unsupported type void", which reads like a
translation-catalog failure because that is the first step that loads the
bundler. Added json-glib + libsoup3 to all four Fedora workflows. A developer
does not see this: a GNOME desktop already has both.

The Flatpak build assembled app-gnome without its dependencies. meson asked for
them with `gjsify workspace @learn6502/app-gnome build --with-dependencies`, but
`collectTransitiveClosure` only follows deps whose spec starts with
`workspace:`, and this monorepo declares its internal deps as plain `^x.y.z`
ranges on purpose (pnpm-workspace.yaml explains why). So the flag matched
nothing, built nothing, reported nothing and exited 0 — and `gjsify build
--app gjs` then aborted on `@learn6502/core` and learn's two generated `.ui`
files as unresolved bare specifiers. Reproduced locally from a clean tree, with
both flag spellings and with --include-dev. meson now spells out core ->
examples -> learn, the order the CI workflows already use; verified from clean,
the chain exits 0 and produces the same 784530-byte executable.

Worth recording: app-web's build has the same missing-dependency condition and
exits 0 anyway, emitting a bundle 91 KB smaller than the correct one, because
only `--app gjs` treats an unresolved bare specifier as fatal.

Claude-Session: https://claude.ai/code/session_01YQ87zT8bbwAhB1Hn264Krx
The second CI run got past the catalogs and died building @learn6502/learn:
"Requiring Pango, version 1.0: Typelib file for namespace 'Pango' not found".

Not learn's own bundle — the plugin's. The CLI auto-bundles the by-name
`@mdx-js/rollup` plugin, and `bundlePluginForGjs` passes no globals policy, so
that bundle is always built with `--globals auto`. Since 0.48.0 auto sees
HTMLCanvasElement, document and navigator in the plugin's graph and injects
GTK-backed registers, leaving a bundle that demands Pango, PangoCairo, Gdk and
GdkPixbuf at import. learn's own `--globals node` scopes learn's bundle and
never reaches the plugin's, and nothing in package.json can reach it either.

Measured in a fedora:43 container: after the workflow's prereq line all four
typelibs are absent, and `pango gtk4 gdk-pixbuf2` supplies all four.

This gives up a property the type-check container had on purpose — it was
GTK-less, and #143's follow-up specifically scoped learn's globals to keep it
that way. Getting it back needs the plugin bundler to stop injecting DOM
globals, or to accept a policy from the consumer; both are gjsify's to fix. The
comment in the workflow says so, so this comes out again rather than settling.

Claude-Session: https://claude.ai/code/session_01YQ87zT8bbwAhB1Hn264Krx
app.css says "keep in sync with the package on a widget bump", and the
bump to @gjsify/adwaita-nativescript 0.48.0 did not carry it: the tracked
copy was byte-identical to the 0.16.3 theme (1490 lines vs the package's
2137, verified against the 0.16.3 tarball).

Nothing failed, because nothing looks at it. CI runs `app-android check`,
which is `gjsify tsc --noEmit` — a stylesheet is invisible to it.

What the drift actually cost, measured by intersecting the class strings
in lib/esm with each sheet: 19 classes the 0.48.0 widgets emit have a
rule upstream and none in the tracked copy. One of them is rendered by
this app — `.adw-preferences-group-header` (+ `.single-line`), which the
five Adw.PreferencesGroup instances build. 0.48.0 moved the header
metrics off `.adw-preferences-group-title` (now `margin: 0`) onto that
wrapper, so with the old sheet the wrapper gets no margin and no 34 DIP
single-line floor. The other 18 (drop-down, entry, data-grid,
shortcut-label, sidebar edges, badges) are latent: no widget in this app
draws them today.

The reverse direction is safe. The two removed declarations are inert
here: `padding-*: var(--safeAreaInsetTop/Bottom, 0)` never resolved (the
NS CSS subset has no custom properties; AdwToolbarView applies the real
inset itself since gjsify #1128), and `.adw-wrap-box`'s margin belongs
to a widget this app does not use.

Not verified: no Android device or emulator here, so this is the
package's own shipped theme taken as authored, not a rendering test.

Claude-Session: https://claude.ai/code/session_01YQ87zT8bbwAhB1Hn264Krx
The table still credited app-android to "Material Design 3". #156/#157
ported that app to native Adwaita widgets and dropped Material: its
package.json depends on @gjsify/adwaita-nativescript and on no Material
package, app.ts calls registerAdwaita(), and every `Material` left in
the tree is a comment describing what was replaced.

The same commit that fixed the app-web row (Jekyll ->
`@gjsify/adwaita-web`) left this one, so the file kept sending readers
to a toolkit the app has not used for two releases.

Claude-Session: https://claude.ai/code/session_01YQ87zT8bbwAhB1Hn264Krx
The learn build needs Pango/Gdk/GdkPixbuf typelibs since 0.48.0, and this
PR added `pango gtk4 gdk-pixbuf2` to ci.yml and deploy-pages.yml but not
here — while issue-to-pr.yml runs `gjsify workspace @learn6502/learn
build` in the same bare fedora:43 image, at its "Type check the generated
example" step.

Nothing on a PR can see it: this workflow is issue-triggered, so all
three PR checks stay green and the break surfaces on the next example
submission instead.

Reproduced in fedora:43 with exactly the old prereq line plus Node 24:
core and examples build, then learn exits 1 with

  gjsify config: failed to import the GJS bundle for plugin
  "@mdx-js/rollup" ... (Requiring Pango, version 1.0: Typelib file for
  namespace 'Pango', version '1.0' not found)

package-managers.yml needs no change — it builds only `@learn6502/core`
and type-checks common-ui, which its own comment already scopes as "gjs
only, no GTK build deps".

Claude-Session: https://claude.ai/code/session_01YQ87zT8bbwAhB1Hn264Krx
@JumpLink
JumpLink merged commit 9c1c9ec into main Sep 5, 2026
3 checks passed
JumpLink added a commit to gjsify/workspace that referenced this pull request Sep 5, 2026
JumpLink/Learn6502#181 landed: `@gjsify/*` 0.16.3 → 0.48.0 across ten manifests,
`@girs/*` pinned at 4.6.0, and all four CLI bootstraps pinned exactly to 0.48.0
(they were `^0.8.0` / `^0.10.0`, so CI had been measuring a different toolchain
than local work did).

The independent review round found three defects none of the three green checks
could see:

- `issue-to-pr.yml` never got the `pango gtk4 gdk-pixbuf2` prerequisites the bump
  added to the other two workflows, and runs in the same bare `fedora:43`. It is
  issue-triggered, so it would have broken on the next example submission, where
  no PR check looks. Reproduced in a container, then fixed.
- `packages/app-android/app/adwaita.css` was still the 0.16.3 theme while the
  widgets shipped 0.48.0 — 19 emitted classes had a rule upstream and none in the
  vendored copy. `check` there is `tsc --noEmit`, which cannot see a stylesheet.
- `AGENTS.md` still credited app-android to Material Design 3, dropped in #156.

The element-versus-class rename trap was checked explicitly rather than assumed:
the four removed `adw-*` tags have zero references in the tree, and the CSS classes
did not move with the tags — the shipped widgets still emit `adw-button`, so the
styling is intact. Verified in headless Chrome, not by type-check.

Claude-Session: https://claude.ai/code/session_01YQ87zT8bbwAhB1Hn264Krx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant