[{"data":1,"prerenderedAt":540},["ShallowReactive",2],{"navigation_docs":3,"-decisions-0006-purity-edge-files-and-lint":223,"-decisions-0006-purity-edge-files-and-lint-surround":535},[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":135,"body":225,"description":528,"extension":529,"links":530,"meta":531,"navigation":532,"path":136,"seo":533,"stem":137,"__hash__":534},"docs\u002F4.decisions\u002F0006-purity-edge-files-and-lint.md",{"type":226,"value":227,"toc":517},"minimark",[228,233,237,241,244,248,291,295,386,390,456,460,495,499],[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 (D1, D2, the purity plan); landed on 2026-10-19 (L0 done).",[229,245,247],{"id":246},"context","Context",[249,250,251,264,271,274,288],"ul",{},[252,253,254,255,259,260,263],"li",{},"The user asked for a cost-effective functional rewrite, with the skills ",[256,257,258],"code",{},"functional-design"," and ",[256,261,262],{},"scala-functional-design"," as the rules: impure code only for performance, and only behind an effect.",[252,265,266,267,270],{},"The audit of 2026-10-17 found 93 impure sites. \"Whiles everywhere\" was not correct (about 12 real loops, most in edge code). The shape was the problem: about 23 volatile or nullable fields, four copies of \"run every step, keep the first error\", exceptions for expected outcomes, a painter that wrote into the jatatui buffer while it decided, and ",[256,268,269],{},"java.util.concurrent"," where Kyo has primitives.",[252,272,273],{},"Classification: 29 sites stay behind one seam each, 2 kernels stay, about 50 are replaced and 13 are deleted.",[252,275,276,277,280,281,284,285,287],{},"A JVM shutdown hook must restore the terminal. Kyo RC7 ",[256,278,279],{},"Finalizer.close"," only spawns a fiber to drain a ",[256,282,283],{},"Scope",", so a hook cannot run ",[256,286,283],{}," finalizers.",[252,289,290],{},"L1 (scroll, overlays) changes the painter, and on a pure painter these features are cheaper and testable with properties. So L0 came before L1 (D1).",[229,292,294],{"id":293},"decision","Decision",[249,296,297,316,327,349,360,371],{},[252,298,299,300,303,304,307,308,311,312,315],{},"Impure code is only in an edge file or on a line with ",[256,301,302],{},"\u002F\u002F scalafix:ok DisableSyntax.\u003Cid>; KEEP: \u003Creason>",". An edge file wraps one foreign API: line 1 ",[256,305,306],{},"\u002F\u002F scalafix:off DisableSyntax",", line 2 ",[256,309,310],{},"\u002F\u002F EDGE: \u003CAPI>",". There are 9 edge files, all in ",[256,313,314],{},"tui4s.internal",", and 15 KEEP sites.",[252,317,318,319,322,323,326],{},"scalafix DisableSyntax checks the main sources in ",[256,320,321],{},"sbt check","; ",[256,324,325],{},"scripts\u002Fedge-check.sh"," checks the headers and the comments in CI.",[252,328,329,330,333,334,337,338,341,342,259,345,348],{},"The painter is pure: ",[256,331,332],{},"Paint"," gives a ",[256,335,336],{},"Plan"," (ops and damaged row spans). ",[256,339,340],{},"Canvas.scala"," has the only two loops over cells, ",[256,343,344],{},"Canvas.write",[256,346,347],{},"Canvas.diff",", each with a law and a property test (P5). The old painter stays in the tests as the oracle.",[252,350,351,352,355,356,359],{},"The release of a run is one synchronous stack (",[256,353,354],{},"ReleaseStack",", P3): each acquire pushes its release; ",[256,357,358],{},"runAll"," runs them once, last first; a failing step does not stop the next. One shutdown hook per run.",[252,361,362,363,366,367,370],{},"The live fibers of a run are one Kyo ",[256,364,365],{},"AtomicRef"," gauge (",[256,368,369],{},"FiberGauge",", P2); start and add are one bracket.",[252,372,373,374,377,378,381,382,385],{},"An enum, not a Boolean, selects runtime behaviour. Property tests use kyo-test-prop with explicit ",[256,375,376],{},"Gen"," values. ",[256,379,380],{},"TuiError"," is ",[256,383,384],{},"NoStackTrace"," with a non-null cause (A4).",[229,387,389],{"id":388},"alternatives-considered","Alternatives considered",[249,391,392,399,415,421,435,441,447],{},[252,393,394,398],{},[395,396,397],"strong",{},"Stay with the imperative runner."," The user rejected the shape of the code as \"badly written\", and L1 needed a testable painter.",[252,400,401,407,408,410,411,414],{},[395,402,403,404,406],{},"A Kyo ",[256,405,283],{}," for the terminal release."," Not possible from a JVM hook: ",[256,409,279],{}," only spawns a drain fiber (Kyo ",[256,412,413],{},"Scope.scala:346-383",").",[252,416,417,420],{},[395,418,419],{},"A purity script, a ratchet and TODO headers."," Cut by the skill review: scalafix DisableSyntax and one CI grep are enough.",[252,422,423,430,431,434],{},[395,424,425,426,429],{},"Move the edge files into a package ",[256,427,428],{},"internal.edge","."," Cut by the skill review (nine ",[256,432,433],{},"git mv"," for no behaviour).",[252,436,437,440],{},[395,438,439],{},"ScalaCheck."," Not used: kyo-test-prop exists for RC7.",[252,442,443,446],{},[395,444,445],{},"P7, a typed data model."," Out of the rewrite (D2 c). Two casts hold by construction.",[252,448,449,452,453,429],{},[395,450,451],{},"A third kernel over cells."," Forbidden: a new write is a new ",[256,454,455],{},"Op",[229,457,459],{"id":458},"consequences","Consequences",[249,461,462,465,472,482,485],{},[252,463,464],{},"Good: the branch shrank by 2,149 lines net (151 files, +6,843\u002F-8,992). Suppressions went 105, 49, 47, 38, 29, 19, then 15 KEEP sites.",[252,466,467,468,471],{},"Good: the restore order is one list, and ",[256,469,470],{},"ReleaseStackTest"," checks it with 100 seeded rounds and a negative control.",[252,473,474,475,478,479,414],{},"Good: the oracles and property tests pin real fixes: the clip counts cells, not UTF-16 chars (",[256,476,477],{},"ClipTest","), and an incremental frame equals a full frame (",[256,480,481],{},"StepPropertyTest",[252,483,484],{},"Bad: the edge files and KEEP sites need review by hand; the lint cannot judge them.",[252,486,487,488,491,492,429],{},"Bad: ",[256,489,490],{},"Component.initial"," is pure, so a per-mount effect value arrives through ",[256,493,494],{},"onMount",[229,496,498],{"id":497},"references","References",[249,500,501,504,514],{},[252,502,503],{},"Roadmap: 2.4, D1, D2. Skill review amendments A1 to A11.",[252,505,506,507,414],{},"Handoff: P0 to P6 and \"L0 DONE 2026-10-19\". Decision log: D20, D23, D24 (open, ",[508,509,513],"a",{"href":510,"rel":511},"https:\u002F\u002Fgithub.com\u002Fyuri4n\u002Ftui4s\u002Fissues\u002F46",[512],"nofollow","#46",[252,515,516],{},"CONTRIBUTING.md: \"Purity\", \"Rules for code\" (the release stack, the fiber gauge). ",{"title":518,"searchDepth":519,"depth":519,"links":520},"",2,[521,522,523,524,525,526,527],{"id":231,"depth":519,"text":232},{"id":239,"depth":519,"text":240},{"id":246,"depth":519,"text":247},{"id":293,"depth":519,"text":294},{"id":388,"depth":519,"text":389},{"id":458,"depth":519,"text":459},{"id":497,"depth":519,"text":498},"The functional rewrite (L0, phases P1 to P6) keeps impure code in nine edge files and a few marked KEEP sites, checked by scalafix DisableSyntax, with a pure painter, a synchronous release stack and one fiber gauge.","md",null,{},true,{"title":135,"description":528},"tfnXc1JRcpOAzgQo0jLFyVJa4CzQJT-JItc0G9OmHB0",[536,538],{"title":131,"path":132,"stem":133,"description":537,"children":-1},"One frame per event with all its consequences, 0 split frames with no load and at most 1 per 10 000 under load; the runner watches signals with its own loop and a repair tick because of two Kyo RC7 Signal bugs.",{"title":139,"path":140,"stem":141,"description":539,"children":-1},"sbt-tpolecat sets the scalac options; warnings stay warnings on a developer machine, and CI (SBT_TPOLECAT_CI) turns them into errors.",1792528756276]