Files
mhoennig f7ca150e1f
werkator build succeeded after 01:50
werkator/samples-linux-amd64 build succeeded after 02:49
Build downloadable AMD64 desktop samples after successful tests
2026-09-16 13:15:34 +02:00

300 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<T>`, `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:<node id>`, `key:<registered 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.