[{"data":1,"prerenderedAt":726},["ShallowReactive",2],{"navigation_docs":3,"-getting-started-first-test":223,"-getting-started-first-test-surround":721},[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":21,"body":225,"description":714,"extension":715,"links":716,"meta":717,"navigation":718,"path":22,"seo":719,"stem":23,"__hash__":720},"docs\u002F1.getting-started\u002F3.first-test.md",{"type":226,"value":227,"toc":712},"minimark",[228,649,685,688,691,702,708],[229,230,236],"pre",{"className":231,"code":232,"filename":233,"language":234,"meta":235,"style":235},"language-scala shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import kyo.*\nimport kyo.UI.Keyboard\nimport munit.FunSuite\nimport tui4s.testkit.Headless\n\n\u002F** The Counter on the headless test kit: no terminal. *\u002F\nclass CounterSuite extends FunSuite:\n\n    \u002F** Runs `v` to its end on the Kyo scheduler (at most 30 s). *\u002F\n    private def run[A](v: => A \u003C (Async & Scope))(using Frame): A =\n        import AllowUnsafe.embrace.danger\n        KyoApp.Unsafe.runAndBlock(30.seconds)(v).getOrThrow\n\n    test(\"+ and - change the count\") {\n      val s = run {\n        for\n            h \u003C- Headless.start(50, 8)(Counter.ui)\n            _ \u003C- h.char('+')\n            _ \u003C- h.char('+')\n            _ \u003C- h.char('-')\n            s \u003C- h.screen\n        yield s\n      }\n      assert(s.contains(\"Count: 1\"), s.text)\n    }\n","CounterSuite.scala","scala","",[237,238,239,259,276,289,307,314,321,339,344,350,422,441,461,466,487,504,511,544,566,583,601,612,621,627,643],"code",{"__ignoreMap":235},[240,241,244,248,252,256],"span",{"class":242,"line":243},"line",1,[240,245,247],{"class":246},"sbssI","import",[240,249,251],{"class":250},"sBMFI"," kyo",[240,253,255],{"class":254},"sMK4o",".",[240,257,258],{"class":250},"*\n",[240,260,262,264,266,268,271,273],{"class":242,"line":261},2,[240,263,247],{"class":246},[240,265,251],{"class":250},[240,267,255],{"class":254},[240,269,270],{"class":250},"UI",[240,272,255],{"class":254},[240,274,275],{"class":250},"Keyboard\n",[240,277,279,281,284,286],{"class":242,"line":278},3,[240,280,247],{"class":246},[240,282,283],{"class":250}," munit",[240,285,255],{"class":254},[240,287,288],{"class":250},"FunSuite\n",[240,290,292,294,297,299,302,304],{"class":242,"line":291},4,[240,293,247],{"class":246},[240,295,296],{"class":250}," tui4s",[240,298,255],{"class":254},[240,300,301],{"class":250},"testkit",[240,303,255],{"class":254},[240,305,306],{"class":250},"Headless\n",[240,308,310],{"class":242,"line":309},5,[240,311,313],{"emptyLinePlaceholder":312},true,"\n",[240,315,317],{"class":242,"line":316},6,[240,318,320],{"class":319},"sHwdD","\u002F** The Counter on the headless test kit: no terminal. *\u002F\n",[240,322,324,327,330,333,336],{"class":242,"line":323},7,[240,325,326],{"class":254},"class",[240,328,329],{"class":250}," CounterSuite",[240,331,332],{"class":254}," extends",[240,334,335],{"class":250}," FunSuite",[240,337,338],{"class":254},":\n",[240,340,342],{"class":242,"line":341},8,[240,343,313],{"emptyLinePlaceholder":312},[240,345,347],{"class":242,"line":346},9,[240,348,349],{"class":319},"    \u002F** Runs `v` to its end on the Kyo scheduler (at most 30 s). *\u002F\n",[240,351,353,357,360,364,368,371,374,378,381,384,387,390,393,396,399,402,405,408,411,414,417,419],{"class":242,"line":352},10,[240,354,356],{"class":355},"spNyl","    private",[240,358,359],{"class":254}," def",[240,361,363],{"class":362},"s2Zo4"," run",[240,365,367],{"class":366},"sTEyZ","[",[240,369,370],{"class":250},"A",[240,372,373],{"class":366},"](",[240,375,377],{"class":376},"sHdIc","v",[240,379,380],{"class":366},": ",[240,382,383],{"class":254},"=>",[240,385,386],{"class":250}," A",[240,388,389],{"class":254}," \u003C",[240,391,392],{"class":366}," (",[240,394,395],{"class":250},"Async",[240,397,398],{"class":254}," &",[240,400,401],{"class":250}," Scope",[240,403,404],{"class":366},"))(",[240,406,407],{"class":254},"using",[240,409,410],{"class":250}," Frame",[240,412,413],{"class":366},")",[240,415,416],{"class":254},":",[240,418,386],{"class":250},[240,420,421],{"class":254}," =\n",[240,423,425,428,431,433,436,438],{"class":242,"line":424},11,[240,426,427],{"class":246},"        import",[240,429,430],{"class":250}," AllowUnsafe",[240,432,255],{"class":254},[240,434,435],{"class":250},"embrace",[240,437,255],{"class":254},[240,439,440],{"class":250},"danger\n",[240,442,444,447,449,452,455,458],{"class":242,"line":443},12,[240,445,446],{"class":250},"        KyoApp",[240,448,255],{"class":366},[240,450,451],{"class":250},"Unsafe",[240,453,454],{"class":366},".runAndBlock(",[240,456,457],{"class":246},"30",[240,459,460],{"class":366},".seconds)(v).getOrThrow\n",[240,462,464],{"class":242,"line":463},13,[240,465,313],{"emptyLinePlaceholder":312},[240,467,469,472,475,479,481,484],{"class":242,"line":468},14,[240,470,471],{"class":366},"    test(",[240,473,474],{"class":254},"\"",[240,476,478],{"class":477},"sfazB","+ and - change the count",[240,480,474],{"class":254},[240,482,483],{"class":366},") ",[240,485,486],{"class":254},"{\n",[240,488,490,493,496,499,502],{"class":242,"line":489},15,[240,491,492],{"class":254},"      val",[240,494,495],{"class":366}," s ",[240,497,498],{"class":254},"=",[240,500,501],{"class":366}," run ",[240,503,486],{"class":254},[240,505,507],{"class":242,"line":506},16,[240,508,510],{"class":509},"s7zQu","        for\n",[240,512,514,517,520,523,526,529,532,535,538,541],{"class":242,"line":513},17,[240,515,516],{"class":366},"            h ",[240,518,519],{"class":254},"\u003C-",[240,521,522],{"class":250}," Headless",[240,524,525],{"class":366},".start(",[240,527,528],{"class":246},"50",[240,530,531],{"class":366},", ",[240,533,534],{"class":246},"8",[240,536,537],{"class":366},")(",[240,539,540],{"class":250},"Counter",[240,542,543],{"class":366},".ui)\n",[240,545,547,550,552,555,558,561,563],{"class":242,"line":546},18,[240,548,549],{"class":366},"            _ ",[240,551,519],{"class":254},[240,553,554],{"class":366}," h.char(",[240,556,557],{"class":254},"'",[240,559,560],{"class":477},"+",[240,562,557],{"class":254},[240,564,565],{"class":366},")\n",[240,567,569,571,573,575,577,579,581],{"class":242,"line":568},19,[240,570,549],{"class":366},[240,572,519],{"class":254},[240,574,554],{"class":366},[240,576,557],{"class":254},[240,578,560],{"class":477},[240,580,557],{"class":254},[240,582,565],{"class":366},[240,584,586,588,590,592,594,597,599],{"class":242,"line":585},20,[240,587,549],{"class":366},[240,589,519],{"class":254},[240,591,554],{"class":366},[240,593,557],{"class":254},[240,595,596],{"class":477},"-",[240,598,557],{"class":254},[240,600,565],{"class":366},[240,602,604,607,609],{"class":242,"line":603},21,[240,605,606],{"class":366},"            s ",[240,608,519],{"class":254},[240,610,611],{"class":366}," h.screen\n",[240,613,615,618],{"class":242,"line":614},22,[240,616,617],{"class":509},"        yield",[240,619,620],{"class":366}," s\n",[240,622,624],{"class":242,"line":623},23,[240,625,626],{"class":254},"      }\n",[240,628,630,633,635,638,640],{"class":242,"line":629},24,[240,631,632],{"class":366},"      assert(s.contains(",[240,634,474],{"class":254},[240,636,637],{"class":477},"Count: 1",[240,639,474],{"class":254},[240,641,642],{"class":366},"), s.text)\n",[240,644,646],{"class":242,"line":645},25,[240,647,648],{"class":254},"    }\n",[650,651,652,655,656,531,659,531,662,531,665,668,669,672,673,676,677,680,681,684],"p",{},[237,653,654],{},"Headless.start(50, 8)"," runs the UI on a screen of 50 columns and 8 rows, with no terminal. ",[237,657,658],{},"char",[237,660,661],{},"key",[237,663,664],{},"press",[237,666,667],{},"typeText"," and ",[237,670,671],{},"paste"," send input, and each one waits for the frame of its input. ",[237,674,675],{},"screen"," gives the current screen. ",[237,678,679],{},"Screen.contains"," finds a text, and ",[237,682,683],{},"Screen.text"," gives all the rows.",[650,686,687],{},"The test runs on munit here. The test kit does not need munit: it gives Kyo values, so any test framework that can run a Kyo value can use it. tui4s itself uses kyo-test for its own suites.",[650,689,690],{},"Run the tests of the examples:",[229,692,696],{"className":693,"code":694,"language":695,"meta":235,"style":235},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","sbt examples\u002Ftest\n","sh",[237,697,698],{"__ignoreMap":235},[240,699,700],{"class":242,"line":243},[240,701,694],{},[650,703,704,707],{},[705,706,78],"a",{"href":79}," shows a test that waits for a signal, and the checks in a real terminal.",[709,710,711],"style",{},"html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":235,"searchDepth":261,"depth":261,"links":713},[],"Test the Counter on the headless test kit, with no terminal.","md",null,{},{"icon":24},{"title":21,"description":714},"coG_yIdXP9k2F43OAaZvkmAg3XUG7WXE8cvtdBvqAZY",[722,724],{"title":16,"path":17,"stem":18,"description":723,"icon":19,"children":-1},"The Counter example, its main class and its start script.",{"title":33,"path":34,"stem":35,"description":725,"icon":36,"children":-1},"Two styles of components, hooks and a class, and how long their state lives.",1792528754462]