A shared UI toolkit for the Lunula family of apps (Termtastic, Notegrow, …). Provides the visual identity used across the family: theme model, color schemes, drop-in DOM/Compose components, and a reusable theme/color-scheme editor for web/Electron.
The toolkit is library-style: it ships helpers and drop-in components, not a framework that takes over an app's architecture. Apps adopt as much or as little as they want.
lunula-core— pure data:ColorScheme,Theme,ResolvedPalette,ThemeResolver, color math, ~80 color schemes, ~30 designer themes,UiSettingsdata class with kotlinx.serialization round-trip helpers. Targets: android, jvm, ios (arm64+sim), js.lunula-store— standalone filesystem helpers:defaultSharedThemesPath(),readUiSettings(path),writeUiSettings(path, settings). JVM/Android/iOS only — browser/Electron filesystem access is the host app's concern (e.g. via Electron IPC to a Node fs call).lunula-web— Kotlin/JS DOM components: theme CSS-var helpers, modal dialogs, top bar, left/right sidebars, pane-tree windowing/layout framework, the parameterized theme/color-scheme editor.lunula-compose— Compose Multiplatform drop-in widgets:Modal,TopBar,Sidebar, slide transitions, plus an optionalLocalLunulaPalettecomposition local. NoLunulaTheme { }wrapper.
demo/ holds a reference app — :demo:client, :demo:web,
:demo:electron-main, :demo:electron — that consumes the toolkit through
direct project deps. It's the boundary regression test: a new app should look
and feel like the demo with no app-side CSS. The demo modules deliberately
do not apply maven-publish, so they never appear in either consumer's
libs-repo/. To run it:
./gradlew :demo:web:jsBrowserDevelopmentRun # web
./gradlew :demo:electron:run # Electron desktopThis repo uses git worktrees. The default working directory is main/;
additional branches are checked out as sibling directories at the same level
(extract-from-termtastic/, etc.).
Apps consume the toolkit through a committed file-Maven-repo
(<app>/libs-repo/) populated from this checkout. A consumer can be cloned
and built with no lunula checkout on disk.
When a sibling lunula checkout is present, the consumer's
settings.gradle.kts auto-detects it and switches to a Gradle composite
build (includeBuild) so toolkit edits flow through with no extra steps.
After changing toolkit code, publish to both consumer libs-repos with a single Gradle command (run from this checkout):
./gradlew publishAllToLibsRepoDefault targets:
../../termtastic/adopt-lunula/libs-repo../../treefacts/adopt-lunula/libs-repo
Override either with -PtermtasticLibsRepo=… or -PtreefactsLibsRepo=…
(absolute or relative to this lunula checkout). Then commit the
updated libs-repo/ tree in each consumer repo.
Even when a sibling toolkit checkout is on disk, a consumer build can be
forced to ignore it and resolve from libs-repo/ by passing
-Plunula.toolkit.useArtifacts=true. Useful for verifying that the
published artifacts actually work end-to-end.
repo-root/
lunula/<worktree>/
termtastic/<worktree>/
notegrow/<worktree>/
MIT — see LICENSE. Copyright © 2026 Robert Söderbjörn. Contributions must be
compatible (no GPL/LGPL).