[{"data":1,"prerenderedAt":539},["ShallowReactive",2],{"navigation_docs":3,"-decisions-0009-use-and-widgets-cross-built":223,"-decisions-0009-use-and-widgets-cross-built-surround":534},[4,26,96,105,202],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":25},"Getting started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,20],{"title":11,"path":12,"stem":13,"icon":14},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F1.installation","i-lucide-download",{"title":16,"path":17,"stem":18,"icon":19},"The first app","\u002Fgetting-started\u002Ffirst-app","1.getting-started\u002F2.first-app","i-lucide-play",{"title":21,"path":22,"stem":23,"icon":24},"The first test","\u002Fgetting-started\u002Ffirst-test","1.getting-started\u002F3.first-test","i-lucide-flask-conical",false,{"title":27,"icon":28,"path":29,"stem":30,"children":31,"page":25},"Guide","i-lucide-book-open","\u002Fguide","2.guide",[32,37,42,47,52,57,62,67,72,77,81,86,91],{"title":33,"path":34,"stem":35,"icon":36},"Components","\u002Fguide\u002Fcomponents","2.guide\u002F01.components","i-lucide-component",{"title":38,"path":39,"stem":40,"icon":41},"Inputs and forms","\u002Fguide\u002Finputs-and-forms","2.guide\u002F02.inputs-and-forms","i-lucide-text-cursor-input",{"title":43,"path":44,"stem":45,"icon":46},"Scrolling and viewports","\u002Fguide\u002Fscrolling","2.guide\u002F03.scrolling","i-lucide-scroll-text",{"title":48,"path":49,"stem":50,"icon":51},"Overlays and layers","\u002Fguide\u002Foverlays","2.guide\u002F04.overlays","i-lucide-layers",{"title":53,"path":54,"stem":55,"icon":56},"Styles and theme","\u002Fguide\u002Fstyles","2.guide\u002F05.styles","i-lucide-palette",{"title":58,"path":59,"stem":60,"icon":61},"Composables","\u002Fguide\u002Fcomposables","2.guide\u002F06.composables","i-lucide-puzzle",{"title":63,"path":64,"stem":65,"icon":66},"Widgets","\u002Fguide\u002Fwidgets","2.guide\u002F07.widgets","i-lucide-blocks",{"title":68,"path":69,"stem":70,"icon":71},"The assistant","\u002Fguide\u002Fassistant","2.guide\u002F08.assistant","i-lucide-message-square",{"title":73,"path":74,"stem":75,"icon":76},"The same app on the web","\u002Fguide\u002Fweb","2.guide\u002F09.web","i-lucide-globe",{"title":78,"path":79,"stem":80,"icon":24},"Testing","\u002Fguide\u002Ftesting","2.guide\u002F10.testing",{"title":82,"path":83,"stem":84,"icon":85},"Running and packaging","\u002Fguide\u002Fpackaging","2.guide\u002F11.packaging","i-lucide-package",{"title":87,"path":88,"stem":89,"icon":90},"The terminal contract","\u002Fguide\u002Fterminal","2.guide\u002F12.terminal","i-lucide-square-terminal",{"title":92,"path":93,"stem":94,"icon":95},"Benchmarks","\u002Fguide\u002Fbenchmarks","2.guide\u002F13.benchmarks","i-lucide-gauge",{"title":97,"icon":98,"path":99,"stem":100,"children":101,"page":25},"AI and agents","i-lucide-bot","\u002Fai","3.ai",[102],{"title":97,"path":103,"stem":104,"icon":98},"\u002Fai\u002Fagents","3.ai\u002F1.agents",{"title":106,"icon":107,"path":108,"stem":109,"children":110},"Decisions","i-lucide-scale","\u002Fdecisions","4.decisions",[111,114,118,122,126,130,134,138,142,146,150,154,158,162,166,170,174,178,182,186,190,194,198],{"title":112,"path":108,"stem":113,"icon":46},"All decisions","4.decisions\u002Findex",{"title":115,"path":116,"stem":117},"0001. kyo-ui is the DSL","\u002Fdecisions\u002F0001-kyo-ui-is-the-dsl","4.decisions\u002F0001-kyo-ui-is-the-dsl",{"title":119,"path":120,"stem":121},"0002. jatatui renders the cells","\u002Fdecisions\u002F0002-jatatui-renders-the-cells","4.decisions\u002F0002-jatatui-renders-the-cells",{"title":123,"path":124,"stem":125},"0003. Pin Kyo 1.0.0-RC7 and JDK 25","\u002Fdecisions\u002F0003-pin-kyo-rc7-and-jdk-25","4.decisions\u002F0003-pin-kyo-rc7-and-jdk-25",{"title":127,"path":128,"stem":129},"0004. Public Kyo APIs only","\u002Fdecisions\u002F0004-public-kyo-apis-only","4.decisions\u002F0004-public-kyo-apis-only",{"title":131,"path":132,"stem":133},"0005. A SOFT frame contract and an own watcher loop","\u002Fdecisions\u002F0005-soft-frame-contract-and-own-watcher-loop","4.decisions\u002F0005-soft-frame-contract-and-own-watcher-loop",{"title":135,"path":136,"stem":137},"0006. Purity: edge files, lint and a pure painter","\u002Fdecisions\u002F0006-purity-edge-files-and-lint","4.decisions\u002F0006-purity-edge-files-and-lint",{"title":139,"path":140,"stem":141},"0007. Compiler flags from sbt-tpolecat","\u002Fdecisions\u002F0007-compiler-flags-from-sbt-tpolecat","4.decisions\u002F0007-compiler-flags-from-sbt-tpolecat",{"title":143,"path":144,"stem":145},"0008. The artifact split: runner, app, testkit and core","\u002Fdecisions\u002F0008-artifact-split-runner-app-testkit-core","4.decisions\u002F0008-artifact-split-runner-app-testkit-core",{"title":147,"path":148,"stem":149},"0009. tui4s-use and tui4s-widgets, cross-built","\u002Fdecisions\u002F0009-use-and-widgets-cross-built","4.decisions\u002F0009-use-and-widgets-cross-built",{"title":151,"path":152,"stem":153},"0010. The flagship assistant is the blueprint of a real app","\u002Fdecisions\u002F0010-the-flagship-assistant-as-blueprint","4.decisions\u002F0010-the-flagship-assistant-as-blueprint",{"title":155,"path":156,"stem":157},"0011. The web target: a standalone Scala.js app and one theme","\u002Fdecisions\u002F0011-web-target-standalone-scalajs-and-one-theme","4.decisions\u002F0011-web-target-standalone-scalajs-and-one-theme",{"title":159,"path":160,"stem":161},"0012. The documentation is example-first","\u002Fdecisions\u002F0012-documentation-is-example-first","4.decisions\u002F0012-documentation-is-example-first",{"title":163,"path":164,"stem":165},"0013. Benchmarks in the repository, with their own build","\u002Fdecisions\u002F0013-benchmarks-in-repo-with-own-build","4.decisions\u002F0013-benchmarks-in-repo-with-own-build",{"title":167,"path":168,"stem":169},"0014. Release per module, by tag, as 0.1.0-RC1","\u002Fdecisions\u002F0014-release-per-module-tags-and-rc1","4.decisions\u002F0014-release-per-module-tags-and-rc1",{"title":171,"path":172,"stem":173},"0015. Packaging: the AOT cache in the start scripts, native-image as an option","\u002Fdecisions\u002F0015-packaging-aot-cache-and-native-image","4.decisions\u002F0015-packaging-aot-cache-and-native-image",{"title":175,"path":176,"stem":177},"0016. Process: lean mode and agent roles","\u002Fdecisions\u002F0016-process-lean-mode-and-agent-roles","4.decisions\u002F0016-process-lean-mode-and-agent-roles",{"title":179,"path":180,"stem":181},"0017. Deferred: templates, mouse, the kitty keyboard protocol and Windows","\u002Fdecisions\u002F0017-deferred-templates-mouse-and-kitty","4.decisions\u002F0017-deferred-templates-mouse-and-kitty",{"title":183,"path":184,"stem":185},"0018. pini4s is a vendored Git submodule","\u002Fdecisions\u002F0018-pini4s-as-a-vendored-submodule","4.decisions\u002F0018-pini4s-as-a-vendored-submodule",{"title":187,"path":188,"stem":189},"0019. Upstream issues are drafts that the user posts","\u002Fdecisions\u002F0019-upstream-kyo-issues-as-drafts","4.decisions\u002F0019-upstream-kyo-issues-as-drafts",{"title":191,"path":192,"stem":193},"0020. Release from the Actions form","\u002Fdecisions\u002F0020-release-from-the-actions-form","4.decisions\u002F0020-release-from-the-actions-form",{"title":195,"path":196,"stem":197},"0021. The rulebook is CONTRIBUTING.md","\u002Fdecisions\u002F0021-the-rulebook-is-contributing-md","4.decisions\u002F0021-the-rulebook-is-contributing-md",{"title":199,"path":200,"stem":201},"0022. The logo is text","\u002Fdecisions\u002F0022-the-logo-is-text","4.decisions\u002F0022-the-logo-is-text",{"title":203,"icon":204,"path":205,"stem":206,"children":207,"page":25},"Project","i-lucide-compass","\u002Fproject","5.project",[208,213,218],{"title":209,"path":210,"stem":211,"icon":212},"Roadmap","\u002Fproject\u002Froadmap","5.project\u002F1.roadmap","i-lucide-map",{"title":214,"path":215,"stem":216,"icon":217},"Contributing","\u002Fproject\u002Fcontributing","5.project\u002F2.contributing","i-lucide-git-pull-request",{"title":219,"path":220,"stem":221,"icon":222},"Security","\u002Fproject\u002Fsecurity","5.project\u002F3.security","i-lucide-shield",{"id":224,"title":147,"body":225,"description":527,"extension":528,"links":529,"meta":530,"navigation":531,"path":148,"seo":532,"stem":149,"__hash__":533},"docs\u002F4.decisions\u002F0009-use-and-widgets-cross-built.md",{"type":226,"value":227,"toc":516},"minimark",[228,233,237,241,244,248,273,277,400,404,446,450,501,505],[229,230,232],"h2",{"id":231},"status","Status",[234,235,236],"p",{},"Accepted",[229,238,240],{"id":239},"date","Date",[234,242,243],{},"2026-10-17 (D6). Landed on 2026-10-19 (L2a, L2b).",[229,245,247],{"id":246},"context","Context",[249,250,251,255,258,266],"ul",{},[252,253,254],"li",{},"The user asked for \"enough widgets to build most terminal applications\", a hook library \"like VueUse\", and one widget tree for the terminal and the web.",[252,256,257],{},"Research of 2026-10-17: most of VueUse is 1 to 3 lines of native Kyo. About 13 composables earn their place. Of 28 common terminal widgets, 5 worked, 22 had only a workaround, and 1 (textarea) could not be built before L1.",[252,259,260,261,265],{},"kyo-ui runs one UI value in the browser (Scala.js ",[262,263,264],"code",{},"runMount","). A widget written as a plain kyo-ui function, with no tui4s import, works on both targets.",[252,267,268,269,272],{},"The user's experiments (",[262,270,271],{},"experiments\u002Fui-state",") found that model and event wrappers did not simplify the real call site.",[229,274,276],{"id":275},"decision","Decision",[249,278,279,304,316,334,365,387,390],{},[252,280,281,284,285,288,289,292,293,296,297,300,301,303],{},[262,282,283],{},"tui4s-use"," (package ",[262,286,287],{},"tui4s.use",") has 13 composables over ",[262,290,291],{},"SignalRef",", ",[262,294,295],{},"Signal"," and ",[262,298,299],{},"Scope",": Pacing, Selection, History, ScrollWindow, TimeAgo, Keymap, AsyncState, Dialog, Storage, useProcess and others. Each takes its lifetime from the caller's ",[262,302,299],{},".",[252,305,306,284,309,312,313,315],{},[262,307,308],{},"tui4s-widgets",[262,310,311],{},"tui4s.widgets",") has 31 widgets on ",[262,314,283],{},": containers, data, input, feedback and navigation families.",[252,317,318,319,322,323,326,327,292,330,333],{},"Both are cross-built (JVM and Scala.js, ",[262,320,321],{},"CrossType.Full",") and depend on kyo-ui only, not on ",[262,324,325],{},"tui4s-core",". Each has its own tag (",[262,328,329],{},"use-v*",[262,331,332],{},"widgets-v*",").",[252,335,336,337,340,341,344,345,348,349,351,352,355,356,348,359,361,362,364],{},"A widget takes typed props, a ",[262,338,339],{},"Model"," for its value (",[262,342,343],{},"Local",": the widget owns it; ",[262,346,347],{},"Bound",": the parent's ",[262,350,291],{},", written before ",[262,353,354],{},"onChange","; ",[262,357,358],{},"Controlled",[262,360,295],{},", and ",[262,363,354],{}," asks), and function slots.",[252,366,367,368,371,372,375,376,379,380,292,383,386],{},"A widget takes its colours from the ",[262,369,370],{},"--tui4s-*"," custom properties (",[262,373,374],{},"Colors",", 8 ",[262,377,378],{},"Color.Var"," names) and also shows its state with a mark (",[262,381,382],{},"›",[262,384,385],{},"[x]",", bold).",[252,388,389],{},"The README section \"You may not need a composable\" comes first: it shows the native Kyo line for each common hook.",[252,391,392,393,296,396,399],{},"The gallery example shows each widget; ",[262,394,395],{},"GallerySuite",[262,397,398],{},"ReadmeSuite"," check them.",[229,401,403],{"id":402},"alternatives-considered","Alternatives considered",[249,405,406,413,424,430,436],{},[252,407,408,412],{},[409,410,411],"strong",{},"A separate UI core repository."," Rejected: \"There is no separate UI core repository\" (CONTRIBUTING.md). tui4s owns the terminal rendering and its mount identity; the widgets stay plain kyo-ui. The reason for the repository choice is not recorded beyond that. ",[252,414,415,420,421,423],{},[409,416,417,418,303],{},"Widgets inside ",[262,419,325],{}," Rejected by D6 (\"no dependency on core\"). The notes do not state the reason. The obvious one: ",[262,422,325],{}," is JVM-only (the crossterm JNI), and a widget must also run on Scala.js.",[252,425,426,429],{},[409,427,428],{},"An own render engine for the web and the terminal."," Estimated at 6k to 12k lines (engine 3-5k, widgets 2-4k, web client 1-2k) and several months. Rejected for the hybrid: plain kyo-ui widgets, and units and a theme fixed instead.",[252,431,432,435],{},[409,433,434],{},"Model and event wrappers over Kyo signals."," Rejected by the user's experiments: no simpler call site.",[252,437,438,441,442,333],{},[409,439,440],{},"pini4s for widget state."," Rejected: a widget owns its local state by default; pini4s stays optional (",[443,444,445],"a",{"href":184},"ADR 0018",[229,447,449],{"id":448},"consequences","Consequences",[249,451,452,461,464,480,494],{},[252,453,454,455,292,458,333],{},"Good: the same widget runs in the terminal and in the browser, and the assistant uses them on both (",[443,456,457],{"href":152},"ADR 0010",[443,459,460],{"href":156},"ADR 0011",[252,462,463],{},"Good: users without tui4s-core (a pure kyo-ui web app) can use the widgets.",[252,465,466,467,296,470,473,474,333],{},"Bad: the widgets have no unit scale for the web. ",[262,468,469],{},"px",[262,471,472],{},"em"," are cells in the terminal; on the web a list or a pager clips about one line in five (",[443,475,479],{"href":476,"rel":477},"https:\u002F\u002Fgithub.com\u002Fyuri4n\u002Ftui4s\u002Fissues\u002F25",[478],"nofollow","#25",[252,481,482,483,486,487,296,490,493],{},"Bad: ",[262,484,485],{},"RadioGroup"," has no click handler, so it is keyboard-only on the web; ",[262,488,489],{},"Toasts",[262,491,492],{},"Spinner"," render again on each tick.",[252,495,496,497,500],{},"Bad: sbt-scalajs in ",[262,498,499],{},"project\u002F"," marks all modules unreleased (accepted before 0.1).",[229,502,504],{"id":503},"references","References",[249,506,507,510,513],{},[252,508,509],{},"Roadmap: 2.6, D6, D7, D8. Handoff: L2a, L2b.",[252,511,512],{},"CONTRIBUTING.md: \"UI architecture and API design\" (the use and widgets items). ",[252,514,515],{},"README: \"Composables (tui4s-use)\", \"Widgets (tui4s-widgets)\".",{"title":517,"searchDepth":518,"depth":518,"links":519},"",2,[520,521,522,523,524,525,526],{"id":231,"depth":518,"text":232},{"id":239,"depth":518,"text":240},{"id":246,"depth":518,"text":247},{"id":275,"depth":518,"text":276},{"id":402,"depth":518,"text":403},{"id":448,"depth":518,"text":449},{"id":503,"depth":518,"text":504},"Composables (tui4s-use) and widgets (tui4s-widgets) are published modules of this repository, plain kyo-ui code for the JVM and Scala.js, with no dependency on tui4s-core.","md",null,{},true,{"title":147,"description":527},"7WNlgH62wRFwT9sL8_vKoA2J2oY-bsV14eAUOqma7qs",[535,537],{"title":143,"path":144,"stem":145,"description":536,"children":-1},"tui4s-core becomes a composite of three artifacts in this repository: tui4s-runner, tui4s-app and tui4s-testkit, each with its own version and tag, in the same packages, with tui4s.internal sealed.",{"title":151,"path":152,"stem":153,"description":538,"children":-1},"One flagship example, an AI chat assistant, shows how to structure a real tui4s app: a pure domain, services as traits with interpreters, one store, Kyo layers and errors as values.",1792528756586]