This is the workspace repo (libnativeapi/nativeapi, formerly nativeapi-flutter and nativeapi-workspace) for the libnativeapi project family. Every binding (bindings/dart/, bindings/rust/, bindings/csharp/, bindings/js/, bindings/python/), the code generator (tools/codegen/), the ./codegen script, the specs and the shared tooling live directly in this repo (the Rust and C# histories were merged in from nativeapi-rust and nativeapi-csharp); only core/ is a git submodule of an independent repository. Work inside core/ is committed and pushed from that subdirectory; everything else is committed here.
core/ # submodule: nativeapi-core — the C++ core library
bindings/
├── dart/ # the Dart binding: nativeapi/, cnativeapi/, nativeapi_flutter/
├── rust/ # the Rust binding: crates/{nativeapi,cnativeapi}
├── csharp/ # the C# binding: src/, tests/, NativeAPI.slnx
├── js/ # the JS/TS binding: a Node-API addon (src/) + TypeScript (lib/)
└── python/ # the Python binding: ctypes package (nativeapi/) + native shim (src/)
examples/ # every binding's example apps, prefixed dart_*, flutter_*, rust_*, csharp_*, js_*, python_*
pubspec.yaml # pub workspace + melos root: Dart packages and Flutter examples
Cargo.toml # cargo workspace root: Rust crates and examples
tools/codegen/ # in-repo Rust workspace: the code generator
tools/gui/ # GUI tests and demo scenarios for the examples (built on the skills)
codegen # Python entry point orchestrating the generators
.agents/skills/ # agent skills: core API changes, GUI testing, demo recording (see below)
.claude/skills # symlink → ../.agents/skills, so Claude Code discovers the same skills
core— the C++ core library (repo:nativeapi-core). The source of truth for the native API surface (windows, tray icons, menus, displays, keyboard, dialogs, storage, etc.) with per-platform implementations (macOS/Windows/Linux).tools/codegen— three crates:shared(libclang parser, IR, naming),capi(C ABI + umbrella header),bindings(Rust/Dart/C#/JS/Python generators, consuming the IR JSON emitted bycapi). Onlycapidepends on libclang. See tools/codegen/README.md.bindings/*— language bindings wrapping the core library. All live in this repo and build against thecore/submodule directly (Rustbuild.rs, the Dartcnativeapipackage's build hook, the C# native CMake, the JS addon's and the Python binding'sCMakeLists.txt). Only a published package carries its own copy of core, incxx_impl/, which the release workflows vendor and never commit. The Rust binding layersnativeapi(safe API) overcnativeapi(FFI). The Python binding has no compiled extension: generatedctypescode (nativeapi/_capi.pyplus one module per header) calls a shared library built from core and a small event loop shim, whichApplication.run_async()pumps from asyncio.
specs/ holds the settled design rules for core/ — layering, the identity/value object
model, the public API style, the platform seam, the event system, managers, and the C ABI.
Start at specs/README.md; read the relevant spec before adding or
reshaping public API in core/src/.
Any diff that touches a public header in core/src/ must pass the checklist at the end of
specs/api-style.md — naming vocabulary, parameter and return types,
failure reporting, platform-availability notes, and the codegen constraints. When existing
headers disagree with each other, follow the spec, not the nearest neighbour: it records
which side of each split is the rule and which is legacy.
There is no separate issue list: each spec carries the open questions and known legacy gaps of its own area inline (an "未决" section, or a "存量缺口" note next to the rule it breaks). When one is resolved, edit the spec text itself.
Always drive the generators through ./codegen at the workspace root:
./codegen— full run: C ABI, then all bindings./codegen capi/./codegen bindings [--lang rust,dart,csharp,js,python]./codegen check— read-only verification, non-zero exit when stale (CI mode)./codegen readme— copy the shared README sections (tools/readme/*.md, e.g. Contributing) into core and every binding;checkflags drift,syncruns it. Edit the snippet, never the copies../codegen sync [-m "msg"] [--push]— full downstream propagation, see below
Generated files start with // AUTO-GENERATED. DO NOT EDIT. (# in Python) — change the C++ headers in core/src/ and regenerate instead of editing outputs. Files without that banner are hand-written and never overwritten. The header list (API_HEADERS) lives in tools/codegen/shared/src/lib.rs.
A core change ripples to every binding. After editing headers in core, run:
./codegen sync -m "<core commit message>"It regenerates everything, reruns bindgen (Rust raw FFI) and ffigen (Dart raw FFI), then commits core and this repo (Sync with core <sha>: the core pointer plus everything regenerated under bindings/). Add --push to publish in dangling-safe order (core → workspace).
Manual follow-ups sync cannot do (details in tools/codegen/README.md):
- New handle types need an
IdTypeTag<T>entry incore/src/foundation/id_allocator.h(append only; a miss is a compile error, not silent). - Hand-written files in the bindings (exports, re-exports, changelogs, examples) are never touched by the generators. Rust's
pub modlist is generated (modules.rs), and so is Dart's (nativeapi/lib/src/generated.dart) and Python's (nativeapi/__init__.py);nativeapi_flutter's exports are not.
The core-api-change skill walks the whole flow, including what to check before sync commits.
.agents/skills/ holds skills (a SKILL.md plus scripts each) for verifying windowing
work on a real desktop. Read the relevant SKILL.md before doing any of this by hand:
| Skill | Use it to |
|---|---|
core-api-change |
carry a public API change from core/src/*.h through codegen, every binding and the commits in core and this repo — including the pre-flight before ./codegen sync |
flutter-ui-probe |
find where texts/widgets are in a running debug Flutter app (VM service) |
gui-test |
end-to-end test an app: launch, drive with guarded synthetic mouse input (read its safety rules first), assert on real window geometry and state |
remote-hosts |
build and run on another machine over SSH — Windows today, Linux/macOS prepared (SSH session vs. logged-on desktop) |
record-demo |
record a scripted demo of an app and cut it into an X-ready MP4 |
Skills hold only generic harnesses, recorders, and templates. The GUI tests and demo scenarios for this project's examples live in tools/gui/.
coretracksbranch = main. Usemake syncto fast-forward it;make statusto see dirty state everywhere;make bumpto stage its pointer.- The leanflutter packages built on nativeapi (
tray_manager,window_manager,launch_at_startup, …) live in their own repos under github.com/leanflutter and depend on the publishednativeapi; they are not part of this repo. To try one against local changes, point adependency_overridesentry in that package atbindings/dart/nativeapi_flutter(andnativeapi,cnativeapi) and never commit the override. - Commit workspace submodule pointer updates only when the combination is compatible (a known-good snapshot).
- Examples live in
examples/<binding>_<name>_example(dart_for plain Dart programs,flutter_,rust_,csharp_,js_,python_), not inside the bindings; a new Dart, Flutter or Rust example must also be listed in the rootpubspec.yaml/Cargo.toml, a C# one inbindings/csharp/NativeAPI.slnx, a JS one in the rootpackage.json. A Python example is a standalone uv project whosepyproject.tomlpointsnativeapiat../../bindings/python(uv run main.pybuilds the wheel). Only the pub.dev package examples (bindings/dart/*/example) stay inside their package. - CI is one workflow per binding (
dart-ci.yml,rust-ci.yml,csharp-ci.yml,js-ci.yml,python-ci.yml), each running only for changes under itsbindings/<lang>/and its examples (examples/dart_*andexamples/flutter_*for Dart,examples/rust_*,examples/csharp_*,examples/js_*,examples/python_*). Release tags are per binding:v*publishes the Dart packages (dart-release.yml),rust-v*publishes the crates (rust-release.yml),python-v*publishes the Python package to PyPI (python-release.yml: an sdist carrying core incxx_impl/, and wheels built from it with cibuildwheel; the tag must equalpython-v+ the version inbindings/python/pyproject.toml); never push a barev*tag for anything but the Dart packages. Nothing has been published to PyPI yet: the first release needsnativeapiset up on PyPI with this workflow and thepypienvironment as a trusted publisher. - The Dart packages
cnativeapi,nativeapiandnativeapi_fluttershare one version: a release bumps all three pubspecs and CHANGELOGs, because pub.dev's automated publishing matches the tagv<version>against each package's own version.cnativeapiandnativeapiare plain Dart (no Flutter dependency; a build hook compiles core);nativeapi_flutteris the Flutter-facing package, holding the widgets, thedart:uiconversions andwindowing.dart, and re-exportingnativeapiminus the names that clash with Flutter's. - Never commit in a submodule while on a detached HEAD — check out
mainfirst (./codegen syncenforces this). - Do not add Co-Authored-By trailers to commits.