Development Setup
Build the workspace and run an example while making changes. CI verifies the change through its Verify workspace check. The package map identifies each package's role.
Prerequisites
Use Linux with Node.js 26.7 or later. The root package.json pins pnpm through its packageManager field. If mise manages your runtimes, run the commands below through mise exec --, for example mise exec -- pnpm install.
Workspace tsc commands use the native TypeScript 7 compiler through the @typescript/native dependency alias. The typescript catalog entry aliases the TypeScript 6 compatibility package so code generation, Vue tooling, and batched semantic tests keep the stable compiler API. pnpm exec tsc --version reports the native compiler and pnpm exec tsc6 --version the compatibility compiler. Application templates and the tutorial declare their own TypeScript dependency.
Install Rust through rustup so rust-toolchain.toml selects the pinned compiler and Clippy. Native formatting and sanitizers use a separate nightly; Change native code shows how to install it.
The root lint target also needs Go and ShellCheck: its workflow check runs pinned actionlint and yq versions through go run, and actionlint checks workflow scripts with ShellCheck.
The full workspace needs more system libraries than a minimal application:
| Work | System dependencies |
|---|---|
| Native addon and bindings | A C build toolchain, pkg-config, GObject Introspection, GLib, GTK4, and libadwaita development files. |
| Workspace library selection | GtkSourceView 5 and WebKitGTK 6.0 development files and GIR metadata. |
| Native integration fixtures | Git, Meson, Ninja, and the introspection scanner. |
| Headless examples and tests | Sway, dbus-daemon, Mesa rendering support, fonts, icons, MIME data, and GSettings schemas. |
| Localization and packaging checks | GNU gettext and the packaging tools used by the target formats, including RPM and Debian tooling and Flatpak helpers. |
GTKX's application baseline is GTK 4.20 and libadwaita 1.8 or later. The full workspace needs GTK 4.22 and libadwaita 1.9 for the GTK and Adwaita demos. Distribution package names and available versions vary. The CI setup action installs the complete verification environment on Ubuntu 26.04, including packaging tools and fonts.
On Debian or Ubuntu releases providing those library versions, the development packages include:
sudo apt install build-essential pkg-config gobject-introspection \
libgirepository1.0-dev libgtk-4-dev libadwaita-1-dev \
libgtksourceview-5-dev libwebkitgtk-6.0-dev meson ninja-buildThis command covers native development and fixtures, not the full headless and packaging environment listed above. Check the installed library versions before building:
pkg-config --modversion gtk4 libadwaita-1Clone and build
Clone the repository, or substitute your fork's URL:
git clone https://github.com/gtkx-org/gtkx.git
cd gtkx
pnpm install
pnpm buildpnpm install links workspace dependencies, prepares the generated binding aliases, and builds the Vitest plugin's TypeScript output through the root postinstall script. pnpm build runs Nx's build targets with their dependencies. This includes the native addon, generated bindings, TypeScript libraries, application bundles, and website build where those targets exist.
The first full build compiles native code, generates OpenGL bindings, and generates the website API references. Building references for a pinned documentation version may also need network access to retrieve that release's source and dependencies.
For subsequent work, use the target for the package you are changing:
pnpm nx run @gtkx/react:buildNx runs the required dependency targets. A package build can therefore rebuild another package or regenerate bindings first. Inspect the graph and an individual project's targets with:
pnpm nx show projects
pnpm nx show project @gtkx/reactUse pnpm format to apply Oxfmt to handwritten JavaScript, TypeScript, Vue, styles, configuration, and Markdown files. CI checks the same files as part of linting. To format only files changed from main, run pnpm nx format:write --base=origin/main; Nx detects the root .oxfmtrc.json configuration.
The formatter preserves import and package manifest ordering. Generated outputs, test fixtures, changelogs, and generated agent instructions are excluded. Embedded code formatting is disabled so tutorial code fences and their patches remain unchanged. Rust formatting uses the pinned nightly rustfmt described below.
CI environment
Testing describes which targets CI runs, the Verify workspace check, and how to reproduce a failure. This section covers the environment those checks run in.
CI uses the Nx affected graph to select projects. Pull requests compare against their merge base, pushes against the last successful run on main, and merge queues against the previous merge-group commit.
With an Nx Cloud token on a trusted checkout, the verify job distributes tasks to Nx Agents, with more agents for larger change sets. The GitHub coordinator and the agents use the same Ubuntu 26.04 image built from .github/ci/Dockerfile, which also pins Node.js; Nx runs directly inside that image.
The image job creates the versioned GHCR image from .nx/workflows/agents.yaml if it does not exist, then resolves its digest for the coordinator. Published image tags are never overwritten. Bump the image version in agents.yaml whenever the Dockerfile, its Node or Go pins, the root package manager pin, or either Rust toolchain changes. Bump it also when refreshing distribution packages. Agent initialization restores dependency downloads and Rust build artifacts, then installs workspace dependencies.
Custom managed images require the Nx Cloud dedicated compute add-on. The first publication creates a private GHCR package: make the gtkx-ci package public in its package settings, then rerun CI so Nx Agents can pull it. Image publication uses the repository's GitHub token and runs only for trusted checkouts with an Nx token. No separate registry credential is required.
Fork pull requests and runs without an Nx token use the same Nx task graph directly on one Ubuntu 26.04 runner. The shared .github/actions/setup-ci action installs that fallback environment, selects Node.js from engines.node in the root package.json, and restores package and Rust caches. The publishing workflow selects Node.js the same way. Nx schedules independent tasks in parallel and uses remote caching when an access token is available. Runtime and native environment fingerprints are included in cache inputs so system library and toolchain changes invalidate the relevant tasks.
Typed Oxlint and scoped ESLint checks depend on built package declarations. Their cache inputs include dependent declarations and the lockfile; the shared compiler-version input also invalidates tasks when the native TypeScript compiler changes.
Nx's standard TypeScript, Oxlint, ESLint, and Vitest plugins infer package targets. Other project commands run directly and declare their dependencies and cache outputs in project.json.
When Nx Cloud is enabled, CI records the Rust advisory audit, requests fixes for failures, and closes the run after checks finish, including after failures. Publication, Pages deployment, and release management remain in their own GitHub Actions workflows.
Generated bindings
The root gtkx.config.base.ts defines the shared configuration imported by workspace packages. It selects the workspace's additional native libraries. The root generation targets select it explicitly with --config; package and application configs keep the normal gtkx.config.ts filename. Codegen reads GTKX_GIR_PATH when GIR files are installed outside the normal search paths.
Regenerate the workspace's bindings and other codegen targets through Nx:
pnpm codegenFor only the root bindings, TypeScript declarations, and widget reference:
pnpm nx run gtkx:codegenThe bindings target runs the compiled generator with gtkx.config.base.ts. It depends only on the generator's build; renderer metadata lives in the config package so React does not need to build first. Packages that consume generated bindings depend on this target explicitly. The public codegen target runs the built CLI and also refreshes .gtkx/reference.
pnpm codegen bypasses Nx task-result caching so explicit regeneration also refreshes the generated blocks in AGENTS.md and CLAUDE.md. These files can contain developer-owned instructions and are never restored from a shared cache. Dependency builds use cached binding and reference outputs during ordinary CI tasks.
Each application example has its own configuration and codegen target. Its build and development targets refresh project declarations and reuse the workspace bindings. Generated bindings live in the root node_modules/.gtkx, with package links under node_modules/@gtkx; generated widget reference pages live in the root .gtkx/reference.
Change the generator, configuration, or source metadata when correcting generated behavior, then regenerate. Editing a generated output alone will be lost on the next run. For widget work, read .gtkx/reference/index.md to find the available element props, signals, and methods. Configuration and Codegen covers the application's view of these outputs.
Run an example
Use an example that exercises the behavior you are changing:
pnpm nx run hello-world:devThe hello-world example is a small application shell and counter. gtk-demo covers a wider set of native widgets. adwaita-demo reimplements the official libadwaita 1.9.3 demo, including its adaptive navigation, controls, dialogs, animations, and tab windows; it needs libadwaita 1.9 or later. storybook-example launches the native story explorer:
pnpm nx run gtk-demo:dev
pnpm nx run adwaita-demo:dev
pnpm nx run storybook-example:devRun these separately as needed. The development target builds dependencies, refreshes generated declarations, and starts the CLI. Changes to application components participate in Fast Refresh. When editing framework packages, rebuild the affected package and restart the example as needed so it loads the changed output.
After the initial dependency build, the example's local CLI can also start a headless session:
pnpm --filter hello-world exec gtkx dev --headless --size 1280x720Use Ctrl+C to stop the development session and release its headless resources. Use the live widget tree, interactions, and screenshots to verify visible changes. The MCP guide explains how to connect, and the Storybook guide covers controls and reusable stories.
Change native code
Rust source lives in packages/native/src. Formatting and sanitizers use the nightly pinned in packages/native/tools/rust-toolchain.toml. After pnpm install, install that toolchain from the repository root:
(cd packages/native/tools && rustup show)Rebuild after changing native code, then start a fresh app or test process to load the new binary:
pnpm nx run @gtkx/native:buildThe build invokes napi build in release mode and produces the platform-specific .node file and generated addon declarations. CI runs the pinned nightly rustfmt check, Clippy with warnings treated as errors, and cargo-audit against the current RustSec advisories. Changes that affect ownership, callbacks, marshalling, or teardown also belong in the native integration coverage described in Testing.
Work on the website
Start the site from the repository root:
pnpm nx run @gtkx/website:dev@gtkx/website:preview builds the site and serves the production output. Prose lives in Markdown; navigation, versioning, and theme behavior live in website/.vitepress. The Contributing section is shared at /contributing/, while Guide, Tutorial, and API Reference pages follow the prefixes declared in website/versions.json. Maintaining Documentation covers style, generated API pages, page registration, version manifests, and promotion.
Prepare a change for review
Add coverage in the suite that owns the behavior, then rely on CI's Verify workspace check; Testing describes coverage ownership and how to reproduce a failing target.
Changes to public APIs, application behavior, or developer workflows need an Nx version plan from pnpm plan. The contribution guide explains which changes need one and how to write it. Publishing Releases is for maintainers.