Decisions
0002. jatatui renders the cells
jatatui 0.31.0 (a Java port of ratatui) with its crossterm JNI library is the terminal backend; tui-scala never goes on the classpath.
Status
Accepted
Date
2026-09-30 (the second round of the decision log).
Context
- The user first asked for a wrapper of "the Scala terminal library", which was tui-scala (
com.olvind.tui). - Research on 2026-09-30 found that the tui-scala repository now redirects to
oyvindberg/jatatui. The release notes of jatatui 0.30.0 call it "the successor to tui-scala". jatatui ports ratatui 0.30; tui-scala 0.0.10 ports tui-rs of 2022 (noLine, no Scrollbar, no Calendar). - jatatui publishes a
TestBackend, so a test can render without a terminal. tui-scala publishes none, and its unpublished one has a cell-aliasing bug. - tui-scala has hazards that a probe showed: a global unbounded
Layout.LAYOUT_CACHE(one entry per frame), live cell references fromBuffer.diff, and 329 KiB per frame for one full-screen block. - Both libraries load a native crossterm library (Rust, JNI) from the jar. Both use the Java package
tui.crosstermwith the same class names, under different Maven coordinates, so eviction cannot remove one of them. - A typical 120x40 jatatui frame costs about 2 ms and 1.7 MiB, also when nothing changed. Buffers, cells and widget states are mutable.
Decision
- tui4s uses
com.olvind.jatatui0.31.0:jatatui-coreandjatatui-crossterm(which bringscrossterm).jatatui-widgetsis not used (D-12 a). com.olvind.tui(tui-scala) never goes on the classpath.- tui4s does the terminal setup itself (raw mode, alternate screen, restore). It does not call
Jatatui.initorrunIo, because they install a global exception handler and a shutdown hook. - All drawing happens on one fiber. Only the input thread reads the terminal, with
poll(<short timeout>)thenread(), because the nativeread()blocks and an interrupt cannot stop it. - No jatatui type is in the public API of tui4s: the test kit has its own
CellStyleandCellColor(D-13 a). - A run in the terminal passes
--enable-native-access=ALL-UNNAMED. Without it JDK 25 prints a warning into the terminal; with--illegal-native-access=denythe run fails withTuiError.NativeAccessDenied.
Alternatives considered
- tui-scala (
com.olvind.tui0.0.10). It lost: development moved to jatatui, its API is older, it has no published test backend, and its layout cache leaks. - An own ANSI writer. Not recorded. The decision log names no evaluation of it.
- Lanterna. Not recorded.
- JLine. Not recorded as a backend. The research names JLine only as the reason that
sbt runfights raw mode.
Consequences
- Good: the cells, the diff and the backend come from a maintained port of ratatui.
TestBackendmakes headless tests possible (tui4s.testkit.Headless). - Good: tui4s owns the painter (
internal.Paintgives aPlan), and only the edge fileinternal/Canvas.scalatouches the jatatui buffers (ADR 0006). - Bad: jatatui is 0.x with one maintainer. Its JNI copy leaves one 904 KB
/tmp/jni-*folder per start; tui4s deletes it on Linux through/proc/self/maps(ADR 0019). - Bad: the JNI cuts a typed character outside the Basic Multilingual Plane to 16 bits. A bracketed paste keeps it.
- Bad: the native library ships for macOS (arm64, x86_64), Linux (x86_64, arm64, glibc 2.34 or later) and Windows x86_64 only. There is no musl or Windows arm64 build.
References
- Decision log: second round (2026-09-30).
- Plan: D-12, D-13. Research reports on tui-scala and jatatui of 2026-09-30 (in the notes; the facts above are copied from them).
- CONTRIBUTING.md: "Rules for code" (one fiber, the input thread, the JVM flag, no tui-scala).
0001. kyo-ui is the DSL
tui4s has no UI DSL of its own: kyo-ui describes the screen, and tui4s is its terminal runner.
0003. Pin Kyo 1.0.0-RC7 and JDK 25
Kyo and kyo-ui stay at 1.0.0-RC7 for the first release, on JDK 25 and Scala 3.9.0, and a tasty-query surface guard pins the kyo-ui members that the runner reads.