[{"data":1,"prerenderedAt":567},["ShallowReactive",2],{"navigation_docs":3,"-decisions-0005-soft-frame-contract-and-own-watcher-loop":223,"-decisions-0005-soft-frame-contract-and-own-watcher-loop-surround":562},[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":131,"body":225,"description":555,"extension":556,"links":557,"meta":558,"navigation":559,"path":132,"seo":560,"stem":133,"__hash__":561},"docs\u002F4.decisions\u002F0005-soft-frame-contract-and-own-watcher-loop.md",{"type":226,"value":227,"toc":544},"minimark",[228,233,237,241,244,248,298,302,397,401,483,487,523,527],[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-11 (gate 0: D-17 c, K29 option 2). Changed on 2026-10-19 (C2, D21, D23).",[229,245,247],{"id":246},"context","Context",[249,250,251,255,258,261,277,295],"ul",{},[252,253,254],"li",{},"A frame must show all the consequences of one event, or the screen shows a half-updated state (\"split frame\").",[252,256,257],{},"Kyo signals give no change notice. The runner \"settles\": it reads the scheduler until it is quiet, then paints.",[252,259,260],{},"Spike S1 (20 000 events per cell): with no load, 0 split frames. Under load (3 JVMs on 2 CPUs), 1.50 per 10 000 with the Kyo scheduler values, and 0.67 with the tui4s values. With the tui4s values every run met the SOFT words. Settle cost: about +55 to +70 us per frame.",[252,262,263,264,268,269,272,273,276],{},"92 of 147 split frames under load were one cause: a lost wakeup in ",[265,266,267],"code",{},"Signal.combineLatest"," (a write between ",[265,270,271],{},"current"," and ",[265,274,275],{},"nextWith"," waits for the 1 s repair). Nothing is pending then, so no probe can see it.",[252,278,279,280,283,284,287,288,291,292,294],{},"Risk K29: one ",[265,281,282],{},"Signal.observe"," watcher keeps about 1.7 KB per change (one ",[265,285,286],{},"Async.race"," per value). 5 regions at 10 changes per second keep about 300 MB per hour. A ",[265,289,290],{},"currentWith","\u002F",[265,293,275],{}," loop with no race stays flat (at most 14 B per change over 100 000 changes).",[252,296,297],{},"With Kyo's defaults the idle scheduler uses about 13% of a core. Kyo's blocking monitor also measures its CPU counter for 86 to 102 ms at start.",[229,299,301],{"id":300},"decision","Decision",[249,303,304,307,322,343,365,372,386],{},[252,305,306],{},"The frame contract is SOFT (D-17 c): one frame per event with all its consequences; 0 split frames with no load; at most 1 per 10 000 under load. A frame is a batch: writes that land inside one settle share one frame.",[252,308,309,310,313,314,317,318,321],{},"The settle uses the public ",[265,311,312],{},"Scheduler.status()"," probe with ",[265,315,316],{},"confirm = 2",", a re-check after the settle, and a pull check of the bound and ",[265,319,320],{},"Foreach"," regions.",[252,323,324,325,291,327,329,330,272,337,342],{},"The runner watches each region with its own ",[265,326,290],{},[265,328,275],{}," loop (no race) and a repair tick every 1 s (K29 option 2). TODOs in the code point at issues ",[331,332,336],"a",{"href":333,"rel":334},"https:\u002F\u002Fgithub.com\u002Fyuri4n\u002Ftui4s\u002Fissues\u002F8",[335],"nofollow","#8",[331,338,341],{"href":339,"rel":340},"https:\u002F\u002Fgithub.com\u002Fyuri4n\u002Ftui4s\u002Fissues\u002F9",[335],"#9",".",[252,344,345,348,349,352,353,356,357,360,361,364],{},[265,346,347],{},"TuiApp",", the start scripts and the test JVM set four scheduler values: ",[265,350,351],{},"cycleIntervalNs"," 10 ms, ",[265,354,355],{},"blockingMonitorIntervalNs"," 20 ms, ",[265,358,359],{},"blockingMonitorMinIntervalNs"," 20 ms (C2: no start measurement), ",[265,362,363],{},"concurrencyCollectIntervalMs"," 100.",[252,366,367,368,371],{},"CI sets ",[265,369,370],{},"TUI4S_LOAD=1",": the glitch tests then allow 1 split frame in 500 events (gate 1, item 2).",[252,373,374,377,378,381,382,385],{},[265,375,376],{},"SchedulerDefaults.defaults"," stays ",[265,379,380],{},"Seq[(String, String)]"," (D21): ",[265,383,384],{},"tui4s.spi"," must load no Kyo class.",[252,387,388,389,392,393,396],{},"The monotonic clock of the runner stays ",[265,390,391],{},"System.nanoTime"," (D23): Kyo RC7 ",[265,394,395],{},"Clock.nowMonotonic"," maps a negative value to 0.",[229,398,400],{"id":399},"alternatives-considered","Alternatives considered",[249,402,403,410,420,429,438,444,458,469],{},[252,404,405,409],{},[406,407,408],"strong",{},"D-17 (a), a strict contract."," It needs G1 GO. G1 was NO-GO on RC7 because of the lost wakeup.",[252,411,412,419],{},[406,413,414,415,418],{},"D-17 (d), \"eventually consistent within ",[265,416,417],{},"max","\" with auto-wait kits."," It reverses the strict test kit. Not chosen.",[252,421,422,425,426,342],{},[406,423,424],{},"Pin Kyo with the upstream fix as a 1.0 blocker."," Not chosen; the fix is tracked in ",[331,427,341],{"href":339,"rel":428},[335],[252,430,431,437],{},[406,432,433,434,342],{},"K29 option 1, wait for an upstream fix of ",[265,435,436],{},"observe"," Not possible for the first release; the draft is not posted.",[252,439,440,443],{},[406,441,442],{},"K29 option 3, accept the leak and document it."," It loses 300 MB per hour in a long run.",[252,445,446,453,454,457],{},[406,447,448,449,452],{},"Compare ",[265,450,451],{},"Reactive"," content another way"," (gate 1, question 3). A region whose UI holds a closure never compares equal, so the 1 s tick renders it again. Not chosen: that render changes no cells and sends no bytes. Gate 1 accepted it until ",[331,455,336],{"href":333,"rel":456},[335]," closes.",[252,459,460,468],{},[406,461,462,465,466],{},[265,463,464],{},"Chunk"," for ",[265,467,376],{}," (A5 a). It loads Kyo before the properties are set.",[252,470,471,478,479,482],{},[406,472,473,474,477],{},"Kyo ",[265,475,476],{},"Clock"," for the settle bounds"," (A11). A negative ",[265,480,481],{},"nanoTime"," would break the settle bounds and the pending timeout.",[229,484,486],{"id":485},"consequences","Consequences",[249,488,489,492,495,509,516],{},[252,490,491],{},"Good: 0 split frames with no load; the glitch tests pin it (500 writes, 500 keys).",[252,493,494],{},"Good: the heap stays flat in a long run, and idle CPU drops to about 4 to 8% of a core.",[252,496,497,498,500,501,504,505,508],{},"Bad: the runner has a second watcher loop and two extra checks to remove when Kyo fixes ",[265,499,436],{}," (",[331,502,336],{"href":333,"rel":503},[335],", ",[331,506,341],{"href":339,"rel":507},[335],").",[252,510,511,512,515],{},"Bad: a frame can arrive up to 1 s late under load. The test kit waits with ",[265,513,514],{},"awaitScreen",", and core suites run one at a time.",[252,517,518,519,522],{},"Bad: an app with its own ",[265,520,521],{},"main"," must set the scheduler values itself.",[229,524,526],{"id":525},"references","References",[249,528,529,532,541],{},[252,530,531],{},"Decision log: eighth round (D-17 c, K29), eleventh round (gate 1), sixteenth round (D21, D23).",[252,533,534,535,504,538,342],{},"Plan: D-16 (armkey), D-17; gates.md G1, K29. Issues ",[331,536,336],{"href":333,"rel":537},[335],[331,539,341],{"href":339,"rel":540},[335],[252,542,543],{},"CONTRIBUTING.md: \"Rules for code\" (the frame contract, the repair tick, the scheduler values). ",{"title":545,"searchDepth":546,"depth":546,"links":547},"",2,[548,549,550,551,552,553,554],{"id":231,"depth":546,"text":232},{"id":239,"depth":546,"text":240},{"id":246,"depth":546,"text":247},{"id":300,"depth":546,"text":301},{"id":399,"depth":546,"text":400},{"id":485,"depth":546,"text":486},{"id":525,"depth":546,"text":526},"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.","md",null,{},true,{"title":131,"description":555},"Gy2Vp4mphZTrz0hdD-Gd6gMLbYq656-K7yiyxVhAAFo",[563,565],{"title":127,"path":128,"stem":129,"description":564,"children":-1},"The runner calls no private[kyo] member and uses no reflection; where kyo-ui keeps a rule private, the runner copies its semantics and the surface guard pins what it reads.",{"title":135,"path":136,"stem":137,"description":566,"children":-1},"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.",1792528756093]