Something went wrong. Try again.
My version of https://github.com/vercel-labs/fx
Something went wrong. Try again.
556 kB · 13817 lines
Zig
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818const std = @import("std");const debug_trace = @import("../../core/shared/debug_trace.zig");const diagnostics = @import("../../core/workspace/diagnostics.zig");const managed_execution = @import("../../core/execution/managed_execution.zig");const display_width = @import("../../core/shared/display_width.zig");const input_action = @import("../../core/input/input_action.zig");const io_mod = @import("../../core/shared/io.zig");const activity_status = @import("../../core/output/activity_status.zig");const activity_runtime = @import("../../core/output/activity_runtime.zig");const transcript_release = @import("../../core/output/transcript_release.zig");const transcript_presentation = @import("../../core/output/transcript_presentation.zig");const full_transcript_page = @import("../../core/output/full_transcript_page.zig");const diff_mod = @import("../../core/output/diff.zig");const worker_status = @import("../../core/output/worker_status.zig");const footer_viewport_runtime = @import("../footer/viewport.zig");const render_request = @import("../render_request.zig");const command_output_runtime = @import("command_output_runtime.zig");const input_visual_layout = @import("../input/visual_layout.zig");const user_message_card = @import("../assistant/user_message_card.zig");const render_engine = @import("../render_engine.zig");const build_checkpoint = @import("../render_engine/build_checkpoint.zig");const transcript_io = @import("io.zig");const transcript_painter = @import("painter.zig");const source_preparation = @import("source_preparation.zig");const full_transcript_worker = @import("full_transcript_worker.zig");const tool_group_projection = @import("tool_group_projection.zig");const transcript_store = @import("store.zig");const full_transcript_screen = @import("../full_transcript_screen.zig");const transcript_viewport_runtime = @import("viewport_runtime.zig");const transcript_writer = @import("writer.zig");const ui_render = @import("../render.zig");const types = @import("../../core/shared/types.zig");
/// One timestamped full-detail record (session assembly, network call,/// recovery transition) shown only in the ctrl+o full transcript. Kept out of/// the transcript entry store so inline rendering, retention, replay, and/// resume never observe it.const FullDetailRecord = struct { notice: types.SemanticNotice, created_at_ms: i64,
pub fn deinit(self: *FullDetailRecord, alloc: Allocator) void { types.freeSemanticNotice(alloc, self.notice); self.* = undefined; }};
/// Synthetic full-detail records share entry id 0: the store never issues 0,/// and the id orders below every real entry, which keeps row-ordered/// bookmark lookups consistent with the records' position at the top.const full_detail_record_entry_id: u32 = 0;
const command_output_content = @import("../../core/tooling/command_output_content.zig");const captured_command = @import("../../core/tooling/captured_command.zig");const tool_result_errors = @import("../../core/tooling/tool_result_errors.zig");const vt_emulator = @import("../../core/terminal/engine.zig");const result_store = @import("../../core/session/result_store.zig");const session_child_store = @import("../../core/session/session_child_store.zig");
const Allocator = std.mem.Allocator;const Layout = types.Layout;const Metrics = types.Metrics;const transcript_blocks = render_engine.transcript_blocks;const viewport_selection = render_engine.viewport_selection;
pub const CommandOutputDisplayState = transcript_blocks.CommandOutputDisplayState;pub const Styles = transcript_blocks.Styles;pub const AssistantTurnSegments = transcript_blocks.AssistantTurnSegments;pub const RawEntryClass = transcript_blocks.RawEntryClass;pub const TranscriptEntry = transcript_blocks.TranscriptEntry;pub const TranscriptPreparationSource = source_preparation.TranscriptPreparationSource;
pub const VisibleTranscriptSnapshot = struct { text: []u8, terminal_rows: u16, terminal_cols: u16, complete: bool,
pub fn deinit(self: *VisibleTranscriptSnapshot, alloc: Allocator) void { alloc.free(self.text); self.* = undefined; }};
pub const ResizeObservationPhase = enum { live, settle_pending, reset_queued,};
pub const ResizeObservationResult = union(enum) { awaiting, measured: i32, unavailable,};
pub const ResizeObservation = struct { layout: types.Layout, apply_after_ms: i64, expected_row: ?u32, result: ResizeObservationResult, phase: ResizeObservationPhase,};
const ViewportSelection = viewport_selection.ViewportSelection;
pub const TranscriptCommitDiagnosticState = enum { invalid, stable, recovering,};
pub const TranscriptCommitDiagnostic = struct { state: TranscriptCommitDiagnosticState, stable_start_line: ?usize = null, visual_offset: u32 = 0, history_visual_offset: u32 = 0, total_visual_rows: u32 = 0, source_bytes: usize = 0, layout_id: u64 = 0, remaining_inline_rows: u32 = 0, remaining_unplanned_scroll_rows: u16 = 0,};
pub const StableTranscriptProjection = struct { selection: ViewportSelection, visual_offset: u32, history_visual_offset: u32, total_visual_rows: u32, flow_unchanged: bool, cursor_row: u16, cursor_col: u16, occupied_last_row: u16, occupied_last_row_blank: bool, layout_id: u64, measured_history_origin: ?transcript_painter.MeasuredHistoryOrigin = null,};
pub const MeasuredResizeProjection = union(enum) { stable, recovering: struct { accepted_visual_offset: u32, source_cols: u16, },};
const CommittedTranscriptAnchor = struct { selection: ViewportSelection, visual_offset: u32, // Footer reflow can move the logical tail without advancing rows into // terminal history. The committed grid projection resumes from this offset. history_visual_offset: u32, total_visual_rows: u32, flow: []u8, retention_identity: source_preparation.RetentionIdentity = .{}, flow_materialized: bool = true, cursor_row: u16, cursor_col: u16, occupied_last_row: u16, occupied_last_row_blank: bool, layout_id: u64, measured_history_origin: ?transcript_painter.MeasuredHistoryOrigin = null, row_provenance: []transcript_blocks.RowProvenance = &.{}, normal_buffer_recovery_pending: bool = false, /// Stable-anchor settlement obligation. It is preserved while final rows /// remain ahead of history and cleared once history reaches the viewport. /// The offset gap alone is insufficient because footer displacement can /// create the same shape without content debt. history_catchup_pending: bool = false,
fn deinit(self: *CommittedTranscriptAnchor, alloc: Allocator) void { self.retention_identity.deinit(alloc); if (self.flow.len > 0) alloc.free(self.flow); if (self.row_provenance.len > 0) alloc.free(self.row_provenance); self.* = undefined; }};
fn committedProjectionVisualOffset(anchor: CommittedTranscriptAnchor) u32 { return if (anchor.normal_buffer_recovery_pending) anchor.history_visual_offset else anchor.visual_offset;}
const TranscriptRecoveryReceipt = struct { accepted_visual_offset: u32 = 0, accepted_history_visual_offset: u32 = 0, attempt_visual_offset: u32 = 0, attempt_total_visual_rows: u32 = 0, materialized_visual_offset: u32 = 0, materialized_visual_rows: u16 = 0, materialized_flow_len: ?usize = null, remaining_resize_reflow_rows: u16 = 0, remaining_semantic_rows: u32 = 0, // Only this remaining semantic suffix may advance the current projection. remaining_semantic_progress_rows: u32 = 0, remaining_unplanned_scroll_rows: u16 = 0, attempt_cols: u16 = 0, attempt_resize_history_row_delta: ?i32 = null, attempt_resize_history_committed: bool = false, projection_rebase_pending: bool = false, tracks_semantic_progress: bool = false, flow: []u8 = &.{}, flow_owned: bool = true, retention_identity: source_preparation.RetentionIdentity = .{}, presentation_resume_bytes: []u8 = &.{}, presentation_pending_wrap: bool = false, presentation_valid: bool = false, cursor_row: u16 = 1, cursor_col: u16 = 1, document_append_committed: bool = false, measured_history_origin: ?transcript_painter.MeasuredHistoryOrigin = null, row_provenance: []transcript_blocks.RowProvenance = &.{}, normal_buffer_recovery_pending: bool = false,
fn deinit(self: *TranscriptRecoveryReceipt, alloc: Allocator) void { self.retention_identity.deinit(alloc); if (self.flow_owned and self.flow.len > 0) alloc.free(self.flow); if (self.presentation_resume_bytes.len > 0) alloc.free(self.presentation_resume_bytes); if (self.row_provenance.len > 0) alloc.free(self.row_provenance); self.* = undefined; }};
const TranscriptCommitState = union(enum) { invalid, stable: CommittedTranscriptAnchor, recovering: TranscriptRecoveryReceipt,
fn deinit(self: *TranscriptCommitState, alloc: Allocator) void { switch (self.*) { .invalid => {}, .stable => |*anchor| anchor.deinit(alloc), .recovering => |*receipt| receipt.deinit(alloc), } self.* = .invalid; }};
test "retention rebase carries stable and recovering source boundaries" { const alloc = std.testing.allocator; var before = try source_preparation.prepareIndexedFullTranscriptWindowSourceInterruptible(alloc, try alloc.dupe(u8, "a\nb\nc\nd\ne\nf"), 80, null); defer before.deinit(alloc); var after = try source_preparation.prepareIndexedFullTranscriptWindowSourceInterruptible(alloc, try alloc.dupe(u8, "c\nd\ne\nf"), 80, null); defer after.deinit(alloc); var provenance: [6]transcript_blocks.LineProvenance = undefined; for (&provenance, 0..) |*line, index| line.* = .{ .entry = .{ .entry_id = @intCast(index + 1), .entry_class = .unknown_raw } }; before.line_provenance = try alloc.dupe(transcript_blocks.LineProvenance, &provenance); after.line_provenance = try alloc.dupe(transcript_blocks.LineProvenance, provenance[2..]); var mapping = try source_preparation.RetentionRebase.init(alloc, &before, &after, &.{}); defer mapping.deinit(alloc); for ([_]bool{ false, true }) |recovering| { var runtime = TranscriptRuntime{ .detached_commit_alloc = alloc }; defer runtime.deinit(alloc); const flow = try alloc.dupe(u8, before.bytes); runtime.transcript_commit_state = if (recovering) .{ .recovering = .{ .flow = flow, .accepted_visual_offset = 4, .accepted_history_visual_offset = 3, .attempt_visual_offset = 5, .attempt_total_visual_rows = 6, .materialized_visual_offset = 4, .materialized_visual_rows = 2, .materialized_flow_len = before.bytes.len, .attempt_cols = 80, .remaining_semantic_rows = 2, .remaining_semantic_progress_rows = 1, } } else .{ .stable = .{ .flow = flow, .visual_offset = 4, .history_visual_offset = 3, .total_visual_rows = 6, .selection = .{ .start_line = 4, .top_row = 1, .bottom_row = 2, .partial_skip_rows = 0, .line_count = 2 }, .cursor_row = 3, .cursor_col = 1, .occupied_last_row = 2, .occupied_last_row_blank = false, .layout_id = 1, } }; var failing = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 0 }); try std.testing.expectError(error.OutOfMemory, runtime.rebaseRetainedTranscript(failing.allocator(), mapping, .{})); try std.testing.expectEqual(@as(u32, 3), runtime.transcriptCommitDiagnostic().history_visual_offset); try runtime.rebaseRetainedTranscript(alloc, mapping, .{}); const diagnostic = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual(@as(u32, 1), diagnostic.history_visual_offset); try std.testing.expectEqual(@as(u32, 2), diagnostic.visual_offset); try std.testing.expectEqual(@as(u32, 4), diagnostic.total_visual_rows); switch (runtime.transcript_commit_state) { .invalid => return error.TestUnexpectedResult, .stable => |anchor| { try std.testing.expectEqualStrings(after.bytes, anchor.flow); try std.testing.expectEqual(@as(usize, 2), anchor.selection.start_line); }, .recovering => |receipt| { try std.testing.expectEqualStrings(after.bytes, receipt.flow); try std.testing.expectEqual(@as(u32, 2), receipt.materialized_visual_offset); try std.testing.expectEqual(@as(u16, 2), receipt.materialized_visual_rows); try std.testing.expectEqual(after.bytes.len, receipt.materialized_flow_len.?); try std.testing.expectEqual(@as(u32, 3), receipt.attempt_visual_offset); try std.testing.expectEqual(@as(u32, 2), receipt.remaining_semantic_rows); try std.testing.expectEqual(@as(u32, 1), receipt.remaining_semantic_progress_rows); }, } }}
test "retention rebase recovery rejects the removed entry partial-row endpoint" { const alloc = std.testing.allocator; var before = try source_preparation.prepareIndexedFullTranscriptWindowSourceInterruptible(alloc, try alloc.dupe(u8, "abcdefghi\njklmnopqr"), 3, null); defer before.deinit(alloc); var after = try source_preparation.prepareIndexedFullTranscriptWindowSourceInterruptible(alloc, try alloc.dupe(u8, "jklmnopqr"), 3, null); defer after.deinit(alloc); const lines = [_]transcript_blocks.LineProvenance{ .{ .entry = .{ .entry_id = 1, .entry_class = .unknown_raw } }, .{ .entry = .{ .entry_id = 2, .entry_class = .unknown_raw } }, }; before.line_provenance = try alloc.dupe(transcript_blocks.LineProvenance, &lines); after.line_provenance = try alloc.dupe(transcript_blocks.LineProvenance, lines[1..]); var mapping = try source_preparation.RetentionRebase.init(alloc, &before, &after, &.{}); defer mapping.deinit(alloc); var runtime = TranscriptRuntime{ .detached_commit_alloc = alloc, .transcript_commit_state = .{ .recovering = .{ .flow = try alloc.dupe(u8, before.bytes), .accepted_visual_offset = 2, .accepted_history_visual_offset = 2, .materialized_visual_offset = 2, .materialized_visual_rows = 4, .materialized_flow_len = before.bytes.len, .attempt_cols = 3, .attempt_visual_offset = 2, .attempt_total_visual_rows = 6, } } }; defer runtime.deinit(alloc); try runtime.rebaseRetainedTranscript(alloc, mapping, .{}); const receipt = runtime.transcript_commit_state.recovering; try std.testing.expectEqual(@as(u32, 0), receipt.accepted_visual_offset); try std.testing.expectEqual(@as(u32, 0), receipt.accepted_history_visual_offset); try std.testing.expectEqual(@as(u32, 0), receipt.materialized_visual_offset); try std.testing.expectEqual(@as(u16, 3), receipt.materialized_visual_rows); try std.testing.expectEqual(@as(?usize, null), receipt.materialized_flow_len);}
fn recoveryAppendPrefixMatches( target_flow: []const u8, receipt: TranscriptRecoveryReceipt, materialized_flow_len: usize,) bool { return materialized_flow_len <= receipt.flow.len and materialized_flow_len <= target_flow.len and std.mem.eql( u8, target_flow[0..materialized_flow_len], receipt.flow[0..materialized_flow_len], );}
pub const TranscriptScrollFacts = struct { publication_hold: enum { none, restore, finality } = .none, source_visual_offset: u32 = 0, target_visual_offset: u32 = 0, resize_reflow_rows: u16 = 0, semantic_rows: u32 = 0, semantic_progress_rows: u32 = 0, planned_rows: u16 = 0, source_compatible: bool = false, geometry_rebase: bool = false, recovery_rebase: bool = false, recovery_progress_compatible: bool = false, recovery_tracks_semantic_progress: bool = false, recovery_projection_deferred: bool = false, /// The finality floor withheld release this frame (mutable rows above /// the viewport top), or final rows remain to settle beyond what this /// frame planned. Routes into history_catchup_pending at commit so the /// existing catch-up machinery settles the debt; footer displacement /// (visual ahead of history with nothing to settle) stays unflagged. finality_hold: bool = false,};
pub const RetainedTranscriptBody = render_engine.frame_retention.RetainedTranscriptBody;
pub const TranscriptBodyDisposition = union(enum) { paint, retain_committed: RetainedTranscriptBody,};
fn nextResizeBottomAnchorPending( pending: bool, terminal_reset_commit: bool, resize_reset_commit: bool, tail_kind: ?TranscriptBlockKind, occupied_last_row: u16, content_bottom_row: u16,) bool { const next = if (terminal_reset_commit) resize_reset_commit else pending; if (tail_kind == .cancel_notice and occupied_last_row == content_bottom_row) { return false; } return next;}
test "resize bottom anchor remains pending until a cancel projection reaches it" { const Case = struct { pending: bool, terminal_reset_commit: bool, resize_reset_commit: bool, tail_kind: ?TranscriptBlockKind, occupied_last_row: u16, content_bottom_row: u16, expected: bool, }; const cases = [_]Case{ .{ .pending = false, .terminal_reset_commit = false, .resize_reset_commit = false, .tail_kind = null, .occupied_last_row = 4, .content_bottom_row = 8, .expected = false }, .{ .pending = false, .terminal_reset_commit = true, .resize_reset_commit = true, .tail_kind = null, .occupied_last_row = 4, .content_bottom_row = 8, .expected = true }, .{ .pending = true, .terminal_reset_commit = false, .resize_reset_commit = false, .tail_kind = .assistant_turn, .occupied_last_row = 4, .content_bottom_row = 8, .expected = true }, .{ .pending = true, .terminal_reset_commit = false, .resize_reset_commit = false, .tail_kind = .cancel_notice, .occupied_last_row = 4, .content_bottom_row = 8, .expected = true }, .{ .pending = true, .terminal_reset_commit = false, .resize_reset_commit = false, .tail_kind = .cancel_notice, .occupied_last_row = 8, .content_bottom_row = 8, .expected = false }, .{ .pending = true, .terminal_reset_commit = true, .resize_reset_commit = false, .tail_kind = null, .occupied_last_row = 4, .content_bottom_row = 8, .expected = false }, };
for (cases) |case| { try std.testing.expectEqual( case.expected, nextResizeBottomAnchorPending( case.pending, case.terminal_reset_commit, case.resize_reset_commit, case.tail_kind, case.occupied_last_row, case.content_bottom_row, ), ); }}
const MaterializedEndpoint = struct { visual_offset: u32 = 0, visual_rows: u16 = 0, flow_len: ?usize = null, cursor_row: u16 = 1, cursor_col: u16 = 1,};
pub const TranscriptTransition = struct { scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, document_append: render_engine.frame_scroll_plan.FrameDocumentAppend, /// Final rows exist beyond materialized history that this frame could /// not release; consuming the transition requests a follow-up frame so /// the debt settles promptly instead of waiting for outside activity. history_settle_pending: bool = false, /// Owns `document_append.bytes` when append preparation allocates wire bytes. document_append_bytes: []u8 = &.{}, resize_reflow_rows: u16 = 0, semantic_rows: u32 = 0, semantic_progress_rows: u32 = 0, recovery_rebase: bool = false, recovery_progress_compatible: bool = false, recovery_tracks_semantic_progress: bool = false, recovery_projection_deferred: bool = false, body_disposition: TranscriptBodyDisposition = .paint, target_flow: []u8, // Painter borrows outlive sealing when the receipt keeps only a prefix. presentation_flow: []u8 = &.{}, target_flow_owned: bool = true, retention_identity: source_preparation.RetentionIdentity = .{}, borrows_full_page: bool = false, presentation_resume_bytes: []u8 = &.{}, presentation_pending_wrap: bool = false, presentation_valid: bool = false, target_cache: std.ArrayList(u8), target_cache_unchanged: bool = false, target_cache_origin_untrimmed: bool = false, folded_summary_indices: []usize, target_layout: render_engine.frame_layout.CommittedLayoutSnapshot, selection: ViewportSelection, visual_offset: u32, history_visual_offset: u32, source_endpoint_visual_offset: u32 = 0, total_visual_rows: u32, materialized: MaterializedEndpoint = .{}, // The exact transcript endpoint if this frame appends no transcript bytes. materialized_without_append: MaterializedEndpoint, cursor_row: u16, cursor_col: u16, replaceable_last_line: bool, replaceable_start: usize, replaceable_row: u16, trace_transcript_frame: bool, trace_visible_rows: u16, measured_history_origin: ?transcript_painter.MeasuredHistoryOrigin = null, row_provenance: []transcript_blocks.RowProvenance = &.{}, normal_buffer_recovery_pending: bool = false, history_catchup_pending: bool = false, consumed: bool = false,
pub fn deinit(self: *TranscriptTransition, alloc: Allocator) void { self.retention_identity.deinit(alloc); if (self.target_flow_owned and self.target_flow.len > 0) alloc.free(self.target_flow); alloc.free(self.presentation_flow); if (self.presentation_resume_bytes.len > 0) alloc.free(self.presentation_resume_bytes); if (self.document_append_bytes.len > 0) { alloc.free(self.document_append_bytes); } self.target_cache.deinit(alloc); if (self.folded_summary_indices.len > 0) alloc.free(self.folded_summary_indices); if (self.row_provenance.len > 0) alloc.free(self.row_provenance); self.* = undefined; }};
const MaterializedTranscriptProjection = struct { raw_flow_exact: bool = false, visual_rows: u16 = 0,};
const TranscriptBlockKind = transcript_blocks.TranscriptBlockKind;const WalkResult = transcript_blocks.WalkResult;const walkText = transcript_blocks.walkText;
fn formatDurationCompact(buf: []u8, duration_ms: u64) []const u8 { const total_seconds = duration_ms / std.time.ms_per_s; if (total_seconds < 60) { return std.fmt.bufPrint(buf, "{d}s", .{total_seconds}) catch "0s"; } if (total_seconds < 60 * 60) { const minutes = total_seconds / 60; const seconds = total_seconds % 60; return std.fmt.bufPrint(buf, "{d}m {d}s", .{ minutes, seconds }) catch "1m 0s"; } const hours = total_seconds / (60 * 60); const minutes = (total_seconds % (60 * 60)) / 60; return std.fmt.bufPrint(buf, "{d}h {d:0>2}m", .{ hours, minutes }) catch "1h 00m";}
fn formatTurnSummaryLine(buf: []u8, summary: types.TurnSummary) []const u8 { var turn_buf: [24]u8 = undefined; const turn = formatDurationCompact(&turn_buf, summary.turn_duration_ms); var out: std.Io.Writer = .fixed(buf); out.print(" {s}", .{turn}) catch return " "; activity_status.appendTokenProgressSuffix(&out, summary.token_progress) catch return out.buffered(); return out.buffered();}
fn lifecycleEventTurnId(event: types.ToolLifecycleEvent) u64 { return switch (event) { .provisional => |payload| payload.id.turn_id, .authoritative_started => |payload| payload.id.turn_id, .progress => |payload| payload.id.turn_id, .terminal => |payload| payload.id.turn_id, .turn_finished => |payload| payload.turn_id, };}
fn admitLifecycleStart( state: *const activity_runtime.ToolActivityState, id: types.ToolLifecycleId,) bool { return switch (state.admitStart(id)) { .accepted => true, .invalid_identity => false, .finalized => |fence| blk: { debug_trace.logf( "ui_activity", "lifecycle rejected by finalized fence turn_id={d} fence={d} kind=start", .{ id.turn_id, fence }, ); break :blk false; }, };}
fn traceLateLifecycleEvent(id: types.ToolLifecycleId, event_kind: []const u8) void { debug_trace.logf( "ui_activity", "late lifecycle event ignored turn_id={d} kind={s}", .{ id.turn_id, event_kind }, );}
fn lifecycleStartLine( alloc: Allocator, tool_name: ?[]const u8,) ![]u8 { var label_buf: [512]u8 = undefined; const label = std.fmt.bufPrint( &label_buf, "● {s}", .{tool_name orelse "tool"}, ) catch "● tool"; return std.fmt.allocPrint( alloc, "{s}{s}\n", .{ label, ui_render.reset_style }, );}
fn lifecycleLine(alloc: Allocator, text: []const u8) ![]u8 { if (std.mem.endsWith(u8, text, "\n")) return alloc.dupe(u8, text); return std.fmt.allocPrint(alloc, "{s}\n", .{text});}
const LifecycleMarker = struct { style: []const u8, glyph: []const u8,};
fn lifecycleMarker(kind: types.ToolOutcomeKind) LifecycleMarker { return switch (kind) { .completed => .{ .style = ui_render.system_notice_text_style, .glyph = "●" }, .failed => .{ .style = ui_render.red_style, .glyph = "●" }, .denied => .{ .style = ui_render.red_style, .glyph = "⊘" }, .cancelled => .{ .style = ui_render.warning_style, .glyph = "■" }, .deferred => .{ .style = ui_render.warning_style, .glyph = "↻" }, };}
fn leadingSgrPrefixEnd(text: []const u8) usize { var index: usize = 0; while (index + 2 <= text.len and text[index] == 0x1b and text[index + 1] == '[') { var end = index + 2; while (end < text.len and (text[end] < 0x40 or text[end] > 0x7e)) : (end += 1) {} if (end == text.len or text[end] != 'm') break; index = end + 1; } return index;}
fn lifecycleTerminalLine( alloc: Allocator, kind: types.ToolOutcomeKind, summary: []const u8,) ![]u8 { const marker = lifecycleMarker(kind); const prefix_end = leadingSgrPrefixEnd(summary); const has_marker = std.mem.startsWith(u8, summary[prefix_end..], "●"); const cancelled = kind == .cancelled; const text_style = if (cancelled) ui_render.hint_style else ""; const text_reset = if (cancelled) ui_render.reset_style else ""; const follow_up = if (cancelled) " · What can fx do differently?" else ""; const normalized = if (has_marker) try std.fmt.allocPrint( alloc, "{s}{s}{s}{s}{s}{s}{s}{s}", .{ marker.style, marker.glyph, ui_render.reset_style, summary[0..prefix_end], text_style, summary[prefix_end + "●".len ..], text_reset, follow_up, }, ) else try std.fmt.allocPrint( alloc, "{s}{s}{s} {s}{s}{s}{s}", .{ marker.style, marker.glyph, ui_render.reset_style, text_style, summary, text_reset, follow_up, }, ); defer alloc.free(normalized); return lifecycleLine(alloc, normalized);}
/// Pure formatter shared by live lifecycle presentation and detached resume/// projection. The caller owns the returned line.pub fn formatHistoricalToolStatusLine( alloc: Allocator, kind: types.ToolOutcomeKind, summary: []const u8,) ![]u8 { return lifecycleTerminalLine(alloc, kind, summary);}
fn lifecycleFallbackLine( alloc: Allocator, outcome: types.TurnPresentationOutcome, was_provisional: bool,) ![]u8 { return switch (outcome) { .completed => lifecycleLine(alloc, if (was_provisional) "● Tool was not executed" else "● Tool completion was not reported"), .interrupted => lifecycleTerminalLine(alloc, .cancelled, "Tool cancelled"), .failed => lifecycleTerminalLine(alloc, .failed, "Tool failed"), .paused => lifecycleLine(alloc, "● Tool state preserved for recovery"), };}
fn lifecycleFallbackDisposition( outcome: types.TurnPresentationOutcome, was_provisional: bool,) ?transcript_blocks.ToolFallbackDisposition { if (outcome != .completed) return null; return if (was_provisional) .not_executed else .completion_unreported;}
fn traceLifecycleFailure( event_kind: []const u8, turn_id: u64, err: anyerror,) void { debug_trace.logf( "ui_activity", "lifecycle transaction failed kind={s} turn_id={d} err={s}", .{ event_kind, turn_id, @errorName(err) }, );}
test "formatTurnSummaryLine renders total duration and tokens" { var buf: [128]u8 = undefined; const line = formatTurnSummaryLine(&buf, .{ .thinking_duration_ms = 15_000, .turn_duration_ms = 130_000, .token_progress = .{ .input_tokens = 10_000, .output_tokens = 5_000 }, }); try std.testing.expectEqualStrings(" 2m 10s (↑10k ↓5k)", line);}
test "formatTurnSummaryLine renders only total duration without token estimate" { var buf: [128]u8 = undefined; const line = formatTurnSummaryLine(&buf, .{ .thinking_duration_ms = 3_000, .turn_duration_ms = 3_600_000, }); try std.testing.expectEqualStrings(" 1h 00m", line);}
test "appendTurnSummaryEntry stores classified dim transcript row" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .owned_top_row = 1, }; defer runtime.deinit(alloc);
_ = try runtime.appendTurnSummaryEntry(alloc, .{ .thinking_duration_ms = 15_000, .turn_duration_ms = 130_000, .token_progress = .{ .input_tokens = 10_000, .output_tokens = 5_000 }, });
try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .raw_bytes); try std.testing.expectEqual(RawEntryClass.turn_summary, runtime.entries.items[0].raw_bytes.class); try std.testing.expect(std.mem.find(u8, runtime.entries.items[0].raw_bytes.bytes, " 2m 10s (↑10k ↓5k)") != null);
const rendered = try renderEntriesToBytes(alloc, runtime.entries.items, runtime.layout.cols, .{}); defer alloc.free(rendered); try std.testing.expectEqualStrings("\x1b[38;5;245m 2m 10s (↑10k ↓5k)\x1b[0m\n", rendered);}
test "recovered route status is transient and final summary stays normal" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .owned_top_row = 1, }; defer runtime.deinit(alloc);
runtime.worker_status.set_route_recovery(.{ .kind = .auto_recovered, .succeeded_attempt = 3, .attempt_limit = 3, }, 1_000);
switch (runtime.activityProjection()) { .turn_thinking => |thinking| { try std.testing.expectEqual(activity_runtime.ActivityProjection.Tone.success, thinking.tone); try std.testing.expectEqualStrings("✓ recovered · attempt 3", thinking.label); }, .none, .tool_slot => return error.TestUnexpectedResult, } try std.testing.expect(!runtime.worker_status.expire_transient(2_499)); try std.testing.expect(runtime.worker_status.expire_transient(2_500)); try std.testing.expect(runtime.activityProjection() == .none);
runtime.worker_status.set_route_recovery(.{ .kind = .auto_recovered, .succeeded_attempt = 3, .attempt_limit = 3, }, 3_000); _ = try runtime.appendTurnSummaryEntry(alloc, .{ .thinking_duration_ms = 15_000, .turn_duration_ms = 130_000, .token_progress = .{ .input_tokens = 10_000, .output_tokens = 5_000 }, });
try std.testing.expect(runtime.activityProjection() == .none); try std.testing.expect(std.mem.find(u8, runtime.entries.items[0].raw_bytes.bytes, ui_render.dim_style) != null); try std.testing.expect(std.mem.find(u8, runtime.entries.items[0].raw_bytes.bytes, " 2m 10s (↑10k ↓5k)") != null); try std.testing.expect(std.mem.find(u8, runtime.entries.items[0].raw_bytes.bytes, "✓ recovered") == null);}
test "appendTurnSummaryEntry leaves terminal route status visible" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .owned_top_row = 1, }; defer runtime.deinit(alloc);
runtime.worker_status.set_route_recovery(.{ .kind = .content_filter }, 0); _ = try runtime.appendTurnSummaryEntry(alloc, .{ .thinking_duration_ms = 15_000, .turn_duration_ms = 130_000, .token_progress = .{ .input_tokens = 10_000, .output_tokens = 5_000 }, });
switch (runtime.activityProjection()) { .turn_thinking => |thinking| try std.testing.expectEqualStrings("⚠ blocked · content filter", thinking.label), .none, .tool_slot => return error.TestUnexpectedResult, } try std.testing.expect(std.mem.find(u8, runtime.entries.items[0].raw_bytes.bytes, " 2m 10s (↑10k ↓5k)") != null);}
test "full transcript projection opens without folded command output" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .owned_top_row = 1, }; defer runtime.deinit(alloc);
try std.testing.expect(try runtime.setTranscriptPresentationDepth(alloc, .full)); try std.testing.expect(runtime.fullTranscriptActive());}
test "closing the full transcript resets bounded paging to the tail" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc);
try std.testing.expect(try runtime.setTranscriptPresentationDepth(alloc, .full)); runtime.full_transcript_page_anchor = .{ .entry_index = 17 };
try std.testing.expect(try runtime.setTranscriptPresentationDepth(alloc, .inline_mode)); try std.testing.expect(std.meta.eql( @as(full_transcript_page.Anchor, .tail), runtime.full_transcript_page_anchor, ));}
test "live command keeps the full transcript stable until completion" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .full_transcript = .{ .depth = .full }, .full_transcript_content_revision = 40, .command_output_display = .{ .open_command_block = 0 }, .full_transcript_installed_page = .{ .source = .{ .request = .{ .content_revision = 40, .cols = 80, .anchor = .tail, }, .range = .{ .start = 0, .end = 0 }, }, .projection = .{ .styles = .{} }, }, }; defer runtime.deinit(alloc);
for (0..32) |_| { runtime.markTranscriptContentDirty(); try std.testing.expect(!runtime.render_requests.hasReason(.transcript)); } runtime.command_output_display.open_command_block = null; runtime.markTranscriptContentDirty(); try std.testing.expect(runtime.render_requests.hasReason(.transcript));
runtime.command_output_display.open_command_block = 0; runtime.full_transcript_open_request = runtime.desiredFullTranscriptPageRequest(); try runtime.prewarmFullTranscriptPage(null, null); try std.testing.expect(runtime.full_transcript_page_load.busy());}
test "installed full transcript stays stable only while its command is active" { var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .full_transcript_content_revision = 41, .full_transcript_installed_page = .{ .source = .{ .request = .{ .content_revision = 40, .cols = 80, .anchor = .tail, }, .range = .{ .start = 0, .end = 0 }, }, .projection = .{ .styles = .{} }, }, }; defer runtime.deinit(std.testing.allocator);
try std.testing.expect(runtime.installedFullTranscriptPageProjection() == null); runtime.full_transcript_prepared_page_visible = true; try std.testing.expect(runtime.installedFullTranscriptPageProjection() != null); runtime.full_transcript_prepared_page_visible = false; runtime.command_output_display.open_command_block = 0; try std.testing.expect(runtime.installedFullTranscriptPageProjection() != null); runtime.full_transcript_content_revision = 48; try std.testing.expect(runtime.installedFullTranscriptPageProjection() != null); runtime.command_output_display.open_command_block = null; try std.testing.expect(runtime.installedFullTranscriptPageProjection() == null);}
test "active full transcript keeps its installed page while replacement loads" { const alloc = std.testing.allocator; const page_alloc = std.heap.c_allocator; const prepared_source = try source_preparation.prepareIndexedFullTranscriptWindowSourceInterruptible( page_alloc, try page_alloc.dupe(u8, "installed page\n"), 80, null, ); var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .full_transcript = .{ .depth = .full }, .full_transcript_content_revision = 41, .full_transcript_installed_page = .{ .source = .{ .request = .{ .content_revision = 40, .cols = 80, .anchor = .tail, }, .range = .{ .start = 0, .end = 0 }, }, .projection = .{ .styles = .{} }, .prepared_window = .{ .source = prepared_source, .start_row = 0, }, }, }; defer runtime.deinit(alloc); const installed = &runtime.full_transcript_installed_page.?.projection; const visible = try runtime.preparedFullTranscriptPageProjectionInterruptible( null, null, null, ); try std.testing.expect(visible == installed); try std.testing.expect(runtime.full_transcript_page_load.busy());}
test "active full transcript defers repaint while width replacement loads" { const alloc = std.testing.allocator; const page_alloc = std.heap.c_allocator; const prepared_source = try source_preparation.prepareIndexedFullTranscriptWindowSourceInterruptible( page_alloc, try page_alloc.dupe(u8, "installed page\n"), 80, null, ); var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 100, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .full_transcript = .{ .depth = .full }, .full_transcript_content_revision = 41, .full_transcript_installed_page = .{ .source = .{ .request = .{ .content_revision = 40, .cols = 80, .anchor = .tail, }, .range = .{ .start = 0, .end = 0 }, }, .projection = .{ .styles = .{} }, .prepared_window = .{ .source = prepared_source, .start_row = 0, }, }, }; defer runtime.deinit(alloc); try std.testing.expectError( error.InputPending, runtime.preparedFullTranscriptPageProjectionInterruptible( null, null, null, ), ); try std.testing.expect(runtime.full_transcript_page_load.busy());}
test "cross-page replacement preserves its pending boundary until adoption" { const alloc = std.testing.allocator; const page_alloc = std.heap.c_allocator; const Case = struct { installed_anchor: full_transcript_page.Anchor, boundary_index: usize, }; const cases = [_]Case{ .{ .installed_anchor = .tail, .boundary_index = 743 }, .{ .installed_anchor = .{ .entry_index = 743 }, .boundary_index = 871 }, };
for (cases) |case| { var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .owned_top_row = 1, .full_transcript = .{ .depth = .full, .follow_tail = false }, }; defer runtime.deinit(alloc); for (0..1_000) |_| { _ = try runtime.appendRawTranscriptEntryClassified( alloc, "row\n", .unknown_raw, ); }
const installed_request = full_transcript_page.Request{ .content_revision = runtime.full_transcript_content_revision, .cols = runtime.layout.cols, .anchor = case.installed_anchor, }; runtime.full_transcript_installed_page = .{ .source = .{ .request = installed_request, .range = full_transcript_page.sourceRange( installed_request, runtime.entries.items.len, ), }, .projection = .{ .styles = .{} }, .prepared_window = .{ .source = try source_preparation.prepareIndexedFullTranscriptWindowSourceInterruptible( page_alloc, try page_alloc.dupe(u8, "installed page\n"), runtime.layout.cols, null, ), .start_row = 0, }, }; runtime.full_transcript_page_anchor = .{ .entry_index = case.boundary_index }; runtime.full_transcript = runtime.full_transcript.select_page_boundary( runtime.entries.items[case.boundary_index].id(), ); const before = runtime.full_transcript.snapshot();
try std.testing.expectError( error.InputPending, runtime.preparedFullTranscriptPageProjectionInterruptible( null, null, null, ), ); try std.testing.expectEqualDeep(before, runtime.full_transcript.snapshot()); try std.testing.expect(runtime.full_transcript_page_load.busy()); }}
test "restored full transcript opens without replacing its exact offset" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 12, .cols = 60, .content_bottom = 8, .divider_top_row = 9, .input_row = 10, .divider_bottom_row = 11, .hint_row = 12, }, .full_transcript = .{ .depth = .full, .scroll_rows = 47, .follow_tail = false, }, }; defer runtime.deinit(alloc);
runtime.deferRestoredFullTranscriptOpen(); try std.testing.expectEqual( transcript_presentation.Depth.inline_mode, runtime.full_transcript.depth, ); try std.testing.expect(try runtime.setTranscriptPresentationDepth(alloc, .full)); const selected = runtime.full_transcript.select_visual_offset(100, 8, &.{}); try std.testing.expectEqual(@as(u32, 47), selected.offset);}
test "full transcript viewport snapshot restores reading position" { var source = TranscriptRuntime{ .full_transcript = .{ .depth = .full, .scroll_rows = 37, .follow_tail = false, .anchor_entry_id = 19, .anchor_pending = true, }, .full_transcript_page_anchor = .{ .entry_index = 17 }, }; defer source.deinit(std.testing.allocator); const snapshot = source.snapshotFullTranscriptViewport();
var restored = TranscriptRuntime{}; defer restored.deinit(std.testing.allocator); restored.restoreFullTranscriptViewport(snapshot);
try std.testing.expect(std.meta.eql( snapshot, restored.snapshotFullTranscriptViewport(), )); try std.testing.expect(std.meta.eql( source.full_transcript_page_anchor, restored.full_transcript_page_anchor, ));}
test "full transcript page snapshot retains active command records" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
const entry_id = try runtime.appendRawTranscriptEntryClassified( alloc, "│ compact output\n", .command_output, ); var block = CommandOutputBlock{ .entry_id = entry_id, .total_lines = 1, .retained_text_bytes = "ACTIVE_SNAPSHOT_RECORD\n".len, }; try block.lines.append(alloc, .{ .stream = .stdout, .text = try alloc.dupe(u8, "ACTIVE_SNAPSHOT_RECORD\n"), .entry_id = entry_id, .terminated = true, }); try block.source_entry_ids.append(alloc, entry_id); try runtime.command_output_blocks.append(alloc, block);
var source = try runtime.snapshotFullTranscriptPage(.{ .content_revision = runtime.full_transcript_content_revision, .cols = 80, .anchor = .tail, }, null, null); defer source.deinit(std.heap.c_allocator);
try std.testing.expectEqual(@as(usize, 1), source.command_blocks.items.len); try std.testing.expectEqual(@as(usize, 1), source.command_blocks.items[0].lines.items.len); try std.testing.expectEqualStrings( "ACTIVE_SNAPSHOT_RECORD\n", source.command_blocks.items[0].lines.items[0].text, );}
test "full transcript prewarm rejects an oversized main-thread snapshot" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
const oversized = try alloc.alloc( u8, full_transcript_snapshot_clone_max_bytes + 1, ); @memset(oversized, 'x'); _ = try runtime.appendRawBytesEntryClassified( alloc, oversized, .unknown_raw, );
try runtime.prewarmFullTranscriptPage(null, null); try std.testing.expect(!runtime.full_transcript_page_load.busy()); try std.testing.expect(runtime.full_transcript_failed_request != null); try std.testing.expect(!runtime.requestFullTranscriptOpen()); try std.testing.expect(runtime.takeFullTranscriptPreparationFailure()); try std.testing.expect(!runtime.fullTranscriptPreparedForOpen());}
test "full transcript page navigation preserves tail intent while the page loads" { var runtime = TranscriptRuntime{ .layout = .{ .rows = 12, .cols = 60, .content_bottom = 8, .divider_top_row = 9, .input_row = 10, .divider_bottom_row = 11, .hint_row = 12, }, .full_transcript = .{ .depth = .full }, }; const task = try std.heap.c_allocator.create(full_transcript_worker.Task); task.* = .{ .source = .{ .request = .{ .content_revision = 0, .cols = 60, .anchor = .tail, }, .range = .{ .start = 0, .end = 0 }, .styles = .{}, } }; runtime.full_transcript_page_load.task = task; defer runtime.deinit(std.testing.allocator);
runtime.scrollFullTranscript(.up, .page);
try std.testing.expect(runtime.full_transcript.follow_tail); try std.testing.expectEqual(@as(u32, 0), runtime.full_transcript.scroll_rows);}
test "cancelled full transcript page completion is never installed" { var runtime = TranscriptRuntime{ .layout = testLayoutWithRows(12) }; runtime.layout.cols = 60; defer runtime.deinit(std.testing.allocator);
const task = try std.heap.c_allocator.create(full_transcript_worker.Task); task.* = .{ .source = .{ .request = .{ .content_revision = 0, .cols = 60, .anchor = .tail, }, .range = .{ .start = 0, .end = 0 }, .styles = .{}, }, .projection = .{ .styles = .{} }, }; task.cancel_requested.store(true, .release); task.done.store(true, .release); runtime.full_transcript_page_load.task = task;
try std.testing.expect(!try runtime.pollFullTranscriptPageLoad()); try std.testing.expect(runtime.full_transcript_installed_page == null);}
test "full transcript page boundaries preserve monotonic navigation" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .owned_top_row = 1, .full_transcript = .{ .depth = .full, .follow_tail = false }, }; defer runtime.deinit(alloc);
for (0..1_000) |_| { _ = try runtime.appendRawTranscriptEntryClassified( alloc, "row\n", .unknown_raw, ); }
const PageWindow = struct { fn install( current: *TranscriptRuntime, request: full_transcript_page.Request, ) !*InstalledFullTranscriptPage { const range = full_transcript_page.sourceRange( request, current.entries.items.len, ); if (current.full_transcript_installed_page == null) { current.full_transcript_installed_page = .{ .source = .{ .request = request, .range = range, }, .projection = .{ .styles = .{} }, }; } const page = ¤t.full_transcript_installed_page.?; page.source.request = request; page.source.range = range; page.projection.measured_item_rows.clearRetainingCapacity(); for (range.start..range.end) |entry_index| { try page.projection.measured_item_rows.append(std.heap.c_allocator, .{ .entry_id = current.entries.items[entry_index].id(), .row = @intCast(entry_index - range.start), }); } page.projection.measured_total_rows = @intCast(range.len()); return page; } };
const tail_request = full_transcript_page.Request{ .content_revision = runtime.full_transcript_content_revision, .cols = runtime.layout.cols, .anchor = .tail, }; var installed = try PageWindow.install(&runtime, tail_request); const tail_start = installed.source.range.start;
runtime.scrollFullTranscript(.up, .page); const older_anchor = switch (runtime.full_transcript_page_anchor) { .entry_index => |index| index, .tail => return error.TestExpectedOlderPage, }; try std.testing.expectEqual(@as(usize, 743), older_anchor); try std.testing.expect(runtime.full_transcript.bookmark_pending); try std.testing.expectEqual( @as(?u32, runtime.entries.items[older_anchor].id()), runtime.full_transcript.bookmark_entry_id, );
var older_request = tail_request; older_request.anchor = .{ .entry_index = older_anchor }; installed = try PageWindow.install(&runtime, older_request); var selected = runtime.full_transcript.select_visual_offset( installed.projection.measured_total_rows, 20, installed.projection.measured_item_rows.items, ); runtime.full_transcript = selected.state; const after_page_up = installed.source.range.start + @as(usize, @intCast(selected.offset)); try std.testing.expectEqual(@as(usize, 743), after_page_up); try std.testing.expect(after_page_up < tail_start);
const max_offset = installed.projection.measured_total_rows - 20; runtime.full_transcript.scroll_rows = max_offset; runtime.full_transcript.follow_tail = true; const before_page_down = installed.source.range.start + @as(usize, max_offset); runtime.scrollFullTranscript(.down, .page); const newer_anchor = switch (runtime.full_transcript_page_anchor) { .entry_index => |index| index, .tail => return error.TestExpectedNewerPage, }; try std.testing.expectEqual(@as(usize, 871), newer_anchor); try std.testing.expect(runtime.full_transcript.bookmark_pending); try std.testing.expectEqual( @as(?u32, runtime.entries.items[newer_anchor].id()), runtime.full_transcript.bookmark_entry_id, );
var newer_request = older_request; newer_request.anchor = .{ .entry_index = newer_anchor }; installed = try PageWindow.install(&runtime, newer_request); selected = runtime.full_transcript.select_visual_offset( installed.projection.measured_total_rows, 20, installed.projection.measured_item_rows.items, ); const after_page_down = installed.source.range.start + @as(usize, @intCast(selected.offset)); try std.testing.expectEqual(@as(usize, 871), after_page_down); try std.testing.expect(after_page_down > before_page_down);}
test "opening the full transcript leaves compact command projection unchanged" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; var metrics: Metrics = .{}; try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "first\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "second\n", true); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); var compact_before = try runtime.prepareTranscriptSource(alloc, null); defer compact_before.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), compact_before.bytes.len);
_ = try runtime.setTranscriptPresentationDepth(alloc, .full); var compact_after = try runtime.prepareTranscriptSource(alloc, null); defer compact_after.deinit(alloc); try std.testing.expectEqualStrings(compact_before.bytes, compact_after.bytes);}
test "full transcript staging paints its selected wheel viewport without reselection" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 8, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }, .owned_top_row = 1, .full_transcript = .{ .depth = .full, .scroll_rows = 3, .follow_tail = false, }, }; defer runtime.deinit(alloc);
_ = try runtime.appendRawTranscriptEntryClassified( alloc, "row-0\nrow-1\nrow-2\nrow-3\nrow-4\nrow-5\nrow-6\nrow-7\n", .unknown_raw, ); var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); var metrics: Metrics = .{}; var staged = try runtime.prepareFullTranscriptSurfacePaint( alloc, &metrics, &projection, null, .{ .top = 1, .bottom = 4 }, ); defer staged.deinit(alloc);
try std.testing.expectEqual(@as(u32, 3), runtime.full_transcript.scroll_rows); try std.testing.expect(!runtime.full_transcript.follow_tail); try std.testing.expectEqual(@as(usize, 0), staged.prepared.selection.start_line); try std.testing.expect(std.mem.find(u8, staged.source().bytes, "row-3") != null); try std.testing.expect(std.mem.find(u8, staged.source().bytes, "row-0") == null);}
test "installed full transcript paints from the worker indexed source" { const alloc = std.testing.allocator; const page_alloc = std.heap.c_allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 8, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }, .owned_top_row = 1, .full_transcript = .{ .depth = .full, .scroll_rows = 3, .follow_tail = false, }, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\nrow-0\nrow-1\nrow-2\nrow-3\nrow-4\nrow-5\nrow-6\nrow-7\n", .unknown_raw, ); var projection = try runtime.buildFullTranscriptProjection(page_alloc, null); const bytes = try full_transcript_screen.renderProjectionViewportSourceBoundedInterruptible( page_alloc, &projection, null, runtime.layout.cols, 256, 0, 2 * 1024 * 1024, null, ); const prepared_source = try source_preparation.prepareIndexedFullTranscriptWindowSourceInterruptible( page_alloc, bytes, runtime.layout.cols, null, ); runtime.full_transcript_installed_page = .{ .source = .{ .request = runtime.desiredFullTranscriptPageRequest(), .range = .{ .start = 0, .end = runtime.entries.items.len }, }, .projection = projection, .prepared_window = .{ .source = prepared_source, .start_row = 0, }, }; const installed = &runtime.full_transcript_installed_page.?; var metrics: Metrics = .{}; var staged = try runtime.prepareFullTranscriptSurfacePaint( alloc, &metrics, &installed.projection, null, .{ .top = 1, .bottom = 4 }, ); defer staged.deinit(alloc);
try std.testing.expect(staged.owned_source == null); try std.testing.expect(staged.borrowed_source == &installed.prepared_window.?.source); try std.testing.expectEqual(@as(?u16, 4), staged.prepared.projection_visual_rows); try std.testing.expect(staged.prepared.selection.last_visible_row <= 4); try std.testing.expect(std.mem.find(u8, staged.source().bytes, "row-0") != null); try std.testing.expect(std.mem.find(u8, staged.source().bytes, "row-7") != null);}
test "clear retains installed full transcript page until window worker terminates" { const alloc = std.testing.allocator; const page_alloc = std.heap.c_allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 8, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, } }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "row-0\nrow-1\nrow-2\nrow-3\nrow-4\n", .unknown_raw, ); runtime.full_transcript_installed_page = .{ .source = .{ .request = runtime.desiredFullTranscriptPageRequest(), .range = .{ .start = 0, .end = runtime.entries.items.len }, }, .projection = try runtime.buildFullTranscriptProjection(page_alloc, null), }; const page = &runtime.full_transcript_installed_page.?; const task = try page_alloc.create(full_transcript_worker.WindowTask); task.* = .{ .request = .{ .page_request = page.source.request, .target_offset = 1, .start_row = 0, .row_count = 4, }, .projection = &page.projection, .capability = null, }; runtime.full_transcript_window_load.task = task;
runtime.clearFullTranscriptDetails(alloc); try std.testing.expect(runtime.full_transcript_installed_page != null); try std.testing.expect(task.cancel_requested.load(.acquire));
task.done.store(true, .release); try std.testing.expect(!try runtime.pollFullTranscriptPageLoad()); try std.testing.expect(runtime.full_transcript_installed_page == null);}
test "compact transcript cache survives navigation and invalidates on content change" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 8, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }, .owned_top_row = 1, .full_transcript = .{ .depth = .full }, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "row-0\nrow-1\nrow-2\nrow-3\nrow-4\n", .unknown_raw, );
var compact = try runtime.cachedTranscriptSource(alloc); defer compact.deinit(alloc); const cached_compact = runtime.compact_transcript_source_cache.find( runtime.full_transcript_content_revision, runtime.layout.cols, runtime.has_committed_frame, ).?; const compact_bytes = cached_compact.bytes.ptr; try std.testing.expect(cached_compact.hard_line_starts.len > 0); try std.testing.expectEqual( cached_compact.hard_line_starts.len, cached_compact.transcript_visible_lines.len, ); try std.testing.expectEqual( cached_compact.hard_line_starts.len, cached_compact.transcript_line_visual_rows.len, ); try std.testing.expectEqual( cached_compact.transcript_visible_lines.len, compact.transcript_visible_lines.len, ); runtime.markTranscriptDirty(); var same_compact = try runtime.cachedTranscriptSource(alloc); defer same_compact.deinit(alloc); try std.testing.expectEqual(compact_bytes, runtime.compact_transcript_source_cache.find( runtime.full_transcript_content_revision, runtime.layout.cols, runtime.has_committed_frame, ).?.bytes.ptr); runtime.has_committed_frame = true; try std.testing.expectEqual(@as(?*TranscriptPreparationSource, null), runtime.compact_transcript_source_cache.find( runtime.full_transcript_content_revision, runtime.layout.cols, runtime.has_committed_frame, )); var committed_compact = try runtime.cachedTranscriptSource(alloc); committed_compact.deinit(alloc); runtime.has_committed_frame = false;
runtime.markTranscriptContentDirty(); var rebuilt_compact = try runtime.cachedTranscriptSource(alloc); defer rebuilt_compact.deinit(alloc); try std.testing.expect(compact_bytes != runtime.compact_transcript_source_cache.find( runtime.full_transcript_content_revision, runtime.layout.cols, runtime.has_committed_frame, ).?.bytes.ptr);}
test "cached compact transcript refreshes after streamed assistant text" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 8, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }, .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "existing transcript\n", .unknown_raw, );
var before = try runtime.cachedTranscriptSource(alloc); defer before.deinit(alloc); try std.testing.expect(std.mem.find(u8, before.bytes, "streamed assistant") == null);
var metrics = Metrics{}; _ = try runtime.streamAssistantChunk(alloc, &metrics, "streamed assistant\n"); var after = try runtime.cachedTranscriptSource(alloc); defer after.deinit(alloc);
try std.testing.expect(std.mem.find(u8, after.bytes, "streamed assistant") != null);}
test "clearing a transcript releases compact source and installed page" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 8, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }, .owned_top_row = 1, .full_transcript = .{ .depth = .full }, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "old session transcript\n", .unknown_raw, );
var compact = try runtime.cachedTranscriptSource(alloc); compact.deinit(alloc); const content_revision = runtime.full_transcript_content_revision; runtime.full_transcript_installed_page = .{ .source = .{ .request = .{ .content_revision = content_revision, .cols = runtime.layout.cols, .anchor = .tail, }, .range = .{ .start = 0, .end = 0 }, }, .projection = .{ .styles = .{} }, }; try std.testing.expect(runtime.compact_transcript_source_cache.find( content_revision, runtime.layout.cols, runtime.has_committed_frame, ) != null); try std.testing.expect(runtime.full_transcript_installed_page != null);
runtime.clearTranscript(alloc);
try std.testing.expect(runtime.compact_transcript_source_cache.find( content_revision, runtime.layout.cols, runtime.has_committed_frame, ) == null); try std.testing.expect(runtime.full_transcript_installed_page == null);}
test "cached compact transcript preserves pending resume flow" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 8, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }, .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "retained tail only\n", .unknown_raw, ); runtime.pending_resume_source = try source_preparation.prepareFullTranscriptViewportSource( &runtime, alloc, try alloc.dupe(u8, "full resume history\nretained tail only\n"), );
var source = try runtime.cachedTranscriptSource(alloc); defer source.deinit(alloc);
try std.testing.expect(std.mem.find(u8, source.bytes, "full resume history") != null);}
const PendingBuildProbe = struct { polls: usize = 0, pending_after: ?usize = null,
fn pending(context: *anyopaque) bool { const self: *PendingBuildProbe = @ptrCast(@alignCast(context)); self.polls += 1; return if (self.pending_after) |limit| self.polls >= limit else false; }};
test "pending resume source publishes its line index once" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = testLayoutWithRows(8) }; runtime.layout.cols = 40; defer runtime.deinit(alloc);
var flow: std.ArrayList(u8) = .empty; defer flow.deinit(alloc); for (0..5_000) |_| try flow.appendSlice(alloc, "resume row\n"); runtime.pending_resume_source = try source_preparation.prepareFullTranscriptViewportSource( &runtime, alloc, try flow.toOwnedSlice(alloc), ); const flow_ptr = runtime.pendingResumeFlow().ptr;
var cancelled_probe = PendingBuildProbe{ .pending_after = 1 }; var cancelled = build_checkpoint.BuildCheckpoint.init( &cancelled_probe, PendingBuildProbe.pending, ); try std.testing.expectError( error.InputPending, runtime.pendingResumeSourceInterruptible(alloc, &cancelled), ); try std.testing.expectEqual(@as(usize, 0), runtime.pending_resume_source.?.hard_line_starts.len); try std.testing.expectEqual(flow_ptr, runtime.pendingResumeFlow().ptr);
var build_probe = PendingBuildProbe{}; var build = build_checkpoint.BuildCheckpoint.init(&build_probe, PendingBuildProbe.pending); const source = (try runtime.pendingResumeSourceInterruptible(alloc, &build)).?; const source_ptr = source; const index_ptr = source.hard_line_starts.ptr; try std.testing.expect(source.hard_line_starts.len > 0);
runtime.layout.cols = 41; var resize_probe = PendingBuildProbe{ .pending_after = 2 }; var resize = build_checkpoint.BuildCheckpoint.init(&resize_probe, PendingBuildProbe.pending); try std.testing.expectError( error.InputPending, runtime.pendingResumeSourceInterruptible(alloc, &resize), ); try std.testing.expectEqual(source_ptr, &runtime.pending_resume_source.?); try std.testing.expectEqual(@as(u16, 40), runtime.pending_resume_source.?.cols); runtime.layout.cols = 40;
for (0..3) |_| { var reuse_probe = PendingBuildProbe{}; var reuse = build_checkpoint.BuildCheckpoint.init(&reuse_probe, PendingBuildProbe.pending); const reused = (try runtime.pendingResumeSourceInterruptible(alloc, &reuse)).?; try std.testing.expectEqual(source_ptr, reused); try std.testing.expectEqual(index_ptr, reused.hard_line_starts.ptr); try std.testing.expectEqual(@as(usize, 1), reuse_probe.polls); }
var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, source, .{ .top = 1, .bottom = 4 }, ); defer prepared.deinit(alloc); try std.testing.expectEqual(source.transcript_visible_lines.ptr, prepared.sourceVisibleLines().ptr); try std.testing.expectEqual(source.transcript_line_visual_rows.ptr, prepared.sourceVisualRows().ptr); try std.testing.expectEqual( source.transcript_visual_row_offsets[source.transcript_visual_row_offsets.len - 1], prepared.sourceTotalVisualRows(), );}
test "full transcript staging keeps its selected viewport visible during resize recovery" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 8, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }, .owned_top_row = 1, .full_transcript = .{ .depth = .full, .scroll_rows = 3, .follow_tail = false, }, .resize_history_row_delta = 22, }; defer runtime.deinit(alloc);
_ = try runtime.appendRawTranscriptEntryClassified( alloc, "row-0\nrow-1\nrow-2\nrow-3\nrow-4\nrow-5\nrow-6\nrow-7\n", .unknown_raw, ); var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); var metrics: Metrics = .{}; var staged = try runtime.prepareFullTranscriptSurfacePaint( alloc, &metrics, &projection, null, .{ .top = 1, .bottom = 4 }, ); defer staged.deinit(alloc);
try std.testing.expect(std.mem.find(u8, staged.source().bytes, "row-3") != null); try std.testing.expect(std.mem.find(u8, staged.source().bytes, "row-0") == null); try std.testing.expectEqual(@as(?i32, 22), runtime.resize_history_row_delta); try std.testing.expectEqual(@as(usize, 0), staged.prepared.selection.start_line); try std.testing.expect(staged.prepared.selection.last_visible_row >= staged.prepared.selection.top_row);}
test "auto permission notice is retained without changing compact transcript geometry" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .owned_top_row = 1, }; defer runtime.deinit(alloc);
const cursor_row_before = runtime.cursor_row; const cursor_col_before = runtime.cursor_col; const viewport_before = runtime.viewport_top_row;
_ = try runtime.appendSemanticNotice(alloc, .{ .topic = "system", .tone = .information, .body = "Auto agent approved this request: Running command.", .visibility = .full_only, });
try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .semantic_notice); try std.testing.expectEqualStrings("system", runtime.entries.items[0].semantic_notice.topic); try std.testing.expectEqual(types.NoticeTone.information, runtime.entries.items[0].semantic_notice.tone); try std.testing.expectEqualStrings( "Auto agent approved this request: Running command.", runtime.entries.items[0].semantic_notice.body, ); try std.testing.expectEqual(types.NoticeVisibility.full_only, runtime.entries.items[0].semantic_notice.visibility); try std.testing.expectEqual(@as(usize, 0), runtime.transcript.items.len); try std.testing.expectEqual(cursor_row_before, runtime.cursor_row); try std.testing.expectEqual(cursor_col_before, runtime.cursor_col); try std.testing.expectEqual(viewport_before, runtime.viewport_top_row);
_ = try runtime.appendRawTranscriptEntryClassified( alloc, "● Ran printf done\n", .tool_status, ); var compact = try runtime.prepareTranscriptSource(alloc, null); defer compact.deinit(alloc); try std.testing.expect(std.mem.find(u8, compact.bytes, "Auto agent approved") == null); try std.testing.expect(std.mem.find(u8, compact.bytes, "1 tool call") != null); try std.testing.expect(std.mem.find(u8, compact.bytes, "Ran printf done") != null);
var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const full = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, runtime.layout.cols, 20, 0, null, ); defer alloc.free(full); const notice_index = std.mem.find(u8, full, "Auto agent approved") orelse return error.TestExpectedAutoPermissionNotice; const status_index = std.mem.find(u8, full, "Ran printf done") orelse return error.TestExpectedToolStatus; try std.testing.expect(notice_index < status_index);}
fn checkAutoPermissionNoticeAllocationFailures(alloc: Allocator) !void { var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .owned_top_row = 1, }; defer runtime.deinit(alloc); const cursor_row_before = runtime.cursor_row; const cursor_col_before = runtime.cursor_col;
_ = runtime.appendSemanticNotice(alloc, .{ .topic = "system", .tone = .information, .body = "Auto agent approved this request: Running command.", .visibility = .full_only, }) catch |err| { try std.testing.expectEqual(@as(usize, 0), runtime.entries.items.len); try std.testing.expectEqual(@as(usize, 0), runtime.transcript.items.len); try std.testing.expectEqual(cursor_row_before, runtime.cursor_row); try std.testing.expectEqual(cursor_col_before, runtime.cursor_col); return switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; };
try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expectEqual(@as(usize, 0), runtime.transcript.items.len);}
test "auto permission notice write is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkAutoPermissionNoticeAllocationFailures, .{}, );}
test "full transcript opening follows the tail instead of consuming its compact anchor" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 8, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }, .owned_top_row = 1, }; defer runtime.deinit(alloc);
_ = try runtime.appendRawTranscriptEntryClassified( alloc, "row-0\nrow-1\nrow-2\n", .unknown_raw, ); const anchor_id = try runtime.appendRawTranscriptEntryClassified( alloc, "anchor\n", .unknown_raw, ); runtime.full_transcript = runtime.full_transcript.capture_anchor(anchor_id).with_depth(.full); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "row-4\nrow-5\nrow-6\nrow-7\n", .unknown_raw, );
var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const measurement = try full_transcript_screen.measureProjectionInterruptible( alloc, &projection, null, runtime.layout.cols, null, ); var metrics: Metrics = .{}; var staged = try runtime.prepareFullTranscriptSurfacePaint( alloc, &metrics, &projection, null, .{ .top = 1, .bottom = 4 }, ); defer staged.deinit(alloc);
try std.testing.expectEqual(@as(usize, 0), staged.prepared.selection.start_line); try std.testing.expectEqual( measurement.total_rows -| 4, runtime.full_transcript.scroll_rows, ); try std.testing.expect(runtime.full_transcript.follow_tail); try std.testing.expect(std.mem.find(u8, staged.source().bytes, "row-7") != null); try std.testing.expect(std.mem.find(u8, staged.source().bytes, "anchor") == null); try std.testing.expect(std.mem.find(u8, staged.source().bytes, "row-0") == null); try std.testing.expect(!runtime.full_transcript.anchor_pending);}
test "full transcript primary restore repairs resize debt after geometry returns" { var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; runtime.full_transcript_open_content_revision = 7; runtime.full_transcript_content_revision = 7; runtime.full_transcript_open_cols = 80; runtime.full_transcript_open_rows = 24;
try std.testing.expectEqual(FullTranscriptPrimaryRestore.exact, runtime.fullTranscriptPrimaryRestore()); runtime.terminal_reset_pending = true; try std.testing.expectEqual(FullTranscriptPrimaryRestore.resized, runtime.fullTranscriptPrimaryRestore()); runtime.full_transcript_content_revision = 8; try std.testing.expectEqual(FullTranscriptPrimaryRestore.resized, runtime.fullTranscriptPrimaryRestore());}
test "full transcript resized close queues primary recovery" { var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; runtime.render_requests.completeResizeGeometry(true); runtime.pending_resize_observation = .{ .layout = runtime.layout, .apply_after_ms = 17, .expected_row = null, .result = .unavailable, .phase = .settle_pending, }; runtime.terminal_reset_pending = true; runtime.resize_history_row_delta = 4; runtime.owned_top_row = 5;
runtime.repaintRestoredPrimaryTranscriptAfterResize();
try std.testing.expect(!runtime.render_requests.pending_settled_width_reflow); try std.testing.expectEqual(@as(?ResizeObservation, null), runtime.pending_resize_observation); try std.testing.expect(runtime.terminal_reset_pending); try std.testing.expectEqual(@as(?i32, null), runtime.resize_history_row_delta); const invalidations = runtime.render_requests.pendingInvalidations(); try std.testing.expectEqual(@as(u8, 1), invalidations.len); try std.testing.expectEqual(@as(u16, 5), invalidations.ranges()[0].top); try std.testing.expectEqual(@as(u16, 24), invalidations.ranges()[0].bottom); try std.testing.expect(runtime.render_requests.hasReason(.external_damage));}
test "full transcript resized restore bounds invalidation to the current terminal" { for ([_]u16{ 0, 25 }) |owned_top| { var runtime = TranscriptRuntime{ .layout = std.mem.zeroes(Layout), .owned_top_row = owned_top }; runtime.layout.rows = 24; runtime.repaintRestoredPrimaryTranscriptAfterResize(); const invalidations = runtime.render_requests.pendingInvalidations(); try std.testing.expectEqual(@as(u8, 1), invalidations.len); try std.testing.expectEqual(@as(u16, 1), invalidations.ranges()[0].top); try std.testing.expectEqual(@as(u16, 24), invalidations.ranges()[0].bottom); } var empty = TranscriptRuntime{ .layout = std.mem.zeroes(Layout) }; empty.repaintRestoredPrimaryTranscriptAfterResize(); try std.testing.expect(empty.render_requests.pendingInvalidations().isEmpty());}
test "full transcript opening waits for primary damage and remains cancellable" { var runtime = TranscriptRuntime{ .layout = std.mem.zeroes(Layout) }; runtime.layout.rows = 24; runtime.layout.cols = 80; try std.testing.expect(runtime.requestFullTranscriptOpen());
runtime.repaintRestoredPrimaryTranscriptAfterResize(); try std.testing.expect(!runtime.requestFullTranscriptOpen()); try std.testing.expect(runtime.full_transcript_open_request != null); var interrupted = (try runtime.render_requests.beginAttempt()).?; interrupted.restore(); try std.testing.expect(runtime.cancelPendingFullTranscriptOpen()); try std.testing.expect(!runtime.requestFullTranscriptOpen());
var committed = (try runtime.render_requests.beginAttempt()).?; committed.commit(0, 0, false); try std.testing.expect(runtime.requestFullTranscriptOpen()); try std.testing.expect(runtime.full_transcript_open_request == null);}
test "authoritative lifecycle detail is retained by its compact status entry" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
const id = types.ToolLifecycleId{ .turn_id = 1, .call_id = "read-1" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .presentation_group_id = .{ .turn_id = 1, .anchor_step_id = 4 }, .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, .arguments_json = "{\"path\":\"README.md\"}", } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Read README.md" }, .result = "# README\nfull result", } });
const entry_id = runtime.toolActivityRecord(id).?.entry_id; const detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expectEqual(types.ToolActivityKind.read, detail.activity_kind.?); try std.testing.expectEqualStrings("{\"path\":\"README.md\"}", detail.arguments_json.?); try std.testing.expectEqualStrings("# README\nfull result", detail.result.?); try std.testing.expectEqual(@as(u64, 1), detail.lifecycle_id.?.turn_id); try std.testing.expectEqualStrings("read-1", detail.lifecycle_id.?.call_id); try std.testing.expect(detail.lifecycle_id.?.call_id.ptr != id.call_id.ptr); try std.testing.expectEqual( types.ToolPresentationGroupId{ .turn_id = 1, .anchor_step_id = 4 }, detail.presentation_group_id.?, );
var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expect(std.mem.find(u8, source.bytes, "Read README.md") != null); try std.testing.expect(std.mem.find(u8, source.bytes, "input") == null); try std.testing.expect(std.mem.find(u8, source.bytes, "full result") == null);}
test "paused tool turn can resume the same lifecycle identity before finalization" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc);
const id = types.ToolLifecycleId{ .turn_id = 41, .call_id = "resume-tool" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = id.turn_id, .outcome = .paused, } }); try runtime.finishLifecycleBatch(alloc);
try std.testing.expectEqual(@as(u64, 0), runtime.finalizedToolTurnWatermark()); try std.testing.expect(runtime.toolActivityRecord(id) == null); var paused_source = try runtime.prepareTranscriptSource(alloc, null); defer paused_source.deinit(alloc); try std.testing.expect(std.mem.find( u8, paused_source.bytes, "Tool state preserved for recovery", ) != null);
_ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, } }); try std.testing.expect(runtime.toolActivityRecord(id) != null); try std.testing.expectEqual(@as(usize, 1), runtime.activeToolActivityCount());
_ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = id.turn_id, .outcome = .completed, } }); try runtime.finishLifecycleBatch(alloc); try std.testing.expectEqual(id.turn_id, runtime.finalizedToolTurnWatermark());}
test "provisional lifecycle owns semantic detail before authoritative reconciliation" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc);
const id = types.ToolLifecycleId{ .turn_id = 7, .call_id = "provisional-command" }; _ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = id, .tool_name = null, .activity_kind = .command, } });
const entry_id = runtime.toolActivityRecord(id).?.entry_id; const initial_detail = runtime.toolDetailForEntry(entry_id); try std.testing.expect(initial_detail != null); try std.testing.expectEqualStrings("tool", initial_detail.?.tool_name); try std.testing.expectEqual(types.ToolActivityKind.command, initial_detail.?.activity_kind.?); try std.testing.expectEqual(@as(u64, 7), initial_detail.?.lifecycle_id.?.turn_id); try std.testing.expectEqualStrings("provisional-command", initial_detail.?.lifecycle_id.?.call_id); try std.testing.expect(initial_detail.?.lifecycle_id.?.call_id.ptr != id.call_id.ptr);
_ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = id, .tool_name = "run_command", .activity_kind = .command, } }); const updated_detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expectEqualStrings("run_command", updated_detail.tool_name); try std.testing.expectEqual(@as(usize, 1), runtime.tool_details.items.len);}
test "nonzero command remains Ran in the current compact projection" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
const id = types.ToolLifecycleId{ .turn_id = 1, .call_id = "exit-7" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "terminal", .activity_kind = .command, .arguments_json = "{\"action\":\"exec\",\"command\":\"false\"}", } }); try std.testing.expect(runtime.toolActivityRecord(id).?.captured_command); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Ran false" }, .result_memory = .{ .command_process_presentation = .{ .exit_code = 7 } }, } });
var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expect(std.mem.find(u8, source.bytes, "Ran \x1b[38;5;245m\x1b[38;5;252mfalse\x1b[39m") != null); try std.testing.expectEqual(@as(usize, 0), std.mem.count(u8, source.bytes, "│ exit code 7")); try std.testing.expect(std.mem.find(u8, source.bytes, "Failed") == null);}
test "terminal lifecycle records distinguish captured exec from durable actions" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const exec_id = types.ToolLifecycleId{ .turn_id = 1, .call_id = "exec" }; const start_id = types.ToolLifecycleId{ .turn_id = 1, .call_id = "start" };
_ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = exec_id, .reconciles_provisional_call_id = null, .tool_name = "terminal", .activity_kind = .command, .arguments_json = "{\"action\":\"exec\",\"command\":\"printf exec\"}", } }); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = start_id, .reconciles_provisional_call_id = null, .tool_name = "terminal", .activity_kind = .command, .arguments_json = "{\"action\":\"start\",\"command\":\"printf start\"}", } });
try std.testing.expect(runtime.toolActivityRecord(exec_id).?.captured_command); try std.testing.expect(!runtime.toolActivityRecord(start_id).?.captured_command);}
test "nonzero streamed command reuses its active output block" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc); const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; const id = types.ToolLifecycleId{ .turn_id = 2, .call_id = "streamed-exit-7" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "terminal", .activity_kind = .command, .arguments_json = "{\"action\":\"exec\",\"command\":\"printf lines; exit 7\"}", } }); const started_entry_id = runtime.toolActivityRecord(id).?.entry_id; try std.testing.expect(runtime.toolDetailForEntry(started_entry_id).?.isCapturedCommand()); var metrics: Metrics = .{}; try runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, id, .stdout, "line-1\nline-2\nline-3\nline-4\nline-5\nline-6\n", true, ); try std.testing.expect(runtime.toolDetailForEntry(started_entry_id).?.isCapturedCommand()); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Ran printf lines; exit 7" }, .result_memory = .{ .command_process_presentation = .{ .exit_code = 7 } }, } });
try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); try runtime.flushCommandOutputSummaryForLifecycle( alloc, &metrics, styles, id, true, );
const block = runtime.command_output_blocks.items[0]; const status_entry_id = runtime.toolActivityRecord(id).?.entry_id; try std.testing.expect(runtime.toolDetailForEntry(status_entry_id).?.isCapturedCommand()); try std.testing.expectEqual( block.entry_id, runtime.toolDetailForEntry(status_entry_id).?.command_output_entry_id, ); try std.testing.expect(command_output_runtime.processPresentationForBlock(&runtime, block) != null); var projection = try command_output_runtime.renderCompactCommandOutputWithProcessPresentation( alloc, block, .{ .styles = styles }, 80, command_output_runtime.processPresentationForBlock(&runtime, block), ); defer projection.deinit(alloc); try std.testing.expectEqual( @as(usize, command_output_runtime.compact_output_row_limit + 1), std.mem.count(u8, projection.bytes.items, "\n") + 1, ); try std.testing.expect(std.mem.find(u8, projection.bytes.items, "│ line-4") != null); try std.testing.expect(std.mem.find(u8, projection.bytes.items, "│ line-5") == null); try std.testing.expect(std.mem.find(u8, projection.bytes.items, "│ … 2 lines more (ctrl+o to view)") != null); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, projection.bytes.items, "│ exit code 7"), );
var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expect(std.mem.find(u8, source.bytes, "\x1b[38;5;252mprintf\x1b[39m") != null); try std.testing.expect(std.mem.find(u8, source.bytes, "\x1b[38;5;252m;\x1b[39m") != null); try std.testing.expect(std.mem.find(u8, source.bytes, "\x1b[38;5;252mexit\x1b[39m") != null); try std.testing.expect(std.mem.find(u8, source.bytes, "\x1b[38;5;250m7\x1b[39m") == null); try std.testing.expect(std.mem.find(u8, source.bytes, "Failed") == null);}
test "compact run command group stays width safe after resize" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 24, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc); const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; const id = types.ToolLifecycleId{ .turn_id = 3, .call_id = "long-command-header" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"printf a deliberately long command preview\"}", } }); var metrics: Metrics = .{}; try runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, id, .stdout, "payload\n", true, ); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Ran printf a deliberately long command preview that exceeds both narrow and normal transcript widths without losing its typed command identity", }, } }); try runtime.flushCommandOutputSummaryForLifecycle( alloc, &metrics, styles, id, true, );
const status_entry_id = runtime.toolActivityRecord(id).?.entry_id; const output_entry_id = runtime.command_output_blocks.items[0].entry_id.?; for ([_]u16{ 24, 80 }) |cols| { runtime.layout.cols = cols; var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc);
var status_line: ?usize = null; var output_line: ?usize = null; var status_line_count: usize = 0; for (source.line_provenance, 0..) |provenance, line_index| { switch (provenance) { .entry => |entry| { if (entry.entry_id == status_entry_id) { if (status_line == null) status_line = line_index; status_line_count += 1; } if (entry.entry_id == output_entry_id and output_line == null) { output_line = line_index; } }, else => {}, } } try std.testing.expectEqual(@as(usize, 2), status_line_count); try std.testing.expect(output_line == null);
try std.testing.expect(status_line != null); var lines = std.mem.splitScalar(u8, source.bytes, '\n'); while (lines.next()) |line| { try std.testing.expect(display_width.visibleWidthIgnoringAnsi(line) <= cols); } try std.testing.expect(std.mem.find(u8, source.bytes, "…") != null); }}
fn checkToolDetailResultReplacementAllocationFailures(alloc: Allocator) !void { var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc);
var tool_name: ?[]u8 = try alloc.dupe(u8, "read_file"); errdefer if (tool_name) |value| alloc.free(value); var previous_result: ?[]u8 = try alloc.dupe(u8, "previous result"); errdefer if (previous_result) |value| alloc.free(value); var previous_artifact: ?[]u8 = try alloc.dupe(u8, "fx-command-previous.log"); errdefer if (previous_artifact) |value| alloc.free(value); try runtime.tool_details.append(alloc, .{ .entry_id = 1, .tool_name = tool_name.?, .result = previous_result.?, .command_artifact_handle = previous_artifact.?, }); tool_name = null; previous_result = null; previous_artifact = null;
runtime.updateToolDetailTerminal( alloc, 1, null, "read_file", .read, .completed, "replacement result", null, "fx-command-replacement.log", null, ) catch |err| { try std.testing.expectEqualStrings( "previous result", runtime.toolDetailForEntry(1).?.result.?, ); try std.testing.expectEqualStrings( "fx-command-previous.log", runtime.toolDetailForEntry(1).?.command_artifact_handle.?, ); return err; }; try std.testing.expectEqualStrings( "replacement result", runtime.toolDetailForEntry(1).?.result.?, ); try std.testing.expectEqualStrings( "fx-command-replacement.log", runtime.toolDetailForEntry(1).?.command_artifact_handle.?, );}
test "tool detail result replacement preserves the prior allocation on failure" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkToolDetailResultReplacementAllocationFailures, .{}, );}
test "historical tool detail attaches to the exact replayed status entry" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
var metrics: Metrics = .{}; const entry_id = try runtime.writeCompletedToolStatusReturningEntryId( alloc, &metrics, .completed, "Read README.md", true, ); try runtime.attachHistoricalToolDetail( alloc, entry_id, .{ .id = "replayed-read", .name = "read_file", .arguments_json = "{\"path\":\"README.md\"}" }, .read, .{ .tool_call_id = @constCast("replayed-read"), .tool_name = @constCast("read_file"), .status = .success, .output = @constCast("# README\nfull replayed result"), .output_bytes = 29, .stored_output_bytes = 29, .created_at_ms = 123, }, );
const detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expectEqualStrings("{\"path\":\"README.md\"}", detail.arguments_json.?); try std.testing.expectEqualStrings("# README\nfull replayed result", detail.result.?); try std.testing.expectEqual(types.ToolOutcomeKind.completed, detail.outcome.?); try std.testing.expectEqual(@as(i64, 123), detail.created_at_ms);}
test "historical context-withheld tool detail keeps the call without result evidence" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc);
var metrics: Metrics = .{}; const entry_id = try runtime.writeCompletedToolStatusReturningEntryId( alloc, &metrics, .deferred, "Reading project instructions before continuing: Running cat nested/input.txt", true, ); try runtime.attachHistoricalToolDetail( alloc, entry_id, .{ .id = "deferred-command", .name = "run_command", .arguments_json = "{\"command\":\"cat nested/input.txt\"}", }, .command, .{ .tool_call_id = @constCast("deferred-command"), .tool_name = @constCast("run_command"), .status = .failure, .output = @constCast(types.context_deferred_tool_result_output), .output_handle = @constCast("should-not-be-exposed.txt"), .preview = @constCast("should not be exposed"), .output_bytes = types.context_deferred_tool_result_output.len, .stored_output_bytes = types.context_deferred_tool_result_output.len, .truncated = true, .command_output_replay = .unavailable, .command_process_presentation = .{ .exit_code = 7 }, }, );
const detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expectEqualStrings("run_command", detail.tool_name); try std.testing.expectEqualStrings("{\"command\":\"cat nested/input.txt\"}", detail.arguments_json.?); try std.testing.expectEqual(types.ToolOutcomeKind.deferred, detail.outcome.?); try std.testing.expect(detail.result == null); try std.testing.expect(detail.result_handle == null); try std.testing.expect(detail.command_artifact_handle == null); try std.testing.expect(detail.command_output_replay == null); try std.testing.expect(detail.command_process_presentation == null); try std.testing.expect(detail.command_output_entry_id == null);
const legacy_entry_id = try runtime.writeCompletedToolStatusReturningEntryId( alloc, &metrics, .denied, "Not executed: Reading stale.txt", true, ); try runtime.attachHistoricalToolDetail( alloc, legacy_entry_id, .{ .id = "legacy-deferred-read", .name = "read_file", .arguments_json = "{\"path\":\"stale.txt\"}", }, .read, .{ .tool_call_id = @constCast("legacy-deferred-read"), .tool_name = @constCast("read_file"), .status = .failure, .output = @constCast(types.deferred_tool_result_output), .output_bytes = types.deferred_tool_result_output.len, .stored_output_bytes = types.deferred_tool_result_output.len, }, ); const legacy_detail = runtime.toolDetailForEntry(legacy_entry_id).?; try std.testing.expectEqual(types.ToolOutcomeKind.denied, legacy_detail.outcome.?); try std.testing.expect(legacy_detail.result == null);
const failed_entry_id = try runtime.writeCompletedToolStatusReturningEntryId( alloc, &metrics, .failed, "Failed printf failure", true, ); try runtime.attachHistoricalToolDetail( alloc, failed_entry_id, .{ .id = "failed-command", .name = "run_command", .arguments_json = "{\"command\":\"printf failure; exit 7\"}", }, .command, .{ .tool_call_id = @constCast("failed-command"), .tool_name = @constCast("run_command"), .status = .failure, .output = @constCast("ordinary failure"), .output_handle = @constCast("ordinary-failure.txt"), .output_bytes = 16, .stored_output_bytes = 16, }, );
const failed_detail = runtime.toolDetailForEntry(failed_entry_id).?; try std.testing.expectEqual(types.ToolOutcomeKind.failed, failed_detail.outcome.?); try std.testing.expectEqualStrings("ordinary failure", failed_detail.result.?); try std.testing.expectEqualStrings("ordinary-failure.txt", failed_detail.result_handle.?);}
test "historical permission denial keeps denied outcome without internal result detail" { const alloc = std.testing.allocator; const denial_output = "{\"error\":{\"type\":\"tool_permission_denied\",\"reason\":\"auto_denied\"}}"; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc);
var metrics: Metrics = .{}; const entry_id = try runtime.writeCompletedToolStatusReturningEntryId( alloc, &metrics, .denied, "Denied by auto agent pwd", true, ); try runtime.attachHistoricalToolDetail( alloc, entry_id, .{ .id = "denied-command", .name = "terminal", .arguments_json = "{\"action\":\"exec\",\"command\":\"pwd\"}", }, .command, .{ .tool_call_id = @constCast("denied-command"), .tool_name = @constCast("terminal"), .status = .failure, .output = @constCast(denial_output), .output_bytes = denial_output.len, .stored_output_bytes = denial_output.len, }, );
const detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expectEqual(types.ToolOutcomeKind.denied, detail.outcome.?); try std.testing.expect(detail.result == null); try std.testing.expect(detail.result_handle == null); try std.testing.expect(detail.command_output_entry_id == null);}
test "historical command detail keeps artifact handles after command block attaches" { const alloc = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); try tmp.dir.createDir( io_mod.getIo(), "session", std.Io.File.Permissions.fromMode(0o700), ); var session_dir = try tmp.dir.openDir(io_mod.getIo(), "session", .{ .iterate = true, .follow_symlinks = false, }); defer session_dir.close(io_mod.getIo()); const session_path = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "session"); defer alloc.free(session_path); var capability = try session_child_store.SessionChildCapability.initForTesting( alloc, session_dir, session_path, .writable, .{}, ); defer capability.deinit(); const stored_result = "exit_code=0\n<stdout>\nRESUMED_EXACT_RESULT\n</stdout>\n"; const result_handle = try result_store.storeLargeResultManaged( alloc, &capability, "replayed-command", "run_command", stored_result, ); defer alloc.free(result_handle); defer result_store.deleteManaged(&capability, result_handle) catch {};
var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
const raw_preview = "exit_code=0\ntruncated=true\nstdout_bytes=21\nstderr_bytes=0\noutput_file=/tmp/fx-command-replayed.log\n<stdout>RESUMED_EXACT_RESULT\n</stdout>"; const raw_result = try std.fmt.allocPrint( alloc, "<tool_result_preview handle=\"{s}\" stored_bytes=\"{d}\">\n{s}\n</tool_result_preview>\n" ++ "<tool_result_handle>{s}</tool_result_handle>", .{ result_handle, stored_result.len, raw_preview, result_handle }, ); defer alloc.free(raw_result); var metrics: Metrics = .{}; const entry_id = try runtime.writeCompletedToolStatusReturningEntryId( alloc, &metrics, .completed, "Ran printf restored", true, ); try runtime.attachHistoricalToolDetail( alloc, entry_id, .{ .id = "replayed-command", .name = "run_command", .arguments_json = "{\"command\":\"printf restored\"}" }, .command, .{ .tool_call_id = @constCast("replayed-command"), .tool_name = @constCast("run_command"), .status = .success, .output = @constCast(raw_result), .output_handle = result_handle, .preview = @constCast(raw_preview), .output_bytes = stored_result.len, .stored_output_bytes = stored_result.len, .truncated = true, }, );
runtime.attachHistoricalCommandOutput(alloc, entry_id);
const fallback_detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expect(fallback_detail.command_output_entry_id == null); try std.testing.expectEqualStrings(raw_result, fallback_detail.result.?); try std.testing.expectEqualStrings(result_handle, fallback_detail.result_handle.?); try std.testing.expectEqualStrings("fx-command-replayed.log", fallback_detail.command_artifact_handle.?);
const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "RESUMED_EXACT_RESULT\n", true, ); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true);
runtime.attachHistoricalCommandOutput(alloc, entry_id);
const command_detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expect(command_detail.command_output_entry_id != null); try std.testing.expectEqualStrings(raw_result, command_detail.result.?); try std.testing.expectEqualStrings(result_handle, command_detail.result_handle.?); try std.testing.expectEqualStrings("fx-command-replayed.log", command_detail.command_artifact_handle.?);
runtime.full_transcript.depth = .full; var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const full_source = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, &capability, runtime.layout.cols, 10, 0, null, ); defer alloc.free(full_source); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, full_source, "RESUMED_EXACT_RESULT"), ); try std.testing.expect(std.mem.find(u8, full_source, "exit_code=0") == null); try std.testing.expect(std.mem.find(u8, full_source, "<stdout>") == null); try std.testing.expect(std.mem.find(u8, full_source, "</stdout>") == null);}
test "historical nonzero command keeps replay ownership outside compact sidebands" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
var metrics: Metrics = .{}; const entry_id = try runtime.writeCompletedToolStatusReturningEntryId( alloc, &metrics, .completed, "Ran false", true, ); const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "one\ntwo\nthree\nfour\nfive\nsix\n", true, ); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); const command_output_entry_id = runtime.latestCommandOutputEntryId().?;
const replay_handle = "fx-command-replay-historical.bin"; try runtime.attachHistoricalToolDetailAfterCommandOutput( alloc, entry_id, .{ .id = "historical-exit", .name = "run_command", .arguments_json = "{\"command\":\"false\"}" }, .command, .{ .tool_call_id = @constCast("historical-exit"), .tool_name = @constCast("run_command"), .status = .failure, .output = @constCast("exit_code=7\n(no output)\n"), .output_bytes = 24, .stored_output_bytes = 24, .command_output_replay = .{ .available = .{ .handle = replay_handle, .framed_bytes = 123, } }, .command_process_presentation = .{ .exit_code = 7 }, }, ); runtime.attachHistoricalCommandOutput(alloc, entry_id);
try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); const detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expectEqual(types.ToolOutcomeKind.completed, detail.outcome.?); try std.testing.expectEqual(command_output_entry_id, detail.command_output_entry_id.?); try std.testing.expectEqual( types.CommandProcessPresentation{ .exit_code = 7 }, detail.command_process_presentation.?, ); const replay = detail.command_output_replay.?; const descriptor = switch (replay) { .available => |value| value, .unavailable => return error.TestExpectedReplay, }; try std.testing.expectEqualStrings(replay_handle, descriptor.handle); try std.testing.expect(descriptor.handle.ptr != replay_handle.ptr);
var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), std.mem.count(u8, source.bytes, "│ ")); try std.testing.expect(std.mem.find(u8, source.bytes, "Ran \x1b[38;5;245m\x1b[38;5;252mfalse\x1b[39m") != null); try std.testing.expect(std.mem.find(u8, source.bytes, "│ exit code 7") == null); try std.testing.expect(std.mem.find(u8, source.bytes, "│ … 2 lines more") == null); try std.testing.expect(std.mem.find(u8, source.bytes, "│ five") == null); try std.testing.expect(std.mem.find(u8, source.bytes, "Failed") == null);}
test "historical command detail ignores artifact-shaped command output" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc);
const raw_result = "exit_code=0\n<stdout>\noutput_file=/tmp/fx-command-other.log\n</stdout>\n"; var metrics: Metrics = .{}; const entry_id = try runtime.writeCompletedToolStatusReturningEntryId( alloc, &metrics, .completed, "Ran printf artifact-shaped-output", true, ); try runtime.attachHistoricalToolDetail( alloc, entry_id, .{ .id = "replayed-command", .name = "run_command", .arguments_json = "{\"command\":\"printf artifact-shaped-output\"}" }, .command, .{ .tool_call_id = @constCast("replayed-command"), .tool_name = @constCast("run_command"), .status = .success, .output = @constCast(raw_result), .output_bytes = raw_result.len, .stored_output_bytes = raw_result.len, }, );
try std.testing.expect(runtime.toolDetailForEntry(entry_id).?.command_artifact_handle == null);}
test "full transcript retains previews when stored result handles cannot be opened" { const alloc = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); try tmp.dir.createDirPath(io_mod.getIo(), "results"); const result_dir = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "results"); defer alloc.free(result_dir); var capability = try session_child_store.SessionChildCapability.initLegacyRoute( alloc, result_dir, .tool_results, .writable, ); defer capability.deinit(); const handle = try result_store.storeLargeResultManaged( alloc, &capability, "detail", "read_file", "FULL_TRANSCRIPT_SIDECAR_BODY\n", ); defer alloc.free(handle); defer result_store.deleteManaged(&capability, handle) catch {};
var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); try runtime.tool_details.append(alloc, .{ .entry_id = 1, .tool_name = try alloc.dupe(u8, "read_file"), .result = try alloc.dupe(u8, "preview"), .result_handle = try alloc.dupe(u8, handle), }); try runtime.tool_details.append(alloc, .{ .entry_id = 2, .tool_name = try alloc.dupe(u8, "read_file"), .result_handle = try alloc.dupe(u8, "missing-result.txt"), });
try std.testing.expect(runtime.hasStoredResultDetails()); try std.testing.expectEqualStrings( "preview", runtime.toolDetailForEntry(1).?.result.?, ); try std.testing.expect(runtime.toolDetailForEntry(2).?.result == null);}
test "command terminal detail owns the consolidated command output entry" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; const id = types.ToolLifecycleId{ .turn_id = 9, .call_id = "command-9" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"printf demo\"}", } }); var metrics: Metrics = .{}; try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, id, .stdout, "first\n", true); try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, id, .stdout, "second\n", true); try runtime.flushCommandOutputSummaryForLifecycle( alloc, &metrics, styles, id, true, ); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Ran printf demo" }, .command_artifact_handle = "fx-command-live.log", } });
const status_entry_id = runtime.toolActivityRecord(id).?.entry_id; const detail = runtime.toolDetailForEntry(status_entry_id).?; try std.testing.expectEqual(runtime.command_output_blocks.items[0].entry_id, detail.command_output_entry_id); try std.testing.expectEqualStrings("fx-command-live.log", detail.command_artifact_handle.?);}
test "late command output reopens its completed lifecycle block" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc); var metrics: Metrics = .{}; const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; const id = types.ToolLifecycleId{ .turn_id = 7, .call_id = "late-output" };
try runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, id, .stdout, "one\ntwo\nthree\nfour\nfive\nsix\n", true, ); try runtime.flushCommandOutputSummaryForLifecycle( alloc, &metrics, styles, id, true, ); const entry_id = runtime.command_output_blocks.items[0].entry_id.?;
try runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, id, .stdout, "seven\neight\n", true, );
try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); try std.testing.expectEqual(entry_id, runtime.command_output_blocks.items[0].entry_id.?); try std.testing.expectEqual(@as(usize, 8), runtime.command_output_blocks.items[0].total_lines); runtime.resetCommandOutputDisplay(alloc, "worker inactive"); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len);}
test "command terminal detail resolves split command output source rows" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; const id = types.ToolLifecycleId{ .turn_id = 9, .call_id = "command-split" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"printf demo\"}", } });
var metrics: Metrics = .{}; try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, id, .stdout, "first\n", true); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "intervening\n", .unknown_raw); try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, id, .stdout, "second\n", true); try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, id, true); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Ran printf demo" }, } });
const status_entry_id = runtime.toolActivityRecord(id).?.entry_id; const block = runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 2), block.source_entry_ids.items.len); try std.testing.expectEqual(status_entry_id, runtime.detailEntryIdForCommandOutputSource(block.source_entry_ids.items[0]).?); try std.testing.expectEqual(status_entry_id, runtime.detailEntryIdForCommandOutputSource(block.source_entry_ids.items[1]).?);}
test "command output consolidation preserves current compact ownership" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 32, .cols = 100, .content_bottom = 28, .divider_top_row = 29, .input_row = 30, .divider_bottom_row = 31, .hint_row = 32, } }; defer runtime.deinit(alloc);
const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; const id = types.ToolLifecycleId{ .turn_id = 1, .call_id = "streaming-command" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"stream\"}", } }); const status_entry_id = runtime.toolActivityRecord(id).?.entry_id;
var metrics: Metrics = .{}; for (0..80) |index| { var line: [32]u8 = undefined; const text = try std.fmt.bufPrint(&line, "STREAM {d:0>3}\n", .{index + 1}); try runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, id, .stdout, text, true, ); }
var live = try runtime.prepareTranscriptSourceOmittingEntry(alloc, status_entry_id); defer live.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), live.bytes.len);
try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, id, true);
var compact = try runtime.prepareTranscriptSource(alloc, null); defer compact.deinit(alloc); try std.testing.expect(std.mem.indexOf(u8, compact.bytes, "STREAM 001") == null); try std.testing.expect(std.mem.indexOf(u8, compact.bytes, "1 tool call") != null);
_ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Ran stream" }, } }); var completed = try runtime.prepareTranscriptSource(alloc, null); defer completed.deinit(alloc);
const status_pos = std.mem.indexOf(u8, completed.bytes, "stream") orelse return error.MissingCommandStatus; try std.testing.expect(std.mem.indexOf(u8, completed.bytes, "STREAM 001") == null); try std.testing.expect(status_pos < completed.bytes.len);
const output_entry_id = runtime.command_output_blocks.items[0].entry_id.?; const output_index = entryIndex(&runtime, output_entry_id).?; const status_index = entryIndex(&runtime, status_entry_id).?; try std.testing.expect(status_index < output_index);}
fn entryIndex(runtime: *const TranscriptRuntime, entry_id: u32) ?usize { for (runtime.entries.items, 0..) |entry, index| { if (entry.id() == entry_id) return index; } return null;}
test "command output records span callbacks and keep first-byte ownership" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 40, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; var metrics: Metrics = .{}; try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "hel", true, ); const first_entry_id = runtime.command_output_blocks.items[0].lines.items[0].entry_id.?; const intervening_entry_id = try runtime.appendRawTranscriptEntryClassified( alloc, "intervening\n", .unknown_raw, ); try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "lo\nnext\n", true, );
const block = runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 2), block.lines.items.len); try std.testing.expectEqualStrings("hello", block.lines.items[0].text); try std.testing.expectEqualStrings("next", block.lines.items[1].text); const second_entry_id = block.lines.items[1].entry_id.?; try std.testing.expect(entryIndex(&runtime, first_entry_id).? < entryIndex(&runtime, intervening_entry_id).?); try std.testing.expect(entryIndex(&runtime, intervening_entry_id).? < entryIndex(&runtime, second_entry_id).?);}
test "split utf8 keeps cross-stream first-byte ownership and pure ansi is pruned" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 40, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc); const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; var metrics: Metrics = .{};
try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "\xe2\x82", true); const stdout_entry_id = runtime.command_output_blocks.items[0].lines.items[0].entry_id.?; try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stderr, "err\n", true); const stderr_entry_id = runtime.command_output_blocks.items[0].lines.items[1].entry_id.?; try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "\xac\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "\x1b[31m", true); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true);
const block = runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 2), block.lines.items.len); try std.testing.expectEqualStrings("€", block.lines.items[0].text); try std.testing.expectEqualStrings("err", block.lines.items[1].text); try std.testing.expectEqual(@as(usize, 0), block.lines.items[0].record_ordinal); try std.testing.expectEqual(@as(usize, 1), block.lines.items[1].record_ordinal); try std.testing.expect(entryIndex(&runtime, stdout_entry_id).? < entryIndex(&runtime, stderr_entry_id).?);}
test "prepared canonical command mutation stays bounded across twenty four thousand records" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 72, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc); const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; var metrics: Metrics = .{}; for (0..24_000) |_| { try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "record\n", true, ); try std.testing.expect( runtime.retainedStructuredBytesForCommandOutput() <= runtime.max_retained_transcript_bytes, ); } const block = runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 24_000), block.total_lines); try std.testing.expect(block.retention_overflow); try std.testing.expect(block.lines.items.len < block.total_lines); for (block.lines.items, 0..) |line, record_ordinal| { try std.testing.expectEqual(record_ordinal, line.record_ordinal); }}
test "oversized open command record stops growing after overflow" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 72, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .max_retained_transcript_bytes = 8 * 1024, }; defer runtime.deinit(alloc); const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; var metrics: Metrics = .{}; var fragment: [4096]u8 = undefined; @memset(&fragment, 'x'); var retained_after_overflow: ?usize = null; var source_entry_id: ?u32 = null; for (0..256) |_| { try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, &fragment, true, ); const block = runtime.command_output_blocks.items[0]; if (source_entry_id == null) source_entry_id = block.lines.items[0].entry_id; try std.testing.expectEqual(source_entry_id, block.lines.items[0].entry_id); try std.testing.expect( runtime.retainedStructuredBytesForCommandOutput() <= runtime.max_retained_transcript_bytes, ); if (block.retention_overflow) { if (retained_after_overflow) |retained| { try std.testing.expectEqual(retained, block.retained_text_bytes); } else { retained_after_overflow = block.retained_text_bytes; } } } try std.testing.expect(retained_after_overflow != null);
var failing = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 0 }); try runtime.writeCommandOutputChunk( failing.allocator(), &metrics, styles, .stdout, &fragment, true, ); try std.testing.expectEqual(@as(usize, 0), failing.allocations);}
test "descriptor capped bare carriage return replaces one dropped record" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 72, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, }; defer runtime.deinit(alloc); const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; var metrics: Metrics = .{};
_ = try command_output_runtime.writeCommandOutputChunkUncommitted( &runtime, alloc, &metrics, styles, null, .stdout, "kept\n", true, ); const kept_entry_id = runtime.command_output_blocks.items[0].lines.items[0].entry_id.?; runtime.max_retained_transcript_bytes = runtime.retainedStructuredBytesForCommandOutput();
_ = try command_output_runtime.writeCommandOutputChunkUncommitted( &runtime, alloc, &metrics, styles, null, .stdout, "dropped\rreplacement", true, ); var block = &runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 2), block.total_lines); try std.testing.expectEqual(@as(usize, 1), block.lines.items.len); try std.testing.expectEqual(kept_entry_id, block.lines.items[0].entry_id.?); try std.testing.expect(block.overflow_open_records[0]); try std.testing.expect(block.overflow_record_visible[0]);
_ = try command_output_runtime.writeCommandOutputChunkUncommitted( &runtime, alloc, &metrics, styles, null, .stdout, "\n", true, ); block = &runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 2), block.total_lines); try std.testing.expectEqual(@as(usize, 1), block.lines.items.len); try std.testing.expectEqual(kept_entry_id, block.lines.items[0].entry_id.?); try std.testing.expect(!block.overflow_open_records[0]);}
test "descriptor capped ansi-only prefix keeps later visible overflow record" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 72, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, }; defer runtime.deinit(alloc); const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; var metrics: Metrics = .{};
_ = try command_output_runtime.writeCommandOutputChunkUncommitted( &runtime, alloc, &metrics, styles, null, .stdout, "kept\n", true, ); runtime.max_retained_transcript_bytes = runtime.retainedStructuredBytesForCommandOutput();
_ = try command_output_runtime.writeCommandOutputChunkUncommitted( &runtime, alloc, &metrics, styles, null, .stdout, "\x1b[31m", true, ); var block = &runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 2), block.total_lines); try std.testing.expect(block.overflow_open_records[0]); try std.testing.expect(!block.overflow_record_visible[0]); try std.testing.expect(block.decoders[0].isIdle());
_ = try command_output_runtime.writeCommandOutputChunkUncommitted( &runtime, alloc, &metrics, styles, null, .stdout, "visible", true, ); block = &runtime.command_output_blocks.items[0]; try std.testing.expect(block.overflow_record_visible[0]); runtime.max_retained_transcript_bytes = std.math.maxInt(usize);
_ = try command_output_runtime.flushCommandOutputSummaryUncommitted( &runtime, alloc, &metrics, styles, null, true, ); try std.testing.expectEqual( @as(usize, 2), runtime.command_output_blocks.items[0].total_lines, );
var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), source.bytes.len);}
test "descriptor capped split osc terminator preserves record boundaries" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 72, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, }; defer runtime.deinit(alloc); const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; var metrics: Metrics = .{};
try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "kept\n", true, ); runtime.max_retained_transcript_bytes = runtime.retainedStructuredBytesForCommandOutput();
try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "\x1b]0;title", true, ); var block = &runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 2), block.total_lines); try std.testing.expect(block.overflow_open_records[0]);
try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "\x07", true, ); try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "\nnext\n", true, );
block = &runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 3), block.total_lines); try std.testing.expect(!block.overflow_open_records[0]);}
test "command terminal detail resolves its lifecycle output instead of the latest block" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; const first = types.ToolLifecycleId{ .turn_id = 9, .call_id = "command-first" }; const second = types.ToolLifecycleId{ .turn_id = 9, .call_id = "command-second" }; for ([_]types.ToolLifecycleId{ first, second }) |id| { _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"printf demo\"}", } }); }
var metrics: Metrics = .{}; try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, first, .stdout, "first\n", true); try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, first, true); try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, second, .stdout, "second\n", true); try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, second, true);
_ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = first, .outcome = .{ .kind = .completed, .summary = "Ran first" }, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = second, .outcome = .{ .kind = .completed, .summary = "Ran second" }, } });
const first_status = runtime.toolActivityRecord(first).?.entry_id; const second_status = runtime.toolActivityRecord(second).?.entry_id; try std.testing.expectEqual(runtime.command_output_blocks.items[0].entry_id, runtime.toolDetailForEntry(first_status).?.command_output_entry_id); try std.testing.expectEqual(runtime.command_output_blocks.items[1].entry_id, runtime.toolDetailForEntry(second_status).?.command_output_entry_id);}
test "command output consolidation keeps compact tool order" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; const first = types.ToolLifecycleId{ .turn_id = 9, .call_id = "command-first" }; const second = types.ToolLifecycleId{ .turn_id = 9, .call_id = "command-second" };
_ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = first, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"printf first\"}", } }); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = second, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"printf second\"}", } });
var metrics: Metrics = .{}; try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, first, .stdout, "FIRST_OUTPUT\n", true); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = first, .outcome = .{ .kind = .completed, .summary = "Ran first" }, } }); try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, first, true);
try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, second, .stdout, "SECOND_OUTPUT\n", true); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = second, .outcome = .{ .kind = .completed, .summary = "Ran second" }, } }); try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, second, true);
var compact = try runtime.prepareTranscriptSource(alloc, null); defer compact.deinit(alloc);
const first_status = std.mem.indexOf(u8, compact.bytes, "first") orelse return error.MissingFirstStatus; const second_status = std.mem.indexOf(u8, compact.bytes, "second") orelse return error.MissingSecondStatus;
try std.testing.expect(first_status < second_status); try std.testing.expect(std.mem.indexOf(u8, compact.bytes, "FIRST_OUTPUT") == null); try std.testing.expect(std.mem.indexOf(u8, compact.bytes, "SECOND_OUTPUT") == null);
const first_status_entry = runtime.toolActivityRecord(first).?.entry_id; const second_status_entry = runtime.toolActivityRecord(second).?.entry_id; try std.testing.expectEqual(runtime.command_output_blocks.items[0].entry_id, runtime.toolDetailForEntry(first_status_entry).?.command_output_entry_id); try std.testing.expectEqual(runtime.command_output_blocks.items[1].entry_id, runtime.toolDetailForEntry(second_status_entry).?.command_output_entry_id);}
test "command output completion ignores mismatched lifecycle" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, } }; defer runtime.deinit(alloc);
const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; const first = types.ToolLifecycleId{ .turn_id = 9, .call_id = "command-first" }; const second = types.ToolLifecycleId{ .turn_id = 9, .call_id = "command-second" };
var metrics: Metrics = .{}; try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, first, .stdout, "first\n", true); try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, null, true); try std.testing.expect(runtime.command_output_display.open_command_block != null); try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, second, true); try std.testing.expect(runtime.command_output_display.open_command_block != null); try std.testing.expect(runtime.command_output_blocks.items[0].entry_id == null);
try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, first, true); try std.testing.expect(runtime.command_output_display.open_command_block == null); try std.testing.expect(runtime.command_output_blocks.items[0].entry_id != null);}
test "retintEntriesForTheme rewrites owned presentation without touching external raw bytes" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .owned_top_row = 1, }; defer runtime.deinit(alloc);
const owned_id = try runtime.appendRawTranscriptEntryClassified( alloc, "\x1b[38;5;252m■\x1b[38;5;255m cancelled\x1b[39m\n", .tool_status, ); const command_id = try runtime.appendRawTranscriptEntryClassified( alloc, "\x1b[38;5;252mcommand\x1b[39m\n", .command_output, ); const unknown_id = try runtime.appendRawTranscriptEntryClassified( alloc, "\x1b[38;5;252munknown\x1b[39m\n", .unknown_raw, ); const assistant_id = try runtime.appendAssistantTurnEntry(alloc); const segments = runtime.lookupAssistantSegments(assistant_id).?; try segments.text.appendSlice(alloc, "\x1b[38;5;245mcode\x1b[39m \x1b[38;5;252m✓\x1b[39m\n");
try runtime.retintEntriesForTheme(alloc, false, true);
try std.testing.expectEqual(owned_id, runtime.entries.items[0].id()); try std.testing.expectEqual(command_id, runtime.entries.items[1].id()); try std.testing.expectEqual(unknown_id, runtime.entries.items[2].id()); try std.testing.expectEqual(assistant_id, runtime.entries.items[3].id()); try std.testing.expectEqualStrings( "\x1b[38;5;238m■\x1b[38;5;235m cancelled\x1b[39m\n", runtime.entries.items[0].raw_bytes.bytes, ); try std.testing.expectEqualStrings( "\x1b[38;5;252mcommand\x1b[39m\n", runtime.entries.items[1].raw_bytes.bytes, ); try std.testing.expectEqualStrings( "\x1b[38;5;252munknown\x1b[39m\n", runtime.entries.items[2].raw_bytes.bytes, ); try std.testing.expectEqualStrings( "\x1b[38;5;247mcode\x1b[39m \x1b[38;5;238m✓\x1b[39m\n", runtime.entries.items[3].assistant_turn.segments.text.items, );}
pub fn wrapAssistantText(alloc: Allocator, text: []const u8, cols: u16) ![]u8 { return transcript_blocks.wrapAssistantText(alloc, text, cols);}
const default_max_retained_transcript_bytes: usize = 1024 * 1024;const full_transcript_snapshot_clone_max_bytes: usize = 8 * 1024 * 1024;const resume_publication_rows_per_frame: u32 = 64;
pub const PaintTraceState = struct { transcript_top: u16 = 0, transcript_bottom: u16 = 0, transcript_start_line: usize = 0, transcript_total_lines: usize = 0, transcript_visible_rows: u16 = 0, transcript_partial_skip_rows: u16 = 0, transcript_entries: usize = 0, transcript_initialized: bool = false, transcript_log_frame: bool = false,
footer_top: u16 = 0, footer_input_base: u16 = 0, footer_hint: u16 = 0, footer_activity_row: ?u16 = null, footer_activity_reserved: u16 = 0, footer_activity_kind: u8 = 0, footer_initialized: bool = false,};
const CommandOutputBlock = command_output_runtime.CommandOutputBlock;const FoldedCommandBlock = command_output_runtime.FoldedCommandBlock;const CommandOutputRenderPolicy = command_output_runtime.CommandOutputRenderPolicy;
pub const ToolDetailRecord = transcript_blocks.ToolDetailRecord;
const PendingToolDetailStart = struct { tool_name: ?[]u8 = null, captured_command: ?bool = null, activity_kind: ?types.ToolActivityKind = null, arguments_json: ?[]u8 = null, lifecycle_id: ?types.ToolLifecycleId = null, replace_lifecycle: bool = false,
fn deinit(self: *PendingToolDetailStart, alloc: Allocator) void { const tool_name = self.tool_name; const arguments_json = self.arguments_json; const lifecycle_id = self.lifecycle_id; self.* = .{}; if (tool_name) |value| alloc.free(value); if (arguments_json) |value| alloc.free(value); if (lifecycle_id) |id| alloc.free(@constCast(id.call_id)); }};
const PendingToolDetailTerminal = struct { result: ?[]u8 = null, result_handle: ?[]u8 = null, command_artifact_handle: ?[]u8 = null, command_output_replay: ?types.CommandOutputReplay = null, command_process_presentation: ?types.CommandProcessPresentation = null, command_output_entry_id: ?u32 = null, command_process_entry: ?PendingCommandProcessEntry = null,
fn deinit(self: *PendingToolDetailTerminal, alloc: Allocator) void { if (self.result) |value| alloc.free(value); if (self.result_handle) |value| alloc.free(value); if (self.command_artifact_handle) |value| alloc.free(value); if (self.command_output_replay) |replay| { types.freeCommandOutputReplay(alloc, replay); } if (self.command_process_entry) |*entry| entry.deinit(alloc); self.* = .{}; }};
const PendingCommandProcessEntry = struct { entry_id: u32, bytes: ?[]u8 = null, block: ?CommandOutputBlock = null,
fn deinit(self: *PendingCommandProcessEntry, alloc: Allocator) void { if (self.bytes) |value| alloc.free(value); if (self.block) |*value| value.deinit(alloc); self.bytes = null; self.block = null; }};
pub fn renderEntriesToBytes( alloc: Allocator, entries: []const TranscriptEntry, cols: u16, styles: Styles,) ![]u8 { return transcript_blocks.renderEntriesToBytes(alloc, entries, cols, styles);}
pub const FullTranscriptPrimaryRestore = enum { changed, exact, resized,};
const InstalledFullTranscriptPage = struct { source: full_transcript_worker.InstalledSource, projection: full_transcript_screen.Projection, prepared_window: ?full_transcript_worker.PreparedWindow = null, measured_total_rows: u32 = 0, measured_anchor_row: ?u32 = null, measured_item_rows: []transcript_presentation.ItemRow = &.{}, measurement_snapshot_ready: bool = false, presented_offset: u32 = 0,
fn deinit(self: *InstalledFullTranscriptPage) void { if (self.prepared_window) |*window| window.deinit(std.heap.c_allocator); if (self.measured_item_rows.len > 0) { std.heap.c_allocator.free(self.measured_item_rows); } self.projection.deinit(std.heap.c_allocator); self.source.deinit(std.heap.c_allocator); self.* = undefined; }};
const CachedCompactTranscriptSource = struct { source: TranscriptPreparationSource, content_revision: u64, cols: u16, has_committed_frame: bool,};
const CompactTranscriptSourceCache = struct { const capacity = 4;
entries: [capacity]?CachedCompactTranscriptSource = .{null} ** capacity, next_replacement: usize = 0,
fn deinit(self: *CompactTranscriptSourceCache, alloc: Allocator) void { for (&self.entries) |*entry| { if (entry.*) |*cached| cached.source.deinit(alloc); } self.* = .{}; }
fn find( self: *CompactTranscriptSourceCache, content_revision: u64, cols: u16, has_committed_frame: bool, ) ?*TranscriptPreparationSource { for (&self.entries) |*entry| { if (entry.*) |*cached| { if (cached.content_revision == content_revision and cached.cols == cols and cached.has_committed_frame == has_committed_frame) { return &cached.source; } } } return null; }
fn insert( self: *CompactTranscriptSourceCache, alloc: Allocator, replacement: CachedCompactTranscriptSource, ) *TranscriptPreparationSource { var index = self.next_replacement; for (self.entries, 0..) |entry, candidate| { if (entry == null) { index = candidate; break; } } else { self.next_replacement = (self.next_replacement + 1) % capacity; } if (self.entries[index]) |*cached| cached.source.deinit(alloc); self.entries[index] = replacement; return &self.entries[index].?.source; }};
test "render diagnostic commit skips unchanged same-row paints" { diagnostics.resetForTest(); defer diagnostics.resetForTest(); const shell: TranscriptRuntime = .{ .layout = .{ .rows = 4, .cols = 20, .content_bottom = 1, .divider_top_row = 2, .input_row = 3, .divider_bottom_row = 4, .hint_row = 4, } }; const before = shell.transcriptCommitDiagnostic(); const scroll_plan = render_engine.frame_scroll_plan.merge(4, 1, 0, 0); var result: render_engine.terminal_diff.FrameCommitResult = .{ .bytes_written = 12, .changed_cells = 1, .full_repaint = false, .committed_cursor_row = 3, .committed_cursor_col = 1, .shadow_state = .committed, .next_invalidation = .{}, }; shell.recordRenderCommit(before, false, false, scroll_plan, result, false); var events: [2]diagnostics.RenderEvent = undefined; try std.testing.expectEqual(@as(usize, 0), diagnostics.snapshotRenderEvents(&events)); result.full_repaint = true; shell.recordRenderCommit(before, false, false, scroll_plan, result, false); try std.testing.expectEqual(@as(usize, 1), diagnostics.snapshotRenderEvents(&events)); try std.testing.expectEqual(.commit, events[0].kind); try std.testing.expect(!events[0].truncated); try std.testing.expect(std.mem.find(u8, events[0].detail(), "repaint=true") != null);}
pub const TranscriptRuntime = struct { stdout_file: std.Io.File = std.Io.File.stdout(), test_frame_sink: if (@import("builtin").is_test) ?render_engine.terminal_diff.FrameSink else void = if (@import("builtin").is_test) null else {}, sync_updates_enabled: bool = true, history_reset_uses_ris: bool = false, layout: Layout = undefined, cursor_row: u16 = 1, cursor_col: u16 = 1, /// Top terminal row owned by fx. Rows above this contain pre-fx scrollback /// and must not be touched by fx repaint logic. /// Initialized to the cursor row captured at `initViewport`, and /// decreased as the transcript scrolls until it reaches row 1. viewport_top_row: u16 = 1, /// First row fx is allowed to clear or repaint. This is set after /// launch-time reservation has created fx-owned rows. Rows above it /// belong to pre-fx terminal content and must not be touched by /// resize/layout repair. owned_top_row: u16 = 1, /// Set when a resize leaves no drawable rows below `owned_top_row`. /// The next successful repaint uses natural terminal scroll to /// compact older visible rows into scrollback before drawing the /// latest fx frame. pending_scroll_compact: bool = false, /// Minimum body rows fx should keep available for the visible /// viewport. Startup sets this to the welcome banner height so a /// later resize cannot leave the renderer with a partial banner /// band and stale resize-reflow cells above it. min_visible_viewport_rows: u16 = 0, /// Resize-only guard rows to clear above `viewport_top_row` on the /// next replay. Real terminals can reflow old fx cells just above /// the logical band during a drag; this lets the settled pass erase /// that residue without widening normal streaming paints. reflow_clear_guard_rows: u16 = 0, /// Signed cursor displacement measured across the current settled /// resize. Positive rows entered history; negative rows returned. resize_history_row_delta: ?i32 = null, /// Pre-paint cursor evidence retained from live geometry through the /// matching canonical reset commit. pending_resize_observation: ?ResizeObservation = null, /// Forces the next viewport diff paint to start from a real terminal /// clear. This is reserved for resize/reflow repair; normal streamed /// token paints must diff in place so cleared frames do not enter /// terminal scrollback. viewport_clear_pending: bool = false, /// A settled terminal resize needs a canonical repaint from row one. /// It remains pending until the reset frame commits successfully. terminal_reset_pending: bool = false, /// A committed resize reset established terminal-bottom ownership that /// the next cancellation projection must preserve. resize_bottom_anchor_pending: bool = false, render_requests: render_request.RenderRequestState = .{}, /// Set after the frame commit path has successfully written and fed /// a frame. Resize-time invalid terminal dimensions before this point /// still use the startup error path. has_committed_frame: bool = false, committed_frame_layout: render_engine.frame_layout.CommittedLayoutSnapshot = .{}, transcript_commit_state: TranscriptCommitState = .invalid, publication_projection_invalid: bool = false, /// True while the terminal reports zero, unreadable, or too-small /// dimensions after the first committed frame. Normal painters stay /// idle until a valid resize clears this flag. terminal_dimensions_invalid: bool = false, transcript: std.ArrayList(u8) = .empty, /// Complete historical flow and its immutable line index, retained until /// the first successful terminal publication. pending_resume_source: ?TranscriptPreparationSource = null, folded_command_blocks: std.ArrayList(FoldedCommandBlock) = .empty, command_output_blocks: std.ArrayList(CommandOutputBlock) = .empty, /// Viewer state used only while Ctrl-O owns the alternate screen. /// The compact transcript keeps its independent viewport selection. full_transcript: transcript_presentation.State = .{}, full_transcript_open_content_revision: ?u64 = null, full_transcript_open_cols: u16 = 0, full_transcript_open_rows: u16 = 0, full_transcript_page_load: full_transcript_worker.Load = .{}, full_transcript_window_load: full_transcript_worker.WindowLoad = .{}, full_transcript_page_anchor: full_transcript_page.Anchor = .tail, full_transcript_installed_page: ?InstalledFullTranscriptPage = null, full_transcript_installed_page_retired: bool = false, full_transcript_failed_request: ?full_transcript_page.Request = null, full_transcript_failure_pending: bool = false, full_transcript_open_request: ?full_transcript_page.Request = null, full_transcript_restore_open_pending: bool = false, full_transcript_prepared_page_visible: bool = false, full_transcript_content_revision: u64 = 0, compact_transcript_source_cache: CompactTranscriptSourceCache = .{}, /// When enabled, compact transcript tool groups render only their summary /// header while the full transcript retains every individual tool call. collapse_tool_calls: bool = false, /// Structured-entry store used to regenerate transcript bytes at the /// current width while retaining the raw byte buffer for append paths /// that still write pre-rendered transcript content. entries: std.ArrayList(TranscriptEntry) = .empty, /// Borrowed only by a detached store transaction; never installed on the live runtime. retention_prefix_trims: ?*std.ArrayList(transcript_store.RetentionPrefixTrim) = null, lifecycle_state: activity_runtime.ToolActivityState = .{}, worker_status: worker_status.State = .none, /// Core-owned producer state and policy for native-history release. transcript_release: transcript_release.State = .{}, /// Sorted by entry id so exact lookup stays bounded as history grows. tool_details: std.ArrayList(ToolDetailRecord) = .empty, /// Full-detail records shown only in the ctrl+o full transcript. Sibling /// of `tool_details`: parallel to the entry store, never inline, never /// persisted, cleared with the transcript. full_detail_records: std.ArrayList(FullDetailRecord) = .empty, /// Monotonically increasing id stamped onto each new entry by the /// `append*Entry` helpers. Starts at 1 so 0 can be reserved as a /// sentinel. Never reset on `clearTranscript` — even after a wipe, /// subsequent ids remain unique within the session, which keeps /// debug traces unambiguous across transcript resets. next_entry_id: u32 = 1, /// Cols at which the transcript byte buffer was last regenerated from /// entries. A cache-origin proof is valid only at this width. /// Zero before the first prepared transcript frame. last_rendered_cols: u16 = 0, /// True only when the visible cache was rebuilt from an untrimmed, /// canonical entry source at `last_rendered_cols`. transcript_cache_origin_untrimmed: bool = false, /// Visible byte-cache cap. This bounds `transcript.items`, which is /// the rendered tail used by viewport paint. Structured entries and /// folded command output are bounded separately by /// `max_retained_transcript_bytes` below. max_transcript_bytes: usize = 256 * 1024, /// Structured retention cap for raw/user/assistant entries plus /// folded command output lines. This model is the source of truth /// for reconstructive paint, resize, and diff restyling, so it gets /// its own conservative budget instead of borrowing the smaller /// visible byte-cache limit. max_retained_transcript_bytes: usize = default_max_retained_transcript_bytes, painting: bool = false, /// Renderer-local cache state set by transcript mutations. The same /// mutation must request `.transcript`; this flag never authorizes a /// frame on its own. transcript_band_dirty: bool = false, /// False until the first non-empty viewport paint has established /// fx's transcript band. Before that point transcript writes are /// model updates only: emitting scroll newlines would move pre-fx /// shell rows before the renderer has had a chance to respect /// `viewport_top_row`. has_painted_transcript: bool = false, footer_viewport: footer_viewport_runtime.FooterViewport = .{}, extra_input_rows: u16 = 0, footer_reserved_base_rows: u16 = 3, command_output_display: CommandOutputDisplayState = .{}, command_output_render: CommandOutputRenderPolicy = .{}, shimmer_active: bool = false, shimmer_is_overlay: bool = false, paint_trace: PaintTraceState = .{}, last_visible_transcript_top_row: u16 = 1, last_visible_transcript_start_line: usize = 0, last_visible_transcript_partial_skip_rows: u16 = 0, last_visible_transcript_split_active: bool = false, last_visible_transcript_split_prefix_lines: usize = 0, last_visible_transcript_split_suffix_start_line: usize = 0, last_visible_transcript_last_row: u16 = 0, last_visible_transcript_last_row_blank: bool = false, last_visible_transcript_tail_kind: ?TranscriptBlockKind = null, last_viewport_selection: ?ViewportSelection = null, /// Detached alternate-screen views may request the ordinary compact /// transcript at a stable distance from its tail. Inline chat leaves this /// unset and keeps the existing viewport policy. tail_viewport_request: ?viewport_selection.TailViewportPolicy = null, tail_viewport_resolution: ?viewport_selection.TailOffsetResolution = null, last_paint_bottom_reserved_rows: u16 = 0, replaceable_last_line: bool = false, replaceable_row: u16 = 1, replaceable_start: usize = 0, /// In-process terminal shadow used as the previous state for cell diffs. /// Bootstrap enables it unconditionally; FX_TRACE dumps it at synchronized /// update boundaries. shadow_vt: ?*vt_emulator.Grid = null, shadow_vt_alloc: ?Allocator = null, /// Detached presentations share a physical terminal shadow with their /// owning surface, but retain independent transcript commit state. /// This allocator releases that commit state without claiming ownership /// of the shared shadow grid. detached_commit_alloc: ?Allocator = null, ui_observer: render_engine.ui_observer.UiObserver = .{},
pub noinline fn init() TranscriptRuntime { var result: TranscriptRuntime = .{ .compact_transcript_source_cache = undefined, }; for (&result.compact_transcript_source_cache.entries) |*entry| { entry.* = null; } result.compact_transcript_source_cache.next_replacement = 0; return result; }
pub fn enableShadowVt(self: *TranscriptRuntime, alloc: Allocator) !void { return transcript_io.enableShadowVt(self, alloc); }
pub fn disableShadowVt(self: *TranscriptRuntime) void { return transcript_io.disableShadowVt(self); }
pub fn bindDetachedCommitAllocator( self: *TranscriptRuntime, alloc: Allocator, ) void { std.debug.assert(self.shadow_vt == null); self.detached_commit_alloc = alloc; }
pub fn deinit(self: *TranscriptRuntime, alloc: Allocator) void { self.disableShadowVt(); self.full_transcript_window_load.deinit(); self.full_transcript_page_load.deinit(); self.discardInstalledFullTranscriptPage(); self.compact_transcript_source_cache.deinit(alloc); self.lifecycle_state.deinit(alloc); for (self.tool_details.items) |*detail| detail.deinit(alloc); self.tool_details.deinit(alloc); self.clearFullDetailRecords(alloc); self.full_detail_records.deinit(alloc); self.transcript.deinit(alloc); _ = self.releasePendingResumeSource(alloc); for (self.folded_command_blocks.items) |*block| block.deinit(alloc); self.folded_command_blocks.deinit(alloc); for (self.command_output_blocks.items) |*block| block.deinit(alloc); self.command_output_blocks.deinit(alloc); for (self.entries.items) |*entry| entry.deinit(alloc); self.entries.deinit(alloc); self.transcript_commit_state.deinit(alloc); self.footer_viewport.deinit(alloc); self.ui_observer.deinit(alloc); }
fn releasePendingResumeSource(self: *TranscriptRuntime, alloc: Allocator) usize { var source = self.pending_resume_source orelse return 0; const released_bytes = source.bytes.len; source.deinit(alloc); self.pending_resume_source = null; return released_bytes; }
pub fn pendingResumeFlow(self: *const TranscriptRuntime) []const u8 { return if (self.pending_resume_source) |*source| source.bytes else &.{}; }
pub fn committedRowProvenance( self: *const TranscriptRuntime, ) []const transcript_blocks.RowProvenance { return switch (self.transcript_commit_state) { .invalid => &.{}, .stable => |anchor| anchor.row_provenance, .recovering => |receipt| receipt.row_provenance, }; }
pub fn beginPaint(self: *TranscriptRuntime) usize { std.debug.assert(!self.painting); self.painting = true; return self.transcript.items.len; }
pub fn endPaint(self: *TranscriptRuntime, entry_len: usize) void { self.painting = false; const exit_len = self.transcript.items.len; if (exit_len != entry_len) { debug_trace.logf("paint", "transcript mutated during paint: entry_len={d} exit_len={d}", .{ entry_len, exit_len }); } }
pub fn beginPaintSilent(self: *TranscriptRuntime) void { std.debug.assert(!self.painting); self.painting = true; }
pub fn endPaintSilent(self: *TranscriptRuntime) void { self.painting = false; }
pub fn assertCanMutateTranscript(self: *TranscriptRuntime) !void { if (@import("builtin").is_test and self.painting) return error.PaintGuardViolated; std.debug.assert(!self.painting); }
pub fn initBacking(self: *TranscriptRuntime, alloc: Allocator) !void { return transcript_store.initBacking(self, alloc); }
pub fn ensurePaintReservation(self: *TranscriptRuntime, alloc: Allocator) !void { return transcript_store.ensurePaintReservation(self, alloc); }
pub fn rebuildTranscriptFromRendered(self: *TranscriptRuntime, alloc: Allocator, bytes: []const u8, label: []const u8) !void { return transcript_store.rebuildTranscriptFromRendered(self, alloc, bytes, label); }
pub fn replaceableEntryId(self: *const TranscriptRuntime) ?u32 { return transcript_store.replaceableEntryId(self); }
pub fn refreshFoldedCommandSummaryIndices(self: *TranscriptRuntime, alloc: Allocator) !void { return transcript_store.refreshFoldedCommandSummaryIndices(self, alloc); }
pub fn rebuildTranscriptCacheFromEntries(self: *TranscriptRuntime, alloc: Allocator, label: []const u8) !void { return transcript_store.rebuildTranscriptCacheFromEntries(self, alloc, label); }
pub fn rebuildTranscriptCacheAfterStructuredRewrite(self: *TranscriptRuntime, alloc: Allocator, label: []const u8) !void { return transcript_store.rebuildTranscriptCacheAfterStructuredRewrite(self, alloc, label); }
pub fn reconcileDetachedInstallSource( self: *TranscriptRuntime, source: []const u8, ) void { transcript_store.reconcileDetachedInstallSource(self, source); }
pub fn enforceStructuredRetention(self: *TranscriptRuntime, alloc: Allocator, protected_id: ?u32) !void { return transcript_store.enforceStructuredRetention(self, alloc, protected_id); }
pub fn enforceStructuredRetentionAndReport( self: *TranscriptRuntime, alloc: Allocator, protected_id: ?u32, ) !bool { return transcript_store.enforceStructuredRetentionAndReport(self, alloc, protected_id); }
pub fn lifecyclePinCount(self: *const TranscriptRuntime) usize { return transcript_store.lifecyclePinCount(self); }
pub fn isLifecyclePinned(self: *const TranscriptRuntime, entry_id: u32) bool { return transcript_store.isLifecyclePinned(self, entry_id); }
pub fn applyToolLifecycle( self: *TranscriptRuntime, alloc: Allocator, event: types.ToolLifecycleEvent, ) !?types.ToolActivityKind { return self.applyToolLifecycleWithAnchorPolicy(alloc, event, false); }
pub fn applyToolLifecyclePreservingNormalBufferAnchor( self: *TranscriptRuntime, alloc: Allocator, event: types.ToolLifecycleEvent, ) !?types.ToolActivityKind { return self.applyToolLifecycleWithAnchorPolicy(alloc, event, true); }
fn applyToolLifecycleWithAnchorPolicy( self: *TranscriptRuntime, alloc: Allocator, event: types.ToolLifecycleEvent, preserve_normal_buffer_anchor: bool, ) !?types.ToolActivityKind { const result = switch (event) { .provisional => |provisional| self.applyProvisionalLifecycle( alloc, provisional.id, provisional.presentation_group_id, provisional.tool_name, provisional.activity_kind, ), .authoritative_started => |started| self.applyAuthoritativeLifecycle( alloc, started.id, started.presentation_group_id, started.reconciles_provisional_call_id, started.tool_name, started.activity_kind, started.arguments_json, started.place_after_current_transcript, ), .progress => |progress| self.applyProgressLifecycle( alloc, progress.id, progress.text, ), .terminal => |terminal| self.applyTerminalLifecycle( alloc, terminal.id, terminal.outcome, terminal.result, terminal.result_memory, terminal.command_artifact_handle, ), .turn_finished => |finished| self.applyTurnFinishedLifecycle( alloc, finished, preserve_normal_buffer_anchor, ), } catch |err| { traceLifecycleFailure( @tagName(event), lifecycleEventTurnId(event), err, ); return err; }; debug_trace.eventf( "ui_activity", "lifecycle_reduced", .{}, "kind={s} turn_id={d} records={d}", .{ @tagName(event), lifecycleEventTurnId(event), self.lifecycle_state.recordCount(), }, ); return result; }
pub fn finishLifecycleBatch( self: *TranscriptRuntime, alloc: Allocator, ) !void { const watermark = self.lifecycle_state.batch_finalized_turn_watermark orelse return; const entry_ids = try self.lifecycle_state.cleanupEntryIds(alloc, watermark); defer alloc.free(entry_ids); transcript_store.clearLifecyclePinsAtomic(self, alloc, entry_ids) catch |err| { traceLifecycleFailure("batch_cleanup", watermark, err); return err; }; self.lifecycle_state.commitCleanup(alloc, watermark); }
pub fn activityProjection( self: *const TranscriptRuntime, ) activity_runtime.ActivityProjection { if (self.worker_status.projection()) |projection| return projection; const record = self.lifecycle_state.focusedRecord() orelse { return .none; }; const label = transcript_store.toolStatusEntryLabel(self, record.entry_id) orelse { debug_trace.logf( "ui_activity", "focused lifecycle missing transcript entry turn_id={d} entry_id={d}", .{ record.id.turn_id, record.entry_id }, ); return .none; }; return .{ .tool_slot = .{ .entry_id = record.entry_id, .fallback_label = label, .active = record.phase != .terminal, .kind = record.activity_kind, } }; }
pub fn worker_status_state(self: *TranscriptRuntime) *worker_status.State { return &self.worker_status; }
pub fn focusedToolEntryId(self: *const TranscriptRuntime) ?u32 { const record = self.lifecycle_state.focusedRecord() orelse return null; return record.entry_id; }
pub fn focusedToolActivityKind( self: *const TranscriptRuntime, ) ?types.ToolActivityKind { const record = self.lifecycle_state.focusedRecord() orelse return null; return record.activity_kind; }
pub fn toolActivityRecord( self: *const TranscriptRuntime, id: types.ToolLifecycleId, ) ?activity_runtime.ToolPresentationRecord { return self.lifecycle_state.record(id); }
pub fn attachCommandOutputEntryForLifecycle( self: *TranscriptRuntime, lifecycle_id: ?types.ToolLifecycleId, command_output_entry_id: u32, ) void { const id = lifecycle_id orelse return; for (self.tool_details.items) |*detail| { if (!command_output_runtime.sameLifecycleId(detail.lifecycle_id, id)) continue; detail.command_output_entry_id = command_output_entry_id; return; } }
pub fn toolActivityRecordCount(self: *const TranscriptRuntime) usize { return self.lifecycle_state.recordCount(); }
pub fn activeToolActivityCount(self: *const TranscriptRuntime) usize { return self.lifecycle_state.activeCount(); }
/// Publishes the terminal cancellation presentation immediately while the /// worker retains lifecycle ownership until each tool actually settles. pub fn presentActiveToolCancellation( self: *TranscriptRuntime, alloc: Allocator, ) !bool { const focused = self.lifecycle_state.focusedRecord() orelse return false; const active = try self.lifecycle_state.collectFallbackRecords( alloc, focused.id.turn_id, ); defer alloc.free(active); if (active.len == 0) return false;
const line = try lifecycleTerminalLine(alloc, .cancelled, "Cancelled"); defer alloc.free(line); const updates = try alloc.alloc(transcript_store.LifecycleEntryUpdate, active.len); defer alloc.free(updates); var detail_starts: std.ArrayList(PendingToolDetailStart) = .empty; defer { for (detail_starts.items) |*detail_start| detail_start.deinit(alloc); detail_starts.deinit(alloc); } try detail_starts.ensureTotalCapacity(alloc, active.len); var missing_detail_count: usize = 0; for (active, 0..) |entry, index| { if (self.toolDetailPtr(entry.record.entry_id) == null) { missing_detail_count += 1; } detail_starts.appendAssumeCapacity(try self.prepareToolDetailStart( alloc, entry.record.entry_id, entry.record.id, entry.record.tool_name orelse "tool", entry.record.activity_kind, null, )); updates[index] = .{ .entry_id = entry.record.entry_id, .bytes = line, }; } try transcript_store.replacePinnedToolStatusesAtomic( self, alloc, updates, missing_detail_count, null, ); self.lifecycle_state.markTurnCancellationPresented(focused.id.turn_id); for (active, detail_starts.items) |entry, *detail_start| { entry.record.cancellation_presentation = .tool_status; self.commitToolDetailStart( alloc, entry.record.entry_id, detail_start, ); const detail = self.toolDetailPtr(entry.record.entry_id).?; detail.outcome = .cancelled; detail.fallback_disposition = null; } return true; }
pub fn finalizedToolTurnWatermark(self: *const TranscriptRuntime) u64 { return self.lifecycle_state.finalized_turn_watermark; }
pub fn clearTranscript(self: *TranscriptRuntime, alloc: Allocator) void { const pending_resume_bytes = self.releasePendingResumeSource(alloc); if (pending_resume_bytes > 0) { debug_trace.logf( "session", "dropping pending resume flow during transcript clear bytes={d}", .{pending_resume_bytes}, ); } self.lifecycle_state.deinit(alloc); self.worker_status.reset(); self.clearToolDetails(alloc); self.clearFullDetailRecords(alloc); return transcript_store.clearTranscript(self, alloc); }
/// Prepends the live full-detail records (session assembly, network, /// recovery) to the first page of a full-transcript source as synthetic /// notice entries. Records never enter the store, so paging, retention, /// and resume keep their existing contracts. fn prependFullDetailRecords( self: *const TranscriptRuntime, alloc: Allocator, source: *full_transcript_worker.Source, ) !void { if (self.full_detail_records.items.len == 0) return; if (source.range.start != 0) return; const insert_at: usize = if (source.entries.items.len > 0 and source.entries.items[0] == .raw_bytes and source.entries.items[0].raw_bytes.class == .welcome) 1 else 0; for (self.full_detail_records.items, 0..) |record, index| { const topic = try alloc.dupe(u8, record.notice.topic); errdefer alloc.free(topic); const body = try alloc.dupe(u8, record.notice.body); errdefer alloc.free(body); try source.entries.insert(alloc, insert_at + index, .{ .semantic_notice = .{ .id = full_detail_record_entry_id, .created_at_ms = record.created_at_ms, .topic = topic, .tone = record.notice.tone, .body = body, .visibility = .full_only, } }); } }
pub fn snapshotVisibleTranscriptText( self: *const TranscriptRuntime, alloc: Allocator, max_bytes: usize, ) !?VisibleTranscriptSnapshot { const grid = self.shadow_vt orelse return null; if (!self.has_painted_transcript or self.last_visible_transcript_top_row == 0 or self.last_visible_transcript_last_row < self.last_visible_transcript_top_row) { return null; } const footer_gap_rows = if (!self.committed_frame_layout.footer_area.isEmpty() and self.committed_frame_layout.footer_area.top > self.last_visible_transcript_last_row) self.committed_frame_layout.footer_area.top - self.last_visible_transcript_last_row - 1 else 0; if (footer_gap_rows >= max_bytes) return null; const text_byte_limit = max_bytes - footer_gap_rows; var out: std.ArrayList(u8) = .empty; errdefer out.deinit(alloc); var start_row = self.last_visible_transcript_top_row; const leading_welcome_visible = if (self.entries.items.len > 0 and self.entries.items[0] == .raw_bytes and self.entries.items[0].raw_bytes.class == .welcome and self.last_viewport_selection != null) self.last_viewport_selection.?.start_line == 0 or (self.last_viewport_selection.?.split_active and self.last_viewport_selection.?.split_prefix_lines > 0) else false; if (leading_welcome_visible) { var saw_welcome_text = false; while (start_row <= self.last_visible_transcript_last_row) : (start_row += 1) { var line: std.ArrayList(u8) = .empty; defer line.deinit(grid.alloc); try grid.rowTextTrimmed(start_row, &line); if (line.items.len > 0) { saw_welcome_text = true; } else if (saw_welcome_text) { start_row += 1; break; } } if (!saw_welcome_text or start_row > self.last_visible_transcript_last_row) return null; } var selected_start: ?u16 = null; var selected_bytes: usize = 0; var row = self.last_visible_transcript_last_row; while (true) { var line: std.Io.Writer.Allocating = .init(grid.alloc); defer line.deinit(); try grid.rowStyledTextTrimmed(row, &line.writer); if (line.written().len >= text_byte_limit - selected_bytes) break; selected_bytes += line.written().len + 1; selected_start = row; if (row == start_row) break; row -= 1; } row = selected_start orelse return null; var has_content = false; while (row <= self.last_visible_transcript_last_row) : (row += 1) { var line: std.Io.Writer.Allocating = .init(grid.alloc); defer line.deinit(); try grid.rowStyledTextTrimmed(row, &line.writer); has_content = has_content or line.written().len > 0; try out.appendSlice(alloc, line.written()); try out.append(alloc, '\n'); } if (!has_content) { out.deinit(alloc); return null; } try out.appendNTimes(alloc, '\n', footer_gap_rows); return .{ .text = try out.toOwnedSlice(alloc), .terminal_rows = grid.rows, .terminal_cols = grid.cols, .complete = selected_start.? == start_row, }; }
pub fn resetVisualEpoch(self: *TranscriptRuntime, alloc: Allocator, welcome: []const u8) !void { return transcript_store.resetVisualEpoch(self, alloc, welcome); }
fn replacePinnedLifecycleStatus( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, line: []const u8, place_after_current_transcript: bool, ) !bool { return if (place_after_current_transcript) transcript_store.replacePinnedToolStatusAtEndAtomic( self, alloc, entry_id, line, ) else transcript_store.replacePinnedToolStatusAtomic( self, alloc, entry_id, line, ); }
fn applyProvisionalLifecycle( self: *TranscriptRuntime, alloc: Allocator, id: types.ToolLifecycleId, presentation_group_id: ?types.ToolPresentationGroupId, tool_name: ?[]const u8, activity_kind: types.ToolActivityKind, ) !?types.ToolActivityKind { if (!admitLifecycleStart(&self.lifecycle_state, id)) return null; if (self.lifecycle_state.recordPtr(id)) |record| { if (record.phase == .terminal) { traceLateLifecycleEvent(id, "provisional"); return null; } const name = tool_name orelse return null; var detail_start = try self.prepareToolDetailStart( alloc, record.entry_id, id, name, activity_kind, null, ); defer detail_start.deinit(alloc); const owned_name = try alloc.dupe(u8, name); errdefer alloc.free(owned_name); const line = try lifecycleStartLine(alloc, name); defer alloc.free(line); if (!try self.replacePinnedLifecycleStatus( alloc, record.entry_id, line, false, )) return error.MissingLifecycleTranscriptEntry; activity_runtime.replaceToolName(alloc, record, owned_name); record.activity_kind = activity_kind; self.commitLifecycleToolDetailStart( alloc, id, record.entry_id, &detail_start, ); self.commitToolPresentationGroup(record.entry_id, id, presentation_group_id); return null; } var detail_start = try self.prepareToolDetailStart( alloc, null, id, tool_name orelse "tool", activity_kind, null, ); defer detail_start.deinit(alloc); const entry_id = try self.createToolActivity( alloc, id, tool_name, activity_kind, .provisional, ); self.commitLifecycleToolDetailStart(alloc, id, entry_id, &detail_start); self.commitToolPresentationGroup(entry_id, id, presentation_group_id); return null; }
fn applyAuthoritativeLifecycle( self: *TranscriptRuntime, alloc: Allocator, id: types.ToolLifecycleId, presentation_group_id: ?types.ToolPresentationGroupId, reconciliation_alias: ?[]const u8, tool_name: []const u8, activity_kind: types.ToolActivityKind, arguments_json: ?[]const u8, place_after_current_transcript: bool, ) !?types.ToolActivityKind { if (!admitLifecycleStart(&self.lifecycle_state, id)) return null;
const alias_id: ?types.ToolLifecycleId = if (reconciliation_alias) |call_id| if (call_id.len > 0) .{ .turn_id = id.turn_id, .call_id = call_id } else null else null; const alias_is_distinct = if (alias_id) |alias| alias.turn_id != id.turn_id or !std.mem.eql(u8, alias.call_id, id.call_id) else false;
if (alias_is_distinct) { const alias = alias_id.?; if (self.lifecycle_state.record(alias)) |alias_record| { if (self.lifecycle_state.record(id) != null) { debug_trace.logf( "ui_activity", "lifecycle identity collision turn_id={d} kind=authoritative_started", .{id.turn_id}, ); return error.LifecycleReconciliationCollision; } if (alias_record.phase == .terminal) { traceLateLifecycleEvent(alias, "authoritative_started"); return null; } var detail_start = try self.prepareToolDetailStart( alloc, alias_record.entry_id, id, tool_name, activity_kind, arguments_json, ); defer detail_start.deinit(alloc); try self.lifecycle_state.ensureRecordCapacity(alloc); const owned_call_id = try alloc.dupe(u8, id.call_id); errdefer alloc.free(owned_call_id); const owned_tool_name = try alloc.dupe(u8, tool_name); errdefer alloc.free(owned_tool_name); const line = try lifecycleStartLine(alloc, tool_name); defer alloc.free(line); if (!try self.replacePinnedLifecycleStatus( alloc, alias_record.entry_id, line, place_after_current_transcript, )) return error.MissingLifecycleTranscriptEntry; self.lifecycle_state.rekeyOwned( alloc, alias, .{ .turn_id = id.turn_id, .call_id = owned_call_id }, owned_tool_name, activity_kind, ); self.commitLifecycleToolDetailStart( alloc, id, alias_record.entry_id, &detail_start, ); self.commitToolPresentationGroup(alias_record.entry_id, id, presentation_group_id); return activity_kind; } }
if (self.lifecycle_state.recordPtr(id)) |record| { if (record.phase == .terminal) { traceLateLifecycleEvent(id, "authoritative_started"); return null; } var detail_start = try self.prepareToolDetailStart( alloc, record.entry_id, id, tool_name, activity_kind, arguments_json, ); defer detail_start.deinit(alloc); const owned_tool_name = try alloc.dupe(u8, tool_name); errdefer alloc.free(owned_tool_name); const line = try lifecycleStartLine(alloc, tool_name); defer alloc.free(line); if (!try self.replacePinnedLifecycleStatus( alloc, record.entry_id, line, place_after_current_transcript, )) return error.MissingLifecycleTranscriptEntry; const first_authoritative = self.lifecycle_state.promoteOwned( alloc, record, owned_tool_name, activity_kind, ); self.commitLifecycleToolDetailStart( alloc, id, record.entry_id, &detail_start, ); self.commitToolPresentationGroup(record.entry_id, id, presentation_group_id); return if (first_authoritative) activity_kind else null; }
var detail_start = try self.prepareToolDetailStart( alloc, null, id, tool_name, activity_kind, arguments_json, ); defer detail_start.deinit(alloc); const entry_id = try self.createToolActivity( alloc, id, tool_name, activity_kind, .authoritative, ); self.commitLifecycleToolDetailStart(alloc, id, entry_id, &detail_start); self.commitToolPresentationGroup(entry_id, id, presentation_group_id); return activity_kind; }
fn applyProgressLifecycle( self: *TranscriptRuntime, alloc: Allocator, id: types.ToolLifecycleId, text: []const u8, ) !?types.ToolActivityKind { const record = self.lifecycle_state.recordPtr(id) orelse { if (self.lifecycle_state.isFenced(id.turn_id)) { traceLateLifecycleEvent(id, "progress"); return null; } debug_trace.logf( "ui_activity", "progress without lifecycle turn_id={d}", .{id.turn_id}, ); return error.UnknownToolLifecycleIdentity; }; if (record.phase == .terminal) { traceLateLifecycleEvent(id, "progress"); return null; } const line = try lifecycleLine(alloc, text); defer alloc.free(line); if (!try self.replacePinnedLifecycleStatus( alloc, record.entry_id, line, false, )) return error.MissingLifecycleTranscriptEntry; return null; }
fn applyTerminalLifecycle( self: *TranscriptRuntime, alloc: Allocator, id: types.ToolLifecycleId, outcome: types.ToolOutcome, result: ?[]const u8, result_memory: ?types.ToolResultMemory, command_artifact_handle: ?[]const u8, ) !?types.ToolActivityKind { const record = self.lifecycle_state.recordPtr(id) orelse { if (self.lifecycle_state.isFenced(id.turn_id)) { traceLateLifecycleEvent(id, "terminal"); return null; } debug_trace.logf( "ui_activity", "terminal without lifecycle turn_id={d}", .{id.turn_id}, ); return error.UnknownToolLifecycleIdentity; }; const line = try lifecycleTerminalLine(alloc, outcome.kind, outcome.summary); defer alloc.free(line); const append_turn_cancellation = outcome.kind != .cancelled and self.lifecycle_state.needsTurnCancellationNoticeAfterTerminal(id); const turn_cancellation_line = if (append_turn_cancellation) try lifecycleTerminalLine(alloc, .cancelled, "Cancelled") else null; defer if (turn_cancellation_line) |value| alloc.free(value); var detail_start: ?PendingToolDetailStart = if (self.toolDetailPtr(record.entry_id) == null) try self.prepareToolDetailStart( alloc, record.entry_id, id, record.tool_name orelse "tool", record.activity_kind, null, ) else null; defer if (detail_start) |*pending| pending.deinit(alloc); var detail_update = try self.prepareToolDetailTerminal( alloc, id, record.activity_kind, result, result_memory, command_artifact_handle, null, @as(u32, @intFromBool(append_turn_cancellation)), ); defer detail_update.deinit(alloc); if (!try transcript_store.replacePinnedToolStatusForTerminalAtomic( self, alloc, record.entry_id, line, @intFromBool(detail_start != null), @intFromBool(detail_update.command_process_entry != null), turn_cancellation_line, )) return error.MissingLifecycleTranscriptEntry; record.phase = .terminal; record.cancellation_presentation = if (outcome.kind == .cancelled) .tool_status else if (record.cancellation_presentation != .none) .replaced else .none; if (append_turn_cancellation) { self.lifecycle_state.markTurnCancellationNoticePresented(id.turn_id); } if (detail_start) |*pending| { self.commitLifecycleToolDetailStart( alloc, id, record.entry_id, pending, ); } self.commitToolDetailTerminal( alloc, record.entry_id, record.activity_kind, outcome.kind, &detail_update, ); return null; }
fn applyTurnFinishedLifecycle( self: *TranscriptRuntime, alloc: Allocator, finished: types.TurnFinished, preserve_normal_buffer_anchor: bool, ) !?types.ToolActivityKind { if (finished.turn_id == 0) return error.InvalidTurnId; if (!self.lifecycle_state.shouldApplyTurnFinished(finished.turn_id)) return null;
const fallbacks = try self.lifecycle_state.collectFallbackRecords( alloc, finished.turn_id, ); defer alloc.free(fallbacks); const updates = try alloc.alloc( transcript_store.LifecycleEntryUpdate, fallbacks.len, ); defer alloc.free(updates); var lines: std.ArrayList([]u8) = .empty; defer { for (lines.items) |line| alloc.free(line); lines.deinit(alloc); } var detail_starts: std.ArrayList(PendingToolDetailStart) = .empty; defer { for (detail_starts.items) |*detail_start| detail_start.deinit(alloc); detail_starts.deinit(alloc); } var missing_detail_count: usize = 0; try lines.ensureTotalCapacity(alloc, fallbacks.len); try detail_starts.ensureTotalCapacity(alloc, fallbacks.len); for (fallbacks, 0..) |fallback, index| { if (self.toolDetailPtr(fallback.record.entry_id) == null) { missing_detail_count += 1; } detail_starts.appendAssumeCapacity(try self.prepareToolDetailStart( alloc, fallback.record.entry_id, fallback.record.id, fallback.record.tool_name orelse "tool", fallback.record.activity_kind, null, )); const line = try lifecycleFallbackLine( alloc, finished.outcome, fallback.was_provisional, ); lines.append(alloc, line) catch |err| { alloc.free(line); return err; }; updates[index] = .{ .entry_id = fallback.record.entry_id, .bytes = line, }; } const fallback_outcome: ?types.ToolOutcomeKind = switch (finished.outcome) { .completed => .completed, .interrupted => .cancelled, .failed => .failed, .paused => null, }; const append_turn_cancellation = finished.outcome == .interrupted and self.lifecycle_state.needsTurnCancellationNoticeAfterTurnFinished( finished.turn_id, ); const turn_cancellation_line = if (append_turn_cancellation) try lifecycleTerminalLine(alloc, .cancelled, "Cancelled") else null; defer if (turn_cancellation_line) |value| alloc.free(value); const replace_result = if (preserve_normal_buffer_anchor) transcript_store.replacePinnedToolStatusesPreservingNormalBufferAnchorAtomic( self, alloc, updates, missing_detail_count, turn_cancellation_line, ) else transcript_store.replacePinnedToolStatusesAtomic( self, alloc, updates, missing_detail_count, turn_cancellation_line, ); replace_result catch |err| { traceLifecycleFailure("turn_finished", finished.turn_id, err); return err; }; for (fallbacks, detail_starts.items) |fallback, *detail_start| { self.commitToolDetailStart(alloc, fallback.record.entry_id, detail_start); const detail = self.toolDetailPtr(fallback.record.entry_id).?; detail.outcome = fallback_outcome; fallback.record.cancellation_presentation = if (fallback_outcome == .cancelled) .tool_status else .none; detail.fallback_disposition = lifecycleFallbackDisposition( finished.outcome, fallback.was_provisional, ); } if (finished.outcome == .interrupted) { self.lifecycle_state.markTurnInterrupted(finished.turn_id); if (append_turn_cancellation) { self.lifecycle_state.markTurnCancellationNoticePresented(finished.turn_id); } } if (finished.outcome == .paused) { const entry_ids = try self.lifecycle_state.pausedTurnEntryIds( alloc, finished.turn_id, ); defer alloc.free(entry_ids); transcript_store.clearLifecyclePinsAtomic(self, alloc, entry_ids) catch |err| { traceLifecycleFailure("turn_paused", finished.turn_id, err); return err; }; self.lifecycle_state.releasePausedTurn(alloc, finished.turn_id); } else { self.lifecycle_state.commitTurnFinished(finished.turn_id, fallbacks); } return null; }
fn createToolActivity( self: *TranscriptRuntime, alloc: Allocator, id: types.ToolLifecycleId, tool_name: ?[]const u8, activity_kind: types.ToolActivityKind, phase: activity_runtime.ToolLifecyclePhase, ) !u32 { try self.lifecycle_state.ensureRecordCapacity(alloc); const owned_call_id = try alloc.dupe(u8, id.call_id); errdefer alloc.free(owned_call_id); const owned_tool_name = if (tool_name) |name| try alloc.dupe(u8, name) else null; errdefer if (owned_tool_name) |name| alloc.free(name); const line = try lifecycleStartLine(alloc, tool_name); defer alloc.free(line); const entry_id = try transcript_store.appendPinnedToolStatusAtomic( self, alloc, line, ); self.lifecycle_state.putOwnedRecord( .{ .turn_id = id.turn_id, .call_id = owned_call_id }, owned_tool_name, activity_kind, entry_id, phase, ); return entry_id; }
const ToolDetailSearch = struct { index: usize, found: bool, };
fn toolDetailSearch(self: *const TranscriptRuntime, entry_id: u32) ToolDetailSearch { var low: usize = 0; var high = self.tool_details.items.len; while (low < high) { const middle = low + (high - low) / 2; if (self.tool_details.items[middle].entry_id < entry_id) { low = middle + 1; } else { high = middle; } } return .{ .index = low, .found = low < self.tool_details.items.len and self.tool_details.items[low].entry_id == entry_id, }; }
pub fn toolDetailForEntry(self: *const TranscriptRuntime, entry_id: u32) ?*const ToolDetailRecord { const search = self.toolDetailSearch(entry_id); if (!search.found) return null; return &self.tool_details.items[search.index]; }
pub fn setToolCommandMetadata( self: *TranscriptRuntime, alloc: Allocator, id: types.ToolLifecycleId, display: []const u8, action_label: []const u8, ) !void { const record = self.lifecycle_state.record(id) orelse return error.UnknownToolLifecycleIdentity; try self.setToolCommandMetadataForEntry( alloc, record.entry_id, display, action_label, ); }
pub fn setToolCommandMetadataForEntry( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, display: []const u8, action_label: []const u8, ) !void { const detail = self.toolDetailPtr(entry_id) orelse return error.MissingToolDetail; const owned_display = try alloc.dupe(u8, display); errdefer alloc.free(owned_display); const owned_label = try alloc.dupe(u8, action_label); if (detail.command_display) |previous| alloc.free(previous); if (detail.command_action_label) |previous| alloc.free(previous); detail.command_display = owned_display; detail.command_action_label = owned_label; self.markTranscriptContentDirtyFrom(entry_id); }
fn toolDetailPtr(self: *TranscriptRuntime, entry_id: u32) ?*ToolDetailRecord { const search = self.toolDetailSearch(entry_id); if (!search.found) return null; return &self.tool_details.items[search.index]; }
pub fn attachHistoricalToolDetail( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, call: types.ToolCall, activity_kind: types.ToolActivityKind, result: types.PersistedToolResult, ) !void { return self.attachHistoricalToolDetailResolved( alloc, entry_id, call, activity_kind, result, null, null, ); }
pub fn attachHistoricalToolDetailWithLifecycle( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, call: types.ToolCall, activity_kind: types.ToolActivityKind, result: types.PersistedToolResult, lifecycle_id: ?types.ToolLifecycleId, ) !void { return self.attachHistoricalToolDetailResolved( alloc, entry_id, call, activity_kind, result, lifecycle_id, null, ); }
pub fn attachHistoricalToolDetailAfterCommandOutput( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, call: types.ToolCall, activity_kind: types.ToolActivityKind, result: types.PersistedToolResult, ) !void { try self.attachHistoricalToolDetailResolved( alloc, entry_id, call, activity_kind, result, null, self.latestCommandOutputEntryId(), ); if (try command_output_runtime.syncCommandOutputBlockEntries(self, alloc)) { try self.rebuildTranscriptCacheAfterStructuredRewrite( alloc, "historical command process presentation", ); self.recomputeCursorFromTranscript(); self.markTranscriptContentDirty(); } }
pub fn attachHistoricalToolDetailAfterCommandOutputDetached( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, call: types.ToolCall, activity_kind: types.ToolActivityKind, result: types.PersistedToolResult, ) !void { try self.attachHistoricalToolDetailResolved( alloc, entry_id, call, activity_kind, result, null, self.latestCommandOutputEntryId(), ); }
fn attachHistoricalToolDetailResolved( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, call: types.ToolCall, activity_kind: types.ToolActivityKind, result: types.PersistedToolResult, lifecycle_id: ?types.ToolLifecycleId, command_output_entry_id: ?u32, ) !void { const context_deferred = types.isContextDeferredToolResult(result); const deferred = types.isDeferredToolResult(result); const permission_denied = tool_result_errors.toolPermissionDenialReason(result.output) != null; const shell_command = !deferred and !permission_denied and activity_kind == .command and std.mem.eql(u8, call.name, "shell"); const projected_shell_result = if (shell_command) try managed_execution.modelOutputDelta(alloc, result.output) else null; defer if (projected_shell_result) |value| alloc.free(value); const presentation_result = projected_shell_result orelse result.output; const command_artifact_handle = if (!deferred and !permission_denied and activity_kind == .command and !shell_command) command_output_runtime.commandArtifactHandleFromResult(result.output) else null; try self.upsertToolDetailStart(alloc, entry_id, lifecycle_id, call.name, activity_kind, call.arguments_json); try self.updateToolDetailTerminal( alloc, entry_id, lifecycle_id, call.name, activity_kind, if (context_deferred) .deferred else if (deferred or permission_denied) .denied else if (result.terminal_action_presentation) |presentation| presentation.outcomeKind() else if (result.status == .success or (activity_kind == .command and result.command_process_presentation != null)) .completed else .failed, if (deferred or permission_denied) null else presentation_result, if (deferred or permission_denied) null else .{ .output_handle = if (shell_command) null else result.output_handle, .preview = projected_shell_result orelse result.preview, .output_bytes = result.output_bytes, .stored_output_bytes = result.stored_output_bytes, .truncated = result.truncated, .command_output_replay = result.command_output_replay, .command_process_presentation = result.command_process_presentation, .terminal_action_presentation = result.terminal_action_presentation, }, command_artifact_handle, if (deferred or permission_denied) null else command_output_entry_id, ); if (self.toolDetailPtr(entry_id)) |detail| { detail.created_at_ms = result.created_at_ms; detail.origin = .recorded; } }
pub fn attachHistoricalToolCallWithoutResult( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, call: types.ToolCall, ) !void { try self.upsertToolDetailStart(alloc, entry_id, null, call.name, null, call.arguments_json); if (self.toolDetailPtr(entry_id)) |detail| { detail.outcome = .failed; detail.origin = .recorded; } }
pub fn attachHistoricalCommandOutput( self: *TranscriptRuntime, _: Allocator, entry_id: u32, ) void { const detail = self.toolDetailPtr(entry_id) orelse return; const command_output_entry_id = self.latestCommandOutputEntryId() orelse return; detail.command_output_entry_id = command_output_entry_id; }
pub fn attachHistoricalCancelledCommandDetail( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, call: types.ToolCall, command_artifact_handle: ?[]const u8, replayed_output: bool, ) !void { try self.upsertToolDetailStart(alloc, entry_id, null, call.name, .command, call.arguments_json); try self.updateToolDetailTerminal( alloc, entry_id, null, call.name, .command, .cancelled, null, null, command_artifact_handle, if (replayed_output) self.latestCommandOutputEntryId() else null, ); if (self.toolDetailPtr(entry_id)) |detail| detail.origin = .recorded; }
fn upsertToolDetailStart( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, lifecycle_id: ?types.ToolLifecycleId, tool_name: []const u8, activity_kind: ?types.ToolActivityKind, arguments_json: ?[]const u8, ) !void { var detail_start = try self.prepareToolDetailStart( alloc, entry_id, lifecycle_id, tool_name, activity_kind, arguments_json, ); defer detail_start.deinit(alloc); self.commitToolDetailStart(alloc, entry_id, &detail_start); }
fn prepareToolDetailStart( self: *TranscriptRuntime, alloc: Allocator, entry_id: ?u32, lifecycle_id: ?types.ToolLifecycleId, tool_name: []const u8, activity_kind: ?types.ToolActivityKind, arguments_json: ?[]const u8, ) !PendingToolDetailStart { const existing = if (entry_id) |id| self.toolDetailPtr(id) else null; if (existing == null) try self.tool_details.ensureUnusedCapacity(alloc, 1);
var pending: PendingToolDetailStart = .{}; errdefer pending.deinit(alloc); pending.tool_name = try alloc.dupe(u8, tool_name); pending.activity_kind = activity_kind; if (arguments_json) |value| { pending.arguments_json = try alloc.dupe(u8, value); pending.captured_command = try captured_command.isToolCall(alloc, tool_name, value); } else if (std.mem.eql(u8, tool_name, "run_command")) { pending.captured_command = true; } if (lifecycle_id) |id| { if (existing == null or !command_output_runtime.sameLifecycleId(existing.?.lifecycle_id, id)) { const owned_call_id = try alloc.dupe(u8, id.call_id); pending.lifecycle_id = .{ .turn_id = id.turn_id, .call_id = owned_call_id, }; pending.replace_lifecycle = true; } } return pending; }
fn commitToolDetailStart( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, pending: *PendingToolDetailStart, ) void { const search = self.toolDetailSearch(entry_id); if (search.found) { const detail = &self.tool_details.items[search.index]; alloc.free(detail.tool_name); detail.tool_name = pending.tool_name.?; pending.tool_name = null; if (pending.captured_command) |value| detail.captured_command = value; if (pending.activity_kind) |kind| detail.activity_kind = kind; if (pending.arguments_json) |arguments_json| { if (detail.arguments_json) |old| alloc.free(old); detail.arguments_json = arguments_json; pending.arguments_json = null; } if (pending.replace_lifecycle) { if (detail.lifecycle_id) |previous| alloc.free(@constCast(previous.call_id)); detail.lifecycle_id = pending.lifecycle_id; pending.lifecycle_id = null; } self.markTranscriptContentDirtyFrom(entry_id); return; }
const detail = ToolDetailRecord{ .entry_id = entry_id, .tool_name = pending.tool_name.?, .captured_command = pending.captured_command orelse false, .activity_kind = pending.activity_kind, .arguments_json = pending.arguments_json, .lifecycle_id = pending.lifecycle_id, }; if (search.index == self.tool_details.items.len) { self.tool_details.appendAssumeCapacity(detail); } else { self.tool_details.insertAssumeCapacity(search.index, detail); } pending.tool_name = null; pending.arguments_json = null; pending.lifecycle_id = null; self.markTranscriptContentDirtyFrom(entry_id); }
fn commitLifecycleToolDetailStart( self: *TranscriptRuntime, alloc: Allocator, lifecycle_id: types.ToolLifecycleId, entry_id: u32, pending: *PendingToolDetailStart, ) void { const classified = pending.captured_command; self.commitToolDetailStart(alloc, entry_id, pending); if (classified) |captured| { if (self.lifecycle_state.recordPtr(lifecycle_id)) |record| { record.captured_command = captured; } } }
fn commitToolPresentationGroup( self: *TranscriptRuntime, entry_id: u32, lifecycle_id: types.ToolLifecycleId, presentation_group_id: ?types.ToolPresentationGroupId, ) void { const incoming = presentation_group_id orelse return; if (incoming.turn_id == 0 or incoming.anchor_step_id == 0 or incoming.turn_id != lifecycle_id.turn_id) { debug_trace.logf( "ui_activity", "invalid tool presentation group ignored turn_id={d} group_turn_id={d} anchor_step_id={d}", .{ lifecycle_id.turn_id, incoming.turn_id, incoming.anchor_step_id }, ); return; } const detail = self.toolDetailPtr(entry_id) orelse return; if (detail.presentation_group_id) |existing| { if (existing.turn_id != incoming.turn_id or existing.anchor_step_id != incoming.anchor_step_id) { debug_trace.logf( "ui_activity", "tool presentation group change ignored turn_id={d} existing_anchor_step_id={d} incoming_anchor_step_id={d}", .{ lifecycle_id.turn_id, existing.anchor_step_id, incoming.anchor_step_id, }, ); } return; } detail.presentation_group_id = incoming; self.markTranscriptContentDirtyFrom(entry_id); }
fn updateToolDetailTerminal( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, lifecycle_id: ?types.ToolLifecycleId, tool_name: []const u8, activity_kind: types.ToolActivityKind, outcome: types.ToolOutcomeKind, result: ?[]const u8, result_memory: ?types.ToolResultMemory, command_artifact_handle: ?[]const u8, command_output_entry_id_override: ?u32, ) !void { var pending = try self.prepareToolDetailTerminal( alloc, lifecycle_id, activity_kind, result, result_memory, command_artifact_handle, command_output_entry_id_override, 0, ); defer pending.deinit(alloc); if (self.toolDetailPtr(entry_id) == null) { try self.upsertToolDetailStart(alloc, entry_id, lifecycle_id, tool_name, activity_kind, null); } self.commitToolDetailTerminal(alloc, entry_id, activity_kind, outcome, &pending); }
fn prepareToolDetailTerminal( self: *TranscriptRuntime, alloc: Allocator, lifecycle_id: ?types.ToolLifecycleId, activity_kind: types.ToolActivityKind, result: ?[]const u8, result_memory: ?types.ToolResultMemory, command_artifact_handle: ?[]const u8, command_output_entry_id_override: ?u32, command_process_entry_offset: u32, ) !PendingToolDetailTerminal { var pending: PendingToolDetailTerminal = .{}; errdefer pending.deinit(alloc); if (result) |value| pending.result = try alloc.dupe(u8, value); const result_handle = if (result_memory) |memory| memory.output_handle else null; if (result_handle) |handle| pending.result_handle = try alloc.dupe(u8, handle); if (command_artifact_handle) |handle| { pending.command_artifact_handle = try alloc.dupe(u8, handle); } if (result_memory) |memory| { if (memory.command_output_replay) |replay| { pending.command_output_replay = try types.dupeCommandOutputReplay(alloc, replay); } } pending.command_process_presentation = if (result_memory) |memory| memory.command_process_presentation else null; pending.command_output_entry_id = if (activity_kind == .command) command_output_entry_id_override orelse self.commandOutputEntryIdForLifecycle(lifecycle_id) else null; if (activity_kind == .command and pending.command_process_presentation != null and pending.command_output_entry_id == null) { pending.command_process_entry = try self.prepareCommandProcessPresentationEntry( alloc, lifecycle_id, command_process_entry_offset, ); pending.command_output_entry_id = pending.command_process_entry.?.entry_id; } return pending; }
fn commitToolDetailTerminal( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, activity_kind: types.ToolActivityKind, outcome: types.ToolOutcomeKind, pending: *PendingToolDetailTerminal, ) void { if (pending.command_process_entry) |*entry| { self.commitCommandProcessPresentationEntry(entry); pending.command_process_entry = null; } const detail = self.toolDetailPtr(entry_id).?; detail.activity_kind = activity_kind; detail.outcome = outcome; detail.fallback_disposition = null; if (pending.result) |owned| { if (detail.result) |old| alloc.free(old); detail.result = owned; pending.result = null; } if (pending.result_handle) |owned| { if (detail.result_handle) |old| alloc.free(old); detail.result_handle = owned; pending.result_handle = null; } if (pending.command_artifact_handle) |owned| { if (detail.command_artifact_handle) |old| alloc.free(old); detail.command_artifact_handle = owned; pending.command_artifact_handle = null; } if (pending.command_output_replay) |owned| { if (detail.command_output_replay) |old| { types.freeCommandOutputReplay(alloc, old); } detail.command_output_replay = owned; pending.command_output_replay = null; } detail.command_process_presentation = pending.command_process_presentation; if (activity_kind == .command) { detail.command_output_entry_id = pending.command_output_entry_id; } self.markTranscriptContentDirtyFrom(entry_id); }
pub fn hasStoredResultDetails(self: *const TranscriptRuntime) bool { for (self.tool_details.items) |detail| { if (detail.result_handle != null or detail.command_artifact_handle != null or detail.command_output_replay != null) return true; } return false; }
fn commandOutputEntryIdForLifecycle( self: *const TranscriptRuntime, lifecycle_id: ?types.ToolLifecycleId, ) ?u32 { const id = lifecycle_id orelse return null; for (self.command_output_blocks.items) |block| { if (!command_output_runtime.sameLifecycleId(block.lifecycle_id, id)) continue; if (block.entry_id) |entry_id| return entry_id; if (block.live_entry_ids.items.len > 0) return block.live_entry_ids.items[0]; if (block.source_entry_ids.items.len > 0) return block.source_entry_ids.items[0]; for (block.lines.items) |line| { if (line.entry_id) |entry_id| return entry_id; } return null; } return null; }
fn prepareCommandProcessPresentationEntry( self: *TranscriptRuntime, alloc: Allocator, lifecycle_id: ?types.ToolLifecycleId, entry_id_offset: u32, ) !PendingCommandProcessEntry { var pending: PendingCommandProcessEntry = .{ .entry_id = self.next_entry_id +% entry_id_offset, }; errdefer pending.deinit(alloc); pending.bytes = try alloc.alloc(u8, 0);
const line_text = try alloc.alloc(u8, 0); var line_text_owned = true; errdefer if (line_text_owned) alloc.free(line_text); pending.block = .{ .entry_id = pending.entry_id }; const block = &pending.block.?; if (lifecycle_id) |id| { const call_id = try alloc.dupe(u8, id.call_id); block.lifecycle_id = .{ .turn_id = id.turn_id, .call_id = call_id, }; } try block.lines.append(alloc, .{ .stream = .stdout, .text = line_text, .entry_id = pending.entry_id, .visible = false, }); line_text_owned = false; try block.source_entry_ids.append(alloc, pending.entry_id); try self.entries.ensureUnusedCapacity(alloc, 1); try self.command_output_blocks.ensureUnusedCapacity(alloc, 1); return pending; }
fn commitCommandProcessPresentationEntry( self: *TranscriptRuntime, pending: *PendingCommandProcessEntry, ) void { std.debug.assert(self.next_entry_id == pending.entry_id); self.next_entry_id +%= 1; self.entries.appendAssumeCapacity(.{ .raw_bytes = .{ .id = pending.entry_id, .created_at_ms = io_mod.milliTimestamp(), .bytes = pending.bytes.?, .class = .command_output, } }); pending.bytes = null; self.command_output_blocks.appendAssumeCapacity(pending.block.?); pending.block = null; self.markTranscriptContentDirtyFrom(pending.entry_id); }
fn latestCommandOutputEntryId(self: *const TranscriptRuntime) ?u32 { var index = self.command_output_blocks.items.len; while (index > 0) { index -= 1; if (self.command_output_blocks.items[index].entry_id) |entry_id| return entry_id; } return null; }
pub fn detailEntryIdForCommandOutputSource( self: *const TranscriptRuntime, source_entry_id: u32, ) ?u32 { for (self.command_output_blocks.items) |block| { var owns_source = block.entry_id == source_entry_id; if (!owns_source) { for (block.source_entry_ids.items) |source_id| { if (source_id == source_entry_id) { owns_source = true; break; } } } if (!owns_source) { for (block.live_entry_ids.items) |live_entry_id| { if (live_entry_id == source_entry_id) { owns_source = true; break; } } } if (!owns_source) continue; const lifecycle_id = block.lifecycle_id orelse return null; for (self.tool_details.items) |detail| { if (command_output_runtime.sameLifecycleId( detail.lifecycle_id, lifecycle_id, )) return detail.entry_id; } return null; } return null; }
fn clearToolDetails(self: *TranscriptRuntime, alloc: Allocator) void { for (self.tool_details.items) |*detail| detail.deinit(alloc); self.tool_details.clearRetainingCapacity(); }
fn clearFullDetailRecords(self: *TranscriptRuntime, alloc: Allocator) void { for (self.full_detail_records.items) |*record| record.deinit(alloc); self.full_detail_records.clearRetainingCapacity(); }
/// Appends one full-only detail record (session assembly, network, /// recovery) and marks the full transcript content dirty so an open /// ctrl+o view reloads it. Never touches the entry store. pub fn appendFullDetailRecord( self: *TranscriptRuntime, alloc: Allocator, notice: types.SemanticNotice, ) !void { const owned = try types.dupeSemanticNotice(alloc, notice); errdefer types.freeSemanticNotice(alloc, owned); try self.full_detail_records.append(alloc, .{ .notice = owned, .created_at_ms = io_mod.milliTimestamp(), }); self.markTranscriptContentDirty(); }
pub fn pruneToolDetailsForRetainedEntries( self: *TranscriptRuntime, alloc: Allocator, retained_entry_ids: *const transcript_store.EntryIdSet, ) void { var write_index: usize = 0; for (self.tool_details.items, 0..) |detail, read_index| { if (!retained_entry_ids.contains(detail.entry_id)) { var removed = detail; removed.deinit(alloc); continue; } if (write_index != read_index) { self.tool_details.items[write_index] = detail; } write_index += 1; } self.tool_details.items.len = write_index; }
pub fn clearFullTranscriptDetails(self: *TranscriptRuntime, alloc: Allocator) void { self.compact_transcript_source_cache.deinit(alloc); self.resetFullTranscriptPageNavigation(); if (self.full_transcript_window_load.busy()) { self.full_transcript_installed_page_retired = self.full_transcript_installed_page != null; } else { self.discardInstalledFullTranscriptPage(); } self.clearToolDetails(alloc); self.clearFullDetailRecords(alloc); self.full_transcript = self.full_transcript.closed(); }
fn discardInstalledFullTranscriptPage(self: *TranscriptRuntime) void { if (self.full_transcript_installed_page) |*page| page.deinit(); self.full_transcript_installed_page = null; self.full_transcript_installed_page_retired = false; }
pub fn resetCommandOutputDisplay(self: *TranscriptRuntime, alloc: Allocator, reason: []const u8) void { return command_output_runtime.resetCommandOutputDisplay(self, alloc, reason); }
pub fn retainedStructuredBytesForCommandOutput( self: *const TranscriptRuntime, ) usize { return transcript_store.retainedStructuredBytes(self); }
/// Append a raw-bytes entry. Takes ownership of `bytes` — the slice /// must have been allocated by `alloc` and must not be freed by the /// caller; the entry is freed on `clearTranscript` or `deinit`. /// Returns the stamped entry id. pub fn appendRawBytesEntry(self: *TranscriptRuntime, alloc: Allocator, bytes: []const u8) !u32 { return transcript_store.appendRawBytesEntry(self, alloc, bytes); }
pub fn appendRawBytesEntryClassified( self: *TranscriptRuntime, alloc: Allocator, bytes: []const u8, class: RawEntryClass, ) !u32 { return transcript_store.appendRawBytesEntryClassified(self, alloc, bytes, class); }
pub fn appendRawBytesEntryClassifiedAt( self: *TranscriptRuntime, alloc: Allocator, bytes: []const u8, class: RawEntryClass, created_at_ms: i64, ) !u32 { return transcript_store.appendRawBytesEntryClassifiedAt( self, alloc, bytes, class, created_at_ms, ); }
pub fn insertRawBytesEntryClassifiedAfter( self: *TranscriptRuntime, alloc: Allocator, after_entry_id: u32, bytes: []const u8, class: RawEntryClass, ) !?u32 { return transcript_store.insertRawBytesEntryClassifiedAfter(self, alloc, after_entry_id, bytes, class); }
pub fn insertRawBytesEntryClassifiedAt( self: *TranscriptRuntime, alloc: Allocator, index: usize, bytes: []const u8, class: RawEntryClass, ) !u32 { return transcript_store.insertRawBytesEntryClassifiedAt(self, alloc, index, bytes, class); }
pub fn rowForEntryId(self: *TranscriptRuntime, entry_id: u32) ?u16 { return transcript_viewport_runtime.rowForEntryId(self, entry_id); }
pub fn toolStatusEntryLabel(self: *const TranscriptRuntime, entry_id: u32) ?[]const u8 { return transcript_store.toolStatusEntryLabel(self, entry_id); }
pub fn transientAssistantGapRows(self: *const TranscriptRuntime) u16 { return transcript_viewport_runtime.transientAssistantGapRows(self); }
pub fn needsIdleFooterBottomReservation(self: *const TranscriptRuntime) bool { return transcript_viewport_runtime.needsIdleFooterBottomReservation(self); }
pub fn idleFooterGapOffset(self: *const TranscriptRuntime) u16 { return transcript_viewport_runtime.idleFooterGapOffset(self); }
pub fn bannerFooterGapOffset(self: *const TranscriptRuntime) u16 { return transcript_viewport_runtime.bannerFooterGapOffset(self); }
pub fn preservedBottomReservationRows(self: *const TranscriptRuntime) u16 { return transcript_viewport_runtime.preservedBottomReservationRows(self); }
pub fn trimTrailingBlankLines(self: *TranscriptRuntime) void { return transcript_store.trimTrailingBlankLines(self); }
pub fn appendRawTranscriptEntry(self: *TranscriptRuntime, alloc: Allocator, text: []const u8) !u32 { return transcript_store.appendRawTranscriptEntry(self, alloc, text); }
pub fn appendRawTranscriptEntryClassified( self: *TranscriptRuntime, alloc: Allocator, text: []const u8, class: RawEntryClass, ) !u32 { return transcript_store.appendRawTranscriptEntryClassified(self, alloc, text, class); }
pub fn appendTurnSummaryEntry(self: *TranscriptRuntime, alloc: Allocator, summary: types.TurnSummary) !u32 { var line_buf: [128]u8 = undefined; const line = formatTurnSummaryLine(&line_buf, summary); const entry = try std.fmt.allocPrint(alloc, "{s}{s}{s}\n", .{ ui_render.dim_style, line, ui_render.reset_style }); errdefer alloc.free(entry); const entry_id = try transcript_store.appendRawBytesEntryClassifiedAt( self, alloc, entry, .turn_summary, if (summary.completed_at_ms > 0) summary.completed_at_ms else io_mod.milliTimestamp(), ); self.markTranscriptContentDirtyFrom(entry_id); if (self.worker_status.clear_recovered_route()) self.render_requests.request(.footer); return entry_id; }
/// Swap the bytes of the `raw_bytes` entry with `entry_id` to a dup of /// `new_bytes` and regenerate the transcript byte buffer. Returns false /// when no such entry exists (e.g. cleared by `clearTranscript`). pub fn updateRawBytesEntry( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, new_bytes: []const u8, ) !bool { return transcript_store.updateRawBytesEntry(self, alloc, entry_id, new_bytes); }
pub fn setRawEntryClass(self: *TranscriptRuntime, entry_id: u32, class: RawEntryClass) bool { return transcript_store.setRawEntryClass(self, entry_id, class); }
pub fn rawEntryClass(self: *const TranscriptRuntime, entry_id: u32) ?RawEntryClass { return transcript_store.rawEntryClass(self, entry_id); }
/// Renders and records one borrowed user turn as a single transcript /// mutation. The caller retains ownership of `user` and `skill_tokens` /// on success and error. pub fn writeUserPromptCard( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, user: types.UserTurn, has_prior_turns: bool, skill_tokens: []const input_visual_layout.SkillTokenSpan, ) !u32 { return transcript_store.writeUserPromptCard( self, alloc, metrics, user, has_prior_turns, skill_tokens, ); }
/// Consumes `turn` regardless of outcome. All owned slices must use `alloc`; /// the entry owns them on success and this method frees them on failure. /// Returns the stamped entry id. pub fn appendUserTurnOwned(self: *TranscriptRuntime, alloc: Allocator, turn: types.UserTurn) !u32 { return transcript_store.appendUserTurnOwned(self, alloc, turn); }
pub fn appendUserTurnOwnedWithSkillTokens( self: *TranscriptRuntime, alloc: Allocator, turn: types.UserTurn, skill_tokens: []const input_visual_layout.SkillTokenSpan, ) !u32 { return transcript_store.appendUserTurnOwnedWithSkillTokens(self, alloc, turn, skill_tokens); }
/// Open a new assistant-turn entry and return its id. Callers that /// need to extend the segments text should re-resolve the pointer /// with `lookupAssistantSegments(id)` immediately before each use; /// a raw segments pointer held across any subsequent `entries` /// append is invalidated by ArrayList realloc. pub fn appendAssistantTurnEntry(self: *TranscriptRuntime, alloc: Allocator) !u32 { return transcript_store.appendAssistantTurnEntry(self, alloc); }
/// Takes ownership of `table` only when it returns successfully. pub fn appendAssistantTableOwned( self: *TranscriptRuntime, alloc: Allocator, table: @import("../../core/agent/assistant_presentation.zig").TablePayload, ) !u32 { return transcript_store.appendAssistantTableOwned(self, alloc, table); }
/// Takes ownership of `block` only when it returns successfully. pub fn appendAssistantCodeBlockOwned( self: *TranscriptRuntime, alloc: Allocator, block: @import("../../core/agent/assistant_presentation.zig").CodeBlockPayload, ) !u32 { return transcript_store.appendAssistantCodeBlockOwned(self, alloc, block); }
pub fn appendAssistantThematicRule(self: *TranscriptRuntime, alloc: Allocator) !u32 { return transcript_store.appendAssistantThematicRule(self, alloc); }
/// Walk `entries` and return a pointer to the segments of the /// assistant-turn entry with `id`, or null if no such entry exists /// (including when `id` belongs to a `raw_bytes` or `user_turn` /// entry). The returned pointer is stable only until the next /// `entries` append; resolve immediately before use. pub fn lookupAssistantSegments(self: *TranscriptRuntime, id: u32) ?*AssistantTurnSegments { return transcript_store.lookupAssistantSegments(self, id); }
/// Return a pointer to the trailing entry's segments if that entry /// is an assistant turn; otherwise null. Used by the pacer to /// append to the in-progress response instead of opening a new /// turn when mid-stream. pub fn tailAssistantSegments(self: *TranscriptRuntime) ?*AssistantTurnSegments { return transcript_store.tailAssistantSegments(self); }
/// Appends paced text to the trailing assistant entry, creating one if needed, /// and mirrors it into the transcript buffer. Using `writeTranscript` here /// would also create raw entries and double-count content during reflow. pub fn streamAssistantChunk( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, text: []const u8, ) !u32 { const transcript_was_pending = self.render_requests.hasReason(.transcript); const entry_id = try transcript_store.streamAssistantChunk(self, alloc, metrics, text); if (!transcript_was_pending and self.nativeHistoryActive() and std.mem.findScalar(u8, text, '\n') == null) { self.render_requests.clearReason(.transcript); debug_trace.logf( "scroll", "assistant_partial_paint_deferred bytes={d} entry_id={d}", .{ text.len, entry_id }, ); } return entry_id; }
pub fn retintEntriesForTheme( self: *TranscriptRuntime, alloc: Allocator, from_light: bool, to_light: bool, ) !void { return transcript_store.retintEntriesForTheme(self, alloc, from_light, to_light); }
pub fn setTranscriptPresentationDepth( self: *TranscriptRuntime, alloc: Allocator, depth: transcript_presentation.Depth, ) !bool { const current = self.full_transcript.depth; if (current == depth) { if (depth == .inline_mode) { self.full_transcript_restore_open_pending = false; } return false; } const restored_open = current == .inline_mode and depth != .inline_mode and self.full_transcript_restore_open_pending; if (current == .inline_mode and depth != .inline_mode) { self.full_transcript_prepared_page_visible = false; self.full_transcript_open_content_revision = self.full_transcript_content_revision; self.full_transcript_open_cols = self.layout.cols; self.full_transcript_open_rows = self.layout.rows; if (!restored_open) try self.captureFullTranscriptAnchor(alloc); } self.full_transcript = self.full_transcript.with_depth(depth); if (depth != .inline_mode) self.full_transcript_restore_open_pending = false; if (depth == .inline_mode) { self.full_transcript_restore_open_pending = false; self.full_transcript_open_content_revision = null; self.full_transcript_open_cols = 0; self.full_transcript_open_rows = 0; self.resetFullTranscriptPageNavigation(); } self.markTranscriptDirty(); return true; }
pub fn fullTranscriptPrimaryRestore(self: *const TranscriptRuntime) FullTranscriptPrimaryRestore { const open_revision = self.full_transcript_open_content_revision orelse return .changed; const same_geometry = self.full_transcript_open_cols == self.layout.cols and self.full_transcript_open_rows == self.layout.rows; const needs_repair = !same_geometry or self.terminal_reset_pending or self.resize_history_row_delta != null; if (needs_repair) return .resized; return if (open_revision != self.full_transcript_content_revision) .changed else .exact; }
pub fn retainRestoredPrimaryTranscript(self: *TranscriptRuntime) void { self.transcript_band_dirty = false; self.render_requests.clearReason(.transcript); self.render_requests.request(.footer); debug_trace.logf( "full_transcript_cache", "close reused restored primary transcript revision={d}", .{self.full_transcript_content_revision}, ); }
pub fn repaintRestoredPrimaryTranscriptAfterResize(self: *TranscriptRuntime) void { self.terminal_reset_pending = true; self.resize_history_row_delta = null; self.pending_resize_observation = null; self.render_requests.acknowledgeSettledResizeCommit(); self.invalidateTranscriptAnchor("full transcript restored resized primary"); // A visible-band repaint can discard rows displaced by terminal reflow. // Rebuild them through normal resize recovery after restoring the primary screen. if (self.layout.rows > 0) { self.render_requests.requestInvalidation(.{ .reason = .external_clear, .top = if (self.owned_top_row == 0 or self.owned_top_row > self.layout.rows) 1 else self.owned_top_row, .bottom = self.layout.rows, }); } debug_trace.logf( "full_transcript_cache", "close repaints restored resized primary revision={d} layout={d}x{d}", .{ self.full_transcript_content_revision, self.layout.cols, self.layout.rows }, ); }
fn captureFullTranscriptAnchor(self: *TranscriptRuntime, alloc: Allocator) !void { _ = alloc; self.full_transcript = self.full_transcript.clear_anchor(); const selection = self.last_viewport_selection orelse return; const provenance = self.committedRowProvenance(); var source: ?transcript_blocks.LineProvenance = null; for (provenance) |row| { if (row.row < selection.top_row) continue; source = row.source; break; } const source_entry_id = switch (source orelse return) { .entry => |entry| entry.entry_id, .folded_command_output => |output| output.entry_id, else => null, }; const anchor_entry_id = if (source_entry_id) |entry_id| self.detailEntryIdForCommandOutputSource(entry_id) orelse entry_id else null; self.full_transcript = self.full_transcript.capture_anchor(anchor_entry_id); }
pub fn fullTranscriptActive(self: *const TranscriptRuntime) bool { return self.full_transcript.depth.active(); }
pub fn fullTranscriptFocusedWorkActive(self: *const TranscriptRuntime) bool { return self.full_transcript_window_load.busy() or (self.full_transcript_page_load.busy() and self.full_transcript_open_request != null); }
pub fn transcriptPresentationDepth( self: *const TranscriptRuntime, ) transcript_presentation.Depth { return self.full_transcript.depth; }
pub const FullTranscriptViewportSnapshot = struct { presentation: transcript_presentation.Snapshot, page_anchor: full_transcript_page.Anchor, };
pub fn snapshotFullTranscriptViewport( self: *const TranscriptRuntime, ) FullTranscriptViewportSnapshot { return .{ .presentation = self.full_transcript.snapshot(), .page_anchor = self.full_transcript_page_anchor, }; }
pub fn restoreFullTranscriptViewport( self: *TranscriptRuntime, snapshot: FullTranscriptViewportSnapshot, ) void { self.full_transcript = .from_snapshot(snapshot.presentation); self.full_transcript_page_anchor = snapshot.page_anchor; self.markTranscriptDirty(); }
pub fn requestTailViewport( self: *TranscriptRuntime, request: viewport_selection.TailViewportPolicy, ) void { const changed = if (self.tail_viewport_request) |current| !std.meta.eql(current, request) else true; self.tail_viewport_request = request; self.tail_viewport_resolution = null; const has_committed_anchor = switch (self.transcript_commit_state) { .invalid => false, .stable, .recovering => true, }; if (changed and has_committed_anchor) { self.invalidateTranscriptAnchor("tail viewport changed"); } }
pub fn resolvedTailViewport( self: *const TranscriptRuntime, ) ?viewport_selection.TailOffsetResolution { return self.tail_viewport_resolution; }
pub fn closeFullTranscriptState(self: *TranscriptRuntime) void { self.full_transcript = self.full_transcript.closed(); self.resetFullTranscriptPageNavigation(); }
fn resetFullTranscriptPageNavigation(self: *TranscriptRuntime) void { self.full_transcript_page_anchor = .tail; self.full_transcript_prepared_page_visible = false; self.full_transcript_open_request = null; self.full_transcript_restore_open_pending = false; self.full_transcript_window_load.cancelActive(); self.full_transcript_page_load.cancelActive(); }
pub fn fullTranscriptAnchorEntryId(self: *const TranscriptRuntime) ?u32 { return self.full_transcript.anchor_entry_id; }
pub fn retargetFullTranscriptAnchor(self: *TranscriptRuntime, entry_id: u32) void { self.full_transcript = self.full_transcript.retarget_anchor(entry_id); }
pub fn scrollFullTranscript( self: *TranscriptRuntime, direction: input_action.MouseWheel, unit: FullTranscriptScrollUnit, ) void { if (self.full_transcript_page_load.busy() and self.installedFullTranscriptPageProjection() == null) { debug_trace.logf( "full_transcript_cache", "scroll_ignored reason=page_loading direction={s} unit={s}", .{ @tagName(direction), @tagName(unit) }, ); return; } const rows = switch (unit) { .wheel => full_transcript_wheel_rows, .page => self.fullTranscriptPageRows(), }; const before = self.full_transcript.scroll_rows; self.full_transcript = self.full_transcript.scroll(switch (direction) { .up => .up, .down => .down, }, rows); const local_visible_rows = @max(@as(u32, 1), self.layout.rows -| 4); const local_total_rows = if (self.full_transcript_installed_page) |*page| self.installedPageMeasurement(page).total_rows else 0; const local_max_offset = local_total_rows -| local_visible_rows; const adjacent_anchor = switch (direction) { .up => if (before == 0 and self.full_transcript.scroll_rows == 0) if (self.full_transcript_installed_page) |*page| full_transcript_page.previousAnchor(page.source.range) else null else null, .down => if (before >= local_max_offset and self.full_transcript_installed_page != null) full_transcript_page.nextAnchor( self.full_transcript_installed_page.?.source.range, self.entries.items.len, ) else null, }; if (adjacent_anchor) |anchor| { const boundary_index = switch (anchor) { .entry_index => |index| index, .tail => unreachable, }; if (boundary_index < self.entries.items.len) { self.full_transcript_page_anchor = anchor; self.full_transcript = self.full_transcript.select_page_boundary( self.entries.items[boundary_index].id(), ); } } debug_trace.logf( "full_transcript_cache", "scroll depth={s} direction={s} unit={s} rows={d} before={d} after={d}", .{ @tagName(self.full_transcript.depth), @tagName(direction), @tagName(unit), rows, before, self.full_transcript.scroll_rows, }, ); }
pub const FullTranscriptScrollUnit = enum { wheel, page, };
const full_transcript_wheel_rows: u32 = 3;
fn fullTranscriptPageRows(self: *const TranscriptRuntime) u32 { const first_row = if (self.owned_top_row == 0) 1 else self.owned_top_row; if (self.layout.content_bottom >= first_row) { return @max(@as(u32, 1), @as(u32, self.layout.content_bottom - first_row + 1)); } return 1; }
pub fn initViewport(self: *TranscriptRuntime, metrics: *Metrics, requested_start_row: u16) !void { return transcript_viewport_runtime.initViewport(self, metrics, requested_start_row); }
pub fn initViewportWithReservedRows(self: *TranscriptRuntime, metrics: *Metrics, requested_start_row: u16, min_body_rows: u16) !void { return transcript_viewport_runtime.initViewportWithReservedRows(self, metrics, requested_start_row, min_body_rows); }
pub fn requestTerminalReset(self: *TranscriptRuntime, metrics: *Metrics) !void { const before = self.transcriptCommitDiagnostic(); diagnostics.recordRenderEvent( .reset, "state={s} view={d} history={d} terminal={d}x{d} owned_top={d} cursor={d},{d}", .{ @tagName(before.state), before.visual_offset, before.history_visual_offset, self.layout.cols, self.layout.rows, self.owned_top_row, self.cursor_row, self.cursor_col }, ); const min_body_rows = self.min_visible_viewport_rows; self.reflow_clear_guard_rows = 0; self.resize_history_row_delta = null; self.viewport_clear_pending = false; // Job-control resume (and other full resets) reanchor at row 1. Drop // pending clears recorded against the pre-reset footer geometry — // e.g. eraseCurrentFrame at owned_top mid-scrollback — or the first // post-reset plan validates them against the new compact bands and // aborts with InvalidInvalidationRange. self.render_requests.clearPendingInvalidations(); self.footer_viewport.has_frame = false; self.footer_viewport.clearExternalInvalidation(); self.terminal_reset_pending = true; try self.initViewportWithReservedRows(metrics, 1, min_body_rows); }
pub fn requestTerminalResetAfterResize( self: *TranscriptRuntime, metrics: *Metrics, history_row_delta: ?i32, ) !void { try self.requestTerminalReset(metrics); self.resize_history_row_delta = history_row_delta; }
pub fn reserveVisibleViewportRows(self: *TranscriptRuntime, min_body_rows: u16) void { return transcript_viewport_runtime.reserveVisibleViewportRows(self, min_body_rows); }
pub fn reanchorTop(self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics) !void { return transcript_viewport_runtime.reanchorTop(self, alloc, metrics); }
pub fn footerTopRowForExtra(self: TranscriptRuntime, extra: u16) u16 { return transcript_viewport_runtime.footerTopRowForExtra(self, extra); }
pub fn forgetShimmer(self: *TranscriptRuntime) void { self.shimmer_active = false; self.shimmer_is_overlay = false; }
pub fn transcriptCommitDiagnostic(self: *const TranscriptRuntime) TranscriptCommitDiagnostic { return switch (self.transcript_commit_state) { .invalid => .{ .state = .invalid }, .stable => |anchor| .{ .state = .stable, .stable_start_line = anchor.selection.start_line, .visual_offset = anchor.visual_offset, .history_visual_offset = anchor.history_visual_offset, .total_visual_rows = anchor.total_visual_rows, .source_bytes = anchor.flow.len, .layout_id = anchor.layout_id, }, .recovering => |receipt| .{ .state = .recovering, .visual_offset = receipt.accepted_visual_offset, .history_visual_offset = receipt.accepted_history_visual_offset, .total_visual_rows = receipt.attempt_total_visual_rows, .source_bytes = receipt.flow.len, .remaining_inline_rows = @as(u32, receipt.remaining_resize_reflow_rows) + receipt.remaining_semantic_rows, .remaining_unplanned_scroll_rows = receipt.remaining_unplanned_scroll_rows, }, }; }
pub fn normalBufferRecoveryPending(self: *const TranscriptRuntime) bool { return switch (self.transcript_commit_state) { .invalid => false, .stable => |anchor| anchor.normal_buffer_recovery_pending, .recovering => |receipt| receipt.normal_buffer_recovery_pending, }; }
pub fn historyCatchupPending(self: *const TranscriptRuntime) bool { return switch (self.transcript_commit_state) { .invalid, .recovering => false, .stable => |anchor| anchor.history_catchup_pending, }; }
pub fn stableTranscriptProjectionForFlow( self: *const TranscriptRuntime, rendered_flow: []const u8, ) ?StableTranscriptProjection { const anchor = switch (self.transcript_commit_state) { .stable => |value| value, .invalid, .recovering => return null, }; if (!std.mem.startsWith(u8, rendered_flow, anchor.flow)) return null; return .{ .selection = anchor.selection, .visual_offset = committedProjectionVisualOffset(anchor), .history_visual_offset = anchor.history_visual_offset, .total_visual_rows = anchor.total_visual_rows, .flow_unchanged = rendered_flow.len == anchor.flow.len, .cursor_row = anchor.cursor_row, .cursor_col = anchor.cursor_col, .occupied_last_row = anchor.occupied_last_row, .occupied_last_row_blank = anchor.occupied_last_row_blank, .layout_id = anchor.layout_id, .measured_history_origin = anchor.measured_history_origin, }; }
pub fn measuredResizeProjectionForFlow( self: *const TranscriptRuntime, rendered_flow: []const u8, ) ?MeasuredResizeProjection { return switch (self.transcript_commit_state) { .invalid => null, .stable => if (self.stableTranscriptProjectionForFlow(rendered_flow) != null) .stable else null, .recovering => |receipt| blk: { if (receipt.attempt_cols == 0 or !std.mem.startsWith(u8, rendered_flow, receipt.flow)) { break :blk null; } break :blk .{ .recovering = .{ .accepted_visual_offset = receipt.accepted_visual_offset, .source_cols = receipt.attempt_cols, } }; }, }; }
pub fn measuredHistoryOriginForFlow( self: *const TranscriptRuntime, rendered_flow: []const u8, ) ?transcript_painter.MeasuredHistoryOriginProjection { return switch (self.transcript_commit_state) { .invalid => null, .stable => |anchor| blk: { if (!std.mem.startsWith(u8, rendered_flow, anchor.flow)) { break :blk null; } const origin = anchor.measured_history_origin orelse break :blk null; break :blk .{ .origin = origin, .current_cols = self.committed_frame_layout.terminal_cols, }; }, .recovering => |receipt| blk: { if (!std.mem.startsWith(u8, rendered_flow, receipt.flow)) { break :blk null; } const origin = receipt.measured_history_origin orelse break :blk null; break :blk .{ .origin = origin, .current_cols = receipt.attempt_cols, }; }, }; }
pub fn invalidateTranscriptAnchor(self: *TranscriptRuntime, reason: []const u8) void { if (self.committedRetentionIdentity()) |identity| { if (identity.publication_entries.len > 0 or (self.fullTranscriptActive() and identity.lines.len > 0)) { switch (self.transcript_commit_state) { .stable => |*anchor| { anchor.flow_materialized = false; anchor.normal_buffer_recovery_pending = true; anchor.history_catchup_pending = true; }, .recovering => |*receipt| { receipt.projection_rebase_pending = true; receipt.materialized_flow_len = null; receipt.presentation_valid = false; }, .invalid => unreachable, } self.publication_projection_invalid = true; debug_trace.logf("scroll", "transcript_publication_invalidation_deferred reason={s}", .{reason}); self.markTranscriptDirty(); return; } } self.resetTranscriptAnchor(reason); }
pub fn resetTranscriptAnchor(self: *TranscriptRuntime, reason: []const u8) void { self.publication_projection_invalid = false; const diagnostic = self.transcriptCommitDiagnostic(); if (diagnostic.state != .invalid) { diagnostics.recordRenderEvent( .source_invalidated, "reason={s} state={s} view={d} history={d} rows={d} source_bytes={d} recovery={} catchup={}", .{ reason, @tagName(diagnostic.state), diagnostic.visual_offset, diagnostic.history_visual_offset, diagnostic.total_visual_rows, diagnostic.source_bytes, self.normalBufferRecoveryPending(), self.historyCatchupPending() }, ); debug_trace.logf( "scroll", "transcript_anchor_invalidate state={s} stable_start={d} visual_offset={d} reason={s}", .{ @tagName(diagnostic.state), diagnostic.stable_start_line orelse 0, diagnostic.visual_offset, reason, }, ); } const alloc = self.shadow_vt_alloc orelse self.detached_commit_alloc orelse { std.debug.assert(self.transcript_commit_state == .invalid); return; }; self.transcript_commit_state.deinit(alloc); }
pub fn committedRetentionIdentity(self: *const TranscriptRuntime) ?source_preparation.RetentionIdentity { return switch (self.transcript_commit_state) { .invalid => null, .stable => |anchor| anchor.retention_identity, .recovering => |receipt| receipt.retention_identity, }; }
pub fn prepareCommittedRetentionSource(self: *const TranscriptRuntime, alloc: Allocator) !?TranscriptPreparationSource { return self.prepareCommittedRetentionSourceInterruptible(alloc, null); }
pub fn prepareCommittedRetentionSourceInterruptible(self: *const TranscriptRuntime, alloc: Allocator, checkpoint: ?*build_checkpoint.BuildCheckpoint) !?TranscriptPreparationSource { try build_checkpoint.poll(checkpoint); const cols = self.retentionSourceCols() orelse return null; const flow = switch (self.transcript_commit_state) { .invalid => return null, .stable => |anchor| anchor.flow, .recovering => |receipt| receipt.flow, }; const identity = self.committedRetentionIdentity().?; var source = try source_preparation.prepareIndexedFullTranscriptWindowSourceInterruptible(alloc, try alloc.dupe(u8, flow), cols, checkpoint); errdefer source.deinit(alloc); source.line_provenance = try alloc.dupe(transcript_blocks.LineProvenance, identity.lines); source.publication_entries = try alloc.dupe(u32, identity.publication_entries); return source; }
pub fn retentionSourceCols(self: *const TranscriptRuntime) ?u16 { const cols = switch (self.transcript_commit_state) { .invalid => return null, .stable => if (self.fullTranscriptActive() and self.full_transcript_open_cols > 0) self.full_transcript_open_cols else self.committed_frame_layout.terminal_cols, .recovering => |receipt| receipt.attempt_cols, }; return if (cols == 0) null else cols; }
fn retainedHistoryOffset(mapping: source_preparation.RetentionRebase, identity: source_preparation.RetentionIdentity, history: u32) u32 { var result = mapping.visual(history); if (identity.publication_entries.len == 0) return result; var owner: ?u32 = null; for (mapping.before.line_provenance, 0..) |line, index| { owner = source_preparation.publication_owner(owner, line); if (mapping.before.transcript_visual_row_offsets[index + 1] <= history) continue; if (owner) |id| { if (std.mem.findScalar(u32, identity.publication_entries, id) != null) result = @min(result, mapping.visual(@max(history, mapping.before.transcript_visual_row_offsets[index]))); } } return result; }
/// Rebase source-relative state before publishing a retention mutation. /// Allocation completes before any live boundary or owned flow is changed. pub fn rebaseRetainedTranscript( self: *TranscriptRuntime, alloc: Allocator, mapping: source_preparation.RetentionRebase, retained_identity: source_preparation.RetentionIdentity, ) !void { const flow = switch (self.transcript_commit_state) { .invalid => return, .stable => |anchor| anchor.flow, .recovering => |receipt| receipt.flow, }; var identity = try retained_identity.clone(alloc); errdefer identity.deinit(alloc); const retained_flow = try alloc.dupe(u8, mapping.retained.bytes[0..mapping.byte(flow.len)]); const diagnostic = self.transcriptCommitDiagnostic(); switch (self.transcript_commit_state) { .invalid => unreachable, .stable => |*anchor| { const old_start = mapping.before.byteAtVisualOffset(anchor.history_visual_offset); const mapped_history = retainedHistoryOffset(mapping, retained_identity, anchor.history_visual_offset); const new_start = mapping.retained.byteAtVisualOffset(mapped_history); anchor.flow_materialized = anchor.flow_materialized and std.mem.eql(u8, mapping.before.bytes[old_start..], mapping.retained.bytes[new_start..]); if (!anchor.flow_materialized) anchor.normal_buffer_recovery_pending = true; alloc.free(anchor.flow); anchor.flow = retained_flow; anchor.retention_identity.deinit(alloc); anchor.retention_identity = identity; anchor.visual_offset = mapping.visual(anchor.visual_offset); anchor.history_visual_offset = mapped_history; anchor.total_visual_rows = mapping.visual(anchor.total_visual_rows); anchor.selection = rebaseRetentionSelection(anchor.selection, mapping); anchor.measured_history_origin = rebaseRetentionHistoryOrigin(anchor.measured_history_origin, mapping); }, .recovering => |*receipt| { if (receipt.materialized_flow_len) |end| { const old_start = mapping.before.byteAtVisualOffset(receipt.materialized_visual_offset); const new_start = mapping.retained.byteAtVisualOffset(mapping.visual(receipt.materialized_visual_offset)); const new_end = mapping.byte(end); if (old_start > end or new_start > new_end or !std.mem.eql(u8, mapping.before.bytes[old_start..end], mapping.retained.bytes[new_start..new_end])) { debug_trace.logf("scroll", "retention invalidated materialized endpoint after projection change", .{}); receipt.materialized_flow_len = null; receipt.presentation_valid = false; } } if (receipt.flow_owned) alloc.free(receipt.flow); receipt.flow = retained_flow; receipt.flow_owned = true; receipt.retention_identity.deinit(alloc); receipt.retention_identity = identity; receipt.remaining_semantic_rows = mapping.visual(receipt.accepted_history_visual_offset +| receipt.remaining_semantic_rows) -| mapping.visual(receipt.accepted_history_visual_offset); receipt.remaining_semantic_progress_rows = mapping.visual(receipt.accepted_visual_offset +| receipt.remaining_semantic_progress_rows) -| mapping.visual(receipt.accepted_visual_offset); receipt.materialized_visual_rows = @intCast(mapping.visual(receipt.materialized_visual_offset +| receipt.materialized_visual_rows) -| mapping.visual(receipt.materialized_visual_offset)); receipt.accepted_visual_offset = mapping.visual(receipt.accepted_visual_offset); receipt.accepted_history_visual_offset = retainedHistoryOffset(mapping, retained_identity, receipt.accepted_history_visual_offset); receipt.attempt_visual_offset = mapping.visual(receipt.attempt_visual_offset); receipt.attempt_total_visual_rows = mapping.visual(receipt.attempt_total_visual_rows); receipt.materialized_visual_offset = mapping.visual(receipt.materialized_visual_offset); if (receipt.materialized_flow_len) |len| receipt.materialized_flow_len = mapping.byte(len); receipt.measured_history_origin = rebaseRetentionHistoryOrigin(receipt.measured_history_origin, mapping); }, } if (self.last_viewport_selection) |*selection| { selection.* = rebaseRetentionSelection(selection.*, mapping); self.last_visible_transcript_partial_skip_rows = selection.partial_skip_rows; self.last_visible_transcript_split_prefix_lines = selection.split_prefix_lines; } self.last_visible_transcript_start_line = mapping.line(self.last_visible_transcript_start_line); if (self.last_visible_transcript_split_active) self.last_visible_transcript_split_suffix_start_line = mapping.line(self.last_visible_transcript_split_suffix_start_line); debug_trace.logf("scroll", "transcript_retention_rebase state={s} history={d}->{d} view={d}->{d}", .{ @tagName(diagnostic.state), diagnostic.history_visual_offset, mapping.visual(diagnostic.history_visual_offset), diagnostic.visual_offset, mapping.visual(diagnostic.visual_offset), }); }
fn rebaseRetentionSelection(value: ViewportSelection, mapping: source_preparation.RetentionRebase) ViewportSelection { var selection = value; selection.start_line = mapping.line(value.start_line); selection.line_count = mapping.line(value.start_line +| value.line_count) -| selection.start_line; if (value.split_active) { selection.split_prefix_lines = mapping.line(value.split_prefix_lines); selection.split_suffix_start_line = mapping.line(value.split_suffix_start_line); } const old_offsets = mapping.before.transcript_visual_row_offsets; const new_offsets = mapping.retained.transcript_visual_row_offsets; selection.partial_skip_rows = if (value.start_line < old_offsets.len and selection.start_line < new_offsets.len) @intCast(mapping.visual(old_offsets[value.start_line] +| value.partial_skip_rows) -| new_offsets[selection.start_line]) else 0; return selection; }
fn rebaseRetentionHistoryOrigin( value: ?transcript_painter.MeasuredHistoryOrigin, mapping: source_preparation.RetentionRebase, ) ?transcript_painter.MeasuredHistoryOrigin { var origin = value orelse return null; if (origin.source_cols != mapping.before.cols) { debug_trace.logf("scroll", "retention dropped measured history origin source_cols={d} retained_cols={d}", .{ origin.source_cols, mapping.before.cols }); return null; } origin.source_visual_offset = mapping.visual(origin.source_visual_offset); if (origin.endpoint) |*endpoint| { endpoint.span_start_flow_offset = mapping.byte(endpoint.span_start_flow_offset); endpoint.flow_offset = mapping.byte(endpoint.flow_offset); } return origin; }
pub fn reconcileTranscriptCommitSource( self: *TranscriptRuntime, rendered_flow: []const u8, force_rebase: bool, reason: []const u8, ) void { self.reconcileTranscriptCommitSourceWithRewriteMode( rendered_flow, force_rebase, reason, .strict, ); }
pub fn reconcileTranscriptCommitSourceWithRewriteMode( self: *TranscriptRuntime, rendered_flow: []const u8, force_rebase: bool, reason: []const u8, mode: transcript_store.TranscriptSourceRewriteMode, ) void { if (!force_rebase and mode == .preserve_same_epoch) { if (self.committedRetentionIdentity()) |identity| { // Source preparation includes these already-published projections // until a receipt releases them; the recorded store does not. if (identity.publication_entries.len > 0) return; } } switch (self.transcript_commit_state) { .invalid => {}, .stable => |*anchor| { const source_compatible = std.mem.startsWith(u8, rendered_flow, anchor.flow); if (force_rebase or !source_compatible) { if (!force_rebase and mode == .preserve_same_epoch) { if (self.structuredRewritePreserveBlocker(anchor.*, rendered_flow)) |blocker| { debug_trace.logf( "scroll", "transcript_anchor_structured_rewrite_fallback blocker={s} reason={s}", .{ blocker, reason }, ); } else { debug_trace.logf( "scroll", "transcript_anchor_structured_rewrite_preserve stable_start={d} visual_offset={d} reason={s}", .{ anchor.selection.start_line, anchor.visual_offset, reason, }, ); diagnostics.recordRenderEvent( .source_rewrite, "action=preserve reason={s} old_bytes={d} new_bytes={d} view={d} history={d} rows={d} start={d} layout={x} recovery={}->true catchup={}", .{ reason, anchor.flow.len, rendered_flow.len, anchor.visual_offset, anchor.history_visual_offset, anchor.total_visual_rows, anchor.selection.start_line, anchor.layout_id, anchor.normal_buffer_recovery_pending, anchor.history_catchup_pending }, ); anchor.normal_buffer_recovery_pending = true; return; } } self.invalidateTranscriptAnchor(reason); } }, .recovering => |*receipt| { if (!force_rebase and std.mem.startsWith(u8, rendered_flow, receipt.flow)) { return; } receipt.measured_history_origin = null; if (receipt.projection_rebase_pending and !receipt.tracks_semantic_progress and receipt.remaining_semantic_rows == 0 and receipt.remaining_semantic_progress_rows == 0 and !receipt.document_append_committed) { return; } debug_trace.logf( "scroll", "transcript_recovery_source_rebase remaining_resize_reflow_rows={d} dropped_semantic_rows={d} append_committed={s} reason={s}", .{ receipt.remaining_resize_reflow_rows, receipt.remaining_semantic_rows, if (receipt.document_append_committed) "true" else "false", reason, }, ); diagnostics.recordRenderEvent( .source_rewrite, "action=rebase_recovery reason={s} old_bytes={d} new_bytes={d} view={d} history={d} dropped_rows={d} append_committed={}", .{ reason, receipt.flow.len, rendered_flow.len, receipt.accepted_visual_offset, receipt.accepted_history_visual_offset, receipt.remaining_semantic_rows, receipt.document_append_committed }, ); receipt.remaining_semantic_rows = 0; receipt.remaining_semantic_progress_rows = 0; receipt.projection_rebase_pending = true; receipt.tracks_semantic_progress = false; receipt.document_append_committed = false; }, } }
fn structuredRewritePreserveBlocker( self: *const TranscriptRuntime, anchor: CommittedTranscriptAnchor, rendered_flow: []const u8, ) ?[]const u8 { if (anchor.flow.len == 0) return "empty_anchor"; if (rendered_flow.len == 0) return "empty_rendered_flow"; if (self.resize_history_row_delta != null) return "resize_history_pending"; if (self.layout.cols == 0 or self.layout.rows == 0) return "empty_runtime_layout";
const committed = self.committed_frame_layout; if (anchor.layout_id != committed.layout_id) return "layout_epoch_mismatch"; if (committed.terminal_cols == 0 or committed.terminal_rows == 0) { return "empty_committed_geometry"; } if (committed.terminal_cols != self.layout.cols or committed.terminal_rows != self.layout.rows) { return "geometry_changed"; } if (committed.transcript_area.isEmpty()) return "empty_transcript_area"; if (anchor.visual_offset > anchor.total_visual_rows) return "invalid_visual_offset"; if (anchor.history_visual_offset > anchor.total_visual_rows) { return "invalid_history_visual_offset"; }
return null; }
fn materializedTranscriptProjection( self: *const TranscriptRuntime, source_visual_offset: u32, area: render_engine.frame_layout.FrameRect, ) ?MaterializedTranscriptProjection { const area_rows = if (area.isEmpty()) 0 else area.height(); return switch (self.transcript_commit_state) { .invalid => null, .stable => |anchor| if (committedProjectionVisualOffset(anchor) != source_visual_offset) null else .{ .raw_flow_exact = anchor.flow_materialized, .visual_rows = projectionVisualRowsInArea( anchor.total_visual_rows, committedProjectionVisualOffset(anchor), area, ), }, .recovering => |receipt| if (receipt.materialized_visual_offset != source_visual_offset) null else .{ .raw_flow_exact = receipt.materialized_flow_len != null, .visual_rows = @min( receipt.materialized_visual_rows, area_rows, ), }, }; }
fn plannedTranscriptRows( self: *const TranscriptRuntime, prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, source_visual_offset: u32, resize_reflow_rows: u16, semantic_rows: u32, semantic_progress_rows: u32, ) u16 { const semantic_cap = if (self.pending_resume_source != null) resume_publication_rows_per_frame else std.math.maxInt(u32); const planned = frameInlineScrollBudgetCapped( resize_reflow_rows, semantic_rows, semantic_cap, ); if (semantic_rows == 0) return planned;
const materialized = self.materializedTranscriptProjection( source_visual_offset, prepared.projectionArea(), ); if (materialized) |projection| { if (!projection.raw_flow_exact) { return frameInlineScrollBudgetCapped( resize_reflow_rows, semantic_rows, @min(projection.visual_rows, semantic_cap), ); } }
const accepted = acceptedTranscriptRowsForInlineRows( planned, resize_reflow_rows, semantic_rows, semantic_progress_rows, ); if (accepted.semantic_progress_rows == 0) return planned;
const accepts_all_semantic_rows = accepted.semantic_rows == semantic_rows and accepted.semantic_progress_rows == semantic_progress_rows; if (accepts_all_semantic_rows) return planned; const exactness = transcript_painter.preparedTranscriptStagedFlowEndExactness( prepared, self.layout.cols, prepared.projectionArea(), source_visual_offset + accepted.semantic_progress_rows, ); if (exactness == null or exactness.?) return planned;
const projection = materialized orelse return planned; return frameInlineScrollBudgetCapped( resize_reflow_rows, semantic_rows, @min(projection.visual_rows, semantic_cap), ); }
pub fn planTranscriptScroll( self: *const TranscriptRuntime, prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, ) TranscriptScrollFacts { return self.planTranscriptScrollForFrame(prepared, false, false); }
/// Selects the finality floor in flow bytes from the prepared paint's /// activity-independent candidates plus the frame-fresh producer facts /// (lifecycle watermark, assistant-tail writability, replaceable tail). /// Null means the whole flow is final. Pure over its inputs, so repeated /// calls within one frame agree. fn finalityFloorBytes( self: *const TranscriptRuntime, prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, ) ?usize { return self.transcript_release.finality_floor( prepared.finality_candidates, self.finalizedToolTurnWatermark(), if (prepared.replaceable_last_line) prepared.replaceable_start else null, ); }
/// Visual rows contributed by the flow prefix [0..flow_offset). The /// prefix boundary always lands on an entry start, which is a hard-line /// start; a mid-line offset rounds down to its line, the safe direction. fn preparedVisualRowsForFlowOffset( prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, flow_offset: usize, ) u32 { if (flow_offset == 0) return 0; if (flow_offset >= prepared.bytes.len) return prepared.sourceTotalVisualRows(); const lines = prepared.sourceVisibleLines(); const visual_rows = prepared.sourceVisualRows(); var rows: u32 = 0; for (lines, 0..) |line, index| { switch (line) { .transcript => |t| if (t.ref.ref.start >= flow_offset) return rows, .folded_line => {}, } if (index < visual_rows.len) rows += visual_rows[index]; } return rows; }
/// The largest visual offset whose rows are all backed by final entries, /// clamped to the frame's target offset. Release permission never /// extends past this value. fn finalityReleasableOffset( self: *const TranscriptRuntime, prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, target_offset: u32, ) u32 { const floor_bytes = self.finalityFloorBytes(prepared) orelse return target_offset; const floor_rows = preparedVisualRowsForFlowOffset(prepared, floor_bytes); return @min(target_offset, floor_rows); }
pub fn planTranscriptScrollForFrame( self: *const TranscriptRuntime, prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, footer_reservation_changed: bool, replay_displaced_footer_history: bool, ) TranscriptScrollFacts { var facts = self.planTranscriptScrollForFrameUnfiltered(prepared, footer_reservation_changed, replay_displaced_footer_history); if (self.committedRetentionIdentity()) |identity| { const restore_projection = self.publication_projection_invalid or self.committed_frame_layout.terminal_cols != self.layout.cols or self.committed_frame_layout.terminal_rows != self.layout.rows or facts.recovery_rebase or facts.recovery_projection_deferred; if (identity.publication_entries.len > 0 and (restore_projection or facts.finality_hold) and !self.terminal_reset_pending) { facts.publication_hold = if (restore_projection) .restore else .finality; if (restore_projection) { facts.semantic_rows = 0; facts.semantic_progress_rows = 0; facts.planned_rows = self.plannedTranscriptRows(prepared, facts.source_visual_offset, facts.resize_reflow_rows, 0, 0); } } } return facts; }
fn planTranscriptScrollForFrameUnfiltered( self: *const TranscriptRuntime, prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, footer_reservation_changed: bool, replay_displaced_footer_history: bool, ) TranscriptScrollFacts { const target_offset = prepared.sourceVisualOffset(); const target_total_visual_rows = prepared.sourceTotalVisualRows(); if (self.terminal_reset_pending) { debug_trace.logf( "scroll", "transcript_transition_plan source_state=reset target_visual_offset={d} target_total_visual_rows={d} planned_rows=0", .{ target_offset, target_total_visual_rows }, ); return .{ .target_visual_offset = target_offset, .recovery_rebase = true, }; } const anchor = switch (self.transcript_commit_state) { .stable => |value| value, .recovering => |receipt| { const source_equal = std.mem.eql(u8, prepared.bytes, receipt.flow); const source_prefix_growth = prepared.bytes.len > receipt.flow.len and std.mem.startsWith(u8, prepared.bytes, receipt.flow); const source_compatible = source_equal or source_prefix_growth; const source_visual_compatible = if (source_equal) target_total_visual_rows == receipt.attempt_total_visual_rows else if (source_prefix_growth) target_total_visual_rows >= receipt.attempt_total_visual_rows else false; const resize_history_compatible = self.resize_history_row_delta == null or (!receipt.attempt_resize_history_committed and self.resize_history_row_delta == receipt.attempt_resize_history_row_delta); const recovery_progress_compatible = source_compatible and source_visual_compatible and receipt.attempt_cols == self.layout.cols and resize_history_compatible; const recovery_rebase = receipt.projection_rebase_pending or !recovery_progress_compatible; const recovery_tracks_semantic_progress = receipt.tracks_semantic_progress and source_compatible; const growth_rows: u32 = if (recovery_progress_compatible and recovery_tracks_semantic_progress and source_prefix_growth) target_total_visual_rows - receipt.attempt_total_visual_rows else 0; const measured_resize_rebase = self.resize_history_row_delta != null and prepared.measured_resize_target_provenance == .recovering; const resize_reflow_rows = if (measured_resize_rebase) 0 else receipt.remaining_resize_reflow_rows; const retained_semantic_rows = if (source_compatible) receipt.remaining_semantic_rows else 0; const retained_semantic_progress_rows = if (recovery_progress_compatible and recovery_tracks_semantic_progress) receipt.remaining_semantic_progress_rows else 0; const candidate_semantic_rows = retained_semantic_rows + growth_rows; const candidate_semantic_progress_rows = retained_semantic_progress_rows + growth_rows; // Retained debt repairs the attempted frame. Only source // growth is a new content release that needs finality. const semantic_rows, const semantic_progress_rows = if (self.finalityFloorBytes(prepared) != null) bounded: { const releasable_offset = self.finalityReleasableOffset( prepared, target_offset, ); const bounded_growth_rows = @min( growth_rows, releasable_offset -| (receipt.accepted_history_visual_offset + retained_semantic_rows), ); const bounded_rows = retained_semantic_rows + bounded_growth_rows; const bounded_progress_growth_rows = @min( growth_rows, releasable_offset -| (receipt.accepted_visual_offset + retained_semantic_progress_rows), ); break :bounded .{ bounded_rows, @min( retained_semantic_progress_rows + bounded_progress_growth_rows, bounded_rows, ), }; } else .{ candidate_semantic_rows, candidate_semantic_progress_rows, }; const acknowledged_visual_offset = receipt.accepted_visual_offset + semantic_progress_rows; const recovery_projection_deferred = recovery_progress_compatible and recovery_tracks_semantic_progress and transcript_painter.preparedTranscriptProjectionExceedsArea( prepared, prepared.projectionArea(), acknowledged_visual_offset, ); const planned_rows = if (recovery_projection_deferred) 0 else self.plannedTranscriptRows( prepared, receipt.accepted_visual_offset, resize_reflow_rows, semantic_rows, semantic_progress_rows, ); debug_trace.logf( "scroll", "transcript_transition_plan source_state=recovering source_visual_offset={d} attempt_visual_offset={d} target_visual_offset={d} attempt_total_visual_rows={d} target_total_visual_rows={d} remaining_resize_reflow_rows={d} remaining_semantic_rows={d} remaining_semantic_progress_rows={d} growth_rows={d} semantic_progress_rows={d} planned_rows={d} source_compatible={s} recovery_progress_compatible={s} recovery_rebase={s} recovery_projection_deferred={s}", .{ receipt.accepted_visual_offset, receipt.attempt_visual_offset, target_offset, receipt.attempt_total_visual_rows, target_total_visual_rows, receipt.remaining_resize_reflow_rows, receipt.remaining_semantic_rows, receipt.remaining_semantic_progress_rows, growth_rows, semantic_progress_rows, planned_rows, if (source_compatible) "true" else "false", if (recovery_progress_compatible) "true" else "false", if (recovery_rebase) "true" else "false", if (recovery_projection_deferred) "true" else "false", }, ); return .{ .source_visual_offset = receipt.accepted_visual_offset, .target_visual_offset = target_offset, .resize_reflow_rows = resize_reflow_rows, .semantic_rows = semantic_rows, .semantic_progress_rows = semantic_progress_rows, .planned_rows = planned_rows, .source_compatible = source_compatible, .recovery_rebase = recovery_rebase, .recovery_progress_compatible = recovery_progress_compatible, .recovery_tracks_semantic_progress = recovery_tracks_semantic_progress, .recovery_projection_deferred = recovery_projection_deferred, }; }, .invalid => { // Materialize authoritative pre-viewport rows into native scrollback. if (self.pendingResumeFlow().len > 0) { const semantic_rows: u32 = frameInlineScrollBudget(0, target_offset); return .{ .target_visual_offset = target_offset, .semantic_rows = semantic_rows, .semantic_progress_rows = semantic_rows, .planned_rows = self.plannedTranscriptRows( prepared, 0, 0, semantic_rows, semantic_rows, ), .source_compatible = true, .recovery_progress_compatible = true, .recovery_tracks_semantic_progress = true, }; } return .{ .target_visual_offset = target_offset, .recovery_rebase = true, }; }, }; const source_equal = std.mem.eql(u8, prepared.bytes, anchor.flow); const source_prefix_growth = prepared.bytes.len > anchor.flow.len and std.mem.startsWith(u8, prepared.bytes, anchor.flow); const source_compatible = source_equal or source_prefix_growth; const source_visual_growth = source_prefix_growth and target_total_visual_rows > anchor.total_visual_rows; const width_stable = self.committed_frame_layout.terminal_cols == 0 or self.committed_frame_layout.terminal_cols == self.layout.cols; const geometry_rebase = footer_reservation_changed and width_stable and self.resize_history_row_delta == null; const displaced_history_replay = if (geometry_rebase and replay_displaced_footer_history) compatibleHistoryReplayRange( prepared, self.layout.cols, prepared.bytes, anchor.flow, anchor.history_visual_offset, target_offset, ) else null; const recovery_rebase = (!source_compatible and !geometry_rebase) or !width_stable or self.resize_history_row_delta != null; // Release requires two independent facts: permission (the rows are // final, expressed by the finality floor) and ability (this frame // materializes their exact bytes, which an incompatible frame // cannot). Either fact missing forces zero release; the frame // re-anchors on the new flow and the derived history debt settles // on the next compatible frame through the existing replay. const releasable_offset = self.finalityReleasableOffset(prepared, target_offset); const releasable_advance = releasable_offset -| anchor.history_visual_offset; const committed_projection_offset = committedProjectionVisualOffset(anchor); const geometry_history_rows: u32 = if (geometry_rebase and ((source_compatible and source_visual_growth) or displaced_history_replay != null)) target_offset -| anchor.history_visual_offset else 0; // Geometry displacement (footer growth over transcript rows) keeps // its pre-existing semantics: displaced rows are preserved into // history even when mutable, because inline rendering has nowhere // else to keep them. The finality clamp governs content-driven // release only. const semantic_rows: u32 = if (geometry_rebase) geometry_history_rows else if (!recovery_rebase) // Normal-buffer recovery restores rows the takeover already // displaced into scrollback; that restore is display repair, // not a content release, so the finality clamp does not apply. if (anchor.normal_buffer_recovery_pending) target_offset -| anchor.history_visual_offset else if (anchor.history_catchup_pending) releasable_advance else // Rows leaving the screen top are [visual..visual+scroll); // the floor clamps that range, so the budget is measured // from the committed viewport, not from history. releasable_offset -| anchor.visual_offset else if (self.compatibleCommittedHistoryReplayRange( prepared, anchor, prepared.bytes, releasable_offset, ) != null) releasable_advance else 0; // A finality hold is either the floor sitting below the viewport // (mutable rows above the top) or a same-geometry incompatible frame // that had permission to release but no ability to materialize; // geometry-driven recovery (resize, reflow) owns its own debt. const finality_hold = releasable_offset < target_offset or (recovery_rebase and width_stable and self.committed_frame_layout.terminal_rows == self.layout.rows and self.resize_history_row_delta == null and releasable_offset > anchor.history_visual_offset +| semantic_rows); const semantic_progress_rows: u32 = if (source_compatible or geometry_rebase) semantic_rows else 0; const resize_reflow_candidate = self.calculateAnchorResizeReflowScrollRows(prepared.selection); const measured_boundary_row_pending = self.resize_history_row_delta != null and self.fullTranscriptActive() and resize_reflow_candidate == 1; const resize_reflow_rows = if (self.resize_history_row_delta == null or measured_boundary_row_pending) resize_reflow_candidate else 0; const planned = self.plannedTranscriptRows( prepared, committed_projection_offset, resize_reflow_rows, semantic_rows, semantic_rows, ); if (semantic_rows > releasable_advance) { diagnostics.recordRenderEvent( .transition, "release_past_finality view={d}->{d} history={d} releasable={d} semantic={d} planned={d} compatible={} geometry={} recovery_rebase={} restore={} catchup={}", .{ committed_projection_offset, target_offset, anchor.history_visual_offset, releasable_offset, semantic_rows, planned, source_compatible, geometry_rebase, recovery_rebase, anchor.normal_buffer_recovery_pending, anchor.history_catchup_pending }, ); } debug_trace.logf( "scroll", "transcript_transition_plan source_state=stable source_start={d} target_start={d} source_visual_offset={d} source_history_visual_offset={d} target_visual_offset={d} releasable_visual_offset={d} source_total_visual_rows={d} target_total_visual_rows={d} resize_reflow_candidate={d} resize_reflow_rows={d} semantic_rows={d} planned_rows={d} width_stable={s} source_compatible={s} footer_reservation_changed={s} replay_displaced_footer_history={s} geometry_rebase={s} recovery_rebase={s}", .{ anchor.selection.start_line, prepared.selection.start_line, anchor.visual_offset, anchor.history_visual_offset, target_offset, releasable_offset, anchor.total_visual_rows, target_total_visual_rows, resize_reflow_candidate, resize_reflow_rows, semantic_rows, planned, if (width_stable) "true" else "false", if (source_compatible) "true" else "false", if (footer_reservation_changed) "true" else "false", if (replay_displaced_footer_history) "true" else "false", if (geometry_rebase) "true" else "false", if (recovery_rebase) "true" else "false", }, ); return .{ .source_visual_offset = committed_projection_offset, .target_visual_offset = target_offset, .resize_reflow_rows = resize_reflow_rows, .semantic_rows = semantic_rows, .semantic_progress_rows = semantic_progress_rows, .planned_rows = planned, .source_compatible = source_compatible, .geometry_rebase = geometry_rebase, .recovery_rebase = recovery_rebase, .finality_hold = finality_hold, .recovery_progress_compatible = !recovery_rebase, .recovery_tracks_semantic_progress = source_compatible or geometry_rebase, }; }
fn calculateAnchorResizeReflowScrollRows( self: *const TranscriptRuntime, next_selection: ViewportSelection, ) u16 { const anchor = switch (self.transcript_commit_state) { .stable => |value| value, .invalid, .recovering => return 0, }; const committed = self.committed_frame_layout; if (anchor.layout_id != committed.layout_id or !self.viewport_clear_pending or self.owned_top_row != 1 or committed.transcript_area.top != 1 or next_selection.top_row != 1 or next_selection.bottom_row < next_selection.top_row or self.layout.cols == 0 or self.layout.rows == 0 or committed.terminal_cols == 0 or committed.terminal_rows == 0 or self.layout.cols >= committed.terminal_cols) { return 0; }
const shadow = self.shadow_vt orelse return 0; if (shadow.cols != committed.terminal_cols or shadow.rows != committed.terminal_rows or committed.transcript_area.isEmpty() or shadow.cursor_row <= committed.transcript_area.bottom) { return 0; }
const cursor_reflowed_row = reflowedRows( shadow, 1, shadow.cursor_row, self.layout.cols, true, ); if (cursor_reflowed_row <= self.layout.rows) return 0;
const chrome_rows = reflowedRows( shadow, committed.transcript_area.bottom + 1, shadow.cursor_row, self.layout.cols, true, ); const physical_transcript_rows = @as(u32, self.layout.rows) -| chrome_rows; const target_transcript_rows = @as(u32, next_selection.bottom_row) - next_selection.top_row + 1; return @intCast(@min( physical_transcript_rows -| target_transcript_rows, @as(u32, std.math.maxInt(u16)), )); }
fn layoutForTranscriptProjection( layout: Layout, target_area: render_engine.frame_layout.FrameRect, ) !Layout { if (target_area.bottom > layout.rows) { return error.InvalidTranscriptTransition; } var projection_layout = layout; projection_layout.content_bottom = @max( projection_layout.content_bottom, target_area.bottom, ); return projection_layout; }
const TransitionTarget = struct { body_disposition: TranscriptBodyDisposition = .paint, selection: ViewportSelection, visual_offset: u32, history_visual_offset: u32, source_endpoint_visual_offset: u32, total_visual_rows: u32, cursor_row: u16, cursor_col: u16, measured_history_origin: ?transcript_painter.MeasuredHistoryOrigin, normal_buffer_recovery_pending: bool = false, history_catchup_pending: bool = false, staged_flow_end: ?usize = null, projection_staged: bool = false, // The frame slides the viewport past withheld release with an // in-place repaint. Any document append is bounded separately to // the rows the frame may release. hold_staged: bool = false,
fn init( prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, scroll_facts: TranscriptScrollFacts, ) TransitionTarget { return .{ .selection = prepared.selection, .visual_offset = scroll_facts.target_visual_offset, .history_visual_offset = scroll_facts.target_visual_offset, .source_endpoint_visual_offset = scroll_facts.target_visual_offset, .total_visual_rows = prepared.sourceTotalVisualRows(), .cursor_row = prepared.cursor.cursor_row, .cursor_col = prepared.cursor.cursor_col, .measured_history_origin = prepared.measured_history_origin, }; }
fn retainCommittedAnchor( self: *TransitionTarget, anchor: CommittedTranscriptAnchor, retained: RetainedTranscriptBody, ) void { self.body_disposition = .{ .retain_committed = retained }; self.selection = anchor.selection; self.visual_offset = anchor.visual_offset; self.history_visual_offset = anchor.history_visual_offset; self.normal_buffer_recovery_pending = anchor.normal_buffer_recovery_pending; self.total_visual_rows = anchor.total_visual_rows; self.cursor_row = anchor.cursor_row; self.cursor_col = anchor.cursor_col; }
fn usePreparedProjection( self: *TransitionTarget, prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, ) void { self.selection = prepared.selection; self.cursor_row = prepared.cursor.cursor_row; self.cursor_col = prepared.cursor.cursor_col; }
fn stagePreparedProjection( self: *TransitionTarget, alloc: Allocator, layout: Layout, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, target_area: render_engine.frame_layout.FrameRect, visual_offset: u32, ) !void { const projection_layout = try layoutForTranscriptProjection(layout, target_area); self.visual_offset = visual_offset; if (visual_offset == self.total_visual_rows) { try transcript_painter.reprojectPreparedTranscriptForVisualOffset( alloc, projection_layout, prepared, target_area, visual_offset, ); self.staged_flow_end = transcript_painter.preparedTranscriptFlowOffsetForVisualOffset( prepared, layout.cols, visual_offset, ); } else { const staged = try transcript_painter.stagePreparedTranscriptForVisualOffset( alloc, projection_layout, prepared, target_area, visual_offset, ); self.staged_flow_end = staged.flow_end; } self.projection_staged = true; self.usePreparedProjection(prepared); }
fn stagePreparedHistoryProjection( self: *TransitionTarget, alloc: Allocator, layout: Layout, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, target_area: render_engine.frame_layout.FrameRect, ) !void { const logical_visual_offset = self.visual_offset; const previous_start_line = self.selection.start_line; try self.stagePreparedProjection( alloc, layout, prepared, target_area, self.history_visual_offset, ); self.visual_offset = logical_visual_offset; self.source_endpoint_visual_offset = self.history_visual_offset; diagnostics.recordRenderEvent( .history_projection, "logical_view={d} history={d} start={d}->{d} endpoint={d} cursor={d},{d} terminal={d}x{d} area={d}..{d}", .{ logical_visual_offset, self.history_visual_offset, previous_start_line, self.selection.start_line, self.source_endpoint_visual_offset, self.cursor_row, self.cursor_col, layout.cols, layout.rows, target_area.top, target_area.bottom }, ); } };
pub const ResolvedTranscriptTarget = struct { target: TransitionTarget, target_layout: render_engine.frame_layout.CommittedLayoutSnapshot, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, scroll_facts: TranscriptScrollFacts, accepted: AcceptedTranscriptRows, source_endpoint_cursor_row: u16, source_endpoint_cursor_col: u16, destructive_invalidation: bool, activity_overlay_active: bool, borrows_pending_resume: bool,
pub fn selection(self: ResolvedTranscriptTarget) ViewportSelection { return self.target.selection; }
pub fn cursorRow(self: ResolvedTranscriptTarget) u16 { return self.target.cursor_row; }
pub fn cursorCol(self: ResolvedTranscriptTarget) u16 { return self.target.cursor_col; }
pub fn bodyDisposition(self: ResolvedTranscriptTarget) TranscriptBodyDisposition { return self.target.body_disposition; }
pub fn occupiedTranscriptRows(self: ResolvedTranscriptTarget) u16 { const area = self.target_layout.transcript_area; if (area.isEmpty() or self.target.selection.last_visible_row < area.top) return 0; return self.target.selection.last_visible_row - area.top + 1; }
pub fn applyToPaintPlan( self: ResolvedTranscriptTarget, plan: *render_engine.paint_plan.PaintPlan, ) void { plan.viewport = self.target.selection; if (plan.cursor_target) |*cursor| { cursor.row = self.target.cursor_row; cursor.col = self.target.cursor_col; } } };
fn stableRetainedTransitionBody( self: *const TranscriptRuntime, anchor: CommittedTranscriptAnchor, source_bytes: []const u8, target_layout: render_engine.frame_layout.CommittedLayoutSnapshot, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, destructive_invalidation: bool, activity_overlay_active: bool, ) ?RetainedTranscriptBody { if (!anchor.flow_materialized) return null; return render_engine.frame_retention.stableRetainedTranscriptBody(.{ .full_transcript_active = self.fullTranscriptActive(), .committed_layout_id = anchor.layout_id, .committed_flow = anchor.flow, .committed_occupied_last_row = anchor.occupied_last_row, .source_layout = self.committed_frame_layout, .source_bytes = source_bytes, .target_layout = target_layout, .scroll_plan = scroll_plan, .destructive_invalidation = destructive_invalidation, .activity_overlay_active = activity_overlay_active, }); }
fn paintedStableTargetWouldRewind( self: *const TranscriptRuntime, target: TransitionTarget, anchor: CommittedTranscriptAnchor, scroll_facts: TranscriptScrollFacts, destructive_invalidation: bool, ) bool { const same_width = self.committed_frame_layout.terminal_cols == self.layout.cols; return !self.fullTranscriptActive() and target.body_disposition == .paint and scroll_facts.source_compatible and !scroll_facts.geometry_rebase and same_width and self.resize_history_row_delta == null and !destructive_invalidation and target.visual_offset >= target.history_visual_offset and scroll_facts.target_visual_offset < anchor.visual_offset; }
fn semanticProgressNeedsStaging( target: TransitionTarget, scroll_facts: TranscriptScrollFacts, accepted_semantic_progress_rows: u32, ) bool { return target.body_disposition == .paint and !scroll_facts.recovery_rebase and scroll_facts.recovery_tracks_semantic_progress and accepted_semantic_progress_rows < scroll_facts.semantic_progress_rows; }
fn stableHistoryFloor( self: *const TranscriptRuntime, target: TransitionTarget, anchor: CommittedTranscriptAnchor, scroll_facts: TranscriptScrollFacts, ) ?u32 { const stable_terminal_geometry = self.committed_frame_layout.terminal_cols == self.layout.cols and self.committed_frame_layout.terminal_rows == self.layout.rows; if (self.fullTranscriptActive() or target.body_disposition != .paint or !stable_terminal_geometry or self.resize_history_row_delta != null) { return null; } if (target.visual_offset < target.history_visual_offset) { return target.history_visual_offset; } const committed_offset = committedProjectionVisualOffset(anchor); if (!scroll_facts.source_compatible and target.visual_offset < committed_offset and target.total_visual_rows > committed_offset) { return committed_offset; } return null; }
fn recoveringCanAdvanceSemanticProgress(scroll_facts: TranscriptScrollFacts) bool { return scroll_facts.recovery_progress_compatible and scroll_facts.recovery_tracks_semantic_progress and !scroll_facts.recovery_projection_deferred; }
fn resolveTransitionTarget( self: *const TranscriptRuntime, alloc: Allocator, source_bytes: []const u8, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, target_layout: render_engine.frame_layout.CommittedLayoutSnapshot, projection_area: render_engine.frame_layout.FrameRect, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, scroll_facts: TranscriptScrollFacts, accepted_semantic_rows: u32, accepted_semantic_progress_rows: u32, destructive_invalidation: bool, activity_overlay_active: bool, ) !TransitionTarget { var target = TransitionTarget.init(prepared, scroll_facts); switch (self.transcript_commit_state) { .stable => |anchor| { target.history_visual_offset = @min( target.total_visual_rows, anchor.history_visual_offset +| accepted_semantic_rows, ); target.normal_buffer_recovery_pending = anchor.normal_buffer_recovery_pending; target.history_catchup_pending = anchor.history_catchup_pending; // A retained body advances only by the released rows. When // the finality clamp withholds release from the plan itself // (as opposed to a partial acceptance, which the recovery // debt machinery owns), the viewport must still follow the // flow, so the frame repaints instead of retaining. const plan_covers_advance = scroll_facts.target_visual_offset -| committedProjectionVisualOffset(anchor) <= scroll_facts.semantic_rows; if (!scroll_facts.geometry_rebase and plan_covers_advance) { if (self.stableRetainedTransitionBody( anchor, source_bytes, target_layout, scroll_plan, destructive_invalidation, activity_overlay_active, )) |retained| { target.retainCommittedAnchor(anchor, retained); } } if (self.paintedStableTargetWouldRewind( target, anchor, scroll_facts, destructive_invalidation, )) { diagnostics.recordRenderEvent( .transition, "rejected=rewind view={d}->{d} history={d}->{d} requested_view={d} compatible={} geometry={} restore={} catchup={}", .{ anchor.visual_offset, target.visual_offset, anchor.history_visual_offset, target.history_visual_offset, scroll_facts.target_visual_offset, scroll_facts.source_compatible, scroll_facts.geometry_rebase, target.normal_buffer_recovery_pending, target.history_catchup_pending }, ); return error.InvalidTranscriptTransition; } if (semanticProgressNeedsStaging( target, scroll_facts, accepted_semantic_progress_rows, )) { try target.stagePreparedProjection( alloc, self.layout, prepared, projection_area, scroll_facts.source_visual_offset + accepted_semantic_progress_rows, ); } if (target.body_disposition == .paint and target.normal_buffer_recovery_pending and scroll_facts.source_compatible and target.visual_offset > target.history_visual_offset) { try target.stagePreparedHistoryProjection( alloc, self.layout, prepared, projection_area, ); } if (target.normal_buffer_recovery_pending and scroll_facts.geometry_rebase and target.history_visual_offset >= target.visual_offset) { target.normal_buffer_recovery_pending = false; } if (!target.projection_staged and target.body_disposition == .paint and !scroll_facts.geometry_rebase and !scroll_facts.recovery_rebase and !self.fullTranscriptActive() and !prepared.selection.split_active and !projection_area.isEmpty() and scroll_facts.target_visual_offset -| committedProjectionVisualOffset(anchor) > scroll_facts.semantic_rows and scroll_facts.target_visual_offset >= target.history_visual_offset) { // The viewport advanced further than the plan may // release: slide the window with an in-place repaint at // the target offset. The rows passed over stay // unreleased and settle later through the replay. try target.stagePreparedProjection( alloc, self.layout, prepared, projection_area, scroll_facts.target_visual_offset, ); target.hold_staged = true; debug_trace.logf( "scroll", "transcript_transition_hold_release target_visual_offset={d} history_visual_offset={d} semantic_rows={d}", .{ scroll_facts.target_visual_offset, target.history_visual_offset, scroll_facts.semantic_rows, }, ); } if (self.stableHistoryFloor(target, anchor, scroll_facts)) |history_floor| { try target.stagePreparedProjection( alloc, self.layout, prepared, projection_area, history_floor, ); target.source_endpoint_visual_offset = history_floor; debug_trace.logf( "scroll", "transcript_transition_hold_history_floor source_visual_offset={d} target_visual_offset={d}", .{ history_floor, scroll_facts.target_visual_offset }, ); } if (scroll_facts.geometry_rebase) { target.history_catchup_pending = target.visual_offset > target.history_visual_offset; } else if (target.history_visual_offset >= target.visual_offset) { target.history_catchup_pending = false; } else if (scroll_facts.finality_hold) { // Finality withheld release. Preserve the obligation on // stable anchors until history catches the viewport; // footer displacement alone never creates one. target.history_catchup_pending = true; } }, .recovering => |receipt| { target.history_visual_offset = @min( target.total_visual_rows, receipt.accepted_history_visual_offset +| accepted_semantic_rows, ); target.normal_buffer_recovery_pending = receipt.normal_buffer_recovery_pending; if (scroll_facts.publication_hold == .none and recoveringCanAdvanceSemanticProgress(scroll_facts)) { target.visual_offset = scroll_facts.source_visual_offset + accepted_semantic_progress_rows; if (accepted_semantic_progress_rows < scroll_facts.semantic_progress_rows) { try target.stagePreparedProjection( alloc, self.layout, prepared, projection_area, target.visual_offset, ); } else { const projection_layout = try layoutForTranscriptProjection( self.layout, projection_area, ); try transcript_painter.reprojectPreparedTranscriptForVisualOffset( alloc, projection_layout, prepared, projection_area, target.visual_offset, ); target.usePreparedProjection(prepared); } } }, .invalid => if (semanticProgressNeedsStaging( target, scroll_facts, accepted_semantic_progress_rows, )) { try target.stagePreparedProjection( alloc, self.layout, prepared, projection_area, accepted_semantic_progress_rows, ); }, } if (scroll_facts.publication_hold != .none) { try target.stagePreparedProjection(alloc, self.layout, prepared, projection_area, target.history_visual_offset); target.source_endpoint_visual_offset = target.history_visual_offset; target.history_catchup_pending = scroll_facts.target_visual_offset > target.history_visual_offset; target.normal_buffer_recovery_pending = scroll_facts.publication_hold == .restore and target.history_catchup_pending; } if (target.measured_history_origin == null and !prepared.measured_history_origin_consumed and scroll_facts.source_compatible and !destructive_invalidation) { target.measured_history_origin = switch (self.transcript_commit_state) { .invalid => null, .stable => |anchor| anchor.measured_history_origin, .recovering => |receipt| receipt.measured_history_origin, }; } if (!scroll_facts.source_compatible) { target.measured_history_origin = null; } return target; }
const TransitionHistoryReplay = struct { flow_start: usize, flow_end: usize, clear_rows: u16, };
fn committedLayoutMatches( self: *const TranscriptRuntime, anchor: CommittedTranscriptAnchor, ) bool { return anchor.layout_id == self.committed_frame_layout.layout_id and self.committed_frame_layout.terminal_cols == self.layout.cols and self.committed_frame_layout.terminal_rows == self.layout.rows; }
fn compatibleHistoryReplayRange( prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, cols: u16, target_flow: []const u8, committed_flow: []const u8, source_history_visual_offset: u32, target_history_visual_offset: u32, ) ?TransitionHistoryReplay { if (target_history_visual_offset <= source_history_visual_offset) return null; const flow_start = transcript_painter.preparedTranscriptFlowOffsetForVisualOffset( prepared, cols, source_history_visual_offset, ) orelse return null; const flow_end = transcript_painter.preparedTranscriptFlowOffsetForVisualOffset( prepared, cols, target_history_visual_offset, ) orelse return null; if (flow_end <= flow_start or flow_start > committed_flow.len) return null; const overlap_end = @min(flow_end, committed_flow.len); if (overlap_end > target_flow.len or !std.mem.eql( u8, target_flow[0..overlap_end], committed_flow[0..overlap_end], )) { return null; } const clear_rows = std.math.cast( u16, target_history_visual_offset - source_history_visual_offset, ) orelse return null; return .{ .flow_start = flow_start, .flow_end = flow_end, .clear_rows = clear_rows, }; }
fn compatibleCommittedHistoryReplayRange( self: *const TranscriptRuntime, prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, anchor: CommittedTranscriptAnchor, target_flow: []const u8, target_history_visual_offset: u32, ) ?TransitionHistoryReplay { if (!self.committedLayoutMatches(anchor)) return null; if (self.resize_history_row_delta != null) return null; return compatibleHistoryReplayRange( prepared, self.layout.cols, target_flow, anchor.flow, anchor.history_visual_offset, target_history_visual_offset, ); }
const TransitionAppendBase = struct { flow_len: usize, history_replay: ?TransitionHistoryReplay = null, visual_offset: u32, visual_rows: u16, cursor_row: u16, cursor_col: u16, };
fn resolveTransitionAppendBase( self: *const TranscriptRuntime, prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, prior_owned_top: u16, scroll_facts: TranscriptScrollFacts, destructive_invalidation: bool, target_flow: []const u8, target_history_visual_offset: u32, ) !?TransitionAppendBase { switch (self.transcript_commit_state) { .stable => |anchor| { if (!destructive_invalidation) { const replay_from_history = target_history_visual_offset > anchor.history_visual_offset and (scroll_facts.geometry_rebase or anchor.normal_buffer_recovery_pending or anchor.history_catchup_pending); if (replay_from_history) { const replay = self.compatibleCommittedHistoryReplayRange( prepared, anchor, target_flow, target_history_visual_offset, ); if (replay) |history_replay| { return .{ .flow_len = history_replay.flow_start, .history_replay = history_replay, .visual_offset = anchor.history_visual_offset, .visual_rows = projectionVisualRowsInArea( anchor.total_visual_rows, anchor.history_visual_offset, self.committed_frame_layout.transcript_area, ), .cursor_row = self.committed_frame_layout.transcript_area.top, .cursor_col = 1, }; } } if (anchor.flow_materialized and self.committedLayoutMatches(anchor) and target_flow.len >= anchor.flow.len and std.mem.startsWith(u8, target_flow, anchor.flow)) { return .{ .flow_len = anchor.flow.len, .visual_offset = committedProjectionVisualOffset(anchor), .visual_rows = projectionVisualRowsInArea( anchor.total_visual_rows, committedProjectionVisualOffset(anchor), self.committed_frame_layout.transcript_area, ), .cursor_row = anchor.cursor_row, .cursor_col = anchor.cursor_col, }; } } }, .recovering => |receipt| { if (receipt.retention_identity.publication_entries.len > 0 and self.committed_frame_layout.terminal_cols == self.layout.cols and self.committed_frame_layout.terminal_rows == self.layout.rows and !self.terminal_reset_pending) { if (compatibleHistoryReplayRange(prepared, self.layout.cols, target_flow, receipt.flow, receipt.accepted_history_visual_offset, target_history_visual_offset)) |replay| { return .{ .flow_len = replay.flow_start, .history_replay = replay, .visual_offset = receipt.accepted_history_visual_offset, .visual_rows = projectionVisualRowsInArea(receipt.attempt_total_visual_rows, receipt.accepted_history_visual_offset, self.committed_frame_layout.transcript_area), .cursor_row = self.committed_frame_layout.transcript_area.top, .cursor_col = 1, }; } } if (scroll_facts.recovery_progress_compatible and scroll_facts.recovery_tracks_semantic_progress and !scroll_facts.recovery_rebase and self.committed_frame_layout.terminal_cols == self.layout.cols and self.committed_frame_layout.terminal_rows == self.layout.rows and receipt.materialized_flow_len != null) { const materialized_flow_len = receipt.materialized_flow_len.?; if (recoveryAppendPrefixMatches( target_flow, receipt, materialized_flow_len, )) { return .{ .flow_len = materialized_flow_len, .visual_offset = receipt.materialized_visual_offset, .visual_rows = receipt.materialized_visual_rows, .cursor_row = receipt.cursor_row, .cursor_col = receipt.cursor_col, }; } } }, .invalid => if (self.pendingResumeFlow().len > 0 and !destructive_invalidation) { return .{ .flow_len = 0, .visual_offset = 0, .visual_rows = 0, .cursor_row = prior_owned_top, .cursor_col = 1, }; }, } return null; }
const PlannedHistoryMaterialization = struct { visual_offset: u32, normal_buffer_recovery_pending: bool, };
fn resolvePlannedHistoryMaterialization( source_visual_offset: u32, target_visual_offset: u32, target_projection_offset: u32, document_append_planned: bool, recovery_pending: bool, ) PlannedHistoryMaterialization { if (!recovery_pending) { return .{ .visual_offset = target_visual_offset, .normal_buffer_recovery_pending = false, }; } if (target_visual_offset <= source_visual_offset) { return .{ .visual_offset = target_visual_offset, .normal_buffer_recovery_pending = recovery_pending, }; } if (document_append_planned) { return .{ .visual_offset = target_visual_offset, .normal_buffer_recovery_pending = recovery_pending and target_visual_offset < target_projection_offset, }; } return .{ .visual_offset = source_visual_offset, .normal_buffer_recovery_pending = true, }; }
test "history offset advances only when transcript bytes are materialized" { const deferred = resolvePlannedHistoryMaterialization( 4, 12, 12, false, true, ); try std.testing.expectEqual(@as(u32, 4), deferred.visual_offset); try std.testing.expect(deferred.normal_buffer_recovery_pending);
const caught_up = resolvePlannedHistoryMaterialization( 4, 12, 12, true, true, ); try std.testing.expectEqual(@as(u32, 12), caught_up.visual_offset); try std.testing.expect(!caught_up.normal_buffer_recovery_pending);
const bounded = resolvePlannedHistoryMaterialization( 4, 8, 12, true, true, ); try std.testing.expectEqual(@as(u32, 8), bounded.visual_offset); try std.testing.expect(bounded.normal_buffer_recovery_pending); }
fn resolveMaterializedEndpoint( target: TransitionTarget, source_endpoint_cursor_row: u16, source_endpoint_cursor_col: u16, append_base: ?TransitionAppendBase, projected_flow_end: ?usize, target_flow_len: usize, transcript_area: render_engine.frame_layout.FrameRect, ) MaterializedEndpoint { var endpoint: MaterializedEndpoint = .{ .visual_offset = target.history_visual_offset, .visual_rows = projectionVisualRowsInArea( target.total_visual_rows, target.history_visual_offset, transcript_area, ), .flow_len = projected_flow_end, .cursor_row = target.cursor_row, .cursor_col = target.cursor_col, }; const base = append_base orelse return endpoint; const flow_end = projected_flow_end orelse return endpoint; if (base.flow_len < flow_end) return endpoint;
endpoint.flow_len = base.flow_len; if (base.flow_len == target_flow_len) { endpoint.visual_offset = target.source_endpoint_visual_offset; endpoint.visual_rows = projectionVisualRowsInArea( target.total_visual_rows, target.source_endpoint_visual_offset, transcript_area, ); endpoint.cursor_row = source_endpoint_cursor_row; endpoint.cursor_col = source_endpoint_cursor_col; } else { endpoint.visual_offset = base.visual_offset; endpoint.visual_rows = base.visual_rows; endpoint.cursor_row = base.cursor_row; endpoint.cursor_col = base.cursor_col; } return endpoint; }
fn materializedEndpointWithoutAppend( self: *const TranscriptRuntime, document_append: render_engine.frame_scroll_plan.FrameDocumentAppend, planned: MaterializedEndpoint, ) MaterializedEndpoint { return switch (self.transcript_commit_state) { .stable => |anchor| .{ .visual_offset = committedProjectionVisualOffset(anchor), .visual_rows = projectionVisualRowsInArea( anchor.total_visual_rows, committedProjectionVisualOffset(anchor), self.committed_frame_layout.transcript_area, ), .flow_len = if (anchor.flow_materialized) anchor.flow.len else null, .cursor_row = anchor.cursor_row, .cursor_col = anchor.cursor_col, }, .recovering => |receipt| .{ .visual_offset = receipt.materialized_visual_offset, .visual_rows = receipt.materialized_visual_rows, .flow_len = receipt.materialized_flow_len, .cursor_row = receipt.cursor_row, .cursor_col = receipt.cursor_col, }, .invalid => if (document_append.isEmpty()) planned else .{ .visual_offset = 0, .visual_rows = 0, .flow_len = 0, .cursor_row = document_append.start_row, .cursor_col = document_append.start_col, }, }; }
fn borrowsPendingResumeSource( self: *const TranscriptRuntime, source: *const TranscriptPreparationSource, ) bool { return if (self.pending_resume_source) |*pending| source == pending else false; }
pub fn prepareTranscriptScrollFactsForFrame( self: *const TranscriptRuntime, alloc: Allocator, source: *const TranscriptPreparationSource, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, footer_reservation_changed: bool, replay_displaced_footer_history: bool, ) !TranscriptScrollFacts { if (self.borrowsPendingResumeSource(source)) switch (self.transcript_commit_state) { .invalid => try transcript_painter.seedPreparedPresentationBoundary( alloc, prepared, 0, .{}, ), .recovering => |receipt| if (receipt.presentation_valid and receipt.materialized_flow_len != null) { try transcript_painter.seedPreparedPresentationBoundary( alloc, prepared, receipt.materialized_flow_len.?, .{ .resume_bytes = receipt.presentation_resume_bytes, .cursor_col = receipt.cursor_col, .pending_wrap = receipt.presentation_pending_wrap, }, ); }, .stable => {}, }; return self.planTranscriptScrollForFrame( prepared, footer_reservation_changed, replay_displaced_footer_history, ); }
pub fn resolveTranscriptTransitionTargetForFrame( self: *const TranscriptRuntime, alloc: Allocator, source: *const TranscriptPreparationSource, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, target_layout: render_engine.frame_layout.CommittedLayoutSnapshot, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, scroll_facts: TranscriptScrollFacts, destructive_invalidation: bool, activity_overlay_active: bool, ) !ResolvedTranscriptTarget { return self.resolveTranscriptTransitionTargetForFrameInArea( alloc, source, prepared, target_layout, target_layout.transcript_area, scroll_plan, scroll_facts, destructive_invalidation, activity_overlay_active, ); }
pub fn resolveTranscriptTransitionTargetForFrameInArea( self: *const TranscriptRuntime, alloc: Allocator, source: *const TranscriptPreparationSource, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, target_layout: render_engine.frame_layout.CommittedLayoutSnapshot, projection_area: render_engine.frame_layout.FrameRect, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, scroll_facts: TranscriptScrollFacts, destructive_invalidation: bool, activity_overlay_active: bool, ) !ResolvedTranscriptTarget { const target_area = target_layout.transcript_area; if (target_area.isEmpty() != projection_area.isEmpty() or (!target_area.isEmpty() and (projection_area.top != target_area.top or projection_area.bottom > target_area.bottom))) { return error.InvalidTranscriptTransition; } try scroll_plan.validate(self.layout.rows); if (scroll_plan.requested_inline_advance_rows != scroll_facts.planned_rows) { return error.InvalidFrameScrollPlan; } const accepted = acceptedTranscriptRows( scroll_plan, scroll_plan.terminal_scroll_rows, scroll_facts.resize_reflow_rows, scroll_facts.semantic_rows, scroll_facts.semantic_progress_rows, ); const source_endpoint_cursor_row = prepared.cursor.cursor_row; const source_endpoint_cursor_col = prepared.cursor.cursor_col; const target = self.resolveTransitionTarget( alloc, source.bytes, prepared, target_layout, projection_area, scroll_plan, scroll_facts, accepted.semantic_rows, accepted.semantic_progress_rows, destructive_invalidation, activity_overlay_active, ) catch |err| { const normal_buffer_recovery_pending = switch (self.transcript_commit_state) { .stable => |anchor| anchor.normal_buffer_recovery_pending, .recovering => |receipt| receipt.normal_buffer_recovery_pending, .invalid => false, }; debug_trace.logf( "scroll", "transcript_target_resolution_failed err={s} source_compatible={s} geometry_rebase={s} recovery_rebase={s} normal_buffer_recovery_pending={s} target_visual_offset={d}", .{ @errorName(err), if (scroll_facts.source_compatible) "true" else "false", if (scroll_facts.geometry_rebase) "true" else "false", if (scroll_facts.recovery_rebase) "true" else "false", if (normal_buffer_recovery_pending) "true" else "false", scroll_facts.target_visual_offset, }, ); return err; }; if (!projection_area.isEmpty() and target.selection.last_visible_row > projection_area.bottom) { debug_trace.logf( "scroll", "transcript_target_outside_candidate last_visible={d} target_area={d}..{d} body_disposition={s}", .{ target.selection.last_visible_row, projection_area.top, projection_area.bottom, @tagName(target.body_disposition), }, ); return error.InvalidTranscriptTransition; } return .{ .target = target, .target_layout = target_layout, .scroll_plan = scroll_plan, .scroll_facts = scroll_facts, .accepted = accepted, .source_endpoint_cursor_row = source_endpoint_cursor_row, .source_endpoint_cursor_col = source_endpoint_cursor_col, .destructive_invalidation = destructive_invalidation, .activity_overlay_active = activity_overlay_active, .borrows_pending_resume = self.borrowsPendingResumeSource(source), }; }
pub fn finalizeTranscriptTransitionForFrame( self: *TranscriptRuntime, alloc: Allocator, source: *TranscriptPreparationSource, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, target_layout: render_engine.frame_layout.CommittedLayoutSnapshot, plan: *render_engine.paint_plan.PaintPlan, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, footer_reservation_changed: bool, replay_displaced_footer_history: bool, ) !TranscriptTransition { const scroll_facts = try self.prepareTranscriptScrollFactsForFrame( alloc, source, prepared, footer_reservation_changed, replay_displaced_footer_history, ); const destructive_invalidation = render_engine.frame_retention.transcriptAreaHasDestructiveInvalidation( self.committed_frame_layout.transcript_area, plan.invalidation, ); const resolved = try self.resolveTranscriptTransitionTargetForFrame( alloc, source, prepared, target_layout, scroll_plan, scroll_facts, destructive_invalidation, plan.activity == .overlay_entry, ); resolved.applyToPaintPlan(plan); return self.sealTranscriptTransition( alloc, source, prepared, plan, resolved, ); }
pub fn sealTranscriptTransition( self: *TranscriptRuntime, alloc: Allocator, source: *TranscriptPreparationSource, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, plan: *const render_engine.paint_plan.PaintPlan, resolved: ResolvedTranscriptTarget, ) !TranscriptTransition { try resolved.scroll_plan.validate(self.layout.rows); const plan_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan.*); if (!std.meta.eql(plan_layout, resolved.target_layout) or !std.meta.eql(plan.viewport, resolved.target.selection) or (plan.activity == .overlay_entry) != resolved.activity_overlay_active) { return error.InvalidTranscriptTransition; } const destructive_invalidation = render_engine.frame_retention.transcriptAreaHasDestructiveInvalidation( self.committed_frame_layout.transcript_area, plan.invalidation, ); if (destructive_invalidation != resolved.destructive_invalidation or self.borrowsPendingResumeSource(source) != resolved.borrows_pending_resume) { return error.InvalidTranscriptTransition; } switch (resolved.target.body_disposition) { .paint => if (!std.meta.eql(prepared.selection, resolved.target.selection) or prepared.cursor.cursor_row != resolved.target.cursor_row or prepared.cursor.cursor_col != resolved.target.cursor_col) { return error.InvalidTranscriptTransition; }, .retain_committed => |retained| { const anchor = switch (self.transcript_commit_state) { .stable => |value| value, .invalid, .recovering => return error.InvalidTranscriptTransition, }; if (!std.meta.eql(anchor.selection, resolved.target.selection) or anchor.cursor_row != resolved.target.cursor_row or anchor.cursor_col != resolved.target.cursor_col or anchor.occupied_last_row != retained.occupied_last_row) { return error.InvalidTranscriptTransition; } }, } const borrows_pending_resume = resolved.borrows_pending_resume; const borrows_full_page = self.borrowsInstalledFullTranscriptSource(source); const borrows_source = borrows_pending_resume or borrows_full_page; const scroll_plan = resolved.scroll_plan; const scroll_facts = resolved.scroll_facts; const accepted = resolved.accepted; const target_layout = resolved.target_layout; const source_endpoint_cursor_row = resolved.source_endpoint_cursor_row; const source_endpoint_cursor_col = resolved.source_endpoint_cursor_col; var target = resolved.target; const source_diagnostic = self.transcriptCommitDiagnostic(); debug_trace.logf( "scroll", "transcript_transition_finalize source_state={s} body_disposition={s} source_start={d} target_start={d} source_visual_offset={d} target_visual_offset={d} target_history_visual_offset={d} target_cursor={d},{d} source_layout_id={x} target_layout_id={x}", .{ @tagName(source_diagnostic.state), @tagName(target.body_disposition), source_diagnostic.stable_start_line orelse 0, target.selection.start_line, source_diagnostic.visual_offset, target.visual_offset, target.history_visual_offset, target.cursor_row, target.cursor_col, source_diagnostic.layout_id, target_layout.layout_id, }, );
var target_cache: std.ArrayList(u8) = .empty; errdefer target_cache.deinit(alloc); const target_cache_start = transcript_store.cappedTailStart(source.bytes, self.max_transcript_bytes); const target_cache_bytes = source.bytes[target_cache_start..]; if (!borrows_source) { try target_cache.ensureTotalCapacityPrecise( alloc, @max(target_cache_bytes.len, self.transcript.capacity), ); try target_cache.appendSlice(alloc, target_cache_bytes); } const target_cache_origin_untrimmed = source.cache_origin_untrimmed and target_cache_start == 0; var transition_replaceable_last_line = prepared.replaceable_last_line; var transition_replaceable_start = prepared.replaceable_start; if (transition_replaceable_last_line) { if (target_cache_bytes.len == 0 or prepared.replaceable_start >= source.bytes.len) { transition_replaceable_last_line = false; transition_replaceable_start = 0; } else if (prepared.replaceable_start > target_cache_start) { transition_replaceable_start = prepared.replaceable_start - target_cache_start; } else { transition_replaceable_start = 0; } }
var retention_identity = try source_preparation.RetentionIdentity.capture(self, alloc, source); errdefer retention_identity.deinit(alloc);
var target_flow = source.bytes; var presentation_flow: []u8 = &.{}; errdefer alloc.free(presentation_flow); if (!borrows_source) source.bytes = &.{}; errdefer if (!borrows_source and target_flow.len > 0) alloc.free(target_flow);
const folded_summary_indices = source.folded_summary_indices; if (!borrows_source) source.folded_summary_indices = &.{}; errdefer if (!borrows_source and folded_summary_indices.len > 0) { alloc.free(folded_summary_indices); }; const source_row_provenance: []const transcript_blocks.RowProvenance = switch (target.body_disposition) { .paint => prepared.row_provenance.items, .retain_committed => switch (self.transcript_commit_state) { .invalid => &.{}, .stable => |anchor| anchor.row_provenance, .recovering => |receipt| receipt.row_provenance, }, }; const row_provenance = try alloc.dupe( transcript_blocks.RowProvenance, source_row_provenance, ); errdefer if (row_provenance.len > 0) alloc.free(row_provenance);
const append_base = try self.resolveTransitionAppendBase( prepared, scroll_plan.prior_owned_top, scroll_facts, destructive_invalidation, target_flow, target.history_visual_offset, ); const append_is_history_replay = if (append_base) |base| base.history_replay != null else false;
const source_flow_end_exact = switch (self.transcript_commit_state) { .recovering => |receipt| receipt.materialized_flow_len != null, .invalid, .stable => true, }; const natural_projected_flow_end: ?usize = if (target.projection_staged) target.staged_flow_end else if (source_flow_end_exact) target_flow.len else null; const held_projected_flow_end: ?usize = if (target.hold_staged) if (append_base) |base| held: { const base_visual_end = base.visual_offset + base.visual_rows; const target_base_flow_end = transcript_painter.preparedTranscriptFlowOffsetForVisualOffset( prepared, self.layout.cols, base_visual_end, ) orelse break :held null; if (target_base_flow_end < base.flow_len) break :held null; // Target growth can terminate the committed line at its byte // endpoint. Charge that cursor movement before advancing into // the final rows this frame may release. const drift = walkText( 1, base.cursor_col, target_flow[base.flow_len..target_base_flow_end], self.layout.cols, ).end_row - 1; if (drift > accepted.semantic_progress_rows) break :held base.flow_len; break :held transcript_painter.preparedTranscriptFlowOffsetForVisualOffset( prepared, self.layout.cols, base_visual_end + accepted.semantic_progress_rows - drift, ); } else null else null; const projected_flow_end: ?usize = if (append_base) |base| if (base.history_replay) |replay| replay.flow_end else held_projected_flow_end orelse natural_projected_flow_end else natural_projected_flow_end; var document_append = render_engine.frame_scroll_plan.FrameDocumentAppend{}; var document_append_bytes: []u8 = &.{}; var append_pending_wrap = false; var presentation_resume_bytes: []u8 = &.{}; var presentation_pending_wrap = false; var presentation_valid = false; errdefer if (document_append_bytes.len > 0) { alloc.free(document_append_bytes); }; errdefer if (presentation_resume_bytes.len > 0) { alloc.free(presentation_resume_bytes); }; if (plan.reset_terminal) { const replay_padding_rows = target_layout.terminal_rows -| target.cursor_row; document_append_bytes = try transcript_painter.prepareResetReplayDocument( self, alloc, target_flow, prepared.sourceVisibleLines(), prepared.sourceVisualRows(), prepared.transcript_ends_with_newline, replay_padding_rows, ); document_append = .{ .bytes = document_append_bytes, .start_row = 1, .start_col = 1, .reset_replay = true, }; debug_trace.logf( "scroll", "document_append_plan raw_bytes={d} wire_bytes={d} start={d},{d} source_visual_offset={d} target_visual_offset={d} planned_scroll_rows={d}", .{ target_flow.len, document_append.bytes.len, document_append.start_row, document_append.start_col, 0, target.visual_offset, scroll_plan.terminal_scroll_rows, }, ); } else if (append_base != null and (!scroll_facts.recovery_rebase or append_is_history_replay) and !scroll_facts.recovery_projection_deferred and (if (append_is_history_replay) accepted.semantic_rows > 0 else accepted.semantic_progress_rows > 0) and projected_flow_end != null and projected_flow_end.? > append_base.?.flow_len and scroll_plan.terminal_scroll_rows > 0) { const base = append_base.?; const raw_append = target_flow[base.flow_len..projected_flow_end.?]; const resume_boundary: ?transcript_painter.PresentationBoundary = if (base.flow_len == 0) .{} else switch (self.transcript_commit_state) { .recovering => |receipt| if (receipt.presentation_valid and receipt.materialized_flow_len == base.flow_len) .{ .resume_bytes = receipt.presentation_resume_bytes, .cursor_col = base.cursor_col, .pending_wrap = receipt.presentation_pending_wrap, } else null, .invalid, .stable => null, }; if (borrows_pending_resume and base.history_replay == null and resume_boundary != null) { var prepared_append = try transcript_painter.prepareResumeDocumentAppend( alloc, target_flow, self.layout.cols, base.flow_len, projected_flow_end.?, target.projection_staged, resume_boundary.?, true, ); defer prepared_append.deinit(alloc); append_pending_wrap = prepared_append.start_pending_wrap; document_append_bytes = prepared_append.bytes; prepared_append.bytes = &.{}; presentation_resume_bytes = prepared_append.endpoint_resume_bytes; prepared_append.endpoint_resume_bytes = &.{}; presentation_pending_wrap = prepared_append.endpoint_pending_wrap; presentation_valid = true; } else { var prepared_append = try transcript_painter.prepareTranscriptDocumentAppend( alloc, target_flow, self.layout.cols, base.flow_len, projected_flow_end.?, target.projection_staged, ); defer prepared_append.deinit(alloc); append_pending_wrap = prepared_append.start_pending_wrap; document_append_bytes = prepared_append.bytes; prepared_append.bytes = &.{}; } document_append = .{ .bytes = if (document_append_bytes.len > 0) document_append_bytes else raw_append, .start_row = base.cursor_row, .start_col = base.cursor_col, // A staged projection may already place the endpoint on the next row. .start_pending_wrap = base.history_replay == null and base.cursor_col == self.layout.cols and append_pending_wrap, .clear = if (base.history_replay) |replay| .{ .rows = replay.clear_rows } else .remainder, }; debug_trace.logf( "scroll", "document_append_plan raw_bytes={d} wire_bytes={d} start={d},{d} source_visual_offset={d} target_visual_offset={d} planned_scroll_rows={d}", .{ raw_append.len, document_append.bytes.len, document_append.start_row, document_append.start_col, base.visual_offset, target.visual_offset, scroll_plan.terminal_scroll_rows, }, ); } const source_history_visual_offset = switch (self.transcript_commit_state) { .invalid => 0, .stable => |anchor| anchor.history_visual_offset, .recovering => |receipt| receipt.accepted_history_visual_offset, }; const planned_history = resolvePlannedHistoryMaterialization( source_history_visual_offset, target.history_visual_offset, target.visual_offset, plan.reset_terminal or !document_append.isEmpty(), target.normal_buffer_recovery_pending, ); if (planned_history.visual_offset != target.history_visual_offset) { debug_trace.logf( "scroll", "transcript_history_materialization_deferred source_visual_offset={d} target_visual_offset={d}", .{ source_history_visual_offset, target.history_visual_offset }, ); } target.history_visual_offset = planned_history.visual_offset; target.normal_buffer_recovery_pending = planned_history.normal_buffer_recovery_pending; var materialized = resolveMaterializedEndpoint( target, source_endpoint_cursor_row, source_endpoint_cursor_col, append_base, if (append_is_history_replay) natural_projected_flow_end else projected_flow_end, target_flow.len, target_layout.transcript_area, ); if (!borrows_source and target.projection_staged and retention_identity.publication_entries.len > 0) { const prefix_end = @max(target.staged_flow_end orelse target_flow.len, source.publication_owned_end); if (prefix_end < target_flow.len) { const prefix = try alloc.dupe(u8, target_flow[0..prefix_end]); errdefer alloc.free(prefix); target.total_visual_rows = try retention_identity.retainPrefix(alloc, self.entries.items, prefix, self.layout.cols); presentation_flow = target_flow; target_flow = prefix; if (materialized.flow_len) |len| materialized.flow_len = @min(len, prefix_end); materialized.visual_rows = @intCast(@min(materialized.visual_rows, target.total_visual_rows -| materialized.visual_offset)); } } if (!scroll_facts.source_compatible or scroll_facts.geometry_rebase or scroll_facts.recovery_rebase or scroll_plan.terminal_scroll_rows > 0 or plan.reset_terminal) { const before = self.transcriptCommitDiagnostic(); diagnostics.recordRenderEvent( .transition, "prepared view={d}->{d} history={d}->{d} start={d}->{d} rows={d}->{d} bytes={d}->{d} compatible={} geometry={} rebase={} hold={} restore={} catchup={} append={d}@{d},{d} scroll={d} reset={} body={d}..{d} footer={d}..{d}", .{ before.visual_offset, target.visual_offset, before.history_visual_offset, target.history_visual_offset, before.stable_start_line orelse 0, target.selection.start_line, before.total_visual_rows, target.total_visual_rows, before.source_bytes, target_flow.len, scroll_facts.source_compatible, scroll_facts.geometry_rebase, scroll_facts.recovery_rebase, target.hold_staged, target.normal_buffer_recovery_pending, target.history_catchup_pending, document_append.bytes.len, document_append.start_row, document_append.start_col, scroll_plan.terminal_scroll_rows, plan.reset_terminal, target_layout.transcript_area.top, target_layout.transcript_area.bottom, target_layout.footer_area.top, target_layout.footer_area.bottom }, ); } return .{ .scroll_plan = scroll_plan, .document_append = document_append, .document_append_bytes = document_append_bytes, .history_settle_pending = scroll_facts.recovery_rebase and !self.fullTranscriptActive() and self.finalityReleasableOffset( prepared, scroll_facts.target_visual_offset, ) > target.history_visual_offset, .resize_reflow_rows = scroll_facts.resize_reflow_rows, .semantic_rows = scroll_facts.semantic_rows, .semantic_progress_rows = scroll_facts.semantic_progress_rows, .recovery_rebase = scroll_facts.recovery_rebase, .recovery_progress_compatible = scroll_facts.recovery_progress_compatible, .recovery_tracks_semantic_progress = scroll_facts.recovery_tracks_semantic_progress, .recovery_projection_deferred = scroll_facts.recovery_projection_deferred, .body_disposition = target.body_disposition, .target_flow = target_flow, .presentation_flow = presentation_flow, .retention_identity = retention_identity, .target_flow_owned = !borrows_source, .borrows_full_page = borrows_full_page, .presentation_resume_bytes = presentation_resume_bytes, .presentation_pending_wrap = presentation_pending_wrap, .presentation_valid = presentation_valid, .target_cache = target_cache, .target_cache_unchanged = borrows_source, .target_cache_origin_untrimmed = target_cache_origin_untrimmed, .folded_summary_indices = folded_summary_indices, .target_layout = target_layout, .selection = target.selection, .visual_offset = target.visual_offset, .history_visual_offset = target.history_visual_offset, .source_endpoint_visual_offset = target.source_endpoint_visual_offset, .total_visual_rows = target.total_visual_rows, .materialized = materialized, .materialized_without_append = self.materializedEndpointWithoutAppend( document_append, materialized, ), .cursor_row = target.cursor_row, .cursor_col = target.cursor_col, .replaceable_last_line = transition_replaceable_last_line, .replaceable_start = transition_replaceable_start, .replaceable_row = prepared.cursor.replaceable_row, .trace_transcript_frame = prepared.trace_transcript_frame, .trace_visible_rows = prepared.trace_visible_rows, .measured_history_origin = target.measured_history_origin, .row_provenance = row_provenance, .normal_buffer_recovery_pending = target.normal_buffer_recovery_pending, .history_catchup_pending = target.history_catchup_pending, }; }
pub fn consumeTranscriptTransition( self: *TranscriptRuntime, alloc: Allocator, transition: *TranscriptTransition, result: render_engine.terminal_diff.FrameCommitResult, ) void { const scroll_commit = result.scrollCommit(transition.scroll_plan); self.consumeTranscriptTransitionWithCommit(alloc, transition, result, scroll_commit); }
pub fn consumeFrameScrollCommit( self: *TranscriptRuntime, alloc: Allocator, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, result: render_engine.terminal_diff.FrameCommitResult, transition: ?*TranscriptTransition, ) void { const before = self.transcriptCommitDiagnostic(); const before_recovery_pending = self.normalBufferRecoveryPending(); const before_history_catchup_pending = self.historyCatchupPending(); const terminal_reset_commit = result.is_committed() and self.terminal_reset_pending; const resize_reset_commit = terminal_reset_commit and (self.pending_resize_observation != null or self.resize_history_row_delta != null); const scroll_commit = result.scrollCommit(scroll_plan); self.ackPreservedRowReleaseCommit(scroll_plan, scroll_commit); if (transition) |value| { std.debug.assert(std.meta.eql(scroll_plan, value.scroll_plan)); self.consumeTranscriptTransitionWithCommit(alloc, value, result, scroll_commit); } const committed_selection = if (result.is_committed()) if (transition) |value| value.selection else null else null; self.resize_bottom_anchor_pending = nextResizeBottomAnchorPending( self.resize_bottom_anchor_pending, terminal_reset_commit, resize_reset_commit, if (committed_selection) |selection| selection.tail_kind else null, if (committed_selection) |selection| selection.last_visible_row else 0, self.layout.content_bottom, ); self.recordRenderCommit( before, before_recovery_pending, before_history_catchup_pending, scroll_plan, result, terminal_reset_commit, ); }
fn recordRenderCommit( self: *const TranscriptRuntime, before: TranscriptCommitDiagnostic, before_recovery_pending: bool, before_history_catchup_pending: bool, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, result: render_engine.terminal_diff.FrameCommitResult, reset: bool, ) void { const after = self.transcriptCommitDiagnostic(); // Idle animation and same-row token paints must not evict the transitions // that explain a jump before the user can request a trace. if (result.is_committed() and !result.full_repaint and !reset and scroll_plan.terminal_scroll_rows == 0 and before.state == after.state and before.visual_offset == after.visual_offset and before.history_visual_offset == after.history_visual_offset and before.total_visual_rows == after.total_visual_rows and before.layout_id == after.layout_id and before_recovery_pending == self.normalBufferRecoveryPending() and before_history_catchup_pending == self.historyCatchupPending()) return;
diagnostics.recordRenderEvent( .commit, "state={s}->{s} result={s} view={d}->{d} history={d}->{d} rows={d}->{d} layout={x}->{x} terminal={d}x{d} cursor={d},{d} planned_scroll={d} accepted_scroll={d} bytes={d} repaint={} reset={} recovery={}->{} catchup={}->{}", .{ @tagName(before.state), @tagName(after.state), @tagName(result.state()), before.visual_offset, after.visual_offset, before.history_visual_offset, after.history_visual_offset, before.total_visual_rows, after.total_visual_rows, before.layout_id, after.layout_id, self.layout.cols, self.layout.rows, result.committed_cursor_row, result.committed_cursor_col, scroll_plan.terminal_scroll_rows, result.terminal_scroll_rows_applied(), result.bytes_written, result.full_repaint, reset, before_recovery_pending, self.normalBufferRecoveryPending(), before_history_catchup_pending, self.historyCatchupPending() }, ); }
fn consumeTranscriptTransitionWithCommit( self: *TranscriptRuntime, alloc: Allocator, transition: *TranscriptTransition, result: render_engine.terminal_diff.FrameCommitResult, scroll_commit: render_engine.frame_scroll_plan.FrameScrollCommit, ) void { std.debug.assert(!transition.consumed); const commit = resolveTranscriptTransitionCommit(transition, result, scroll_commit); if (commit.accepted.resize_reflow_rows > 0 and transition.measured_history_origin != null) { debug_trace.logf( "resize", "measured_history_endpoint_commit rows={d} before={s} after={s}", .{ commit.accepted.resize_reflow_rows, if (transition.measured_history_origin.?.endpoint != null) "exact" else "unavailable", if (commit.committed_measured_history_origin.?.endpoint != null) "exact" else "unavailable", }, ); }
if (result.is_committed()) { self.committed_frame_layout = transition.target_layout; }
if (transition.borrows_full_page) { transition.target_flow = &.{}; transition.folded_summary_indices = &.{}; transition.consumed = true; debug_trace.logf( "scroll", "transcript_transition_commit state=full_page_borrowed", .{}, ); return; }
self.publication_projection_invalid = !result.is_committed() and transition.retention_identity.publication_entries.len > 0; const borrowed_resume_source = if (!transition.target_flow_owned) if (self.pending_resume_source) |*source| if (transition.target_flow.len == 0 or source.bytes.ptr == transition.target_flow.ptr) source else null else null else null; if (commit.stable and !transition.target_flow_owned and borrowed_resume_source == null) { debug_trace.logf( "scroll", "transcript_transition_commit state=invalid reason=missing_borrowed_resume_source", .{}, ); transition.target_flow = &.{}; var previous_state = self.transcript_commit_state; self.transcript_commit_state = .invalid; previous_state.deinit(alloc); self.markTranscriptDirty(); } else if (commit.stable) { var previous_cache: std.ArrayList(u8) = .empty; if (!transition.target_cache_unchanged) { previous_cache = self.transcript; self.transcript = transition.target_cache; transition.target_cache = .empty; self.transcript_cache_origin_untrimmed = transition.target_cache_origin_untrimmed; } const target_has_content = transition.target_flow.len > 0; const retained_body = transition.body_disposition == .retain_committed;
const summary_count = @min( self.folded_command_blocks.items.len, transition.folded_summary_indices.len, ); for (self.folded_command_blocks.items[0..summary_count], 0..) |*block, index| { block.summary_transcript_index = transition.folded_summary_indices[index]; }
const stable_flow = transition.target_flow; const pending_resume_bytes = if (transition.target_flow_owned) self.releasePendingResumeSource(alloc) else blk: { const pending = borrowed_resume_source.?; pending.bytes = &.{}; const bytes = stable_flow.len; _ = self.releasePendingResumeSource(alloc); break :blk bytes; }; transition.target_flow = &.{};
var previous_state = self.transcript_commit_state; self.transcript_commit_state = .{ .stable = .{ .selection = transition.selection, .visual_offset = transition.visual_offset, .history_visual_offset = transition.history_visual_offset, .total_visual_rows = transition.total_visual_rows, .flow = stable_flow, .retention_identity = transition.retention_identity, .flow_materialized = if (!retained_body) (transition.retention_identity.publication_entries.len == 0 or transition.materialized.flow_len == stable_flow.len) else switch (previous_state) { .stable => |anchor| anchor.flow_materialized, .invalid, .recovering => false, }, .cursor_row = transition.cursor_row, .cursor_col = transition.cursor_col, .occupied_last_row = transition.selection.last_visible_row, .occupied_last_row_blank = transition.selection.last_visible_row_blank, .layout_id = transition.target_layout.layout_id, .measured_history_origin = commit.committed_measured_history_origin, .row_provenance = transition.row_provenance, .normal_buffer_recovery_pending = transition.normal_buffer_recovery_pending and (transition.retention_identity.publication_entries.len == 0 or transition.history_catchup_pending or transition.history_visual_offset < transition.visual_offset), .history_catchup_pending = transition.history_catchup_pending, } }; transition.row_provenance = &.{}; transition.retention_identity = .{};
self.last_rendered_cols = transition.target_layout.terminal_cols; if (pending_resume_bytes > 0) { self.invalidateTranscriptAnchor("resume publication retained source handoff"); if (transition.target_flow_owned) { debug_trace.logf( "session", "pending resume flow dropped reason=superseded bytes={d}", .{pending_resume_bytes}, ); } else { debug_trace.logf( "session", "resume historical flow committed bytes={d}", .{pending_resume_bytes}, ); } self.markTranscriptDirty(); } if (!retained_body and !self.fullTranscriptActive()) { self.last_visible_transcript_top_row = transition.selection.top_row; self.last_visible_transcript_start_line = transition.selection.start_line; self.last_visible_transcript_partial_skip_rows = transition.selection.partial_skip_rows; self.last_visible_transcript_split_active = transition.selection.split_active; self.last_visible_transcript_split_prefix_lines = transition.selection.split_prefix_lines; self.last_visible_transcript_split_suffix_start_line = transition.selection.split_suffix_start_line; self.last_visible_transcript_last_row = transition.selection.last_visible_row; self.last_visible_transcript_last_row_blank = transition.selection.last_visible_row_blank; self.last_visible_transcript_tail_kind = transition.selection.tail_kind; self.last_viewport_selection = transition.selection; self.cursor_row = transition.cursor_row; self.cursor_col = transition.cursor_col; self.replaceable_last_line = transition.replaceable_last_line; self.replaceable_start = transition.replaceable_start; self.replaceable_row = transition.replaceable_row; self.paint_trace.transcript_initialized = true; self.paint_trace.transcript_top = transition.selection.top_row; self.paint_trace.transcript_bottom = transition.selection.bottom_row; self.paint_trace.transcript_start_line = transition.selection.start_line; self.paint_trace.transcript_total_lines = transition.selection.line_count; self.paint_trace.transcript_visible_rows = transition.trace_visible_rows; self.paint_trace.transcript_partial_skip_rows = transition.selection.partial_skip_rows; self.paint_trace.transcript_entries = self.entries.items.len; self.paint_trace.transcript_log_frame = transition.trace_transcript_frame; } if (target_has_content and !self.has_painted_transcript) { self.has_painted_transcript = true; }
previous_state.deinit(alloc); previous_cache.deinit(alloc); debug_trace.logf( "scroll", "transcript_transition_commit state=stable start={d} visual_offset={d} history_visual_offset={d} layout_id={x} cursor={d},{d}", .{ transition.selection.start_line, transition.visual_offset, transition.history_visual_offset, transition.target_layout.layout_id, transition.cursor_row, transition.cursor_col, }, ); if (transition.normal_buffer_recovery_pending or (commit.accepted.semantic_rows > 0 and if (self.committedRetentionIdentity()) |identity| identity.publication_entries.len > 0 else false)) { self.markTranscriptDirty(); } } else { const recovery = resolveTranscriptRecoveryCommit(self, transition, result, commit); var previous_state = self.transcript_commit_state; var presentation_resume_bytes: []u8 = &.{}; var presentation_pending_wrap = false; var presentation_valid = false; if (result.document_append_applied() and transition.presentation_valid) { presentation_resume_bytes = transition.presentation_resume_bytes; presentation_pending_wrap = transition.presentation_pending_wrap; presentation_valid = true; transition.presentation_resume_bytes = &.{}; transition.presentation_valid = false; } else switch (previous_state) { .recovering => |*receipt| { presentation_resume_bytes = receipt.presentation_resume_bytes; presentation_pending_wrap = receipt.presentation_pending_wrap; presentation_valid = receipt.presentation_valid; receipt.presentation_resume_bytes = &.{}; receipt.presentation_valid = false; }, .invalid, .stable => {}, } self.transcript_commit_state = .{ .recovering = .{ .accepted_visual_offset = recovery.accepted_visual_offset, .accepted_history_visual_offset = recovery.accepted_history_visual_offset, .attempt_visual_offset = transition.source_endpoint_visual_offset, .attempt_total_visual_rows = transition.total_visual_rows, .materialized_visual_offset = recovery.materialized_visual_offset, .materialized_visual_rows = recovery.materialized_visual_rows, .materialized_flow_len = recovery.materialized_flow_len, .remaining_resize_reflow_rows = commit.remaining_resize_reflow_rows, .remaining_semantic_rows = commit.remaining_semantic_rows, .remaining_semantic_progress_rows = commit.remaining_semantic_progress_rows, .remaining_unplanned_scroll_rows = commit.remaining_unplanned_scroll_rows, .attempt_cols = transition.target_layout.terminal_cols, .attempt_resize_history_row_delta = self.resize_history_row_delta, .attempt_resize_history_committed = result.is_committed(), .projection_rebase_pending = transition.recovery_rebase, .tracks_semantic_progress = transition.recovery_tracks_semantic_progress, .flow = transition.target_flow, .retention_identity = transition.retention_identity, .flow_owned = transition.target_flow_owned, .presentation_resume_bytes = presentation_resume_bytes, .presentation_pending_wrap = presentation_pending_wrap, .presentation_valid = presentation_valid, .cursor_row = recovery.materialized_cursor_row, .cursor_col = recovery.materialized_cursor_col, .document_append_committed = recovery.document_append_committed, .measured_history_origin = commit.committed_measured_history_origin, .row_provenance = transition.row_provenance, .normal_buffer_recovery_pending = transition.normal_buffer_recovery_pending, } }; transition.target_flow = &.{}; transition.row_provenance = &.{}; transition.retention_identity = .{}; previous_state.deinit(alloc); if (commit.complete and !transition.recovery_projection_deferred and (commit.remaining_resize_reflow_rows > 0 or commit.remaining_semantic_rows > 0)) { self.markTranscriptDirty(); } debug_trace.logf( "scroll", "transcript_transition_commit state=recovering accepted_resize_reflow_rows={d} accepted_semantic_rows={d} accepted_semantic_progress_rows={d} remaining_resize_reflow_rows={d} remaining_semantic_rows={d} remaining_semantic_progress_rows={d} remaining_unplanned_scroll_rows={d} recovery_rebase={s} recovery_projection_deferred={s} append_committed={s} shadow_state={s}", .{ commit.accepted.resize_reflow_rows, commit.accepted.semantic_rows, commit.accepted.semantic_progress_rows, commit.remaining_resize_reflow_rows, commit.remaining_semantic_rows, commit.remaining_semantic_progress_rows, commit.remaining_unplanned_scroll_rows, if (transition.recovery_rebase) "true" else "false", if (transition.recovery_projection_deferred) "true" else "false", if (recovery.document_append_committed) "true" else "false", @tagName(result.state()), }, ); }
if (transition.folded_summary_indices.len > 0) { alloc.free(transition.folded_summary_indices); transition.folded_summary_indices = &.{}; } if (transition.history_settle_pending) { // Final rows remain unmaterialized past this commit; guarantee a // follow-up frame so the debt settles without external activity. self.render_requests.request(.transcript); } transition.consumed = true; }
/// Writes only to the byte buffer for callers that separately append a /// structured entry. Use `writeTranscript` when raw-entry mirroring is needed. pub fn writeTranscriptBytes(self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, text: []const u8, record: bool) !void { return transcript_writer.writeTranscriptBytes(self, alloc, metrics, text, record); }
pub fn estimatedReflowedCursorRow(self: *const TranscriptRuntime, target_cols: u16) ?u32 { const shadow = self.shadow_vt orelse return null; if (target_cols == 0 or shadow.cursor_row == 0) return null; return reflowedRows(shadow, 1, shadow.cursor_row, target_cols, true); }
pub fn ackPreservedRowRelease( self: *TranscriptRuntime, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, committed_terminal_scroll_rows: u16, ) !void { try scroll_plan.validate(self.layout.rows); self.ackPreservedRowReleaseAssumeValid(scroll_plan, committed_terminal_scroll_rows); }
pub fn ackPreservedRowReleaseAssumeValid( self: *TranscriptRuntime, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, committed_terminal_scroll_rows: u16, ) void { const scroll_commit = scroll_plan.commit(committed_terminal_scroll_rows); self.ackPreservedRowReleaseCommit(scroll_plan, scroll_commit); }
fn ackPreservedRowReleaseCommit( self: *TranscriptRuntime, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, scroll_commit: render_engine.frame_scroll_plan.FrameScrollCommit, ) void { const accepted = scroll_commit.accepted_rows; const raw_owned_top = self.owned_top_row; const raw_viewport_top = self.viewport_top_row; const normalized_viewport_top = render_engine.frame_scroll_plan.normalizeOwnedTop( self.viewport_top_row, self.layout.rows, );
self.owned_top_row = scroll_plan.prior_owned_top -| accepted.preserved_release_rows; self.viewport_top_row = @max( self.owned_top_row, normalized_viewport_top -| accepted.preserved_release_rows, ); debug_trace.logf( "scroll", "preserved_row_release requested={d} planned={d} committed_terminal={d} accepted_terminal={d} unplanned_terminal={d} accepted={d} raw_owned_top={d} raw_viewport_top={d} normalized_owned_top={d} normalized_viewport_top={d} owned_top={d} viewport_top={d}", .{ scroll_plan.requested_release_rows, scroll_plan.preserved_release_rows, scroll_commit.physical_terminal_scroll_rows, scroll_commit.accepted_terminal_scroll_rows, scroll_commit.unplanned_terminal_scroll_rows, accepted.preserved_release_rows, raw_owned_top, raw_viewport_top, scroll_plan.prior_owned_top, normalized_viewport_top, self.owned_top_row, self.viewport_top_row, }, ); }
/// Writes to the byte buffer and mirrors the content as a raw entry. /// Structured writers must use `writeTranscriptBytes` to avoid duplication. pub fn writeTranscript(self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, text: []const u8, record: bool) !void { return transcript_writer.writeTranscript(self, alloc, metrics, text, record); }
pub fn writeTranscriptClassified( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, text: []const u8, record: bool, class: RawEntryClass, ) !void { return transcript_writer.writeTranscriptClassified(self, alloc, metrics, text, record, class); }
pub fn writeTranscriptBytesFrom( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, text: []const u8, record: bool, entry_id: u32, ) !void { return transcript_writer.writeTranscriptBytesFrom( self, alloc, metrics, text, record, entry_id, ); }
pub fn writeCompletedToolStatus( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, kind: types.ToolOutcomeKind, summary: []const u8, record: bool, ) !void { _ = try self.writeCompletedToolStatusReturningEntryId( alloc, metrics, kind, summary, record, ); }
pub fn writeCompletedToolStatusReturningEntryId( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, kind: types.ToolOutcomeKind, summary: []const u8, record: bool, ) !u32 { const line = try lifecycleTerminalLine(alloc, kind, summary); defer alloc.free(line); return transcript_writer.writeTranscriptReturningEntryIdClassified( self, alloc, metrics, line, record, .tool_status, ); }
pub fn writeTurnCancellation( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, record: bool, ) !void { const line = try lifecycleTerminalLine(alloc, .cancelled, "Cancelled"); defer alloc.free(line); try transcript_writer.writeTranscriptClassified( self, alloc, metrics, line, record, .turn_cancellation, ); }
/// Rebuild the transcript cache at the current width and request a /// diagnostic full-frame repaint. Terminal mutation remains owned /// by the frame commit path. pub fn reconstructivePaint(self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics) !void { return transcript_writer.reconstructivePaint(self, alloc, metrics); }
pub fn appendReplaceableTranscriptLine(self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, text: []const u8) !u32 { return transcript_store.appendReplaceableTranscriptLine(self, alloc, metrics, text); }
pub fn appendReplaceableTranscriptLineClassified( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, text: []const u8, class: RawEntryClass, ) !u32 { return transcript_store.appendReplaceableTranscriptLineClassified(self, alloc, metrics, text, class); }
pub fn replaceTrailingTranscriptLine(self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, text: []const u8) !bool { return transcript_store.replaceTrailingTranscriptLine(self, alloc, metrics, text); }
/// Returns the id of the mirrored `raw_bytes` entry, or `0` when /// `text` is empty (sentinel: `next_entry_id` starts at 1). pub fn appendReplaceableTranscriptLineSilent(self: *TranscriptRuntime, alloc: Allocator, text: []const u8) !u32 { return transcript_store.appendReplaceableTranscriptLineSilent(self, alloc, text); }
pub fn appendReplaceableTranscriptLineSilentClassified(self: *TranscriptRuntime, alloc: Allocator, text: []const u8, class: RawEntryClass) !u32 { return transcript_store.appendReplaceableTranscriptLineSilentClassified(self, alloc, text, class); }
pub fn replaceTrailingTranscriptLineSilent(self: *TranscriptRuntime, alloc: Allocator, text: []const u8) !bool { return transcript_store.replaceTrailingTranscriptLineSilent(self, alloc, text); }
pub fn writeNotice( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, notice: types.SemanticNotice, record: bool, ) !void { return transcript_store.writeNotice(self, alloc, metrics, notice, record) catch |err| switch (err) { error.InputPending => unreachable, else => |other| return other, }; }
pub fn appendReplaceableSemanticNotice( self: *TranscriptRuntime, alloc: Allocator, notice: types.SemanticNotice, ) !u32 { return transcript_store.appendReplaceableSemanticNoticeAtomic(self, alloc, notice); }
pub fn appendSemanticNotice( self: *TranscriptRuntime, alloc: Allocator, notice: types.SemanticNotice, ) !u32 { return transcript_store.appendSemanticNoticeAtomic(self, alloc, notice); }
pub fn replaceSemanticNotice( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, notice: types.SemanticNotice, ) !bool { return transcript_store.replaceSemanticNoticeAtomic(self, alloc, entry_id, notice); }
pub fn setCommandOutputRenderPolicy(self: *TranscriptRuntime, styles: Styles) void { return command_output_runtime.setCommandOutputRenderPolicy(self, styles); }
pub fn retainedTranscriptStyles(self: *const TranscriptRuntime) Styles { return self.command_output_render.styles; }
pub fn writeRecordedCommandOutputChunkAtomic( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, styles: Styles, lifecycle_id: ?types.ToolLifecycleId, stream: command_output_content.Stream, text: []const u8, ) !?u32 { return transcript_store.writeRecordedCommandOutputChunkAtomic( self, alloc, metrics, styles, lifecycle_id, stream, text, ); }
pub fn flushRecordedCommandOutputSummaryAtomic( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, styles: Styles, lifecycle_id: ?types.ToolLifecycleId, ) !void { return transcript_store.flushRecordedCommandOutputSummaryAtomic( self, alloc, metrics, styles, lifecycle_id, ); }
pub fn writeCommandOutputChunk(self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, styles: Styles, stream: command_output_content.Stream, text: []const u8, record: bool) !void { return command_output_runtime.writeCommandOutputChunk(self, alloc, metrics, styles, stream, text, record); }
pub fn writeCommandOutputChunkForLifecycle( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, styles: Styles, lifecycle_id: ?types.ToolLifecycleId, stream: command_output_content.Stream, text: []const u8, record: bool, ) !void { return command_output_runtime.writeCommandOutputChunkForLifecycle( self, alloc, metrics, styles, lifecycle_id, stream, text, record, ); }
pub fn flushCommandOutputSummary(self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, styles: Styles, record: bool) !void { return command_output_runtime.flushCommandOutputSummary(self, alloc, metrics, styles, record); }
pub fn flushCommandOutputSummaryForLifecycle( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, styles: Styles, lifecycle_id: ?types.ToolLifecycleId, record: bool, ) !void { return command_output_runtime.flushCommandOutputSummaryForLifecycle( self, alloc, metrics, styles, lifecycle_id, record, ); }
pub fn openCommandOutputLifecycleId( self: *const TranscriptRuntime, ) ?types.ToolLifecycleId { return command_output_runtime.openCommandOutputLifecycleId(self); }
pub fn frameSink(self: *TranscriptRuntime) render_engine.terminal_diff.FrameSink { if (comptime @import("builtin").is_test) { if (self.test_frame_sink) |sink| return sink; } return transcript_io.frameSinkFrom(@ptrCast(self), writeFrameSink); }
pub fn writeNotificationBell( self: *TranscriptRuntime, metrics: *Metrics, ) void { switch (transcript_io.writeFrameBytes(self, metrics, "\x07")) { .complete => {}, .partial => |partial| debug_trace.logf( "notifications", "terminal bell write failed accepted_bytes={d} err={s}", .{ partial.accepted_bytes, @errorName(partial.err) }, ), } }
fn writeFrameSink(ctx: *anyopaque, metrics: *Metrics, bytes: []const u8) render_engine.terminal_diff.FrameSinkWriteResult { const self: *TranscriptRuntime = @ptrCast(@alignCast(ctx)); return transcript_io.writeFrameBytes(self, metrics, bytes); }
pub fn recomputeCursorFromTranscript(self: *TranscriptRuntime) void { self.resetCursorTracking(); if (self.transcript.items.len == 0) return; _ = self.advanceCursor(self.transcript.items); }
pub fn prepareTranscriptSurfacePaint( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, bottom_reserved_rows: u16, ) !transcript_painter.PreparedTranscriptSurfacePaint { return transcript_painter.prepareTranscriptSurfacePaint(self, alloc, metrics, bottom_reserved_rows); }
pub fn prepareTranscriptSource( self: *TranscriptRuntime, alloc: Allocator, tracked_entry_id: ?u32, ) !TranscriptPreparationSource { _ = try transcript_store.settleTranscriptPublication(self, alloc, null); if (self.pending_resume_source) |*source| { return source.clone(alloc); } return source_preparation.prepareTranscriptSource(self, alloc, tracked_entry_id); }
pub fn cachedTranscriptSource( self: *TranscriptRuntime, alloc: Allocator, ) !TranscriptPreparationSource { return self.cachedTranscriptSourceInterruptible(alloc, null) catch |err| switch (err) { error.InputPending => unreachable, else => |other| return other, }; }
pub fn pendingResumeSourceInterruptible( self: *TranscriptRuntime, alloc: Allocator, checkpoint: ?*build_checkpoint.BuildCheckpoint, ) !?*TranscriptPreparationSource { var source = if (self.pending_resume_source) |*value| value else return null; if (source.cols != self.layout.cols) { debug_trace.logf( "full_transcript_cache", "pending_resume_rebuild old_cols={d} new_cols={d}", .{ source.cols, self.layout.cols }, ); try build_checkpoint.poll(checkpoint); const bytes = try alloc.dupe(u8, source.bytes); var replacement = try source_preparation.prepareFullTranscriptViewportSourceInterruptible( self, alloc, bytes, checkpoint, ); errdefer replacement.deinit(alloc); try replacement.ensureLineIndexInterruptible(alloc, checkpoint); const previous_bytes = source.bytes; source.deinit(alloc); self.pending_resume_source = replacement; source = &self.pending_resume_source.?; switch (self.transcript_commit_state) { .recovering => |*receipt| if (!receipt.flow_owned) { std.debug.assert(receipt.flow.ptr == previous_bytes.ptr); receipt.flow = source.bytes; }, .invalid, .stable => {}, } } else if (source.hard_line_starts.len == 0) { try source.ensureLineIndexInterruptible(alloc, checkpoint); } else { try build_checkpoint.poll(checkpoint); } return source; }
pub fn cachedTranscriptSourceInterruptible( self: *TranscriptRuntime, alloc: Allocator, checkpoint: ?*build_checkpoint.BuildCheckpoint, ) !TranscriptPreparationSource { _ = try transcript_store.settleTranscriptPublication(self, alloc, checkpoint); if (self.pending_resume_source != null) { return self.prepareTranscriptSource(alloc, null); } if (self.compact_transcript_source_cache.find( self.full_transcript_content_revision, self.layout.cols, self.has_committed_frame, )) |source| { source.preview.cursor_row = self.cursor_row; source.preview.cursor_col = self.cursor_col; source.preview.replaceable_row = self.replaceable_row; source.replaceable_row = self.replaceable_row; return source.clone(alloc); }
var source = try source_preparation.prepareTranscriptSourceInterruptible( self, alloc, null, null, null, checkpoint, ); errdefer source.deinit(alloc); try source.ensureLineIndexInterruptible(alloc, checkpoint); const cached = self.compact_transcript_source_cache.insert(alloc, .{ .source = source, .content_revision = self.full_transcript_content_revision, .cols = self.layout.cols, .has_committed_frame = self.has_committed_frame, }); return cached.clone(alloc); }
pub fn prepareTranscriptSourceForFrameInterruptible( self: *TranscriptRuntime, alloc: Allocator, focused_entry_id: ?u32, omitted_entry_id: ?u32, checkpoint: ?*build_checkpoint.BuildCheckpoint, ) !TranscriptPreparationSource { _ = try transcript_store.settleTranscriptPublication(self, alloc, checkpoint); return source_preparation.prepareTranscriptSourceInterruptible( self, alloc, focused_entry_id, focused_entry_id, omitted_entry_id, checkpoint, ); }
pub fn prepareTranscriptSourceOmittingEntry( self: *TranscriptRuntime, alloc: Allocator, omitted_entry_id: u32, ) !TranscriptPreparationSource { return source_preparation.prepareTranscriptSourceOmittingEntry(self, alloc, omitted_entry_id); }
pub fn prepareTranscriptSourceWithFocusedEntry( self: *TranscriptRuntime, alloc: Allocator, focused_entry_id: u32, ) !TranscriptPreparationSource { return source_preparation.prepareTranscriptSourceWithFocusedEntry(self, alloc, focused_entry_id); }
pub fn prepareFullTranscriptViewportSource( self: *TranscriptRuntime, alloc: Allocator, bytes: []u8, ) !TranscriptPreparationSource { return source_preparation.prepareFullTranscriptViewportSource(self, alloc, bytes); }
pub const FullTranscriptSurfacePaint = struct { owned_source: ?TranscriptPreparationSource = null, borrowed_source: ?*TranscriptPreparationSource = null, prepared: transcript_painter.PreparedTranscriptSurfacePaint,
pub fn source(self: *FullTranscriptSurfacePaint) *TranscriptPreparationSource { if (self.owned_source) |*owned| return owned; return self.borrowed_source.?; }
pub fn deinit(self: *FullTranscriptSurfacePaint, alloc: Allocator) void { self.prepared.deinit(alloc); if (self.owned_source) |*source_value| source_value.deinit(alloc); self.* = undefined; } };
pub noinline fn buildFullTranscriptProjection( self: *TranscriptRuntime, alloc: Allocator, full_diff_resolver: ?full_transcript_screen.FullDiffResolver, ) !full_transcript_screen.Projection { self.prepareFullTranscriptProjectionLayout(); return self.buildFullTranscriptProjectionUncached( alloc, full_diff_resolver, self.fullTranscriptAnchorEntryId(), null, ) catch |err| switch (err) { error.InputPending => unreachable, else => |other| return other, }; }
pub fn pollFullTranscriptPageLoad( self: *TranscriptRuntime, ) !bool { if (self.full_transcript_window_load.takeCompleted()) |window_task| { defer window_task.deinit(); if (self.full_transcript_installed_page_retired) { self.discardInstalledFullTranscriptPage(); return false; } const page = if (self.full_transcript_installed_page) |*value| value else null; if (page) |installed| { if (installed.measurement_snapshot_ready and full_transcript_page.sameRequest(installed.source.request, window_task.request.page_request) and installed.projection.measurement_cols != installed.source.request.cols) { debug_trace.logf( "full_transcript_cache", "page_invalidated reason=stored_result_changed revision={d} cols={d}", .{ installed.source.request.content_revision, installed.source.request.cols }, ); self.discardInstalledFullTranscriptPage(); self.full_transcript_failed_request = null; return true; } } if (window_task.cancel_requested.load(.acquire)) { // Superseded scroll windows are expected and have no visible // failure state; the latest offset schedules on the next tick. } else if (window_task.failure) |failure| { self.full_transcript_failed_request = window_task.request.page_request; self.full_transcript_failure_pending = true; if (failure != error.InputPending) { debug_trace.logf( "full_transcript_cache", "window_build_failed revision={d} cols={d} offset={d} err={s}", .{ window_task.request.page_request.content_revision, window_task.request.page_request.cols, window_task.request.target_offset, @errorName(failure), }, ); } } else if (page != null and !window_task.cancel_requested.load(.acquire) and full_transcript_page.sameRequest( page.?.source.request, window_task.request.page_request, )) { if (window_task.takePreparedWindow()) |window| { if (page.?.prepared_window) |*current| { current.deinit(std.heap.c_allocator); } page.?.prepared_window = window; page.?.presented_offset = window.target_offset; self.full_transcript_failed_request = null; return true; } } } const task = self.full_transcript_page_load.takeCompleted() orelse return false; defer task.deinit();
const request = task.source.request; const desired = self.desiredFullTranscriptPageRequest(); var installed = false; if (task.cancel_requested.load(.acquire)) { // Closing, resizing, or superseding a page intentionally cancels // its worker. It must not poison the next open request. } else if (task.failure) |failure| { self.full_transcript_failed_request = request; self.full_transcript_failure_pending = true; if (failure != error.InputPending) { debug_trace.logf( "full_transcript_cache", "page_build_failed revision={d} cols={d} err={s}", .{ request.content_revision, request.cols, @errorName(failure) }, ); } } else if (!task.cancel_requested.load(.acquire) and full_transcript_page.sameSurface(desired, request)) { if (task.projection) |projection| { const prepared_window = task.prepared_window orelse return error.MissingPreparedFullTranscriptSource; const measured_item_rows = try std.heap.c_allocator.dupe( transcript_presentation.ItemRow, projection.measured_item_rows.items, ); errdefer if (measured_item_rows.len > 0) { std.heap.c_allocator.free(measured_item_rows); }; const source = task.takeInstalledSource(); std.debug.assert(!self.full_transcript_installed_page_retired); if (self.full_transcript_installed_page) |*page| page.deinit(); self.full_transcript_installed_page = .{ .source = source, .projection = task.takeProjection().?, .prepared_window = task.takePreparedWindow().?, .measured_total_rows = projection.measured_total_rows, .measured_anchor_row = projection.measured_anchor_row, .measured_item_rows = measured_item_rows, .measurement_snapshot_ready = true, .presented_offset = prepared_window.target_offset, }; self.full_transcript_installed_page_retired = false; self.full_transcript_failed_request = null; installed = true; } }
return installed or self.full_transcript.depth == .full; }
pub fn prewarmFullTranscriptPage( self: *TranscriptRuntime, capability: ?*session_child_store.SessionChildCapability, full_diff_resolver: ?full_transcript_screen.FullDiffResolver, ) !void { try self.ensureFullTranscriptPageLoad(capability, full_diff_resolver); }
pub fn fullTranscriptPreparedForOpen(self: *const TranscriptRuntime) bool { if (self.entries.items.len == 0) return true; if (self.full_transcript_installed_page_retired) return false; const page = if (self.full_transcript_installed_page) |*value| value else return false; return page.prepared_window != null and full_transcript_page.sameRequest( self.desiredFullTranscriptPageRequest(), page.source.request, ); }
pub fn requestFullTranscriptOpen(self: *TranscriptRuntime) bool { self.full_transcript_restore_open_pending = false; // Pending primary damage must commit before another buffer can consume it. if (self.fullTranscriptPreparedForOpen() and self.render_requests.pendingInvalidations().isEmpty()) { self.full_transcript_open_request = null; debug_trace.logf("full_transcript", "open_request state=ready", .{}); return true; } if (self.full_transcript_open_request != null) { self.full_transcript_open_request = null; debug_trace.logf("full_transcript", "open_request state=cancelled", .{}); return false; } const request = self.desiredFullTranscriptPageRequest(); if (self.full_transcript_failed_request) |failed| { if (full_transcript_page.sameRequest( request, failed, )) { self.full_transcript_open_request = request; self.full_transcript_failure_pending = true; debug_trace.logf("full_transcript", "open_request state=failed", .{}); return false; } } self.full_transcript_open_request = request; debug_trace.logf("full_transcript", "open_request state=pending", .{}); return false; }
pub fn deferRestoredFullTranscriptOpen(self: *TranscriptRuntime) void { std.debug.assert(self.full_transcript.depth == .full); self.full_transcript_open_request = self.desiredFullTranscriptPageRequest(); self.full_transcript = self.full_transcript.defer_full_open(); self.full_transcript_restore_open_pending = true; debug_trace.logf( "full_transcript", "restored_open deferred scroll_rows={d} follow_tail={} bookmark_pending={} bookmark_entry_id={d}", .{ self.full_transcript.scroll_rows, self.full_transcript.follow_tail, self.full_transcript.bookmark_pending, self.full_transcript.bookmark_entry_id orelse 0, }, ); }
pub fn cancelPendingFullTranscriptOpen(self: *TranscriptRuntime) bool { if (self.full_transcript_open_request == null) return false; self.full_transcript_open_request = null; self.full_transcript_restore_open_pending = false; debug_trace.logf("full_transcript", "open_request state=cancelled", .{}); return true; }
pub fn takeReadyFullTranscriptOpen(self: *TranscriptRuntime) bool { const requested = self.full_transcript_open_request orelse return false; if (!self.render_requests.pendingInvalidations().isEmpty()) return false; if (self.full_transcript_installed_page_retired) return false; const page = if (self.full_transcript_installed_page) |*value| value else return false; if (page.prepared_window == null or !full_transcript_page.sameRequest(requested, page.source.request)) return false; self.full_transcript_open_request = null; return true; }
pub fn takeFullTranscriptPreparationFailure(self: *TranscriptRuntime) bool { const pending = self.full_transcript_failure_pending and (self.fullTranscriptActive() or self.full_transcript_open_request != null); if (!pending) return false; self.full_transcript_failure_pending = false; self.full_transcript_open_request = null; return true; }
pub fn preparedFullTranscriptPageProjectionInterruptible( self: *TranscriptRuntime, full_diff_resolver: ?full_transcript_screen.FullDiffResolver, capability: ?*session_child_store.SessionChildCapability, checkpoint: ?*build_checkpoint.BuildCheckpoint, ) !*full_transcript_screen.Projection { try build_checkpoint.poll(checkpoint); std.debug.assert(self.full_transcript.depth == .full);
try self.ensureFullTranscriptPageLoad(capability, full_diff_resolver); if (self.installedFullTranscriptPageProjection()) |projection| return projection; if (!self.full_transcript_installed_page_retired) { if (self.full_transcript_installed_page) |*page| { if (page.prepared_window != null and full_transcript_page.sameSurface( self.desiredFullTranscriptPageRequest(), page.source.request, )) { return &page.projection; } } } return error.InputPending; }
fn installedFullTranscriptPageProjection( self: *TranscriptRuntime, ) ?*full_transcript_screen.Projection { if (self.full_transcript_installed_page_retired) return null; const page = if (self.full_transcript_installed_page) |*value| value else return null; const desired = self.desiredFullTranscriptPageRequest(); if (full_transcript_page.sameRequest(desired, page.source.request)) { return &page.projection; } if (!full_transcript_page.sameSurface(desired, page.source.request)) { return null; } if (self.full_transcript_prepared_page_visible or (self.full_transcript_page_load.busy() and self.command_output_display.open_command_block != null)) { return &page.projection; } if (self.command_output_display.open_command_block == null) return null; return &page.projection; }
pub fn preparedFullTranscriptPageCapability( self: *TranscriptRuntime, ) ?*session_child_store.SessionChildCapability { if (self.full_transcript_installed_page_retired) return null; const page = if (self.full_transcript_installed_page) |*value| value else return null; return if (page.source.capability) |*capability| capability else null; }
fn desiredFullTranscriptPageRequest( self: *const TranscriptRuntime, ) full_transcript_page.Request { return .{ .content_revision = self.full_transcript_content_revision, .cols = self.layout.cols, .anchor = self.full_transcript_page_anchor, }; }
fn ensureFullTranscriptPageLoad( self: *TranscriptRuntime, capability: ?*session_child_store.SessionChildCapability, full_diff_resolver: ?full_transcript_screen.FullDiffResolver, ) !void { if (self.full_transcript_installed_page_retired) { if (self.full_transcript_window_load.busy()) { self.full_transcript_window_load.cancelActive(); return; } self.discardInstalledFullTranscriptPage(); } const request = self.full_transcript_open_request orelse self.desiredFullTranscriptPageRequest(); if (self.full_transcript_failed_request) |failed| { if (full_transcript_page.sameRequest(request, failed)) return; self.full_transcript_failed_request = null; } if (self.full_transcript_installed_page) |*page| { if (full_transcript_page.sameRequest(request, page.source.request)) return; } if (self.command_output_display.open_command_block != null and self.full_transcript_open_request == null) { if (self.full_transcript_installed_page) |*page| { if (full_transcript_page.sameSurface( request, page.source.request, )) return; } }
if (self.full_transcript_page_load.hasRequest(request)) return; if (self.full_transcript_window_load.busy()) { self.full_transcript_window_load.cancelActive(); return; } if (self.full_transcript_page_load.busy()) { if (!self.full_transcript_page_load.hasCompatibleRequest(request)) { self.full_transcript_page_load.cancelActive(); } return; } var source = self.snapshotFullTranscriptPage( request, capability, full_diff_resolver, ) catch |err| switch (err) { error.FullTranscriptPageSnapshotTooLarge => { self.full_transcript_failed_request = request; self.full_transcript_failure_pending = true; debug_trace.logf( "full_transcript_cache", "page_snapshot_rejected revision={d} cols={d} err={s}", .{ request.content_revision, request.cols, @errorName(err) }, ); return; }, else => |other| return other, }; var source_owned = true; errdefer if (source_owned) source.deinit(std.heap.c_allocator); try self.full_transcript_page_load.schedule(source); self.full_transcript_failed_request = null; source_owned = false; }
fn snapshotFullTranscriptPage( self: *const TranscriptRuntime, request: full_transcript_page.Request, capability: ?*session_child_store.SessionChildCapability, full_diff_resolver: ?full_transcript_screen.FullDiffResolver, ) !full_transcript_worker.Source { const alloc = std.heap.c_allocator; const range = full_transcript_page.sourceRange( request, self.entries.items.len, ); if (self.fullTranscriptPageSnapshotBytes( range, full_diff_resolver, ) > full_transcript_snapshot_clone_max_bytes) { return error.FullTranscriptPageSnapshotTooLarge; } var source = full_transcript_worker.Source{ .request = request, .range = range, .styles = self.command_output_render.styles, .presentation = self.full_transcript, .visible_rows = @intCast(@min( self.fullTranscriptPageRows(), std.math.maxInt(u16), )), }; errdefer source.deinit(alloc); try source.entries.ensureTotalCapacity(alloc, range.len()); try source.details.ensureTotalCapacity(alloc, range.len()); try source.command_blocks.ensureTotalCapacity( alloc, @min(self.command_output_blocks.items.len, range.len()), ); for (self.entries.items[range.start..range.end]) |entry| { source.entries.appendAssumeCapacity( try transcript_store.cloneEntryForSnapshot(alloc, entry), ); if (self.toolDetailForEntry(entry.id())) |detail| { source.details.appendAssumeCapacity( try transcript_store.cloneToolDetailForSnapshot( alloc, detail.*, ), ); } } const page_id_bounds = snapshotEntryIdBounds(source.entries.items); for (self.command_output_blocks.items) |block| { if (!snapshotCommandBlockIntersects( source.entries.items, page_id_bounds, block, )) continue; const block_index = try appendSnapshotCommandBlock( alloc, &source, block, ); const snapshot_block = &source.command_blocks.items[block_index]; if (page_id_bounds) |bounds| { try appendSnapshotEntryIds( alloc, &snapshot_block.source_entry_ids, block.source_entry_ids.items, bounds, ); try appendSnapshotEntryIds( alloc, &snapshot_block.live_entry_ids, block.live_entry_ids.items, bounds, ); } if (block.lifecycle_id) |lifecycle_id| { for (self.tool_details.items) |detail| { if (!command_output_runtime.sameLifecycleId( detail.lifecycle_id, lifecycle_id, )) continue; try appendSnapshotToolDetail(alloc, &source, detail); try appendSnapshotToolHeading(alloc, &source, detail); break; } } } var detail_index: usize = 0; while (detail_index < source.details.items.len) : (detail_index += 1) { const detail = source.details.items[detail_index]; for (self.command_output_blocks.items) |block| { const same_output_entry = detail.command_output_entry_id != null and block.entry_id == detail.command_output_entry_id; const same_lifecycle = if (detail.lifecycle_id) |lifecycle_id| command_output_runtime.sameLifecycleId( block.lifecycle_id, lifecycle_id, ) else false; if (!same_output_entry and !same_lifecycle) continue; _ = try appendSnapshotCommandBlock(alloc, &source, block); break; } } if (full_diff_resolver) |resolver| { try appendSnapshotFullDiffs(alloc, &source, resolver); } if (capability) |current| { source.capability = try current.cloneReadOnly(alloc); } try self.prependFullDetailRecords(alloc, &source); debug_trace.logf( "full_transcript_cache", "page_snapshot revision={d} cols={d} range={d}..{d} entries={d} details={d} blocks={d} diffs={d}", .{ request.content_revision, request.cols, range.start, range.end, source.entries.items.len, source.details.items.len, source.command_blocks.items.len, source.full_diffs.items.len, }, ); return source; }
fn fullTranscriptPageSnapshotBytes( self: *const TranscriptRuntime, range: full_transcript_page.SourceRange, full_diff_resolver: ?full_transcript_screen.FullDiffResolver, ) usize { const entries = self.entries.items[range.start..range.end]; var total: usize = entries.len *| @sizeOf(TranscriptEntry); for (entries) |entry| { total +|= transcript_store.entrySnapshotRetainedBytes(entry); if (full_diff_resolver) |resolver| { const raw = switch (entry) { .raw_bytes => |value| value, else => continue, }; if (raw.class != .diff_block) continue; const marker_id = diff_mod.markedDiffBlockId(raw.bytes) orelse continue; if (resolver.full_for_marker(resolver.context, marker_id)) |content| { total +|= content.len; } } if (total > full_transcript_snapshot_clone_max_bytes) return total; } const bounds = snapshotEntryIdBounds(entries); for (self.tool_details.items) |detail| { if (bounds) |page_bounds| { if (detail.entry_id < page_bounds.min or detail.entry_id > page_bounds.max) { continue; } } else continue; total +|= toolDetailSnapshotBytes(detail); if (total > full_transcript_snapshot_clone_max_bytes) return total; } for (self.command_output_blocks.items) |block| { if (!snapshotCommandBlockIntersects(entries, bounds, block)) continue; total +|= command_output_runtime.commandOutputBlockRetainedBytes(block); if (total > full_transcript_snapshot_clone_max_bytes) return total; } return total; }
fn appendSnapshotFullDiffs( alloc: Allocator, source: *full_transcript_worker.Source, resolver: full_transcript_screen.FullDiffResolver, ) !void { for (source.entries.items) |entry| { const raw = switch (entry) { .raw_bytes => |value| value, else => continue, }; if (raw.class != .diff_block) continue; const marker_id = diff_mod.markedDiffBlockId(raw.bytes) orelse continue; var duplicate = false; for (source.full_diffs.items) |existing| { if (existing.marker_id == marker_id) { duplicate = true; break; } } if (duplicate) continue; const content = resolver.full_for_marker( resolver.context, marker_id, ) orelse continue; try source.appendFullDiff(alloc, marker_id, content); }
for (source.details.items) |detail| { const lifecycle_id = detail.lifecycle_id orelse continue; if (!resolver.has_full_for_lifecycle( resolver.context, lifecycle_id, )) continue; var duplicate = false; for (source.full_diff_lifecycles.items) |existing| { if (command_output_runtime.sameLifecycleId( existing, lifecycle_id, )) { duplicate = true; break; } } if (duplicate) continue; try source.appendFullDiffLifecycle(alloc, lifecycle_id); } }
fn snapshotContainsEntry( entries: []const TranscriptEntry, entry_id: u32, ) bool { for (entries) |entry| { if (entry.id() == entry_id) return true; } return false; }
const SnapshotEntryIdBounds = struct { min: u32, max: u32, };
fn toolDetailSnapshotBytes(detail: ToolDetailRecord) usize { var total = @sizeOf(ToolDetailRecord) +| detail.tool_name.len; if (detail.arguments_json) |value| total +|= value.len; if (detail.result) |value| total +|= value.len; if (detail.result_handle) |value| total +|= value.len; if (detail.command_artifact_handle) |value| total +|= value.len; if (detail.command_output_replay) |replay| switch (replay) { .available => |value| total +|= value.handle.len, .unavailable => {}, }; if (detail.lifecycle_id) |value| total +|= value.call_id.len; return total; }
fn snapshotEntryIdBounds( entries: []const TranscriptEntry, ) ?SnapshotEntryIdBounds { if (entries.len == 0) return null; var bounds = SnapshotEntryIdBounds{ .min = entries[0].id(), .max = entries[0].id(), }; for (entries[1..]) |entry| { bounds.min = @min(bounds.min, entry.id()); bounds.max = @max(bounds.max, entry.id()); } return bounds; }
fn snapshotCommandBlockIntersects( entries: []const TranscriptEntry, bounds: ?SnapshotEntryIdBounds, block: CommandOutputBlock, ) bool { if (block.entry_id) |entry_id| { if (snapshotContainsEntry(entries, entry_id)) return true; } const page_bounds = bounds orelse return false; return sortedEntryIdsIntersect(block.source_entry_ids.items, page_bounds) or sortedEntryIdsIntersect(block.live_entry_ids.items, page_bounds); }
fn sortedEntryIdsIntersect( ids: []const u32, bounds: SnapshotEntryIdBounds, ) bool { var low: usize = 0; var high = ids.len; while (low < high) { const middle = low + (high - low) / 2; if (ids[middle] < bounds.min) { low = middle + 1; } else { high = middle; } } return low < ids.len and ids[low] <= bounds.max; }
fn appendSnapshotEntryIds( alloc: Allocator, destination: *std.ArrayList(u32), ids: []const u32, bounds: SnapshotEntryIdBounds, ) !void { var low: usize = 0; var high = ids.len; while (low < high) { const middle = low + (high - low) / 2; if (ids[middle] < bounds.min) { low = middle + 1; } else { high = middle; } } var index = low; while (index < ids.len and ids[index] <= bounds.max) : (index += 1) { try destination.append(alloc, ids[index]); } }
fn appendSnapshotToolDetail( alloc: Allocator, source: *full_transcript_worker.Source, detail: ToolDetailRecord, ) !void { for (source.details.items) |existing| { if (existing.entry_id == detail.entry_id) return; } try source.details.append( alloc, try transcript_store.cloneToolDetailForSnapshot(alloc, detail), ); }
fn appendSnapshotToolHeading( alloc: Allocator, source: *full_transcript_worker.Source, detail: ToolDetailRecord, ) !void { if (snapshotContainsEntry(source.entries.items, detail.entry_id)) return; const bytes = try std.fmt.allocPrint( alloc, "● {s}\n", .{detail.tool_name}, ); errdefer alloc.free(bytes); var created_at_ms: i64 = 0; for (source.entries.items) |entry| { if (entry.createdAtMs() <= 0) continue; created_at_ms = entry.createdAtMs(); break; } try source.entries.insert(alloc, 0, .{ .raw_bytes = .{ .id = detail.entry_id, .created_at_ms = created_at_ms, .bytes = bytes, .class = .tool_status, } }); }
fn appendSnapshotCommandBlock( alloc: Allocator, source: *full_transcript_worker.Source, block: CommandOutputBlock, ) !usize { for (source.command_blocks.items, 0..) |existing, index| { if (existing.entry_id == block.entry_id and command_output_runtime.sameLifecycleId( existing.lifecycle_id, block.lifecycle_id, )) return index; } try source.command_blocks.append( alloc, try full_transcript_worker.cloneCommandBlockForPageSnapshot(alloc, block), ); return source.command_blocks.items.len - 1; }
fn prepareFullTranscriptProjectionLayout(self: *TranscriptRuntime) void { self.full_transcript = self.full_transcript.prepare_projection( self.layout.cols, ); }
fn buildFullTranscriptProjectionUncached( self: *TranscriptRuntime, alloc: Allocator, full_diff_resolver: ?full_transcript_screen.FullDiffResolver, anchor_entry_id: ?u32, checkpoint: ?*build_checkpoint.BuildCheckpoint, ) !full_transcript_screen.Projection { return self.buildFullTranscriptProjectionUncachedFrom( alloc, full_diff_resolver, anchor_entry_id, 0, checkpoint, ); }
fn buildFullTranscriptProjectionUncachedFrom( self: *TranscriptRuntime, alloc: Allocator, full_diff_resolver: ?full_transcript_screen.FullDiffResolver, anchor_entry_id: ?u32, start_index: usize, checkpoint: ?*build_checkpoint.BuildCheckpoint, ) !full_transcript_screen.Projection { var relationships = try tool_group_projection.buildExpandedRelationshipsInterruptible( alloc, self.entries.items, self.tool_details.items, checkpoint, ); defer relationships.deinit(alloc); var entry_actions = try tool_group_projection.materializeExpandedRelationshipsRangeInterruptible( alloc, &relationships, start_index, self.layout.cols, .{ .marker_style = user_message_card.promptMarkerStyle(), .text_style = ui_render.statusline_style, .reset_style = "\x1b[0m", }, checkpoint, ); defer entry_actions.deinit(alloc); return full_transcript_screen.buildProjectionWithEntryActionsInterruptible( alloc, self.entries.items[start_index..], self.tool_details.items, self.command_output_blocks.items, self.command_output_render.styles, self.layout.cols, anchor_entry_id, full_diff_resolver, entry_actions.entry_actions.items, checkpoint, ); }
pub noinline fn prepareFullTranscriptSurfacePaint( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, projection: *full_transcript_screen.Projection, capability: ?*session_child_store.SessionChildCapability, area: render_engine.frame_layout.FrameRect, ) !FullTranscriptSurfacePaint { return self.prepareFullTranscriptSurfacePaintInterruptible( alloc, metrics, projection, capability, area, null, ) catch |err| switch (err) { error.InputPending => unreachable, else => |other| return other, }; }
pub noinline fn prepareFullTranscriptSurfacePaintInterruptible( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, projection: *full_transcript_screen.Projection, capability: ?*session_child_store.SessionChildCapability, area: render_engine.frame_layout.FrameRect, checkpoint: ?*build_checkpoint.BuildCheckpoint, ) !FullTranscriptSurfacePaint { if (self.installedFullTranscriptPreparedWindow(projection)) |window| { self.full_transcript_prepared_page_visible = true; const page = &self.full_transcript_installed_page.?; const measurement = self.installedPageMeasurement(page); const offset = selectProjectionViewportOffset( self, measurement, area.height(), ); const cached_rows = @as(u32, window.source.preview.natural_visual_rows); const cache_end = window.start_row +| cached_rows; if (offset >= window.start_row and (offset +| area.height() <= cache_end or cache_end == measurement.total_rows)) { const local_offset = offset - window.start_row; debug_trace.logf( "full_transcript_cache", "window cols={d} offset={d} local_offset={d} visible={d} source=indexed rows={d} cache_start={d} cache_rows={d}", .{ self.layout.cols, offset, local_offset, area.height(), measurement.total_rows, window.start_row, cached_rows, }, ); const prepared = try transcript_painter.prepareIndexedFullTranscriptSurfacePaintForArea( self, alloc, metrics, &window.source, area, local_offset, ); page.presented_offset = offset; return .{ .borrowed_source = &window.source, .prepared = prepared, }; } try self.ensureFullTranscriptWindowLoad( page, measurement, offset, area.height(), ); const stable_offset = @min( @max(page.presented_offset, window.start_row), cache_end -| @min(@as(u32, area.height()), cached_rows), ); const prepared = try transcript_painter.prepareIndexedFullTranscriptSurfacePaintForArea( self, alloc, metrics, &window.source, area, stable_offset -| window.start_row, ); return .{ .borrowed_source = &window.source, .prepared = prepared, }; } const source_bytes = try full_transcript_screen.renderProjectionViewportSourceWithSelectorInterruptible( alloc, projection, capability, self.layout.cols, area.height(), .{ .context = self, .select_offset = selectProjectionViewportOffset }, checkpoint, ); var source = try self.prepareFullTranscriptViewportSource(alloc, source_bytes); errdefer source.deinit(alloc); const prepared = try transcript_painter.preparePreselectedTranscriptSurfacePaintFromSourceForArea( self, alloc, metrics, &source, area, ); return .{ .owned_source = source, .prepared = prepared }; }
fn installedFullTranscriptPreparedWindow( self: *TranscriptRuntime, projection: *const full_transcript_screen.Projection, ) ?*full_transcript_worker.PreparedWindow { if (self.full_transcript_installed_page_retired) return null; const page = if (self.full_transcript_installed_page) |*value| value else return null; if (&page.projection != projection) return null; return if (page.prepared_window) |*window| window else null; }
fn installedPageMeasurement( self: *const TranscriptRuntime, page: *const InstalledFullTranscriptPage, ) full_transcript_screen.ProjectionMeasurement { _ = self; if (page.measurement_snapshot_ready) { return .{ .total_rows = page.measured_total_rows, .anchor_row = page.measured_anchor_row, .item_rows = page.measured_item_rows, }; } return .{ .total_rows = page.projection.measured_total_rows, .anchor_row = page.projection.measured_anchor_row, .item_rows = page.projection.measured_item_rows.items, }; }
fn ensureFullTranscriptWindowLoad( self: *TranscriptRuntime, page: *InstalledFullTranscriptPage, measurement: full_transcript_screen.ProjectionMeasurement, target_offset: u32, visible_rows: u16, ) !void { if (self.full_transcript_page_load.busy()) return; if (self.full_transcript_window_load.hasTarget(target_offset)) return; if (self.full_transcript_window_load.busy()) { self.full_transcript_window_load.cancelActive(); return; } const request = full_transcript_worker.preparedWindowRequest( page.source.request, measurement.total_rows, target_offset, visible_rows, ); try self.full_transcript_window_load.schedule( request, &page.projection, if (page.source.capability) |*capability| capability else null, ); }
fn borrowsInstalledFullTranscriptSource( self: *TranscriptRuntime, source: *const TranscriptPreparationSource, ) bool { if (self.full_transcript_installed_page_retired) return false; const page = if (self.full_transcript_installed_page) |*value| value else return false; const window = if (page.prepared_window) |*value| value else return false; return &window.source == source; }
fn selectProjectionViewportOffset( context: *anyopaque, measurement: full_transcript_screen.ProjectionMeasurement, visible_rows: u16, ) u32 { const self: *TranscriptRuntime = @ptrCast(@alignCast(context)); const selection = self.full_transcript.select_visual_offset( measurement.total_rows, visible_rows, measurement.item_rows, ); self.full_transcript = selection.state; return selection.offset; }
pub fn prepareTranscriptSurfacePaintFromSourceForArea( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, source: *const TranscriptPreparationSource, area: render_engine.frame_layout.FrameRect, ) !transcript_painter.PreparedTranscriptSurfacePaint { return self.prepareTranscriptSurfacePaintFromSourceForFrame( alloc, metrics, source, area, false, ); }
pub fn prepareTranscriptSurfacePaintFromSourceForFrame( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, source: *const TranscriptPreparationSource, area: render_engine.frame_layout.FrameRect, footer_reservation_changed: bool, ) !transcript_painter.PreparedTranscriptSurfacePaint { return transcript_painter.prepareTranscriptSurfacePaintFromSourceForFrame( self, alloc, metrics, source, area, footer_reservation_changed, ); }
pub fn prepareTranscriptSurfacePaintForArea( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, area: render_engine.frame_layout.FrameRect, ) !transcript_painter.PreparedTranscriptSurfacePaint { return transcript_painter.prepareTranscriptSurfacePaintForArea(self, alloc, metrics, area); }
pub fn prepareTranscriptSurfacePaintForAreaTrackingEntry( self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, area: render_engine.frame_layout.FrameRect, entry_id: ?u32, ) !transcript_painter.PreparedTranscriptSurfacePaint { return transcript_painter.prepareTranscriptSurfacePaintForAreaTrackingEntry( self, alloc, metrics, area, entry_id, ); }
pub fn previewTranscriptFlow( self: *TranscriptRuntime, alloc: Allocator, ) !render_engine.frame_layout.TranscriptFlowPreview { return transcript_painter.previewTranscriptFlow(self, alloc); }
pub fn paintPreparedTranscriptIntoSurface( self: *TranscriptRuntime, alloc: Allocator, surface: *render_engine.frame_surface.FrameSurface, prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, ) !render_engine.paint_plan.TranscriptPaintResult { return transcript_painter.paintPreparedTranscriptIntoSurface(self, alloc, surface, prepared); }
pub fn markTranscriptDirty(self: *TranscriptRuntime) void { self.transcript_band_dirty = true; self.render_requests.request(.transcript); }
fn markTranscriptContentPaint(self: *TranscriptRuntime) void { self.transcript_band_dirty = true; if (self.deferLiveFullTranscriptRefresh()) return; self.render_requests.request(.transcript); }
fn deferLiveFullTranscriptRefresh(self: *const TranscriptRuntime) bool { if (self.full_transcript.depth != .full or self.command_output_display.open_command_block == null) { return false; } if (self.full_transcript_page_load.busy() or self.full_transcript_window_load.busy()) return true; if (self.full_transcript_installed_page_retired) return false; const page = if (self.full_transcript_installed_page) |*value| value else return false; if (page.source.request.cols != self.layout.cols or !std.meta.eql(page.source.request.anchor, self.full_transcript_page_anchor)) { return false; } return true; }
pub fn nativeHistoryActive(self: *const TranscriptRuntime) bool { return switch (self.transcript_commit_state) { .invalid => false, .stable => |anchor| anchor.history_visual_offset > 0, .recovering => |receipt| receipt.accepted_history_visual_offset > 0, }; }
pub fn markTranscriptContentDirty(self: *TranscriptRuntime) void { self.full_transcript_content_revision +%= 1; debug_trace.logf( "full_transcript_cache", "content_dirty content_revision={d} command_open={s}", .{ self.full_transcript_content_revision, if (self.command_output_display.open_command_block == null) "false" else "true", }, ); self.markTranscriptContentPaint(); }
pub fn markTranscriptStructureDirty(self: *TranscriptRuntime) void { self.full_transcript_content_revision +%= 1; debug_trace.logf( "full_transcript_cache", "structure_dirty content_revision={d}", .{self.full_transcript_content_revision}, ); self.markTranscriptDirty(); }
pub fn markTranscriptContentDirtyFrom( self: *TranscriptRuntime, entry_id: u32, ) void { self.full_transcript_content_revision +%= 1; debug_trace.logf( "full_transcript_cache", "content_dirty_from entry_id={d} content_revision={d} command_open={s}", .{ entry_id, self.full_transcript_content_revision, if (self.command_output_display.open_command_block == null) "false" else "true", }, ); self.markTranscriptContentPaint(); }
pub fn markTranscriptCommandOutputDirty(self: *TranscriptRuntime) void { self.full_transcript_content_revision +%= 1; debug_trace.logf( "full_transcript_cache", "command_output_dirty content_revision={d}", .{self.full_transcript_content_revision}, ); self.markTranscriptContentPaint(); }
pub fn markTranscriptCommandOutputDirtyFrom( self: *TranscriptRuntime, entry_id: u32, ) void { self.full_transcript_content_revision +%= 1; debug_trace.logf( "full_transcript_cache", "command_output_dirty_from entry_id={d} content_revision={d}", .{ entry_id, self.full_transcript_content_revision, }, ); self.markTranscriptContentPaint(); }
pub fn recordFrameInvalidation( shell: *TranscriptRuntime, range: render_engine.paint_plan.FrameInvalidationRange, ) !void { if (range.top == 0 or range.bottom == 0 or range.bottom < range.top) { return error.InvalidFrameInvalidationRange; }
const request_invalidations = shell.render_requests.pendingInvalidations(); if (request_invalidations.len >= render_engine.paint_plan.max_frame_invalidation_ranges) { var collapsed = request_invalidations.collapsedWith(range); const bottom = if (shell.layout.rows == 0) range.bottom else shell.layout.rows; var top = if (shell.owned_top_row == 0) @as(u16, 1) else shell.owned_top_row; if (bottom == 0) { top = range.top; } else if (top > bottom) { top = 1; } collapsed.top = top; collapsed.bottom = bottom; debug_trace.logf( "frame_plan", "collapse_pending_invalidations count={d} incoming_reason={s} collapsed_reason={s} top={d} bottom={d}", .{ request_invalidations.len, @tagName(range.reason), @tagName(collapsed.reason), collapsed.top, collapsed.bottom, }, ); shell.render_requests.replacePendingInvalidation(collapsed); } else { shell.render_requests.requestInvalidation(range); } }
pub fn normalizeFrameInvalidations( shell: *const TranscriptRuntime, pending: *render_engine.paint_plan.FrameInvalidationSet, ) void { if (shell.layout.rows == 0) return; var i: usize = 0; while (i < pending.len) : (i += 1) { const range = &pending.items[i]; if (range.bottom <= shell.layout.rows and range.top <= range.bottom) continue; range.top = if (shell.owned_top_row == 0 or shell.owned_top_row > shell.layout.rows) 1 else shell.owned_top_row; range.bottom = shell.layout.rows; } }
/// Updates the capped terminal cursor and returns the uncapped text walk. /// Scroll calculations must use `result.end_row`, not `self.cursor_row`, /// which cannot represent overflow below the viewport. Wrap-exact walks may /// return `layout.cols + 1` in `end_col`. pub fn advanceCursor(self: *TranscriptRuntime, text: []const u8) WalkResult { const result = walkText(@as(u32, self.cursor_row), self.cursor_col, text, self.layout.cols); self.cursor_col = result.end_col; self.cursor_row = @intCast(@min(result.end_row, @as(u32, self.layout.rows))); return result; }
pub fn updateExtraInputRows(self: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, new_extra: u16) !void { _ = alloc; _ = metrics; if (new_extra == self.extra_input_rows) return; const old_extra = self.extra_input_rows; self.extra_input_rows = new_extra; self.markTranscriptDirty(); self.footer_viewport.invalidateAfterExternalClear();
if (self.layout.rows > 0) { const top = if (self.owned_top_row == 0 or self.owned_top_row > self.layout.rows) @as(u16, 1) else self.owned_top_row; try self.recordFrameInvalidation(.{ .reason = .external_clear, .top = top, .bottom = self.layout.rows, }); } debug_trace.logf( "frame_plan", "footer_extra_update_frame_deferred old={d} new={d} owned_top={d} rows={d}", .{ old_extra, new_extra, self.owned_top_row, self.layout.rows }, ); }
pub fn resetCursorTracking(self: *TranscriptRuntime) void { self.cursor_row = 1; self.cursor_col = 1; }};
test "transcript runtime initializer preserves an empty compact source cache" { var runtime = TranscriptRuntime.init(); defer runtime.deinit(std.testing.allocator);
for (runtime.compact_transcript_source_cache.entries) |entry| { try std.testing.expect(entry == null); } try std.testing.expectEqual( @as(usize, 0), runtime.compact_transcript_source_cache.next_replacement, );}
fn totalVisualRows(line_rows: []const u16) u32 { var total: u32 = 0; for (line_rows) |rows| { total += rows; } return total;}
const AcceptedTranscriptRows = struct { resize_reflow_rows: u16, semantic_rows: u32, semantic_progress_rows: u32,};
const TranscriptTransitionCommit = struct { accepted: AcceptedTranscriptRows, remaining_resize_reflow_rows: u16, remaining_semantic_rows: u32, remaining_semantic_progress_rows: u32, remaining_unplanned_scroll_rows: u16, complete: bool, stable: bool, committed_measured_history_origin: ?transcript_painter.MeasuredHistoryOrigin,};
const TranscriptRecoveryCommit = struct { accepted_visual_offset: u32, accepted_history_visual_offset: u32, materialized_visual_offset: u32, materialized_visual_rows: u16, materialized_flow_len: ?usize, materialized_cursor_row: u16, materialized_cursor_col: u16, document_append_committed: bool,};
fn resolveRecoveryMaterializedEndpoint( transition: *const TranscriptTransition, result: render_engine.terminal_diff.FrameCommitResult, commit: TranscriptTransitionCommit,) MaterializedEndpoint { var planned = transition.materialized; if (!commit.complete and !transition.document_append.isEmpty() and transition.document_append.clear == .rows) { // Replaying history does not prove that the subsequent viewport paint // reached its endpoint. Recovery must repaint before resuming there. debug_trace.logf("scroll", "history replay endpoint unconfirmed after partial frame", .{}); planned.flow_len = null; return planned; } if (commit.complete or result.document_append_applied() or !transition.recovery_progress_compatible or !transition.recovery_tracks_semantic_progress or transition.recovery_rebase) { return planned; } return transition.materialized_without_append;}
fn resolveTranscriptTransitionCommit( transition: *const TranscriptTransition, result: render_engine.terminal_diff.FrameCommitResult, scroll_commit: render_engine.frame_scroll_plan.FrameScrollCommit,) TranscriptTransitionCommit { const accepted = acceptedTranscriptRowsForInlineRows( scroll_commit.accepted_rows.inline_advance_rows, transition.resize_reflow_rows, transition.semantic_rows, transition.semantic_progress_rows, ); const remaining_resize_reflow_rows = transition.resize_reflow_rows - accepted.resize_reflow_rows; const remaining_semantic_rows = transition.semantic_rows - accepted.semantic_rows; const remaining_semantic_progress_rows = transition.semantic_progress_rows - accepted.semantic_progress_rows; const remaining_unplanned_scroll_rows = scroll_commit.unplanned_terminal_scroll_rows; const complete = result.is_committed() and scroll_commit.complete(); return .{ .accepted = accepted, .remaining_resize_reflow_rows = remaining_resize_reflow_rows, .remaining_semantic_rows = remaining_semantic_rows, .remaining_semantic_progress_rows = remaining_semantic_progress_rows, .remaining_unplanned_scroll_rows = remaining_unplanned_scroll_rows, .complete = complete, .stable = complete and !transition.recovery_projection_deferred and remaining_resize_reflow_rows == 0 and remaining_semantic_rows == 0 and remaining_unplanned_scroll_rows == 0, .committed_measured_history_origin = transcript_painter.advanceMeasuredHistoryOriginForCommittedRows( transition.target_flow, transition.measured_history_origin, transition.target_layout.terminal_cols, accepted.resize_reflow_rows, ), };}
fn resolveTranscriptRecoveryCommit( runtime: *const TranscriptRuntime, transition: *const TranscriptTransition, result: render_engine.terminal_diff.FrameCommitResult, commit: TranscriptTransitionCommit,) TranscriptRecoveryCommit { const source_offset = switch (runtime.transcript_commit_state) { .stable => |anchor| committedProjectionVisualOffset(anchor), .recovering => |receipt| receipt.accepted_visual_offset, .invalid => 0, }; const source_history_offset = switch (runtime.transcript_commit_state) { .stable => |anchor| anchor.history_visual_offset, .recovering => |receipt| receipt.accepted_history_visual_offset, .invalid => 0, }; const projection_base_offset = if (transition.recovery_rebase and !transition.recovery_progress_compatible) transition.visual_offset else source_offset; const accepted_visual_offset = projection_base_offset + commit.accepted.semantic_progress_rows; const materialized = resolveRecoveryMaterializedEndpoint(transition, result, commit); return .{ .accepted_visual_offset = accepted_visual_offset, .accepted_history_visual_offset = @min( transition.total_visual_rows, source_history_offset +| commit.accepted.semantic_rows, ), .materialized_visual_offset = materialized.visual_offset, .materialized_visual_rows = materialized.visual_rows, .materialized_flow_len = materialized.flow_len, .materialized_cursor_row = materialized.cursor_row, .materialized_cursor_col = materialized.cursor_col, .document_append_committed = transition.recovery_tracks_semantic_progress and materialized.visual_offset > accepted_visual_offset, };}
test "resume view snapshot captures visible transcript styling" { const alloc = std.testing.allocator; var runtime: TranscriptRuntime = .{}; defer runtime.deinit(alloc); runtime.layout = .{ .rows = 6, .cols = 12, .content_bottom = 3, .divider_top_row = 4, .input_row = 5, .divider_bottom_row = 5, .hint_row = 6, }; try runtime.enableShadowVt(alloc); try runtime.shadow_vt.?.feed( "\x1b[1;1Hnot cached\x1b[2;1H" ++ "\x1b]8;;https://example.com\x1b\\" ++ "\x1b[1;3;38;2;1;2;3m\x1b[48;5;245mvisible one\x1b[0m" ++ "\x1b]8;;\x1b\\\x1b[3;1H\x1b[31mvisible two\x1b[0m" ++ "\x1b[4;1Hfooter", ); runtime.has_painted_transcript = true; runtime.last_visible_transcript_top_row = 2; runtime.last_visible_transcript_last_row = 3;
var snapshot = (try runtime.snapshotVisibleTranscriptText(alloc, 128)).?; defer snapshot.deinit(alloc); try std.testing.expect(snapshot.complete); try std.testing.expectEqual(@as(u16, 6), snapshot.terminal_rows); try std.testing.expectEqual(@as(u16, 12), snapshot.terminal_cols); try std.testing.expectEqualStrings( "\x1b[0m\x1b[1m\x1b[3m\x1b[38;2;1;2;3m" ++ "\x1b[48;5;245mvisible one\x1b[0m\n" ++ "\x1b[0m\x1b[31mvisible two\x1b[0m\n", snapshot.text, );}
test "resume view snapshot preserves the committed footer gap" { const alloc = std.testing.allocator; var runtime: TranscriptRuntime = .{}; defer runtime.deinit(alloc); runtime.layout.rows = 6; runtime.layout.cols = 12; runtime.committed_frame_layout.footer_area = .{ .top = 4, .bottom = 6 }; try runtime.enableShadowVt(alloc); try runtime.shadow_vt.?.feed("\x1b[1;1Hvisible"); runtime.has_painted_transcript = true; runtime.last_visible_transcript_top_row = 1; runtime.last_visible_transcript_last_row = 1;
var snapshot = (try runtime.snapshotVisibleTranscriptText(alloc, 128)).?; defer snapshot.deinit(alloc); try std.testing.expect(snapshot.complete); try std.testing.expectEqualStrings("visible\n\n\n", snapshot.text);}
test "resume view snapshot omits a visible leading welcome entry" { const alloc = std.testing.allocator; var runtime: TranscriptRuntime = .{}; defer runtime.deinit(alloc); runtime.layout = .{ .rows = 6, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 6, .hint_row = 6, }; var metrics: Metrics = .{}; try runtime.writeTranscriptClassified( alloc, &metrics, "fx · Run /help for commands\n\n", true, .welcome, ); try runtime.enableShadowVt(alloc); try runtime.shadow_vt.?.feed( "\x1b[1;1Hfx · Run /help for commands" ++ "\x1b[3;1Hvisible one\x1b[4;1Hvisible two", ); runtime.has_painted_transcript = true; runtime.last_visible_transcript_top_row = 1; runtime.last_visible_transcript_last_row = 4; runtime.last_viewport_selection = .{ .top_row = 1, .bottom_row = 4, .start_line = 0, .partial_skip_rows = 0, .line_count = 4, };
var snapshot = (try runtime.snapshotVisibleTranscriptText(alloc, 128)).?; defer snapshot.deinit(alloc); try std.testing.expect(snapshot.complete); try std.testing.expectEqualStrings("visible one\nvisible two\n", snapshot.text);}
test "resume view snapshot keeps one complete UTF-8 row at the byte limit" { const alloc = std.testing.allocator; var runtime: TranscriptRuntime = .{}; defer runtime.deinit(alloc); runtime.layout.rows = 6; runtime.layout.cols = 12; try runtime.enableShadowVt(alloc); try runtime.shadow_vt.?.feed("\x1b[1;1Hééé"); runtime.has_painted_transcript = true; runtime.last_visible_transcript_top_row = 1; runtime.last_visible_transcript_last_row = 1;
var snapshot = (try runtime.snapshotVisibleTranscriptText(alloc, 7)).?; defer snapshot.deinit(alloc); try std.testing.expect(snapshot.complete); try std.testing.expectEqualStrings("ééé\n", snapshot.text); try std.testing.expect(std.unicode.utf8ValidateSlice(snapshot.text));}
test "resume view snapshot drops oldest rows and retains the newest tail" { const alloc = std.testing.allocator; var runtime: TranscriptRuntime = .{}; defer runtime.deinit(alloc); runtime.layout.rows = 6; runtime.layout.cols = 12; try runtime.enableShadowVt(alloc); try runtime.shadow_vt.?.feed( "\x1b[1;1Holdest\x1b[2;1Høø\x1b[3;1HTAIL", ); runtime.has_painted_transcript = true; runtime.last_visible_transcript_top_row = 1; runtime.last_visible_transcript_last_row = 3;
var snapshot = (try runtime.snapshotVisibleTranscriptText(alloc, 10)).?; defer snapshot.deinit(alloc); try std.testing.expect(!snapshot.complete); try std.testing.expectEqualStrings("øø\nTAIL\n", snapshot.text);}
test "resume view snapshot returns no text for empty visible rows" { const alloc = std.testing.allocator; var runtime: TranscriptRuntime = .{}; defer runtime.deinit(alloc); runtime.layout.rows = 6; runtime.layout.cols = 12; try runtime.enableShadowVt(alloc); runtime.has_painted_transcript = true; runtime.last_visible_transcript_top_row = 1; runtime.last_visible_transcript_last_row = 2;
try std.testing.expect( try runtime.snapshotVisibleTranscriptText(alloc, 128) == null, );}
fn acceptedTranscriptRows( scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, committed_terminal_scroll_rows: u16, resize_reflow_rows: u16, semantic_rows: u32, semantic_progress_rows: u32,) AcceptedTranscriptRows { const accepted_scroll_rows = scroll_plan.commit(committed_terminal_scroll_rows).accepted_rows; return acceptedTranscriptRowsForInlineRows( accepted_scroll_rows.inline_advance_rows, resize_reflow_rows, semantic_rows, semantic_progress_rows, );}
fn acceptedTranscriptRowsForInlineRows( accepted_inline_rows: u32, resize_reflow_rows: u16, semantic_rows: u32, semantic_progress_rows: u32,) AcceptedTranscriptRows { const accepted_resize_reflow_rows: u16 = @intCast(@min( accepted_inline_rows, @as(u32, resize_reflow_rows), )); const accepted_semantic_rows = @min( accepted_inline_rows - @as(u32, accepted_resize_reflow_rows), semantic_rows, ); std.debug.assert(semantic_progress_rows <= semantic_rows); const unprojected_semantic_rows = semantic_rows - semantic_progress_rows; const accepted_unprojected_semantic_rows = @min( accepted_semantic_rows, unprojected_semantic_rows, ); return .{ .resize_reflow_rows = accepted_resize_reflow_rows, .semantic_rows = accepted_semantic_rows, .semantic_progress_rows = accepted_semantic_rows - accepted_unprojected_semantic_rows, };}
fn resolveAndSealTranscriptTransitionForRuntimeTest( runtime: *TranscriptRuntime, alloc: Allocator, source: *TranscriptPreparationSource, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, target_layout: render_engine.frame_layout.CommittedLayoutSnapshot, plan: *render_engine.paint_plan.PaintPlan, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan,) !TranscriptTransition { const scroll_facts = try runtime.prepareTranscriptScrollFactsForFrame( alloc, source, prepared, false, false, ); const destructive_invalidation = render_engine.frame_retention.transcriptAreaHasDestructiveInvalidation( runtime.committed_frame_layout.transcript_area, plan.invalidation, ); const resolved = try runtime.resolveTranscriptTransitionTargetForFrame( alloc, source, prepared, target_layout, scroll_plan, scroll_facts, destructive_invalidation, plan.activity == .overlay_entry, ); resolved.applyToPaintPlan(plan); return runtime.sealTranscriptTransition( alloc, source, prepared, plan, resolved, );}
fn testFrameCommitResult( committed_rows: u16, shadow_state: render_engine.terminal_diff.ShadowCommitState,) render_engine.terminal_diff.FrameCommitResult { return .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = false, .terminal_scroll_rows_committed = committed_rows, .document_append_committed = false, .committed_cursor_row = 1, .committed_cursor_col = 1, .shadow_state = shadow_state, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), };}
test "transition commit snapshot derives accepted rows and stable state from frame result" { const scroll_plan = render_engine.frame_scroll_plan.merge(8, 1, 0, 2); var transition = TranscriptTransition{ .scroll_plan = scroll_plan, .document_append = .{}, .materialized_without_append = .{}, .resize_reflow_rows = 1, .semantic_rows = 1, .semantic_progress_rows = 1, .target_flow = &.{}, .target_cache = .empty, .folded_summary_indices = &.{}, .target_layout = .{ .terminal_rows = 8, .terminal_cols = 40, }, .selection = .{ .top_row = 1, .bottom_row = 4, .start_line = 0, .partial_skip_rows = 0, .line_count = 1, }, .visual_offset = 0, .history_visual_offset = 0, .total_visual_rows = 2, .cursor_row = 4, .cursor_col = 1, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = 4, .trace_transcript_frame = false, .trace_visible_rows = 4, };
const partial_result = testFrameCommitResult(1, .terminal_partial_write); const partial = resolveTranscriptTransitionCommit( &transition, partial_result, scroll_plan.commit(partial_result.terminal_scroll_rows_committed), ); try std.testing.expectEqual(@as(u16, 1), partial.accepted.resize_reflow_rows); try std.testing.expectEqual(@as(u32, 0), partial.accepted.semantic_rows); try std.testing.expectEqual(@as(u16, 0), partial.remaining_resize_reflow_rows); try std.testing.expectEqual(@as(u32, 1), partial.remaining_semantic_rows); try std.testing.expect(!partial.complete); try std.testing.expect(!partial.stable);
const complete_result = testFrameCommitResult(2, .committed); const complete = resolveTranscriptTransitionCommit( &transition, complete_result, scroll_plan.commit(complete_result.terminal_scroll_rows_committed), ); try std.testing.expectEqual(@as(u16, 1), complete.accepted.resize_reflow_rows); try std.testing.expectEqual(@as(u32, 1), complete.accepted.semantic_rows); try std.testing.expectEqual(@as(u32, 1), complete.accepted.semantic_progress_rows); try std.testing.expectEqual(@as(u16, 0), complete.remaining_resize_reflow_rows); try std.testing.expectEqual(@as(u32, 0), complete.remaining_semantic_rows); try std.testing.expect(complete.complete); try std.testing.expect(complete.stable);
transition.recovery_projection_deferred = true; const deferred_result = testFrameCommitResult(2, .committed); const deferred = resolveTranscriptTransitionCommit( &transition, deferred_result, scroll_plan.commit(deferred_result.terminal_scroll_rows_committed), ); try std.testing.expect(deferred.complete); try std.testing.expect(!deferred.stable);}
test "resume recovery selects a safe materialized endpoint for every outcome" { var target_flow = [_]u8{ 'f', 'i', 'r', 's', 't', '\n', 's', 'e', 'c', 'o', 'n', 'd' }; const scroll_plan = render_engine.frame_scroll_plan.merge(8, 1, 0, 1); var runtime = TranscriptRuntime{ .layout = testLayoutWithRows(8), .committed_frame_layout = .{ .terminal_rows = 8, .terminal_cols = 80, .transcript_area = .{ .top = 1, .bottom = 8 }, }, }; defer runtime.deinit(std.testing.allocator); const document_append: render_engine.frame_scroll_plan.FrameDocumentAppend = .{ .bytes = "first\n", .start_row = 2, .start_col = 3, }; const planned: MaterializedEndpoint = .{ .visual_offset = 1, .visual_rows = 1, .flow_len = 6, .cursor_row = 3, .cursor_col = 1, }; var transition = TranscriptTransition{ .scroll_plan = scroll_plan, .document_append = document_append, .semantic_rows = 2, .semantic_progress_rows = 2, .recovery_progress_compatible = true, .recovery_tracks_semantic_progress = true, .target_flow = &target_flow, .target_cache = .empty, .folded_summary_indices = &.{}, .target_layout = .{ .terminal_rows = 8, .terminal_cols = 80, }, .selection = .{ .top_row = 1, .bottom_row = 8, .start_line = 0, .partial_skip_rows = 0, .line_count = 2, }, .visual_offset = 1, .history_visual_offset = 1, .total_visual_rows = 2, .materialized = planned, .materialized_without_append = runtime.materializedEndpointWithoutAppend( document_append, planned, ), .cursor_row = 3, .cursor_col = 1, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = 3, .trace_transcript_frame = false, .trace_visible_rows = 2, }; const result = testFrameCommitResult(0, .terminal_partial_write); const commit = resolveTranscriptTransitionCommit( &transition, result, scroll_plan.commit(result.terminal_scroll_rows_committed), );
const unapplied = resolveTranscriptRecoveryCommit(&runtime, &transition, result, commit); try std.testing.expectEqual(@as(u32, 0), unapplied.materialized_visual_offset); try std.testing.expectEqual(@as(u16, 0), unapplied.materialized_visual_rows); try std.testing.expectEqual(@as(?usize, 0), unapplied.materialized_flow_len); try std.testing.expectEqual(@as(u16, 2), unapplied.materialized_cursor_row); try std.testing.expectEqual(@as(u16, 3), unapplied.materialized_cursor_col);
try std.testing.expectEqualDeep( transition.materialized_without_append, resolveRecoveryMaterializedEndpoint(&transition, result, commit), );
var complete_commit = commit; complete_commit.complete = true; try std.testing.expectEqualDeep( planned, resolveRecoveryMaterializedEndpoint(&transition, result, complete_commit), );
var appended_result = result; appended_result.document_append_committed = true; try std.testing.expectEqualDeep( planned, resolveRecoveryMaterializedEndpoint(&transition, appended_result, commit), );
transition.recovery_progress_compatible = false; try std.testing.expectEqualDeep( planned, resolveRecoveryMaterializedEndpoint(&transition, result, commit), ); transition.recovery_progress_compatible = true;
transition.recovery_tracks_semantic_progress = false; try std.testing.expectEqualDeep( planned, resolveRecoveryMaterializedEndpoint(&transition, result, commit), ); transition.recovery_tracks_semantic_progress = true;
transition.recovery_rebase = true; try std.testing.expectEqualDeep( planned, resolveRecoveryMaterializedEndpoint(&transition, result, commit), ); transition.recovery_rebase = false;
transition.document_append = .{}; transition.materialized_without_append = runtime.materializedEndpointWithoutAppend( .{}, planned, ); const absent = resolveTranscriptRecoveryCommit(&runtime, &transition, result, commit); try std.testing.expectEqual(@as(u32, 1), absent.materialized_visual_offset); try std.testing.expectEqual(@as(u16, 1), absent.materialized_visual_rows); try std.testing.expectEqual(@as(?usize, 6), absent.materialized_flow_len); try std.testing.expectEqual(@as(u16, 3), absent.materialized_cursor_row); try std.testing.expectEqual(@as(u16, 1), absent.materialized_cursor_col);}
test "partial resume publication retains its cursor and requests the next frame" { const alloc = std.testing.allocator; const scroll_plan = render_engine.frame_scroll_plan.merge(8, 1, 0, 128); var runtime = TranscriptRuntime{ .layout = testLayoutWithRows(8) }; defer runtime.deinit(alloc); runtime.pending_resume_source = try source_preparation.prepareFullTranscriptViewportSource( &runtime, alloc, try alloc.dupe(u8, "pending"), ); var transition = TranscriptTransition{ .scroll_plan = scroll_plan, .document_append = .{}, .materialized_without_append = .{}, .semantic_rows = 256, .semantic_progress_rows = 256, .recovery_progress_compatible = true, .recovery_tracks_semantic_progress = true, .target_flow = runtime.pending_resume_source.?.bytes, .target_flow_owned = false, .target_cache = .empty, .target_cache_unchanged = true, .folded_summary_indices = &.{}, .target_layout = .{ .terminal_rows = 8, .terminal_cols = 40 }, .selection = .{ .top_row = 1, .bottom_row = 4, .start_line = 252, .partial_skip_rows = 0, .line_count = 256, }, .visual_offset = 256, .history_visual_offset = 128, .source_endpoint_visual_offset = 256, .total_visual_rows = 256, .cursor_row = 4, .cursor_col = 1, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = 4, .trace_transcript_frame = false, .trace_visible_rows = 4, }; defer transition.deinit(alloc);
runtime.consumeTranscriptTransition( alloc, &transition, testFrameCommitResult(128, .committed), );
switch (runtime.transcript_commit_state) { .recovering => |receipt| { try std.testing.expectEqual(@as(u32, 128), receipt.accepted_visual_offset); try std.testing.expectEqual(@as(u32, 128), receipt.remaining_semantic_rows); }, .invalid, .stable => return error.TestExpectedRecoveringTranscript, } try std.testing.expectEqualStrings("pending", runtime.pendingResumeFlow()); try std.testing.expect(runtime.render_requests.hasReason(.transcript));}
test "owned stable transition traces a superseded pending resume source" { const alloc = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const root = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "."); defer alloc.free(root); const trace_path = try std.fs.path.join(alloc, &.{ root, "resume-source-drop.log" }); defer alloc.free(trace_path);
debug_trace.resetForTest(); defer debug_trace.resetForTest(); try debug_trace.configureForTestWithScopes(alloc, trace_path, "session");
var runtime = TranscriptRuntime{ .layout = testLayoutWithRows(8) }; defer runtime.deinit(alloc); runtime.bindDetachedCommitAllocator(alloc); runtime.pending_resume_source = try source_preparation.prepareFullTranscriptViewportSource( &runtime, alloc, try alloc.dupe(u8, "stale"), ); var transition = TranscriptTransition{ .scroll_plan = render_engine.frame_scroll_plan.FrameScrollPlan.none(8, 1), .document_append = .{}, .target_flow = try alloc.dupe(u8, "fresh"), .target_cache = .empty, .folded_summary_indices = &.{}, .target_layout = .{ .terminal_rows = 8, .terminal_cols = 80 }, .selection = .{ .top_row = 1, .bottom_row = 1, .start_line = 0, .partial_skip_rows = 0, .line_count = 1, }, .visual_offset = 0, .history_visual_offset = 0, .total_visual_rows = 1, .materialized_without_append = .{}, .cursor_row = 1, .cursor_col = 6, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = 1, .trace_transcript_frame = false, .trace_visible_rows = 1, }; defer transition.deinit(alloc);
runtime.consumeTranscriptTransition( alloc, &transition, testFrameCommitResult(0, .committed), ); debug_trace.shutdown();
var trace_file = try std.Io.Dir.openFileAbsolute(std.testing.io, trace_path, .{}); defer trace_file.close(std.testing.io); const trace = try io_mod.readFileToEnd(alloc, &trace_file, 4096); defer alloc.free(trace); try std.testing.expect(std.mem.find( u8, trace, "pending resume flow dropped reason=superseded bytes=5", ) != null);}
test "stable resume publication starts a fresh retained source epoch" { const alloc = std.testing.allocator; const retained_flow = "tail-1\n" ++ "tail-2\n" ++ "tail-3\n" ++ "tail-final"; const full_flow = "old-1\nold-2\nold-3\nold-4\n" ++ "old-5\nold-6\nold-7\nold-8\n" ++ retained_flow; const layout: Layout = .{ .rows = 8, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }; var runtime = TranscriptRuntime{ .layout = layout, .owned_top_row = 1, .viewport_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); runtime.pending_resume_source = try source_preparation.prepareFullTranscriptViewportSource( &runtime, alloc, try alloc.dupe(u8, full_flow), ); try runtime.transcript.appendSlice(alloc, retained_flow);
var transition = TranscriptTransition{ .scroll_plan = render_engine.frame_scroll_plan.FrameScrollPlan.none( layout.rows, runtime.owned_top_row, ), .document_append = .{}, .materialized_without_append = .{}, .target_flow = runtime.pending_resume_source.?.bytes, .target_flow_owned = false, .target_cache = .empty, .target_cache_unchanged = true, .folded_summary_indices = &.{}, .target_layout = .{ .layout_id = 17, .owned_top = 1, .owned_band = .{ .top = 1, .bottom = 8 }, .body_area = .{ .top = 1, .bottom = 4 }, .transcript_area = .{ .top = 1, .bottom = 4 }, .footer_area = .{ .top = 5, .bottom = 8 }, .solved_frame_height = 8, .terminal_rows = 8, .terminal_cols = 40, }, .selection = .{ .top_row = 1, .bottom_row = 4, .start_line = 8, .partial_skip_rows = 0, .line_count = 12, .last_visible_row = 4, }, .visual_offset = 8, .history_visual_offset = 8, .total_visual_rows = 12, .cursor_row = 4, .cursor_col = 11, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = 4, .trace_transcript_frame = false, .trace_visible_rows = 4, }; defer transition.deinit(alloc); runtime.consumeTranscriptTransition( alloc, &transition, testFrameCommitResult(0, .committed), );
try std.testing.expectEqual(@as(usize, 0), runtime.pendingResumeFlow().len); try std.testing.expectEqualStrings(retained_flow, runtime.transcript.items); try std.testing.expectEqual( TranscriptCommitDiagnosticState.invalid, runtime.transcriptCommitDiagnostic().state, );
var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqualStrings(retained_flow, source.bytes); var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, &source, .{ .top = 1, .bottom = 4 }, ); defer prepared.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), prepared.selection.start_line); try std.testing.expectEqual(@as(usize, 4), prepared.selection.line_count); try std.testing.expectEqual(@as(u16, 4), prepared.cursor.cursor_row);
const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expectEqual(@as(u16, 0), facts.planned_rows); try std.testing.expectEqual(@as(u32, 0), facts.target_visual_offset); try std.testing.expect(facts.recovery_rebase);}
test "oversized resume publication starts without a prior committed frame" { const alloc = std.testing.allocator; const layout = invalidationTestLayout(); var runtime = TranscriptRuntime{ .layout = layout, .owned_top_row = 1, }; defer runtime.deinit(alloc);
var flow: std.ArrayList(u8) = .empty; defer flow.deinit(alloc); for (0..@as(usize, std.math.maxInt(u16)) + 21) |_| { try flow.appendSlice(alloc, "row\n"); } runtime.pending_resume_source = try source_preparation.prepareFullTranscriptViewportSource( &runtime, alloc, try alloc.dupe(u8, flow.items), );
var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, &source, .{ .top = 1, .bottom = layout.content_bottom }, ); defer prepared.deinit(alloc);
const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(facts.target_visual_offset > std.math.maxInt(u16)); try std.testing.expectEqual(@as(u32, std.math.maxInt(u16)), facts.semantic_rows); try std.testing.expectEqual( @as(u16, resume_publication_rows_per_frame), facts.planned_rows, ); try std.testing.expect(facts.recovery_progress_compatible); try std.testing.expect(facts.recovery_tracks_semantic_progress); const scroll_plan = render_engine.frame_scroll_plan.merge( layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); const accepted = acceptedTranscriptRows( scroll_plan, scroll_plan.terminal_scroll_rows, facts.resize_reflow_rows, facts.semantic_rows, facts.semantic_progress_rows, ); try std.testing.expectEqual( @as(u32, resume_publication_rows_per_frame), accepted.semantic_rows, ); try std.testing.expect(accepted.semantic_rows < facts.semantic_rows);
const append_base = (try runtime.resolveTransitionAppendBase( &prepared, scroll_plan.prior_owned_top, facts, false, source.bytes, facts.target_visual_offset, )) orelse return error.TestExpectedResumeAppendBase; try std.testing.expectEqual(@as(usize, 0), append_base.flow_len);}
test "pending tail projection seals against the complete frame layout" { const alloc = std.testing.allocator; const layout = invalidationTestLayout(); var runtime = TranscriptRuntime{ .layout = layout, .owned_top_row = 1, }; defer runtime.deinit(alloc);
var flow: std.ArrayList(u8) = .empty; defer flow.deinit(alloc); for (0..100) |_| try flow.appendSlice(alloc, "row\n"); runtime.pending_resume_source = try source_preparation.prepareFullTranscriptViewportSource( &runtime, alloc, try alloc.dupe(u8, flow.items), );
const candidate = render_engine.frame_layout.solve(.{ .terminal = layout, .owned_top = runtime.owned_top_row, .footer = .{ .natural_rows = 3, .min_rows = 3, .max_rows = 3, }, .transcript = runtime.pending_resume_source.?.preview, .prior = runtime.committed_frame_layout, }); const projection_area = render_engine.frame_layout.FrameRect{ .top = candidate.transcript_area.top, .bottom = candidate.transcript_area.bottom - 1, }; const source = (try runtime.pendingResumeSourceInterruptible(alloc, null)) orelse return error.TestExpectedPendingResumeSource; var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, source, projection_area, ); errdefer prepared.deinit(alloc); const facts = try runtime.prepareTranscriptScrollFactsForFrame( alloc, source, &prepared, false, false, ); const scroll_plan = render_engine.frame_scroll_plan.merge( layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); const footer_rows = render_engine.footer_layout.resolve(.{ .footer_top_for_extra = candidate.footer_area.top, .terminal_rows = layout.rows, .activity_offset = 0, .extra_input_rows = 0, .input_extra = 0, .composer_top_chrome_rows = 0, .picker_rows = 0, .banner_active = false, }); var plan = candidate.toPaintPlan(.{ .footer_rows = footer_rows, .viewport = prepared.selection, .cursor_target = .{ .row = prepared.cursor.cursor_row, .col = prepared.cursor.cursor_col, .visible = true, }, }); const target_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromLayout(candidate); try std.testing.expectError( error.InvalidTranscriptTransition, runtime.resolveTranscriptTransitionTargetForFrameInArea( alloc, source, &prepared, target_layout, .{ .top = projection_area.top, .bottom = target_layout.transcript_area.bottom + 1, }, scroll_plan, facts, false, false, ), ); const resolved = try runtime.resolveTranscriptTransitionTargetForFrameInArea( alloc, source, &prepared, target_layout, projection_area, scroll_plan, facts, false, false, ); try std.testing.expectEqual(projection_area.bottom, resolved.selection().bottom_row); try std.testing.expectEqual( candidate.transcript_area.bottom, resolved.target_layout.transcript_area.bottom, ); resolved.applyToPaintPlan(&plan); var transition = try runtime.sealTranscriptTransition( alloc, source, &prepared, &plan, resolved, ); prepared.deinit(alloc); defer transition.deinit(alloc); try std.testing.expectEqual( candidate.transcript_area.bottom, transition.target_layout.transcript_area.bottom, );}
test "history recovery preserves retained geometry and stages painted frames" { const alloc = std.testing.allocator; for ([_]bool{ false, true }) |repaint| { const layout = invalidationTestLayout(); var runtime = TranscriptRuntime{ .layout = layout, .owned_top_row = 1 }; defer runtime.deinit(alloc); var flow: std.ArrayList(u8) = .empty; defer flow.deinit(alloc); for (0..79) |_| try flow.appendSlice(alloc, "row\n"); try flow.appendSlice(alloc, "row"); var source = try source_preparation.prepareFullTranscriptViewportSource( &runtime, alloc, try alloc.dupe(u8, flow.items), ); defer source.deinit(alloc); const activity = render_engine.frame_layout.ActivityState{ .thinking = .{ .gap_above_activity = 0, .footer_gap_after_activity = 1, } }; const frame_input = render_engine.frame_layout.SolveInput{ .terminal = layout, .owned_top = 1, .footer = .{ .natural_rows = 3, .min_rows = 3, .max_rows = 3 }, .transcript = source.preview, .activity = activity, }; const candidate = render_engine.frame_layout.solve(frame_input); var prior_input = frame_input; if (repaint) prior_input.footer = .{ .natural_rows = 5, .min_rows = 5, .max_rows = 5 }; const committed = render_engine.frame_layout.CommittedLayoutSnapshot.fromLayout( render_engine.frame_layout.solve(prior_input), ); runtime.committed_frame_layout = committed; const committed_selection = ViewportSelection{ .top_row = 1, .bottom_row = 16, .start_line = 62, .partial_skip_rows = 0, .line_count = 80, .last_visible_row = 16, .replaceable_start_row = 1, }; // A pending card left fewer canonical rows in the committed frame. runtime.transcript_commit_state = .{ .stable = .{ .selection = committed_selection, .visual_offset = 64, .history_visual_offset = 62, .total_visual_rows = 80, .flow = try alloc.dupe(u8, flow.items), .cursor_row = 17, .cursor_col = 1, .occupied_last_row = 16, .occupied_last_row_blank = false, .layout_id = committed.layout_id, .normal_buffer_recovery_pending = true, } }; const area = render_engine.frame_layout.FrameRect{ .top = 1, .bottom = if (repaint) 16 else 18 }; var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintFromSourceForFrame( alloc, &metrics, &source, area, repaint, ); defer prepared.deinit(alloc); const facts = try runtime.prepareTranscriptScrollFactsForFrame( alloc, &source, &prepared, repaint, false, ); const scroll = render_engine.frame_scroll_plan.merge(layout.rows, 1, 0, facts.planned_rows); if (repaint) { try std.testing.expectEqual(@as(u32, 64), facts.target_visual_offset); try std.testing.expectEqual(@as(u32, 0), facts.semantic_rows); } const resolved = try runtime.resolveTranscriptTransitionTargetForFrameInArea( alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromLayout(candidate), area, scroll, facts, repaint, false, ); if (repaint) { try std.testing.expect(resolved.bodyDisposition() == .paint); try std.testing.expectEqual(@as(usize, 62), resolved.selection().start_line); try std.testing.expectEqual(@as(u16, 17), resolved.cursorRow()); try std.testing.expectEqual(@as(u16, 1), resolved.cursorCol()); try std.testing.expectEqual(@as(u16, 16), resolved.selection().last_visible_row); } else { try std.testing.expect(resolved.bodyDisposition() == .retain_committed); try std.testing.expectEqualDeep(committed_selection, resolved.selection()); try std.testing.expectEqual(@as(u16, 17), resolved.cursorRow()); try std.testing.expectEqual(@as(u16, 1), resolved.cursorCol()); } const footer_rows = render_engine.footer_layout.resolve(.{ .footer_top_for_extra = candidate.footer_area.top, .terminal_rows = layout.rows, .activity_offset = 0, .extra_input_rows = 0, .input_extra = 0, .composer_top_chrome_rows = 0, .picker_rows = 0, .banner_active = false, }); var plan = candidate.toPaintPlan(.{ .footer_rows = footer_rows, .viewport = resolved.selection(), .activity = render_engine.activity_placement.resolve( .{ .turn_thinking = .{ .label = "Thinking" } }, activity, candidate, ), .cursor_target = .{ .row = resolved.cursorRow(), .col = resolved.cursorCol(), .visible = true, }, }); if (repaint) try plan.invalidation.append(.{ .reason = .external_clear, .top = 1, .bottom = 18 }); var transition = try runtime.sealTranscriptTransition(alloc, &source, &prepared, &plan, resolved); defer transition.deinit(alloc); try std.testing.expectEqualDeep(resolved.selection(), transition.selection); try std.testing.expectEqual(resolved.cursorRow(), transition.cursor_row); try std.testing.expectEqual(resolved.cursorCol(), transition.cursor_col); }}
test "transition commit keeps unplanned physical scroll as recovery debt" { const scroll_plan = render_engine.frame_scroll_plan.merge(8, 1, 0, 2); var transition = TranscriptTransition{ .scroll_plan = scroll_plan, .document_append = .{}, .materialized_without_append = .{}, .semantic_rows = 2, .semantic_progress_rows = 2, .target_flow = &.{}, .target_cache = .empty, .folded_summary_indices = &.{}, .target_layout = .{ .terminal_rows = 8, .terminal_cols = 40, }, .selection = .{ .top_row = 1, .bottom_row = 4, .start_line = 0, .partial_skip_rows = 0, .line_count = 2, }, .visual_offset = 0, .history_visual_offset = 0, .total_visual_rows = 2, .cursor_row = 4, .cursor_col = 1, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = 4, .trace_transcript_frame = false, .trace_visible_rows = 4, };
const result = testFrameCommitResult(scroll_plan.terminal_scroll_rows + 1, .committed); const commit = resolveTranscriptTransitionCommit( &transition, result, scroll_plan.commit(result.terminal_scroll_rows_committed), ); try std.testing.expectEqual(@as(u32, 2), commit.accepted.semantic_rows); try std.testing.expectEqual(@as(u32, 0), commit.remaining_semantic_rows); try std.testing.expectEqual(@as(u16, 1), commit.remaining_unplanned_scroll_rows); try std.testing.expect(!commit.complete); try std.testing.expect(!commit.stable);
var runtime = TranscriptRuntime{ .layout = testLayoutWithRows(8), .owned_top_row = 1, .viewport_top_row = 1, }; defer runtime.deinit(std.testing.allocator); runtime.consumeFrameScrollCommit( std.testing.allocator, scroll_plan, result, &transition, ); const diagnostic = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual(TranscriptCommitDiagnosticState.recovering, diagnostic.state); try std.testing.expectEqual(@as(u16, 1), diagnostic.remaining_unplanned_scroll_rows);}
fn frameInlineScrollBudget(resize_reflow_rows: u16, semantic_rows: u32) u16 { return frameInlineScrollBudgetCapped( resize_reflow_rows, semantic_rows, std.math.maxInt(u32), );}
test "pending resume publication yields after a bounded row batch" { const alloc = std.testing.allocator; var runtime: TranscriptRuntime = .{}; defer runtime.deinit(alloc); const prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .selection = .{ .top_row = 1, .bottom_row = 1, .start_line = 0, .partial_skip_rows = 0, .line_count = 1, } };
try std.testing.expectEqual( @as(u16, 1_000), runtime.plannedTranscriptRows(&prepared, 0, 0, 1_000, 0), ); runtime.pending_resume_source = try source_preparation.prepareFullTranscriptViewportSource( &runtime, alloc, try alloc.dupe(u8, "pending"), ); try std.testing.expectEqual( @as(u16, resume_publication_rows_per_frame), runtime.plannedTranscriptRows(&prepared, 0, 0, 1_000, 0), );}
fn frameInlineScrollBudgetCapped( resize_reflow_rows: u16, semantic_rows: u32, semantic_cap: u32,) u16 { const semantic_budget = @min( @min(semantic_rows, semantic_cap), @as(u32, std.math.maxInt(u16) - resize_reflow_rows), ); return resize_reflow_rows + @as(u16, @intCast(semantic_budget));}
fn projectionVisualRows(total_visual_rows: u32, visual_offset: u32) u16 { return @intCast(@min( total_visual_rows -| visual_offset, @as(u32, std.math.maxInt(u16)), ));}
fn projectionVisualRowsInArea( total_visual_rows: u32, visual_offset: u32, area: render_engine.frame_layout.FrameRect,) u16 { if (area.isEmpty()) return 0; return @min( projectionVisualRows(total_visual_rows, visual_offset), area.height(), );}
fn visualOffsetForSelection(line_rows: []const u16, selection: ViewportSelection) u32 { var offset: u32 = 0; const end = @min(selection.start_line, line_rows.len); for (line_rows[0..end]) |rows| { offset += rows; } return offset + selection.partial_skip_rows;}
fn reflowedRows( grid: *const vt_emulator.Grid, start_row: u16, end_row: u16, target_cols: u16, include_cursor: bool,) u32 { if (target_cols == 0 or start_row == 0 or start_row > end_row or start_row > grid.rows) return 0;
var total: u32 = 0; var row = start_row; const last_row = @min(end_row, grid.rows); while (row <= last_row) : (row += 1) { var occupied_col: u16 = 0; var col: u16 = 1; while (col <= grid.cols) : (col += 1) { const cell = grid.cellAt(row, col).?; if (cell.codepoint != ' ' or cell.combining_suffix_id != 0 or !cell.style.eql(.{})) { occupied_col = col; } } if (include_cursor and row == grid.cursor_row) { occupied_col = @max(occupied_col, grid.cursor_col); } total += if (occupied_col == 0) 1 else (@as(u32, occupied_col) - 1) / target_cols + 1; } return total;}
test "reflowed rows count a combining suffix attached to a space" { var grid = try vt_emulator.Grid.init(std.testing.allocator, 4, 1); defer grid.deinit(); try grid.feed(" \u{0301}");
try std.testing.expectEqual( @as(u32, 2), reflowedRows(&grid, 1, 1, 2, false), );}
fn invalidationTestLayout() Layout { return .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, };}
test "frame invalidations move into one request snapshot" { var runtime = TranscriptRuntime{ .layout = invalidationTestLayout(), .owned_top_row = 5 }; defer runtime.deinit(std.testing.allocator);
try runtime.recordFrameInvalidation(.{ .reason = .resize, .top = 5, .bottom = 24 }); try runtime.recordFrameInvalidation(.{ .reason = .external_clear, .top = 21, .bottom = 24 });
var attempt = (try runtime.render_requests.beginAttempt()).?; try std.testing.expectEqual(@as(u8, 2), attempt.snapshot.invalidations.len); try std.testing.expectEqual(render_engine.paint_plan.FrameInvalidationReason.resize, attempt.snapshot.invalidations.ranges()[0].reason); try std.testing.expectEqual(render_engine.paint_plan.FrameInvalidationReason.external_clear, attempt.snapshot.invalidations.ranges()[1].reason); attempt.commit(0, render_request.animation_interval_ms, false); try std.testing.expect((try runtime.render_requests.beginAttempt()) == null);}
test "recording a frame invalidation schedules request-owned work" { var runtime = TranscriptRuntime{ .layout = invalidationTestLayout(), .owned_top_row = 5 }; defer runtime.deinit(std.testing.allocator);
try runtime.recordFrameInvalidation(.{ .reason = .external_clear, .top = 5, .bottom = 24 });
try std.testing.expect(runtime.render_requests.hasReason(.external_damage));}
test "terminal reset drops stale mid-scrollback footer clear before reanchor" { var metrics: Metrics = .{}; var runtime = TranscriptRuntime{ .layout = .{ .rows = 36, .cols = 120, .content_bottom = 32, .divider_top_row = 33, .input_row = 34, .divider_bottom_row = 35, .hint_row = 36, }, .owned_top_row = 22, .viewport_top_row = 22, .has_painted_transcript = true, .min_visible_viewport_rows = 11, }; defer runtime.deinit(std.testing.allocator); runtime.footer_viewport.has_frame = true; runtime.footer_viewport.geometry.top = 25; runtime.footer_viewport.externally_invalidated = true;
try runtime.recordFrameInvalidation(.{ .reason = .external_clear, .top = 25, .bottom = 36, });
try runtime.requestTerminalReset(&metrics);
try std.testing.expectEqual(@as(u16, 1), runtime.owned_top_row); try std.testing.expectEqual(@as(u16, 1), runtime.viewport_top_row); try std.testing.expect(!runtime.footer_viewport.has_frame); try std.testing.expect(!runtime.footer_viewport.externally_invalidated);
const pending = runtime.render_requests.pendingInvalidations(); try std.testing.expectEqual(@as(u8, 1), pending.len); try std.testing.expectEqual( render_engine.paint_plan.FrameInvalidationReason.viewport_reanchor, pending.ranges()[0].reason, ); try std.testing.expectEqual(@as(u16, 1), pending.ranges()[0].top); try std.testing.expectEqual(@as(u16, 36), pending.ranges()[0].bottom);
var plan = render_engine.paint_plan.PaintPlan{ .layout = runtime.layout, .viewport = .{ .top_row = 1, .bottom_row = 3, .start_line = 0, .partial_skip_rows = 0, .line_count = 3, .last_visible_row = 3, .last_visible_row_blank = false, .tail_kind = null, .replaceable_start_row = 1, .split_active = false, .split_prefix_lines = 0, .split_suffix_start_line = 0, }, .footer = .{ .top = 4, .top_divider = 4, .banner = 4, .banner_active = false, .input_base = 5, .picker_divider = 6, .picker_start = 7, .bottom_divider = 6, .hint = 7, .total_rows = 4, }, .activity = .none, .preserved_band = render_engine.paint_plan.FrameBand.empty(.preserved_shell), .transcript_band = .{ .top = 1, .bottom = 3, .owner = .transcript }, .blank_band = render_engine.paint_plan.FrameBand.empty(.gap), .activity_band = render_engine.paint_plan.FrameBand.empty(.activity), .footer_gap_band = render_engine.paint_plan.FrameBand.empty(.gap), .footer_band = .{ .top = 4, .bottom = 7, .owner = .footer }, .invalidation = pending, .footer_clean_allowed = false, .synchronized_update = false, .cursor_target = null, .bottom_reserved_rows = 0, .preserve_scrollback = true, }; try plan.validate();
try std.testing.expectError( error.InvalidInvalidationRange, render_engine.paint_plan.validateInvalidationRange(plan, .{ .reason = .external_clear, .top = 25, .bottom = 36, }), );}
test "marking transcript cache dirty also schedules transcript work" { var runtime = TranscriptRuntime{}; defer runtime.deinit(std.testing.allocator);
runtime.markTranscriptDirty();
try std.testing.expect(runtime.transcript_band_dirty); try std.testing.expect(runtime.render_requests.hasReason(.transcript));}
test "frame invalidation overflow collapses to owned band with dominant reason" { var runtime = TranscriptRuntime{ .layout = invalidationTestLayout(), .owned_top_row = 7 }; defer runtime.deinit(std.testing.allocator);
var i: u8 = 0; while (i < render_engine.paint_plan.max_frame_invalidation_ranges) : (i += 1) { const row: u16 = 7 + i; try runtime.recordFrameInvalidation(.{ .reason = .external_clear, .top = row, .bottom = row }); } try runtime.recordFrameInvalidation(.{ .reason = .shadow_feed_failure, .top = 12, .bottom = 12 });
const pending = runtime.render_requests.pendingInvalidations(); try std.testing.expectEqual(@as(u8, 1), pending.len); try std.testing.expectEqual(render_engine.paint_plan.FrameInvalidationReason.shadow_feed_failure, pending.ranges()[0].reason); try std.testing.expectEqual(@as(u16, 7), pending.ranges()[0].top); try std.testing.expectEqual(@as(u16, 24), pending.ranges()[0].bottom);}
test "preserved row acknowledgment lowers ownership and viewport boundaries" { var runtime = TranscriptRuntime{ .layout = invalidationTestLayout(), .owned_top_row = 5, .viewport_top_row = 7, }; defer runtime.deinit(std.testing.allocator); const scroll_plan = render_engine.frame_scroll_plan.merge(runtime.layout.rows, runtime.owned_top_row, 2, 0);
try runtime.ackPreservedRowRelease(scroll_plan, 2);
try std.testing.expectEqual(@as(u16, 3), runtime.owned_top_row); try std.testing.expectEqual(@as(u16, 5), runtime.viewport_top_row);}
test "preserved row acknowledgment applies only committed rows inside the plan" { var partial = TranscriptRuntime{ .layout = invalidationTestLayout(), .owned_top_row = 5, .viewport_top_row = 7, }; defer partial.deinit(std.testing.allocator); const scroll_plan = render_engine.frame_scroll_plan.merge(partial.layout.rows, partial.owned_top_row, 2, 0);
try partial.ackPreservedRowRelease(scroll_plan, 1); try std.testing.expectEqual(@as(u16, 4), partial.owned_top_row); try std.testing.expectEqual(@as(u16, 6), partial.viewport_top_row);
var above_plan = TranscriptRuntime{ .layout = invalidationTestLayout(), .owned_top_row = 5, .viewport_top_row = 7, }; defer above_plan.deinit(std.testing.allocator); try above_plan.ackPreservedRowRelease(scroll_plan, 20); try std.testing.expectEqual(@as(u16, 3), above_plan.owned_top_row); try std.testing.expectEqual(@as(u16, 5), above_plan.viewport_top_row);}
test "preserved row acknowledgment normalizes stale resize boundaries before release" { var full = TranscriptRuntime{ .layout = testLayoutWithRows(5), .owned_top_row = 8, .viewport_top_row = 9, }; defer full.deinit(std.testing.allocator); const full_plan = render_engine.frame_scroll_plan.merge(full.layout.rows, full.owned_top_row, 4, 0); try full.ackPreservedRowRelease(full_plan, 4); try std.testing.expectEqual(@as(u16, 1), full.owned_top_row); try std.testing.expectEqual(@as(u16, 1), full.viewport_top_row);
var partial = TranscriptRuntime{ .layout = testLayoutWithRows(5), .owned_top_row = 8, .viewport_top_row = 9, }; defer partial.deinit(std.testing.allocator); const partial_plan = render_engine.frame_scroll_plan.merge(partial.layout.rows, partial.owned_top_row, 4, 0); try partial.ackPreservedRowRelease(partial_plan, 2); try std.testing.expectEqual(@as(u16, 3), partial.owned_top_row); try std.testing.expectEqual(@as(u16, 3), partial.viewport_top_row);}
test "preserved row acknowledgment normalizes shrink boundaries even without accepted release" { var runtime = TranscriptRuntime{ .layout = testLayoutWithRows(5), .owned_top_row = 8, .viewport_top_row = 9, }; defer runtime.deinit(std.testing.allocator); const scroll_plan = render_engine.frame_scroll_plan.FrameScrollPlan.none(runtime.layout.rows, runtime.owned_top_row);
try runtime.ackPreservedRowRelease(scroll_plan, 0);
try std.testing.expectEqual(@as(u16, 5), runtime.owned_top_row); try std.testing.expectEqual(@as(u16, 5), runtime.viewport_top_row);}
test "preserved row acknowledgment keeps viewport at or below owned top boundary" { var runtime = TranscriptRuntime{ .layout = invalidationTestLayout(), .owned_top_row = 5, .viewport_top_row = 2, }; defer runtime.deinit(std.testing.allocator); const scroll_plan = render_engine.frame_scroll_plan.merge(runtime.layout.rows, runtime.owned_top_row, 4, 0);
try runtime.ackPreservedRowRelease(scroll_plan, 4);
try std.testing.expectEqual(@as(u16, 1), runtime.owned_top_row); try std.testing.expectEqual(@as(u16, 1), runtime.viewport_top_row);}
test "transcript runtime owns render request state" { try std.testing.expect(@hasField(TranscriptRuntime, "render_requests"));}
test "recovery append prefix requires exact materialized bytes" { const alloc = std.testing.allocator; const receipt_flow = try alloc.dupe(u8, "prefix-good"); defer alloc.free(receipt_flow); const receipt = TranscriptRecoveryReceipt{ .flow = receipt_flow, .materialized_flow_len = 7, };
try std.testing.expect(recoveryAppendPrefixMatches("prefix-next", receipt, 7)); try std.testing.expect(!recoveryAppendPrefixMatches("prefiy-next", receipt, 7)); try std.testing.expect(!recoveryAppendPrefixMatches("short", receipt, 7));}
test "measured recovery rebases from accepted projection before retiring resize debt" { const alloc = std.testing.allocator; const flow = "aaaaaaaaaaaaaaaaaaaa\n" ++ "bbbbbbbbbbbbbbbbbbbb\n" ++ "cccccccccccccccccccc\n" ++ "dddddddddddddddddddd\n" ++ "eeeeeeeeeeeeeeeeeeee\n" ++ "ffffffffffffffffffff\n" ++ "gggggggggggggggggggg\n" ++ "hhhhhhhhhhhhhhhhhhhh\n"; const layout: Layout = .{ .rows = 8, .cols = 10, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }; var runtime = TranscriptRuntime{ .layout = layout, .owned_top_row = 1, .resize_history_row_delta = 2, .committed_frame_layout = .{ .layout_id = 17, .owned_top = 1, .owned_band = .{ .top = 1, .bottom = 8 }, .body_area = .{ .top = 1, .bottom = 4 }, .transcript_area = .{ .top = 1, .bottom = 4 }, .footer_area = .{ .top = 5, .bottom = 8 }, .solved_frame_height = 8, .terminal_rows = 8, .terminal_cols = 20, }, }; defer runtime.deinit(alloc); try runtime.transcript.appendSlice(alloc, flow); runtime.transcript_commit_state = .{ .recovering = .{ .accepted_visual_offset = 6, .accepted_history_visual_offset = 6, .attempt_visual_offset = 8, .attempt_total_visual_rows = 8, .materialized_visual_offset = 7, .materialized_visual_rows = 1, .materialized_flow_len = flow.len, .remaining_resize_reflow_rows = 3, .remaining_semantic_rows = 2, .remaining_semantic_progress_rows = 2, .attempt_cols = 20, .projection_rebase_pending = false, .tracks_semantic_progress = true, .flow = try alloc.dupe(u8, flow), .cursor_row = 4, .cursor_col = 1, } }; const projection = runtime.measuredResizeProjectionForFlow(flow) orelse return error.TestExpectedMeasuredResizeProjection; switch (projection) { .stable => return error.TestExpectedRecoveringProjection, .recovering => |recovering| { try std.testing.expectEqual( @as(u32, 6), recovering.accepted_visual_offset, ); try std.testing.expectEqual(@as(u16, 20), recovering.source_cols); }, }
var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, &source, .{ .top = 1, .bottom = 4 }, ); defer prepared.deinit(alloc);
try std.testing.expectEqual( transcript_painter.MeasuredResizeTargetProvenance.recovering, prepared.measured_resize_target_provenance, ); const endpoint = prepared.measured_history_origin.?.endpoint orelse return error.TestExpectedMeasuredHistoryEndpoint; try std.testing.expectEqual(@as(usize, 147), endpoint.flow_offset); try std.testing.expectEqual( @as(u32, 14), visualOffsetForSelection(prepared.sourceVisualRows(), prepared.selection), ); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expectEqual(@as(u16, 0), facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 2), facts.semantic_rows); try std.testing.expectEqual(@as(u32, 14), facts.target_visual_offset);
const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan: render_engine.paint_plan.PaintPlan = .{ .layout = runtime.layout, .viewport = prepared.selection, .footer = .{ .top = 5, .top_divider = 5, .banner = 5, .banner_active = false, .input_base = 6, .picker_divider = 7, .picker_start = 8, .bottom_divider = 7, .hint = 8, .total_rows = 4, }, .activity = .none, .preserved_band = render_engine.paint_plan.FrameBand.empty(.preserved_shell), .transcript_band = .{ .top = 1, .bottom = 4, .owner = .transcript }, .activity_band = render_engine.paint_plan.FrameBand.empty(.activity), .footer_band = .{ .top = 5, .bottom = 8, .owner = .footer }, .invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), .footer_clean_allowed = true, .synchronized_update = true, .cursor_target = .{ .row = prepared.cursor.cursor_row, .col = prepared.cursor.cursor_col, .visible = true, }, .bottom_reserved_rows = 0, .preserve_scrollback = true, }; var transition = try resolveAndSealTranscriptTransitionForRuntimeTest( &runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = transition.cursor_row, .committed_cursor_col = transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), });
const committed = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual(TranscriptCommitDiagnosticState.recovering, committed.state); try std.testing.expectEqual(@as(u32, 14), committed.visual_offset); switch (runtime.transcript_commit_state) { .recovering => |receipt| { try std.testing.expectEqual(@as(u16, 0), receipt.remaining_resize_reflow_rows); try std.testing.expectEqual(@as(u32, 2), receipt.remaining_semantic_rows); }, .invalid, .stable => return error.TestExpectedRecoveringTranscript, }
runtime.resize_history_row_delta = null; var retry_source = try runtime.prepareTranscriptSource(alloc, null); defer retry_source.deinit(alloc); var retry_prepared = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, &retry_source, .{ .top = 1, .bottom = 4 }, ); defer retry_prepared.deinit(alloc); const retry = runtime.planTranscriptScroll(&retry_prepared); try std.testing.expectEqual(@as(u16, 0), retry.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 2), retry.semantic_rows);}
test "source rewrite replays unchanged history prefix after footer projection rebase" { try expectHistoryReplayBoundary( "rewrite-row-01\nrewrite-row-02\nrewrite-row-03\nrewrite-row-04\n" ++ "rewrite-row-05\nrewrite-row-06\nrewrite-row-07\nrewrite-row-08\n" ++ "rewrite-row-09\nrewrite-row-10\nrewrite-row-11\nrewrite-row-12\n", false, );}
test "append pending wrap history replay starts a fresh row at a soft wrap" { try expectHistoryReplayBoundary("12345678" ** 60, true);}
fn expectHistoryReplayBoundary(target_flow: []const u8, source_pending_wrap: bool) !void { const alloc = std.testing.allocator; const layout: Layout = .{ .rows = 8, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }; var runtime = TranscriptRuntime{ .layout = layout, .owned_top_row = 1, .committed_frame_layout = .{ .layout_id = 17, .owned_top = 1, .owned_band = .{ .top = 1, .bottom = 8 }, .body_area = .{ .top = 1, .bottom = 4 }, .transcript_area = .{ .top = 1, .bottom = 4 }, .footer_area = .{ .top = 5, .bottom = 8 }, .solved_frame_height = 8, .terminal_rows = 8, .terminal_cols = 40, }, }; defer runtime.deinit(alloc);
var source = try runtime.prepareFullTranscriptViewportSource( alloc, try alloc.dupe(u8, target_flow), ); defer source.deinit(alloc); var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, &source, .{ .top = 1, .bottom = 4 }, ); defer prepared.deinit(alloc);
const target_offset = visualOffsetForSelection( prepared.sourceVisualRows(), prepared.selection, ); try std.testing.expect(target_offset > 5); const history_offset = target_offset - 5; const previous_flow = try alloc.dupe(u8, target_flow); previous_flow[previous_flow.len - 2] = 'X'; runtime.transcript_commit_state = .{ .stable = .{ .selection = prepared.selection, .visual_offset = target_offset, .history_visual_offset = history_offset, .total_visual_rows = totalVisualRows(prepared.sourceVisualRows()), .flow = previous_flow, .cursor_row = prepared.cursor.cursor_row, .cursor_col = prepared.cursor.cursor_col, .occupied_last_row = prepared.selection.last_visible_row, .occupied_last_row_blank = prepared.selection.last_visible_row_blank, .layout_id = 17, .normal_buffer_recovery_pending = true, } };
const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(!facts.source_compatible); try std.testing.expect(facts.recovery_rebase); try std.testing.expectEqual(target_offset, facts.target_visual_offset); try std.testing.expectEqual(target_offset - history_offset, facts.semantic_rows);
const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan: render_engine.paint_plan.PaintPlan = .{ .layout = runtime.layout, .viewport = prepared.selection, .footer = .{ .top = 5, .top_divider = 5, .banner = 5, .banner_active = false, .input_base = 6, .picker_divider = 7, .picker_start = 8, .bottom_divider = 7, .hint = 8, .total_rows = 4, }, .activity = .none, .preserved_band = render_engine.paint_plan.FrameBand.empty(.preserved_shell), .transcript_band = .{ .top = 1, .bottom = 4, .owner = .transcript }, .activity_band = render_engine.paint_plan.FrameBand.empty(.activity), .footer_band = .{ .top = 5, .bottom = 8, .owner = .footer }, .invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), .footer_clean_allowed = true, .synchronized_update = true, .cursor_target = .{ .row = prepared.cursor.cursor_row, .col = prepared.cursor.cursor_col, .visible = true, }, .bottom_reserved_rows = 0, .preserve_scrollback = true, }; var transition = try resolveAndSealTranscriptTransitionForRuntimeTest( &runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expect(!transition.document_append.isEmpty()); try std.testing.expect(!transition.document_append.start_pending_wrap); try std.testing.expectEqual(@as(u16, 1), transition.document_append.start_col); const base = (try runtime.resolveTransitionAppendBase(&prepared, scroll_plan.prior_owned_top, facts, false, transition.target_flow, facts.target_visual_offset)).?; var boundary = try transcript_painter.prepareTranscriptDocumentAppend(alloc, transition.target_flow, layout.cols, base.flow_len, base.flow_len, false); defer boundary.deinit(alloc); try std.testing.expectEqual(source_pending_wrap, boundary.start_pending_wrap); // A history replay publishes a prefix, but the committed paint also // materializes the viewport. Its cursor must pair with that later byte end. try std.testing.expectEqual(transition.target_flow.len, transition.materialized.flow_len.?); for ([_]bool{ false, true }) |complete| { const result = render_engine.terminal_diff.FrameCommitResult{ .bytes_written = 1, .changed_cells = 0, .full_repaint = false, .document_append_committed = true, .terminal_scroll_rows_committed = transition.scroll_plan.terminal_scroll_rows, .committed_cursor_row = transition.cursor_row, .committed_cursor_col = transition.cursor_col, .shadow_state = if (complete) .committed else .terminal_partial_write, .next_invalidation = .empty(), }; const commit = resolveTranscriptTransitionCommit(&transition, result, result.scrollCommit(transition.scroll_plan)); const endpoint = resolveRecoveryMaterializedEndpoint(&transition, result, commit); try std.testing.expectEqual(if (complete) @as(?usize, transition.target_flow.len) else null, endpoint.flow_len); } switch (transition.document_append.clear) { .rows => |rows| try std.testing.expectEqual( @as(u16, @intCast(facts.semantic_rows)), rows, ), .remainder => return error.TestExpectedHistoryReplay, }}
test "history replay starts at committed source top and follows resize eligibility" { const alloc = std.testing.allocator; const committed_flow = "source-row-01\nsource-row-02\nsource-row-03\nsource-row-04\n"; const target_flow = committed_flow ++ "target-row-05\ntarget-row-06\ntarget-row-07\ntarget-row-08\n" ++ "target-row-09\ntarget-row-10\ntarget-row-11\ntarget-row-12\n"; const layout: Layout = .{ .rows = 12, .cols = 40, .content_bottom = 8, .divider_top_row = 9, .input_row = 10, .divider_bottom_row = 11, .hint_row = 12, }; var runtime = TranscriptRuntime{ .layout = layout, .owned_top_row = 5, .viewport_top_row = 5, .committed_frame_layout = .{ .layout_id = 17, .owned_top = 5, .owned_band = .{ .top = 5, .bottom = 12 }, .body_area = .{ .top = 5, .bottom = 8 }, .transcript_area = .{ .top = 5, .bottom = 8 }, .footer_area = .{ .top = 9, .bottom = 12 }, .solved_frame_height = 8, .terminal_rows = 12, .terminal_cols = 40, }, .transcript_commit_state = .{ .stable = .{ .selection = .{ .top_row = 5, .bottom_row = 8, .start_line = 0, .partial_skip_rows = 0, .line_count = 4, .last_visible_row = 8, }, .visual_offset = 0, .history_visual_offset = 0, .total_visual_rows = 4, .flow = try alloc.dupe(u8, committed_flow), .cursor_row = 8, .cursor_col = 1, .occupied_last_row = 8, .occupied_last_row_blank = true, .layout_id = 17, .history_catchup_pending = true, } }, }; defer runtime.deinit(alloc);
var source = try runtime.prepareFullTranscriptViewportSource( alloc, try alloc.dupe(u8, target_flow), ); defer source.deinit(alloc); var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, &source, .{ .top = 1, .bottom = 8 }, ); defer prepared.deinit(alloc); try std.testing.expectEqual(@as(u16, 1), prepared.projectionArea().top);
const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(facts.source_compatible); try std.testing.expectEqual(@as(u32, 4), facts.target_visual_offset); try std.testing.expectEqual(@as(u16, 4), facts.planned_rows);
const append_base = (try runtime.resolveTransitionAppendBase( &prepared, runtime.owned_top_row, facts, false, source.bytes, facts.target_visual_offset, )) orelse return error.TestExpectedHistoryReplay; try std.testing.expect(append_base.history_replay != null); try std.testing.expectEqual( runtime.committed_frame_layout.transcript_area.top, append_base.cursor_row, ); try std.testing.expectEqual(@as(u16, 1), append_base.cursor_col);
const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, runtime.owned_top_row - 1, facts.planned_rows, ); try std.testing.expectEqual(@as(u16, 4), scroll_plan.preserved_release_rows); try std.testing.expectEqual(@as(u16, 8), scroll_plan.terminal_scroll_rows);
runtime.resize_history_row_delta = 1; const resize_facts = runtime.planTranscriptScroll(&prepared); const resize_append_base = (try runtime.resolveTransitionAppendBase( &prepared, scroll_plan.prior_owned_top, resize_facts, false, source.bytes, resize_facts.target_visual_offset, )) orelse return error.TestExpectedNaturalAppendBase; try std.testing.expect(resize_append_base.history_replay == null);}
test "preserved source rewrite holds the normal buffer at committed history" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 8, .cols = 40, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }, .committed_frame_layout = .{ .layout_id = 17, .owned_top = 1, .owned_band = .{ .top = 1, .bottom = 8 }, .body_area = .{ .top = 1, .bottom = 4 }, .transcript_area = .{ .top = 1, .bottom = 4 }, .footer_area = .{ .top = 5, .bottom = 8 }, .solved_frame_height = 8, .terminal_rows = 8, .terminal_cols = 40, }, .transcript_commit_state = .{ .stable = .{ .selection = .{ .top_row = 1, .bottom_row = 4, .start_line = 2, .partial_skip_rows = 0, .line_count = 6, .last_visible_row = 4, }, .visual_offset = 5, .history_visual_offset = 2, .total_visual_rows = 6, .flow = try alloc.dupe(u8, "old source\n"), .cursor_row = 4, .cursor_col = 1, .occupied_last_row = 4, .occupied_last_row_blank = false, .layout_id = 17, } }, }; defer runtime.deinit(alloc);
runtime.reconcileTranscriptCommitSourceWithRewriteMode( "rewritten source\n", false, "test rewrite", .preserve_same_epoch, );
switch (runtime.transcript_commit_state) { .stable => |anchor| { try std.testing.expect(anchor.normal_buffer_recovery_pending); try std.testing.expectEqual( anchor.history_visual_offset, committedProjectionVisualOffset(anchor), ); }, .invalid, .recovering => return error.TestExpectedStableTranscript, }}
test "frame scroll commit consumption releases rows before transition advancement" { var runtime = TranscriptRuntime{ .layout = testLayoutWithRows(8), .owned_top_row = 4, .viewport_top_row = 4, }; defer runtime.deinit(std.testing.allocator);
const scroll_plan = render_engine.frame_scroll_plan.merge(8, 4, 3, 1); var transition = TranscriptTransition{ .scroll_plan = scroll_plan, .document_append = .{}, .materialized_without_append = .{}, .semantic_rows = 1, .semantic_progress_rows = 1, .target_flow = &.{}, .target_cache = .empty, .folded_summary_indices = &.{}, .target_layout = .{ .layout_id = 18, .owned_top = 1, .owned_band = .{ .top = 1, .bottom = 8 }, .body_area = .{ .top = 1, .bottom = 4 }, .transcript_area = .{ .top = 1, .bottom = 4 }, .footer_area = .{ .top = 5, .bottom = 8 }, .solved_frame_height = 8, .terminal_rows = 8, .terminal_cols = 80, }, .selection = .{ .top_row = 1, .bottom_row = 4, .start_line = 1, .partial_skip_rows = 0, .line_count = 4, .last_visible_row = 4, }, .visual_offset = 1, .history_visual_offset = 1, .total_visual_rows = 5, .cursor_row = 4, .cursor_col = 1, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = 4, .trace_transcript_frame = false, .trace_visible_rows = 4, }; defer transition.deinit(std.testing.allocator);
const result = testFrameCommitResult(scroll_plan.terminal_scroll_rows, .committed); runtime.consumeFrameScrollCommit( std.testing.allocator, scroll_plan, result, &transition, );
try std.testing.expect(transition.consumed); try std.testing.expectEqual(@as(u16, 1), runtime.owned_top_row); try std.testing.expectEqual(@as(u16, 1), runtime.viewport_top_row); const committed = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual(TranscriptCommitDiagnosticState.stable, committed.state); try std.testing.expectEqual(@as(u32, 1), committed.visual_offset);}
fn testLayoutWithRows(rows: u16) Layout { return .{ .rows = rows, .cols = 80, .content_bottom = rows, .divider_top_row = rows, .input_row = rows, .divider_bottom_row = rows, .hint_row = rows, };}
test "notification bell writes standalone BEL bytes" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var file = try tmp.dir.createFile(std.testing.io, "notification-bells.log", .{ .read = true }); defer file.close(io_mod.getIo());
var runtime = TranscriptRuntime{ .stdout_file = file }; defer runtime.deinit(std.testing.allocator); var metrics: Metrics = .{}; runtime.writeNotificationBell(&metrics);
var bytes: [1]u8 = undefined; try std.testing.expectEqual( @as(usize, 1), try file.readPositionalAll(io_mod.getIo(), &bytes, 0), ); try std.testing.expectEqualSlices(u8, "\x07", &bytes);}
test { _ = @import("runtime_tests.zig");}
test "changed stored result retires stale full transcript geometry" { for ([_]bool{ false, true }) |cancelled| { const request = full_transcript_page.Request{ .content_revision = 1, .cols = 80, .anchor = .tail, }; var runtime = TranscriptRuntime{ .layout = testLayoutWithRows(24), .full_transcript = .{ .depth = .full }, .full_transcript_installed_page = .{ .source = .{ .request = request, .range = .{ .start = 0, .end = 0 } }, .projection = .{ .styles = .{}, .measurement_cols = null }, .measured_total_rows = 800, .measurement_snapshot_ready = true, }, }; defer runtime.deinit(std.testing.allocator); const task = try std.heap.c_allocator.create(full_transcript_worker.WindowTask); task.* = .{ .request = full_transcript_worker.preparedWindowRequest(request, 800, 700, 20), .projection = &runtime.full_transcript_installed_page.?.projection, .capability = null, }; task.cancel_requested.store(cancelled, .release); task.done.store(true, .release); runtime.full_transcript_window_load.task = task;
try std.testing.expect(try runtime.pollFullTranscriptPageLoad()); try std.testing.expect(runtime.full_transcript_installed_page == null); }}
test "prependFullDetailRecords injects records only into the first page" { const alloc = std.testing.allocator; var runtime: TranscriptRuntime = .{}; defer runtime.deinit(alloc); try runtime.appendFullDetailRecord(alloc, .{ .topic = "session", .tone = .neutral, .body = "provider: test", });
const request: full_transcript_page.Request = .{ .content_revision = 1, .cols = 80, .anchor = .tail }; var source = full_transcript_worker.Source{ .request = request, .range = .{ .start = 0, .end = 0 }, .styles = .{} }; defer source.deinit(alloc); try runtime.prependFullDetailRecords(alloc, &source); try std.testing.expectEqual(@as(usize, 1), source.entries.items.len); const notice = source.entries.items[0].semantic_notice; try std.testing.expectEqual(@as(u32, 0), notice.id); try std.testing.expectEqualStrings("session", notice.topic); try std.testing.expectEqualStrings("provider: test", notice.body);
var later = full_transcript_worker.Source{ .request = request, .range = .{ .start = 1, .end = 1 }, .styles = .{} }; defer later.deinit(alloc); try runtime.prependFullDetailRecords(alloc, &later); try std.testing.expectEqual(@as(usize, 0), later.entries.items.len);}
test "prependFullDetailRecords keeps the welcome banner first" { const alloc = std.testing.allocator; var runtime: TranscriptRuntime = .{}; defer runtime.deinit(alloc); try runtime.appendFullDetailRecord(alloc, .{ .topic = "network", .tone = .neutral, .body = "finish: stop", });
const request: full_transcript_page.Request = .{ .content_revision = 1, .cols = 80, .anchor = .tail }; var source = full_transcript_worker.Source{ .request = request, .range = .{ .start = 0, .end = 1 }, .styles = .{} }; defer source.deinit(alloc); try source.entries.append(alloc, .{ .raw_bytes = .{ .id = 1, .bytes = try alloc.dupe(u8, "banner"), .class = .welcome, } }); try runtime.prependFullDetailRecords(alloc, &source); try std.testing.expectEqual(@as(usize, 2), source.entries.items.len); try std.testing.expect(source.entries.items[0] == .raw_bytes); try std.testing.expect(source.entries.items[1] == .semantic_notice);}