Skip to content

Contributing ​

For specs in your own application, see Testing applications. This guide covers development and verification of CryKit itself. For changes to the documentation website, see Maintaining the docs.

For framework design, see Architecture and How it works. Rendering describes retained templates and updates; Runtime protocol specifies the Crystal/browser message contract.

Repository map ​

LocationResponsibility
src/crykit/Public application API and subsystem entry points
src/crykit/view/Public view extension macros
src/crykit/views/Mounted view lifetimes and registry ownership
src/crykit/execution/Lifetime scopes, delivery queues, worker scheduling and cancellation
src/crykit/content/Resource navigation and buffered responses
src/crykit/transport/Native document bridge, adapter contract, resource translation and browser runtime
src/crykit/assets/Asset embedding and change watching
src/crykit/native/Native build hooks, file notifications and bundled WebView loading
src/crykit/capabilities/Application capabilities and their backends: windows, dialogs, system, menus and trays
src/crykit/events/Event protocol, dispatch, channel delivery and scoped listeners
src/crykit/templates/ECR compiler, retained bindings and regions, template sessions and literal updates
src/crykit/reactive/Reactive dependency sources and consumer publication
pkg/crykit-webview/Owned native WebView engine, Crystal bindings, and engine tests
ext/Native shims
make/Focused build, runtime, native, desktop and benchmark targets
runtime/src/TypeScript event connection and DOM interpreter
runtime/dist/Committed, self-contained runtime bundle
runtime/tests/Browser contracts and Crystal fixtures
tests/Native regression fixtures and desktop smoke applications
examples/demo/Complete application example
examples/tray/Windowless macOS status items with native menus and click listeners
benchmarks/Crystal benchmark fixtures and recorded comparison
doc/Framework guides and implementation documentation

Keep implementations in their owning namespace and directory. Public types such as Application, View, ReactiveState, Job, AppJob and JobExecutor retain their documented names. The capabilities/ directory is an organizational group for the existing Windows, Dialogs, System, Menus and Trays namespaces. Internal types and direct implementation-file imports are unsupported application APIs; use require "crykit" in application code.

CLI validation and other expected command failures use CryKit::CLI::Error. Command entry points call CryKit::CLI.handle_errors to print Error: <message> to standard error and return status 1. This boundary also handles option parsing, YAML parsing, and I/O errors. Helpers still raise their errors so callers can handle them; unexpected exceptions propagate for diagnosis. Keep new expected failures typed explicitly rather than rescuing every exception.

Keep resource loading independent of mounted rendering: Content depends on registry owner/document identities, while handlers load the full registry. DOM guards use the lightweight execution context without loading worker jobs. Contracts and injected managers must continue compiling without native engines.

Development setup ​

Complete the Crystal and WebView setup. The engine lives in pkg/crykit-webview, ships inside the CryKit shard, and is developed alongside CryKit. Applications declare only a crykit dependency; they do not install the engine as a separate shard. Install Ameba for make lint. Runtime development additionally requires Node.js 22.12.0 or newer; the runtime package declares npm 11.18.0. Native build-cache tests require Ruby with Minitest. Install the contributor dependencies explicitly:

sh
make setup

This runs shards install, installs locked npm dependencies, and installs Playwright's Chromium and WebKit. PLAYWRIGHT_INSTALL_FLAGS can supply browser installer flags. Checks do not install dependencies or regenerate the committed runtime bundle.

Native shim builds ​

The shard's postinstall hook builds the bundled WebView engine and native shims. Crystal compilation also invokes their native builds, so path dependencies and changed sources work without a manual build command. make webview-build and make native-build remain available for focused development.

On macOS, Crystal builds automatically invoke make for the native shims under ext/darwin. Unchanged object files are reused. Changing a .m source rebuilds its object. Window feature implementations live under ext/darwin/windows/, each owning its native state and cleanup; the small windows.m entry point preserves teardown order. The build combines their objects into the existing windows.o link boundary. Changing a window feature recompiles that feature and relinks the aggregate; changing windows/internal.h recompiles all window features. Changing the native Makefile, compiler selection, or compiler flags rebuilds all shims. SDKROOT and MACOSX_DEPLOYMENT_TARGET changes also invalidate the objects. A failed compile preserves the previous object and reports the build failure.

Use make native-clean after replacing a compiler or SDK in place at the same path. The build records the selected command and configuration; it does not inspect toolchain contents for changes.

make native-build-test runs the real native Makefile in temporary directories with a fake compiler. It requires Ruby with Minitest and make, and checks cache reuse, invalidation, failure recovery, and overlapping builds of the same configuration without the macOS SDK or a GUI. It is included in make check.

Desktop smoke fixtures use tests/support/native_probe.cr and tests/native_probes.mk to build only their native probe. Cached probe objects live in the ignored tests/.native/ directory, separate from production shims. These builds honor CLANG, CPPFLAGS, OBJCFLAGS, SDKROOT, and MACOSX_DEPLOYMENT_TARGET, with the same configuration tracking and atomic publication as the shims. make native-build-test checks both build caches. Use make native-probes-build for a compile-only check of all probes, and make native-probes-clean after replacing a toolchain at the same path.

The WebView engine has the same configuration tracking and atomic publication for its native objects and archives. Its separate make -C pkg/crykit-webview native-build-test covers compiler/SDK changes, failed compilation and linking, and overlapping builds; make webview-check includes it. Use the package's make clean after replacing a toolchain at the same path, and separate checkouts for simultaneous builds with different configurations.

Runtime build and embedding ​

Edit TypeScript under runtime/src/, then run:

sh
make runtime-build
make runtime-check
make runtime-consumer-check

Commit the regenerated runtime/dist/crykit-runtime.js with source changes. Vite produces one self-contained IIFE without imports, dynamic imports, or source maps. The syntax target is Safari 14, Chrome 87, and Edge 87; this is not a claim of complete native platform verification. The bundle freshness check rebuilds for comparison without repairing the committed file.

CryKit::Transport::Runtime::SOURCE embeds the shipped file through ECR at Crystal compile time. Application consumers do not need a Node.js build step. The consumer check compiles and runs an embedding fixture outside the repository working directory and compares its bytes with the committed bundle.

Browser setup compiles runtime/tests/fixtures.cr once before starting workers. The generator calls each fixture's RuntimeFixtures method and emits a JSON object of payload strings, which setup validates and exports through the existing CRYKIT_* environment variables. Keep fixture construction inside its method so local variables and state stay scoped to that fixture. To inspect all generated payloads directly, run crystal run --error-on-warnings runtime/tests/fixtures.cr.

Changing the template engine ​

Application markup is ordinary HTML with the bindings and region directives described in Compiled templates. The browser owns HTML parsing; the compiler scans only the supported ECR forms and records explicit target markers.

Changes to template syntax belong in templates/compiler.cr. Keep diagnostics, byte offsets, quoted/percent Crystal expressions, and raw HTML contexts covered by the compiler specs. The generated Crystal executes in the caller's lexical scope; paths resolve relative to that Crystal source file.

Retained bindings and regions live in templates/instance.cr, with publication coordinated by templates/session.cr. Evaluation stages values, dependency subscriptions, callbacks, and child owners before publication. Keep failed candidates from changing committed output or subscriptions. Preserve keyed DOM identities and view lifecycle scopes across successful updates.

Protocol changes require matching updates to Crystal mutations and the browser interpreter. Test both generated HTML and real Crystal mutation batches in Chromium and WebKit. Registration must reject missing, duplicate, wrongly typed, or misplaced managed targets before publishing an insertion. See Runtime protocol for the wire contract.

Development literal updates also need matching compiler-signature, watcher, and mounted-update tests. Compatible changes cover eligible ordinary text and the presentation attributes class, style, title, placeholder, alt, role, and aria-*. Changes to structure, actions, bindings, or other source require a rebuild. This mechanism updates declared literal bindings; it does not morph arbitrary static DOM.

Verification ​

Use make for the common workflow and make help-all for the complete grouped command catalog. During development, run the Crystal suite or a focused spec:

sh
make test
make test SPEC=spec/capabilities/windows/window_focus_spec.cr

Use make format to format Crystal sources. For the full automated verification path, run:

sh
make verify

This runs two stages in order:

  1. make check — regression suite. Checks formatting, Ameba and capability contract separation; builds and checks the WebView engine, including Crystal specs, JavaScript bindings, example compilation and macOS native tests; runs CryKit specs, native shim build-cache tests, clean shard-consumer integration and native capability regressions; then checks TypeScript, committed bundle freshness, Chromium/WebKit contracts and Crystal embedding.
  2. make desktop-smoke — real desktop suite. Runs activation/lifetime scenarios, resident trays, lifecycle and resource cleanup, window controls, appearance, behavior, events, focus, close interception and geometry, plus native content, menus, automated file drops and the real demo's WebKit interactions (make demo-smoke).

On macOS, check includes native AppKit/WebKit checks and requires a logged-in desktop session. On other platforms it runs the available regression checks. verify requires macOS and foreground access; let the fixtures control focus. Make keeps the suites sequential even with -j. Activation fixtures use fresh unbundled processes, so Launch Services and LSUIElement packaging need separate verification. Browser or fake-backend tests alone do not establish native OS support.

For framework changes, run focused specs and make check; run make verify before considering native lifecycle work fully checked. For documentation-only changes, check links, examples and git diff --check. Keep user guides aligned with implemented APIs and discuss future designs in a repository issue.

Focused checks ​

AreaCommandIncluded in
Engine specs, bindings, examples and native engine testsmake webview-checkcheck
Failed provisional navigation, renderer termination and asynchronous file-input sheetsmake -C pkg/crykit-webview lifecycle-testcheck on macOS
Injected managers, capability contracts and bridge compile without native enginesmake capability-contracts-checkcheck
Source parsing, retained literal reload, CLI rebuild and failure recoverymake source-reload-checkcheck
Shim cache reuse, invalidation, failure recovery and overlapping buildsmake native-build-testcheck
Fresh applications install and compile with only a CryKit dependencymake shard-consumer-checkcheck
Native capability boundariesmake native-<capability>-test for dialogs, windows, system, menus or trayscheck on macOS
TypeScript, bundle freshness and browser contractsmake runtime-checkcheck
Compiled Crystal embedding matches the shipped bundlemake runtime-consumer-checkcheck
Regular/Accessory activation, hidden readiness and resident lifetimemake application-activation-smokedesktop-smoke
Resident trays and native menu interactionsmake trays-smoke, make menus-smokedesktop-smoke
Native document readiness, navigation/reload, local assets and CSS hot reloadmake native-content-smokedesktop-smoke
Demo navigation, theme inheritance and window playgroundmake demo-smokedesktop-smoke
Standalone notes editing, debounce, selection, deletion and JSON import/exportmake notes-smokedesktop-smoke

make shard-consumer-check uses a clean source checkout and temporary application fixtures to verify both Git and path dependencies. Each fixture declares only crykit, installs with Shards, and compiles an application using require "crykit" without prebuilt native artifacts or a manual engine build. It checks the shipped runtime as part of the consuming application and requires Ruby with Minitest, Git, Shards, Crystal, and the native build tools.

Individual window fixtures can also run directly, for example:

sh
CRYKIT_DESKTOP_SMOKE=1 crystal run --error-on-warnings tests/window_focus_smoke.cr

Other focused fixtures cover controls, appearance, behavior, events, close interception and geometry. They are included in desktop-smoke. Focus checks briefly activate Finder and automatically dismiss a test dialog. The native content fixture checks document readiness, navigation, and reload.

Manual native review ​

These checks require interaction or visual inspection and run separately from make verify:

CommandReview
make dialogs-smokeAccept and cancel file/directory selections; check starting directories, filters, default save filename, messages, confirmation keys, and custom question actions.
make file-drop-smokeDrop files from Finder onto declared targets; check disabled receiving and drops outside targets.
make window-drag-smokeDrag from declared regions; check control exclusions and the configured titlebar double-click action.
crystal run examples/tray/src/main.crCheck monochrome icons in light/dark appearance, left/right clicks and popup menus.

For the appearance comparison, run:

sh
CRYKIT_DESKTOP_SMOKE=1 CRYKIT_APPEARANCE_VISUAL=1 crystal run --error-on-warnings tests/window_appearance_smoke.cr

This holds the final dark-transparent/light-opaque comparison for 45 seconds.

Benchmarks ​

Benchmarks are optional measurements and are not included in make verify.

make benchmark-compile runs the compiled-template growth fixture with 8, 16, and 32 view types. Each generated executable mounts its views, dispatches callbacks, and checks the resulting values. Builds use separate temporary compiler caches and record compiler statistics and LLVM output. The required compile-growth-check uses this fixture without a wall-clock threshold. Pass options with COMPILE_BENCH_ARGS, for example make benchmark-compile COMPILE_BENCH_ARGS='--counts 8,16,32 --keep'.

make benchmark-runtime measures sparse target registration and range reversal, removal, and replacement using the shipped runtime in WebKit. Run npm run benchmark -- chromium 1000 5000 from runtime/ to choose another engine or list sizes. Rebuild the runtime after source changes; the benchmark uses the same installed browsers as the tests. HTML and payload construction, transport, layout, and painting are outside its measured intervals.

make benchmark-rendering measures compiled ECR scalar updates and unchanged keyed-list invalidation as unrelated rows grow. See the fixture and correctness boundaries; the scheduling specs also count actual part visits instead of inferring sparse work from unchanged output.

Other focused measurements are make benchmark-notes for the real editor's input and queued-save path, make benchmark-reactive-dependencies for dependency tracking and publication, and make benchmark-native-assets for resource delivery. Inspect each fixture's checks and measured scope before comparing results. Use the same machine and compiler, run comparisons serially, and retain all results.

Built with Crystal. Documented with VitePress.