-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
glyphora is a stack of small modules joined by one render pipeline. Applications can use the complete DSL or stop at any lower tier; widgets never depend on a terminal, and the terminal never knows about signals.
flowchart LR
DSL["tui-dsl<br/>elements · focus · chrome"] --> Widgets["tui-widgets<br/>render · layout · input"]
Widgets --> Core["tui-core<br/>buffer · cells · style"]
Core --> Terminal["tui-terminal<br/>diff → ANSI"]
Runtime["tui-runtime<br/>signals · loop · effects"] --> Core
DSL --> Runtime
Macros["tui-macros<br/>compile-time derivation"] -. generated calls .-> DSL
Each arrow in the module graph is a real Mill dependency — nothing above tui-core reaches back
down into a layer above it, so you can also depend on any single tier directly (for
example, tui-widgets with a backend of your own, skipping the DSL entirely).
| Module | What it owns | API reference |
|---|---|---|
tui-core |
Buffer/Cell, Style, Layout solver, Widget traits, event ADT, CharWidth (UCD-generated width table) |
tui-core |
tui-terminal |
Backend trait, JLine 3 impl (diff flush, input decoding), HeadlessBackend
|
tui-terminal |
tui-widgets |
every built-in widget — backend-agnostic, render-to-Buffer tested |
tui-widgets |
tui-runtime |
Signal/Computed, render thread, runner loop, Effect engine |
tui-runtime |
tui-dsl |
TuiApp, Element tree, focus/mouse routing, chrome presets, screens/toasts/palette |
tui-dsl |
tui-macros |
deriveForm/bindAction — compile-time only, keeps native-image reflect-config-free |
tui-macros |
test-support |
Pilot driver + buffer assertions (not published; test-only) |
— |
Foundational types, no dependencies, no terminal I/O, no reflection — the maximum-stability tier everything else builds on:
-
Geometry:
Rect,Position,Size. -
Frame buffer:
Buffer(mutable cell grid, absolute coordinates, silent clipping),Cell(aStringsymbol, because one cell can hold a multi-codepoint grapheme cluster). -
Styling:
Style,Color,Modifiers(allocation-free bitset). -
Text:
Text/Line/Span. -
CharWidth: terminal display-width arithmetic (CJK, combining marks, emoji ZWJ sequences, flags, variation selectors) — generated from the Unicode Character Database bytools/generate-width-table.py. -
Layout:
Constraint(Length/Percentage/Ratio/Min/Max/Fill) and theLayout.splitsolver. -
Widget traits:
Widget,StatefulWidget[S]— SAM-convertible. -
Input events:
Event/KeyEvent/MouseEventADT, defined here (not intui-terminal) so widgets stay backend-agnostic.
import io.worxbend.tui.core.*
val buffer = Buffer(Rect(0, 0, 20, 3))
val areas = Layout.vertical(1, Constraint.fill).split(buffer.area)
buffer.setString(areas(0).x, areas(0).y, "Title", Style.Default.bold.withFg(Color.Cyan))The terminal backend layer. Everything above (tui-runtime, widgets, DSL) talks to
Backend only:
-
Backend— raw mode, alternate screen, cursor visibility, mouse capture, diff-baseddraw(buffer),readEvent(timeout). All fallible operations returnEither[BackendError, A]. -
JLine3Backend— the production implementation overorg.jline:jline3.30.x, pinned. Keeps a snapshot of the last flushed frame and writes only changed cells, batched into one ANSI string per frame, with OSC 8 hyperlink transitions. -
InputDecoder— ANSI/CSI/SS3/SGR-mouse decoder, injected with a plainread(timeoutMillis) => Intfunction so it is fully unit-tested without a TTY. -
HeadlessBackend— in-memory backend for thePilotend-to-end test harness.
The trait is deliberately JLine-free: a fake backend can implement it without
importing a single JLine type, and every example runs against HeadlessBackend in
tests and JLine3Backend live (JVM or native binary).
Every built-in widget. Depends only on tui-core — widgets are
backend-agnostic and render into a Buffer, nothing else. See the full
Widget catalog.
The mid-level framework tier:
-
Signal[A]/Computed[A]/ReactiveScope— fine-grained signals. See State & signals. -
RenderThread— single-render-thread contract:checkRenderThread()is a no-op when no runtime is running (so plain unit tests need no setup),runOnRenderThread,runLater.Signal.setasserts it. -
Runner/TerminalRunner/Frame/RunnerConfig— the event/render loop: terminal setup/teardown, diff-driven redraws, tick emission, resize handling. -
Effect— the post-render motion engine. See Motion.
The high-level declarative API — what applications use day-to-day: Element,
TuiApp, the chrome presets (scaffold, topBar, statusBar, sidebar), themes,
screens, toasts, the command palette, and focus/mouse routing. See
The app shell and Mouse & focus.
Compile-time codegen: everywhere the framework bridges user-defined code, the bridge is generated at compile time — never runtime reflection. This is the constraint that keeps GraalVM native-image builds free of reflect-config JSON.
-
deriveForm[A]derives aFormSpec[A]from a case class viaMirror.ProductOf(inline, stdlib-only): field names becomeFieldSpecs, field types choose the input kind (String/Int/Boolean); anything else is a compile error. -
bindAction[A](handler)binds an action handler as a direct call. -
Field[A]is cue4s-style lazily-composed parsing/validation:Field.int("age").mapValidated(a => if a >= 18 then Right(a) else Left("must be 18+")).
CI enforces the zero-reflection rule with a grep over all main sources.
- No
java.lang.reflect/Class.forNameanywhere outsidetui-macros' compile-time codegen. - No
String.length/substringfor layout math outsideCharWidth— grapheme clusters and wide codepoints must always go through the Unicode-aware table. - Warnings are errors (
-Wunused:all -Werror). - Scalafmt owns formatting; CI checks formatting, doesn't just apply it.
Documentation is maintained in website/docs. Read the styled guide · API reference · MIT license