Desktop app: framework, native build and testing (design plan)
Status: the build has started (D-76); the rest of this plan is still a proposal. D-76 records the platforms, the Nucleus shell with a plain dev window, our own editor adapter and the screens. The open questions it lists (native image or JVM for release, a shared Compose module, database encryption, minimum OS versions) are not decided. Before D-76 this was a future plan, not a decision. Desktop is Phase 5 in
04-roadmap.mdand out of Release 1 scope (D-07). This plan compares ways to build it, with the goal of a fast, lightweight app. Nothing here is agreed until it is recorded as a new D-xx. Several of the points below conflict with D-01 (native UI per platform) and D-12 (the editor is built on the platform text view). If we choose a Compose desktop app, new decisions must say how they apply to desktop.Research dates: 2026-10-07, with options E and F added 2026-10-08. Facts about third-party projects come from their docs, release notes and community threads. The spike results below replace those claims where we measured something ourselves. Desktop has to run on Linux, macOS and Windows, so option C (native macOS only) is out.
Spike results (2026-10-07, macOS)
Branch spike/desktop-nucleus. It has three modules: app/desktopUi (the screens, editor and database setup, with no Nucleus), app/desktopApp (the Nucleus release shell and native build) and app/desktopDev (a plain Compose window for development, see surprise 1). Together they contain a notes list and an editor on the shared NotesCore and EditorSession, Nucleus 2.5.18 with the tao backend, Compose Multiplatform 1.12.1, and SQLDelight with sqlite-jdbc writing to a database file. Everything below was measured on an M-series Mac (8 cores, 16 GB) in a session with no screen access, so nothing was checked by eye. Linux, macOS and Windows ran in CI on 2026-10-08 (see the table below).
What worked
-
GraalVM native image with SQLDelight. Nucleus downloaded GraalVM CE 25.0.4 itself. The
sqlite-jdbcnative-image feature was picked up without any configuration. Inside the native binary, the smoke run (JOTRY_SMOKE=1) edits a note throughEditorSession, saves it, reopens the file with a second core, searches, reads history and exports. It passes. No reflection config was written by hand. -
Native build time: about 1 min 10 s, with a peak of 2.9 GB of build memory (on 16 GB, native-image uses 8 GB at most). Nucleus’s docs say 5–15 min on CI runners.
-
Startup and memory (time to first frame and resident memory, the same app on each runtime):
Native image JVM (jlink’d runtime) First frame, warm ~235 ms ~700 ms First launch after a build ~2.9 s (macOS checks the new binary) ~2.5 s Idle memory, empty database ~127 MB ~246 MB Idle memory, 10,000 notes ~195 MB (142 MB with -Xmx64m)~273 MB App size 126 MB (binary 87 MB) 159 MB Seeding 10,000 notes through
NotesCoretook 13.7 s in native. The 10,000-note memory is mostly the list loading everyNoteSummaryat once, which the real app should page. -
The editor on
BasicTextField. It holds the visible text and sends each change toEditorSession.replaceTextas a prefix/suffix diff. Styling is drawn from the session’sRichTextthrough aVisualTransformation. Cmd+B, Cmd+I, Cmd+Z and Cmd+Shift+Z go to the session before the text field sees them, so the field’s own undo never runs. That matches the phones (D-12’s rule that platform undo is routed to the session). -
UI tests are fast and simple. Five editor tests and three database tests run in about 1 s with
runComposeUiTest, with no window, emulator or simulator. -
Hot reload works with the tao backend.
hotRunbrought the app up, which contradicts the Slack report. Through the hot-reload MCP server, an agent can reload changed code into the running app, andstatus,get_logs,get_ui_errorandrestartwork. -
Configuration cache, Kotlin 2.4.20 and Compose 1.12.1: no conflicts with the existing build.
CI on all three OSes (GitHub-hosted runners, .github/workflows/desktop-spike.yml, run 37787707391, before the module split):
| Linux (ubuntu-latest) | Windows (windows-latest) | macOS (macos-latest) | |
|---|---|---|---|
| JVM tests | pass | pass | pass |
| Native build | 5 min 8 s, 4.0 GB peak | 9 min 19 s, 3.4 GB peak | 12 min, 2.1 GB peak |
| Native smoke test (SQLDelight) | pass | pass | pass |
| App folder | 168 MB (binary 107 MB) | 136 MB (binary 92 MB) | 126 MB |
| First frame, warm | ~170 ms after main (~920 ms since the process started, under xvfb-run) | ~180 ms | ~420 ms |
| Result | pass | pass | hung on exit in the 2nd startup run (surprise 8) |
On Linux, about 750 ms pass between the process starting and main. That may be xvfb-run or loading the larger binary, and it needs checking on a real desktop.
Surprises (each one is fixed or worked around in the spike)
- The MCP server can’t see inside a Nucleus window.
take_screenshot,get_semantic_tree,clickandtype_texttime out with the tao window, andlist_windowsshows it with no title. A stock Compose desktop app (AWT window, no Nucleus) on the same machine and versions worked fully: tree, screenshot and window title. So with Nucleus, an agent can reload and restart the app but can’t look at it or drive it. Workaround in the spike: the screens live inapp/desktopUi, which has no Nucleus dependency, andapp/desktopDevshows them in a plain Compose window for development. Run it with./gradlew :app:desktopDev:hotRun -Pcompose.reload.jbr.autoProvisioningEnabled=true, and its MCP server with./gradlew :app:desktopDev:hotMcpServer. Through that server an agent read the UI tree as JSON (with the test tags, the editor text and the checkbox states), unchecked a task, typed into the editor, and saw the shared editor continue the checklist. The autosave then reached SQLite. Reading the tree costs far fewer tokens than screenshots. Window-level behavior (the tao title bar, menus, tray) is still checked only inapp/desktopApp. - A stock Compose window doesn’t work alongside Nucleus. With Nucleus on the classpath,
application { Window { } }crashes (“addObserver must be called on the main thread”). Nucleus redefines the main thread. You can’t fall back to a plain window in the same app, or use one for debugging. - About 85 MB of other platforms’ native libraries ended up in the binary. Native-image embedded Skiko for both Mac architectures, Windows ANGLE, Linux tao, and sqlite-jdbc builds for about 20 OS/CPU combinations, making a 149 MB binary. An
-H:ExcludeResourcespattern inapp/desktopApp/build.gradle.ktskeeps only the current platform’s libraries, which gives 87 MB. Nucleus doesn’t do this itself.optimization = SIZEsaved only 6 MB and doubled the build time, so it’s not used. - The JVM build left out
java.sql. The jlink’d runtime doesn’t include it and sqlite-jdbc needs it, so the JVM app crashed on open untilmodules("java.sql")was added. Native image wasn’t affected. - Toolbar buttons stole the editor’s focus, and the selection went with it, so Bold did nothing. The buttons are now not focusable (
focusProperties { canFocus = false }). Every desktop toolbar and menu action needs this. - Compose text fields can’t hold inline content. The phones put checkboxes in the text as a span or attachment. On desktop, list and task paragraphs get a text indent, and checkboxes, bullets and numbers are drawn in the gap from the text layout. It works and is tested, but it’s a different design from D-12’s adapters.
- Hot reload needs JetBrains Runtime 25. It downloads one with
-Pcompose.reload.jbr.autoProvisioningEnabled=true. On macOS, the JVMrunandhotRuntasks need-XstartOnFirstThreadfor tao, but that flag hangs a plain AWT window. - A
DecoratedWindowneeds aTitleBar. Without one, the content is drawn under the macOS window buttons (seen in a screenshot). Separately, the native build sometimes hangs when the app exits right after its first frame. Locally that happened in 2 of 5 runs with aTitleBarand 0 of 5 without one, but on the macOS CI runner it also hung without one. Exiting 2 s after the first frame worked 5 of 5 times, and the JVM build never hung, so it looks like a startup race that users quitting normally won’t hit. Measurement runs now wait 1 s before exiting. Worth reporting upstream. - Smaller things:
GRAALVM_HOMEon the dev machine pointed at Temurin, which would override Nucleus’s own GraalVM, so builds run with it unset. JDK 25 warns that sqlite-jdbc’sSystem.loadwill be blocked in a future release unless--enable-native-accessis set. SQLDelight’s checked-in.dbschema snapshots have nouser_version, so the migration test sets it.
Still open after the spike
- Memory on Linux and Windows (CI measured startup only), and startup on real desktops rather than CI runners.
- Code signing and notarization, installers (DMG/NSIS/DEB), and auto-update weren’t tried.
- IME composition (Chinese, Japanese), paste, and links in the desktop editor.
- Nucleus upgrades: issue #671 tracks Compose 1.13 breaking changes, and #585 reports high memory use when switching themes.
ComposeTextEditor spike (2026-10-08, macOS, local)
Question: bring in ComposeTextEditor 3.0.3 (MIT, desktop artifact com.darkrockstudios:composetexteditor-desktop), or keep our own editor (DesktopEditor, a BasicTextField with a VisualTransformation)? The spike is LibraryEditor.kt in app/desktopUi, next to DesktopEditor.kt and not wired into the screens, with five tests in LibraryEditorUiTest. GitHub CI is out of credits, so everything ran locally.
What the spike does. The library is only the text surface (layout, caret, selection, IME, clipboard). EditorSession stays the single model. The library’s editOperations (insert, delete, replace) are mapped from line/char to flat offsets and forwarded to session.replaceText. Its own list and block behaviors are switched off (editBehaviors.clear()), or Enter on a checklist line would be continued twice. Undo, redo, Bold and checklist come from the session, and the session’s styled document replaces the library’s text with setText. Cmd/Ctrl+Z, Y and B are caught before the library sees them. All five tests pass, and the existing eight still pass.
What it showed
- The library owns a second document model: lines of
AnnotatedString, rich spans, block spans (lists, tasks, quotes, fences, tables), a Markdown dialect with its own converter, and its own undo history. Using it as a pure text surface works, but that leaves ourRichText,MarkdownCodecandEditorSessionas the only model and uses a small part of the library. - Every session-driven change (undo, redo, formatting, Enter in a list, anything with
resetText) callssetText. That drops the library’s IME composition and its own state, and it is visible whenever the user is mid-word.DesktopEditoravoids this by only replacing the text when the session says so. - The library’s own undo history still records every edit. Cmd+Z is routed to the session, but the library’s context menu or touch toolbar Undo would bypass it (not tried).
- The checkbox gutter does not port. The library draws tasks, bullets and numbers as its own block spans, and exposes no single
TextLayoutResultto position our gutter on. Using its tasks means using its block model, and so a second Markdown codec to keep in step withMarkdownCodec(fidelity risk). - Text typed after a bold run only keeps the style through the library’s cursor styles, which differ from the session’s
typingStyles. Not tested. - Cost: a 1.6 MB jar, one new transitive dependency (
kotlinx-datetime), and a single-maintainer project (about 74 stars). The README itself says it may not be a permanent solution onceBasicTextField2can render rich text. Native image compatibility is untested.
What we’d gain. Lazy line layout for very large notes, a context menu, selection handles, accessibility and tuned input handling (dead keys, CJK composition) that we would otherwise build or test ourselves. DesktopEditor has IME composition, paste and links on its gap list.
Not measured: native image, typing latency on a 10k-line note, real key events and IME (the tests drive the state API), the library’s context menu.
Recommendation (to confirm): keep our own editor. Bringing the library in as a text surface keeps EditorSession intact, but it adds a dependency, a second undo history, setText resets and no checkbox support, in exchange for features a notes app rarely needs at note size. Revisit if BasicTextField shows real problems with IME or large notes. The spike code can stay on the branch as the reference for that.
Goal
A desktop Jotry that:
- Starts fast and stays small in memory. The bar is “feels like a native notes app”, not “feels like an IDE”.
- Uses the same
NotesCore, database and file format as the phones, so notes round-trip safely (the Phase 5 exit criterion). - Can be tested well: UI tests in CI, plus an agent (Claude Code) that can launch, inspect and drive the running app while we work on it.
What we start with
- Shared core:
app/sharedLogicalready has ajvm()target with the SQLDelightsqlite-driver(used for host tests today).corebuilds for JVM, JS and Wasm. A JVM desktop app can callNotesCorewith almost no new shared code. A Kotlin/Native macOS target (macosArm64) would need adding, but it uses the same SQLDelight native driver as iOS. - Android UI (
app/androidApp, about 19.5k lines with tests) is Jetpack Compose, but it is Android-only code. Moving screens into a shared Compose Multiplatform module means removing Android-only APIs (resources,ViewModelwiring, intents, the file picker, Gemini Nano titles). - The editor is the hard part. Per D-12, Android uses
EditText(JotryEditText,EditorSpans) and iOS usesUITextView. Neither exists on desktop Compose. The formatting logic is shared (EditorSession, covered byEditorSessionTest), so each platform only needs a text-view adapter. On desktop Compose that adapter has to be built onBasicTextField, or a Swing text component hosted throughSwingPanel. Nucleus’s tao backend (below) has no AWT, so it rules outSwingPanel. - iOS UI (
app/iosApp, about 5.2k lines of Swift) is SwiftUI with a TextKit editor. TextKit came from AppKit, so the editor is the part of the iOS app that ports to macOS most directly. app/desktopAppandapp/sharedUIare template modules from the project wizard. They aren’t built (seesettings.gradle.kts).
Options
A. Compose Multiplatform desktop on the JVM (official JetBrains plugin)
The default path. Ship a jlink’d JVM with the app (DMG/MSI/DEB through the Compose Gradle plugin).
- Speed and size: the weakest of the options. Cold start is typically around a second or more, and idle memory is a few hundred MB, because a full HotSpot JVM and Skia are bundled. JDK 25+ AOT cache (Project Leyden) and ProGuard reduce startup, but not to native levels. Measured in the spike: about 700 ms to the first frame and about 250 MB idle.
- Tooling: the best of the options. Normal IntelliJ debugging, Compose Hot Reload, and the hot-reload MCP server (see “Agent access” below) all target this setup.
- Risk: low. It’s JetBrains’ supported path.
B. Compose desktop with Nucleus (JVM for development, GraalVM native image for release)
Nucleus is an MIT-licensed layer on top of Compose Multiplatform desktop. It isn’t a different UI toolkit. It adds:
- Native windowing through a Rust
taobackend with no AWT (the AWT backends are deprecated, and the docs say they are removed in 2.6), decorated windows, a native menu bar, tray, notifications, deep links, file associations and single-instance locking. - Packaging for 18 formats, store publishing (Mac App Store, Microsoft Store, Snap, Flathub), signing and notarization, and an auto-updater (GitHub or S3, SHA-512, delta updates).
- GraalVM native image as a one-line switch, with reachability metadata generated automatically for dependencies. Its docs list SQLite, Ktor, kotlinx.serialization and Compose as working without extra configuration.
Reported numbers (from the project, not independent):
- A Hello-World Compose window cold-starts in about 0.2 s using about 30 MB of RAM (Windows 11).
- In their benchmark app (a heavy CPU and rendering stress test on an M4 Mac), native image reached a peak of 269–326 MB of RAM, compared with 616–695 MB on the JVM. App size was about the same (119–174 MB), because Skia dominates. A notes app should land well under those peaks, but it won’t get close to a Swift app’s size.
Concerns:
- Maturity: about 400 stars and 28 forks, mostly one maintainer, version 2.5.x, with a 3.0 branch in progress. Some real apps use it (Tasks.org, AB Download Manager, CrossPaste). If the project stalls, we’d need to fall back to the official plugin. That’s realistic, because the UI is plain Compose, but we’d lose native windowing and native image.
- Version coupling: it needs Compose Multiplatform 1.12.x, Kotlin 2.4.10+ and Gradle 9+. We’re on Compose 1.12.1 and Kotlin 2.4.20, so we fit today, but upgrades become gated on Nucleus releases.
- Hot reload works in the spike, despite a Slack report. The hot-reload MCP server, though, can’t screenshot or read the UI of a tao window (see the spike results).
- Native image limits: a closed world (no runtime reflection or class loading the build can’t see), no cross-compilation (build on each OS in a CI matrix), slower and memory-hungry builds, and debugging a native binary is much harder than on the JVM. In practice: develop and test on the JVM, and run a smoke test of the native build in CI.
sqlite-jdbcand SQLCipher: plain SQLite is listed as working. An encrypted database through SQLCipher on the JVM, and then inside native image, is a separate unknown (see the “Revisit if” note under D-09).
C. Native macOS app (SwiftUI/AppKit) on a Kotlin/Native core
Add a macosArm64 (and macosX64 if needed) target to sharedLogic, and build a macOS app that reuses the iOS SwiftUI code and the TextKit editor (NSTextView).
- Speed and size: the best of the options. A Swift app with a static Kotlin/Native framework starts in a fraction of a second and idles at tens of MB. No GraalVM step is needed: Kotlin/Native already compiles ahead of time.
- Fits D-01 and D-12 directly: native UI and a platform text view.
- Reuses a lot: iOS screens,
AppModel, the editor’s TextKit code, and the XCUITest setup. Apple’s on-device Foundation Models are available for generated titles on macOS too. Compose desktop has no equivalent. - Downside: macOS only. Windows and Linux would need option A or B later, so that would be a fourth UI.
- Testing: XCUITest on macOS works like iOS, but it’s slow, and agent access goes through the macOS accessibility tree (a computer-use or AX-based MCP) rather than a Compose-aware tool.
D. Others considered
- Tauri (a webview with a Rust shell): small binaries (about 10 MB) and low memory, but the UI and editor would be rewritten in web tech, and the core would run as Kotlin/JS or Wasm with a new SQLite bridge. That’s a third UI stack with no reuse. Not recommended.
- Electron: heavy, the opposite of the goal. Rejected.
- Compose on Kotlin/Native for desktop (no JVM at all): Compose Multiplatform has no supported Kotlin/Native desktop target, so this isn’t available.
E. A Rust UI toolkit with the Kotlin/Native core behind a C ABI
Analysis only (2026-10-08), nothing was built. Candidates are Slint, egui, iced, Dioxus, Xilem and GPUI. Kotlin/Native would export the shared logic as a static library with a C header, and the Rust side would call it (or the reverse).
- Cost: high. It needs
macosArm64,linuxX64andmingwX64targets insharedLogic(to check against SQLDelight and our dependencies), a hand-written flat C API (no coroutines, Flow or generics across the boundary, manual ownership of strings and lists), and Gradle, Cargo and a C toolchain on every CI OS. It is a fourth UI stack with no reuse of the Android Compose screens. - Editor: the Rust toolkits have weak rich text editing and accessibility. Jotry’s editor would be custom work, probably worse than a platform text view or Compose’s.
- Upside: small binary, fast start, no JVM or Skia, no GraalVM closed-world limits, no dependence on Nucleus.
- Verdict: not recommended. Revisit only if native image misses the pass bar and all three OSes still matter.
F. Compose on JetBrains’ kotlin-desktop-toolkit (KDT)
kotlin-desktop-toolkit wraps native windowing in a Kotlin API and offers Skia-compatible rendering surfaces. Status read from its README on 2026-10-08, not tried:
- It is labelled an internal JetBrains project, has no releases, no published Maven artifacts and about 200 stars. It is the foundation of JetBrains’ Air framework. Its README says it “will later” provide the OS integration layer for Compose for Desktop, so Compose integration is planned, not delivered. A third-party summary says its native layer is Rust (not verified).
- Platform status: macOS is the most complete. Windows has no application menu, no Vulkan, and file dialogs are in progress. Linux X11 is decent, Wayland has no application menu. System tray and accessibility are missing everywhere, and custom text input is missing on Windows.
- Not usable for Jotry today. Accessibility and text input matter for a notes editor.
- Switching later:
desktopUiis plain Compose with no Nucleus dependency, so the screens, state,EditorSessionand tests carry over. Only the window shell (desktopApp, about 40 lines of Kotlin plus Gradle configuration) would be replaced. That is a few days of work on menus, tray and packaging. Going to KDT from Nucleus loses native image, the updater and the packaging formats until KDT or the official plugin covers them. Going from the plain JVM plugin (option A) is cheaper, because KDT may arrive under Compose with a version bump. - Plan: don’t build on it. Keep
desktopUifree of Nucleus and KDT. Re-check KDT releases and the Compose Multiplatform changelog every few months, and revisit when a Compose-on-KDT artifact exists.
Answers to the questions asked
Is Nucleus good for Jotry? It’s the most complete way to get a Compose desktop app that feels native and starts fast: native windows and menus, installers, auto-update and native image in one plugin. The costs are dependence on a small project, an MCP server that can’t see inside its windows, and no Swing interop (which limits editor options). It’s worth a spike, not a commitment. Keep the UI as plain Compose so that dropping back to the official plugin (option A) stays a build change, not a rewrite.
Is desktop UI testing easier with Compose? Yes, much easier than Espresso or XCUITest:
runComposeUiTest(or the JUnitcreateComposeRule) runs in-process on the JVM. There’s no emulator or simulator, it renders offscreen, and tests take milliseconds to seconds. The finders and assertions are the same semantics API as our Android Compose tests.- Roborazzi, which we already use on Android, supports desktop screenshot tests.
- Limits: these tests cover composables, not the native window shell. Nucleus’s tao window, native menu bar, tray, file dialogs and the native-image binary need a few separate smoke tests. Headless Linux CI may need
xvfb. The desktop test API is still marked experimental. - If we share screens between Android and desktop in a Compose Multiplatform module, many UI tests can be written once in
commonTest. Today the Android UI tests are Android-only.
Can an agent run and debug the app?
- Compose Hot Reload MCP server (experimental, Hot Reload 1.2 / Compose Multiplatform 1.12): run the
hotMcpServerGradle task and point Claude Code at it in.mcp.json. It can reload code, restart the app or reset the UI, and read logs and UI exceptions. Community write-ups also list screenshots, reading the semantics tree, clicking and typing. Reports show rough edges: a log line on stdout breaking JSON-RPC, and Claude Code failing to connect. It works only on the JVM, in development, not on a native-image build. With Nucleus’s tao window, reload, restart, logs and errors work, but screenshots, the semantics tree, clicking and typing time out (spike result). - A small in-app test hook we control: a debug-only local endpoint (or a stdio MCP that’s part of the app in debug builds) that dumps the semantics tree and performs actions through the same API the UI tests use. This is more work, but it works for any backend and doesn’t depend on hot reload. It must never ship in release builds.
- OS accessibility tools (computer use, AX-tree MCPs) work on any option, including native image and option C, if the app exposes accessibility correctly. Nucleus says its accessibility layer is checked against macOS AX, UI Automation and AT-SPI in CI.
- Debugging: a full JVM debugger on JVM runs. A native-image binary can only be debugged with gdb/lldb and debug info, so reproduce bugs on the JVM build.
Is GraalVM native image the way to “fast and lightweight”? For a Compose app, it’s the biggest available win: much faster cold start, and roughly half the memory of the JVM. It doesn’t shrink the app much (Skia is large), and it adds build complexity. Only option C (native macOS) beats it, and only on the Mac.
Recommendation (to confirm)
Decide the platform priority first.Decided by the user on 2026-10-07: Linux, macOS and Windows together, so option B with option A as the fallback.- Spike before any D-xx. Done on macOS (see spike results). The original brief was: Measure Nucleus with native image against the JVM on a real Jotry slice, not Hello World:
- Build:
NotesCorewith SQLite (and SQLCipher, if encryption is in scope by then), a notebook list, a notes list, and aBasicTextFieldeditor wired toEditorSessionwith bold and a checklist. - Measure on the Mac (and on Windows if it’s in scope): cold start to first frame, idle RSS, RSS with 10k notes, typing latency, app size, and native build time in CI.
- Check: hot reload and the hot-reload MCP under the Nucleus tao backend,
runComposeUiTestplus Roborazzi in CI, and whethersqlite-jdbcworks in native image. - Pass bar (proposal): cold start under 0.5 s, and idle RSS under 150 MB with 10k notes. Result on macOS: the native start passes (~235 ms). Memory passes only with a heap cap (142 MB with
-Xmx64m, 195 MB without one), and paging the notes list would help more. Linux and Windows are still to run.
- Build:
- Shared Compose UI is a separate decision. If desktop uses Compose, moving Android screens into a shared Compose Multiplatform module would give one UI and one set of UI tests for Android and desktop. It touches every Android screen, and it needs a desktop editor that matches D-12’s behavior.
Decisions needed
Which desktop platforms come first?All three (the user, 2026-10-07). This still needs to be recorded as a D-xx that refines D-01 and D-07.- Nucleus (B) or the official plugin (A)? Agent access is the main trade-off: the hot-reload MCP server fully supports only stock windows.
- The desktop editor:
BasicTextFieldadapter, or something else? This needs a D-xx that extends D-12. - Native image or JVM for release builds, based on the spike numbers.
- Whether Android screens move to a shared Compose Multiplatform module.
- Rough reuse estimate (2026-10-08, from file sizes, not checked import by import):
app/androidApp/src/mainis about 4.4k lines in 18 Kotlin files. Roughly 2.5–3k lines (theme, formatting, components, bars, drawer and the notes, search, trash, history and settings screens) could be shared after replacing Android resources,Context/Intentand file-picker calls and theandroidx.lifecycleViewModel. About 1k lines cannot:NoteEditor(601 lines),RichTextContent,MainActivity,JotryApplicationand Gemini Nano titles. Desktop also needs its own layout (sidebar, menu bar, shortcuts), not the phone layout.
- Rough reuse estimate (2026-10-08, from file sizes, not checked import by import):
- Desktop database encryption, the driver, and the minimum OS versions.
Sources
- Nucleus on GitHub and docs: architecture, runtimes, GraalVM, benchmarks
- Nucleus GraalVM maintainer post (Kotlin Slack) and the Nucleus 2.0 thread with the hot reload report
- Testing Compose Multiplatform UI and desktop UI testing with JUnit
- Compose Hot Reload 1.2.0 release, InfoWorld on Compose Multiplatform 1.12 and MCP, Slack: hot reload MCP with Claude, Slack: getting the MCP server running
- Slack: Compose desktop and native image