# Keluna **Kotlin Embedded Local UI for Native Applications** An initial Linux x86-64 Kotlin/Native framework prototype: Kotlin owns the UI tree, state and click handlers; the system WebView renders HTML and CSS. There is no HTTP server, bundled browser, Node dependency, or JVM in the resulting application. A JDK is required to run Gradle during development. ## License Keluna, including its original examples and documentation, is licensed under the [Apache License 2.0](LICENSE). See also [NOTICE](NOTICE). Third-party code, dependencies, and tools retain their respective licenses; existing third-party notices take precedence for those files. See [third-party code policy](docs/third-party-code.md) before copying or porting code. ## Install a development JDK with SDKMAN! **Recommended: Eclipse Temurin JDK 25 (LTS).** The project's Gradle 9.3.0 wrapper supports running on Java 25. Java 17 is only the minimum Gradle runtime version; use JDK 25 for a new development setup. Java is needed for the build tools, not for the compiled Keluna application. The repository pins the JDK in `.sdkmanrc`. With SDKMAN! already installed, run: ```sh source "${SDKMAN_DIR:-$HOME/.sdkman}/bin/sdkman-init.sh" cd ~/Projekte/keluna sdk env install sdk env java -version ./gradlew --version ./gradlew check ``` `sdk env install` installs the recorded JDK if necessary. `sdk env` selects it for the current shell. The global Java default does not need to change. `25.0.4-tem` was available in SDKMAN!'s Linux x86-64 catalog on 2026-09-16. For later patch updates, use `sdk list java` to find a Temurin **25.x** identifier and update the `java=` entry in `.sdkmanrc`. This is the single source of truth for both manual SDKMAN! activation and the included direnv configuration. ### Automatic activation with direnv The included `.envrc` loads SDKMAN!, watches `.sdkmanrc` for changes, and runs `sdk env`. It activates an installed JDK; it does not install missing versions. Install prerequisites once with `sdk env install` as shown above. If direnv is not installed, on Ubuntu/Debian run `sudo apt install direnv`. Enable its shell hook once in your personal shell configuration: ```sh # Bash: add to ~/.bashrc after SDKMAN! initialization eval "$(direnv hook bash)" # Zsh alternative: add to ~/.zshrc after SDKMAN! initialization eval "$(direnv hook zsh)" ``` Use only the line matching your shell, then open a new terminal. Authorize the repository's `.envrc` locally: ```sh cd ~/Projekte/keluna direnv allow java -version ``` Entering the repository now selects its JDK; leaving restores the previous environment. The shell hook and `direnv allow` are personal machine setup and cannot be enabled by checking in repository files. After an `.envrc` change, review it and run `direnv allow` again. Without direnv, use `sdk env` manually, or enable `sdkman_auto_env=true` in your personal `~/.sdkman/etc/config`. Choose one automatic activation mechanism; SDKMAN!'s auto-env is unnecessary when using direnv. In IntelliJ IDEA, also select this JDK as the project's **Gradle JVM**. Gradle itself is supplied by `./gradlew`; a separate SDKMAN! Gradle installation is unnecessary. See the [Gradle 9.3.0 Java compatibility matrix](https://docs.gradle.org/9.3.0/userguide/compatibility.html#java_runtime), [Temurin support roadmap](https://adoptium.net/support), and [SDKMAN! usage documentation](https://sdkman.io/usage/), and [direnv shell setup](https://direnv.net/docs/hook.html). ## Build and test the native core Requires the development JDK above, an Internet connection on the first build, and a Linux x86-64 host. Gradle downloads Kotlin/Native and its toolchain. ```sh ./gradlew check ./gradlew :examples:counter:runDebugExecutableLinuxX64 ``` Optional JavaScript runtime contract tests (Node.js 18+ for development only): ```sh node --test keluna-web-runtime/runtime.test.cjs ``` The headless counter prints the initial DOM batch, then only the patches caused by a simulated button click. Tests also run as native executables. ## Run the desktop counter On Ubuntu 24.04 / Debian with suitable packages: ```sh sudo apt install build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.1-dev ./gradlew -Pwebview=true :keluna-platform-linux:runDebugExecutableLinuxX64 ``` Run from a graphical desktop session. Desktop configuration is opt-in so GTK headers are not required for framework development. Build a release executable: ```sh ./gradlew -Pwebview=true :keluna-platform-linux:linkReleaseExecutableLinuxX64 ``` Output: `keluna-platform-linux/build/bin/linuxX64/releaseExecutable/keluna-platform-linux.kexe`. The HTML, framework JavaScript and CSS are embedded into the native executable. GTK 3 and WebKitGTK 4.1 remain system runtime dependencies on the target machine. ## Play Snake Snake is a native Keluna example with its game rules, state, scoring and timer callbacks in Kotlin. CSS controls presentation; the framework's browser runtime only updates DOM cells and forwards input. It uses the same Linux dependencies as the desktop counter. ```sh ./gradlew -Pwebview=true :keluna-platform-linux:runSnakeDebugExecutableLinuxX64 ``` Controls: **arrow keys / W A S D** steer, **Space / Enter** start or pause, and **R** starts a new game. Buttons provide the same actions. Losing window focus pauses a running game automatically. Collect food for 10 points, avoid walls and your own body, and fill the 20 × 20 board to win. The best score lasts for the current application session. The game moves eight times per second. Build an optimized executable once, then launch it directly without Gradle: ```sh ./gradlew -Pwebview=true :keluna-platform-linux:linkSnakeReleaseExecutableLinuxX64 ./keluna-platform-linux/build/bin/linuxX64/snakeReleaseExecutable/snake.kexe ``` The debug executable can also be launched directly: ```sh ./keluna-platform-linux/build/bin/linuxX64/snakeDebugExecutable/snake.kexe ``` Game logic and UI live in `examples/snake/src/commonMain/kotlin/dev/keluna/snake/`: `SnakeGame.kt`, `SnakeView.kt` and adjacent `Snake.css`. The Linux entry point is `keluna-platform-linux/src/linuxX64Main/kotlin/dev/keluna/snake/Main.kt`. Snake and Counter share the window host and native adapter. GTK schedules ticks on the UI thread and removes the timer when the window closes. The DOM grid is created once; ticks send only changed cell classes and labels. The eight game/view tests cover movement, pause, growth, walls, self-collision, turn buffering, moving into the vacating tail, winning and incremental patches. They require no graphical session or GTK development packages: ```sh ./gradlew :examples:snake:linuxX64Test ``` ## Workshop registration form This example uses the new **Vaadin-oriented component API**: `Component`, typed fields, `HasValue`, `HasValidation`, value-change events and a buffered `Binder`. See [the API guide](docs/component-api.md) for semantics, examples and current limits. This direction is also recorded in `AGENTS.md` for future development. ```sh ./gradlew -Pwebview=true :keluna-platform-linux:runWorkshopDebugExecutableLinuxX64 ``` Or build once and run the optimized native application directly: ```sh ./gradlew -Pwebview=true :keluna-platform-linux:linkWorkshopReleaseExecutableLinuxX64 ./keluna-platform-linux/build/bin/linuxX64/workshopReleaseExecutable/workshop.kexe ``` The form demonstrates text, email, telephone, date, time, integer, select, radio-button, slider, textarea and checkbox inputs. Required fields, ranges, conversion errors and custom rules are validated in Kotlin with field-level messages. Catering is visible/required only for on-site attendance. A valid submission displays a typed local summary and total price; editing invalidates that preview. No booking is sent or stored. Reset restores defaults, and “Beispiel ausfüllen” loads sample data while leaving consent unchecked. Application code is in `examples/workshop/src/commonMain/kotlin/dev/keluna/workshop/`: `WorkshopForm.kt`, `WorkshopRegistration.kt`, and adjacent `WorkshopForm.css`. It uses no HTML tags, raw attributes or JavaScript. Components can be composed before `UiTree.mount(view)`. Input synchronization preserves DOM identity, caret position and IME composition, and prevents stale updates from replacing newer edits. Run native component/binding/form tests without GTK development packages: ```sh ./gradlew :keluna-ui:linuxX64Test :examples:workshop:linuxX64Test node keluna-web-runtime/runtime.test.cjs ``` ## Project layout - `keluna-core`: UI-thread-confined elements, stable internal IDs, change queue and JSON patches. - `keluna-ui`: typed components, field values, buffered Binder and Kotlin DSL helpers. - `keluna-runtime`: click dispatch and incremental synchronization. - `keluna-web-runtime`: internal DOM interpreter and framework CSS defaults. - `keluna-platform-linux`: small C interop adapter for GTK/WebKitGTK and desktop entry point. - `examples/shared`: shared Kotlin counter and adjacent component CSS, plus the application theme. - `examples/counter`: headless native counter executable. - `examples/snake`: pure Kotlin Snake game model, Keluna UI, colocated CSS and native tests. - `examples/workshop`: Vaadin-oriented registration form, data model, CSS and native tests. - `docs`: original concept documents; proposals, not claims of completed functionality. ## Styling CSS loads in this order: framework defaults, application theme, component CSS. Use semantic classes, explicit DOM IDs for accessibility, and CSS custom properties for theming. `data-keluna-id` is reserved for internal synchronization. `Counter.css` uses explicit `data-keluna-scope` selectors. Automatic CSS selector rewriting and a reusable resource/Gradle plugin are future work; the prototype's resource task embeds each demo's known stylesheets without a JavaScript bundler. ## Validation With Temurin JDK 25.0.4 on Ubuntu 24.04, all 34 Kotlin/Native tests and all nine JavaScript runtime tests passed. The Counter and Snake debug executables and Workshop debug/release executables built successfully. `ldd -r` found no missing libraries or unresolved symbols in the Workshop release executable. Native-window checks in an isolated X11 display verified Workshop field errors, editing within text without moving the caret, valid submission previews, online attendance with hidden catering, and reset. The release executable also passed the sample-data/consent/validation workflow. The demo keeps all data local. Regression checks verified Snake's movement, keyboard turns, pause/resume, wall collision, restart and automatic pause when focus moves to another window. Clicking Increment in Counter still changes the value from 0 to 1. ### Linux linking and Java warnings Kotlin/Native invokes the linker directly. The Linux build retains system library paths from `pkg-config --keep-system-libs --libs`, translates `-pthread` into `-lpthread`, and unwraps compiler-driver `-Wl,` options before passing them on. C compilation continues to use the original `pkg-config --cflags` output. The host's GTK/WebKitGTK shared libraries may require newer glibc symbols than Kotlin/Native's bundled sysroot provides. `--allow-shlib-undefined` lets those shared-library imports resolve against the host at runtime; unresolved symbols in application object files remain link errors. This does not provide compatibility with older Linux distributions: the target system must satisfy the installed GTK/WebKitGTK libraries' ABI requirements. Verify dependencies on each target: ```sh ldd -r keluna-platform-linux/build/bin/linuxX64/debugExecutable/keluna-platform-linux.kexe ``` Kotlin/Native 2.3.21's interop tools can emit `sun.misc.Unsafe` and restricted `System.load` warnings under JDK 25. These warnings did not prevent the verified build and are separate from linker errors. Disabling compiler caches is not required for this linking fix. ## Prototype boundaries and next steps Currently implemented: one Linux window, local content, initial mount, text, attributes, DOM properties, click/key/focus/input/change events, typed components, field validation, buffered form binding, and a UI-thread timer with incremental updates. State and callbacks are confined to the GTK main thread. The inbound event wire format is `click:`, `key:`, `blur` and revisioned field values; see the API guide for the value envelope. Outbound updates are JSON arrays. Text uses `textContent`. Navigation away from the local document is blocked. Not yet implemented: removal/reparenting and lifecycle disposal, dynamic data providers, reactive state/coroutines, multiple windows, native dialogs, packaging, macOS WKWebView and Windows WebView2 adapters. No cross-platform support or size, startup-time or memory claims have been measured yet. Next steps are lifecycle and richer components, packaging, and additional platform adapters. Toolchain: Kotlin 2.3.21, Gradle 9.3.0. Kotlin's 2.3.20 line documents Gradle 9.3 compatibility: https://kotlinlang.org/docs/whatsnew2320.html#compatibility-with-gradle-9-3-0 WebKitGTK bridge API: https://webkitgtk.org/reference/webkit2gtk/stable/signal.UserContentManager.script-message-received.html ## CI desktop downloads Werkator builds the AMD64 desktop examples as the `samples-linux-amd64` follow-up after the tests pass. Download the sample archive from the [Keluna build history](https://werkator.javagil.de/repos/keluna/history). See [the sample guide](docs/samples-linux-amd64.md) for runtime requirements, usage, and the build environment.