Something went wrong. Try again.
My version of https://github.com/vercel-labs/fx
Something went wrong. Try again.
469 kB · 11931 lines
Zig
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932const std = @import("std");const debug_trace = @import("../../core/shared/debug_trace.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 worker_status = @import("../../core/output/worker_status.zig");const footer_viewport_runtime = @import("../footer/viewport.zig");const render_request = @import("../render_request.zig");const presentation_mode = @import("../../core/config/presentation_mode.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 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");const command_output_content = @import("../../core/tooling/command_output_content.zig");const captured_command = @import("../../core/tooling/captured_command.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, 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, 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 { 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, 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 { 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; }};
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 { 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, target_flow_owned: bool = true, 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 { if (self.target_flow_owned and self.target_flow.len > 0) alloc.free(self.target_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/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, .review)); try std.testing.expect(runtime.fullTranscriptActive());}
test "full transcript viewport snapshot restores reading position" { var source = TranscriptRuntime{ .full_transcript = .{ .depth = .review, .scroll_rows = 37, .follow_tail = false, .anchor_entry_id = 19, .anchor_pending = true, }, }; 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(), ));}
test "opening the full transcript leaves compact command projection unchanged" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .maxxing_mode = .legacy, .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.expect(std.mem.indexOf(u8, compact_before.bytes, "│ first") != null); try std.testing.expect(std.mem.indexOf(u8, compact_before.bytes, "│ second") != null);
_ = try runtime.setTranscriptPresentationDepth(alloc, .review); 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 = .review, .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.source.deinit(alloc); defer staged.prepared.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 "full transcript projection 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 = .review }, }; 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.maxxing_mode, 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.maxxing_mode, 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.maxxing_mode, runtime.has_committed_frame, )); var committed_compact = try runtime.cachedTranscriptSource(alloc); committed_compact.deinit(alloc); runtime.has_committed_frame = false;
const review = try runtime.cachedFullTranscriptProjection(alloc, null); _ = try full_transcript_screen.measureProjectionInterruptible(alloc, review, null, 40, null); try std.testing.expectEqual(@as(?u16, 40), review.measurement_cols); const same_review = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expectEqual(@as(?u16, 40), same_review.measurement_cols); runtime.full_transcript = runtime.full_transcript.capture_anchor(1); const reanchored_review = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expectEqual(review, reanchored_review);
for ([_]u16{ 20, 30, 50 }) |cols| { runtime.layout.cols = cols; const resized = try runtime.cachedFullTranscriptProjection(alloc, null); _ = try full_transcript_screen.measureProjectionInterruptible(alloc, resized, null, cols, null); } runtime.layout.cols = 40; const original_width = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expectEqual(@as(?u16, 40), original_width.measurement_cols);
_ = try runtime.setTranscriptPresentationDepth(alloc, .full); _ = try runtime.cachedFullTranscriptProjection(alloc, null); _ = try runtime.setTranscriptPresentationDepth(alloc, .review); const restored_review = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expectEqual(@as(?u16, 40), restored_review.measurement_cols);
runtime.markTranscriptCommandOutputDirty(); const review_after_live_output = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expectEqual(@as(?u16, 40), review_after_live_output.measurement_cols);
_ = try runtime.setTranscriptPresentationDepth(alloc, .full); const full_before_live_output = try runtime.cachedFullTranscriptProjection(alloc, null); _ = try full_transcript_screen.measureProjectionInterruptible(alloc, full_before_live_output, null, 40, null); runtime.markTranscriptCommandOutputDirty(); const full_after_live_output = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expectEqual(@as(?u16, null), full_after_live_output.measurement_cols);
_ = try runtime.setTranscriptPresentationDepth(alloc, .review); const review_still_cached = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expectEqual(@as(?u16, 40), review_still_cached.measurement_cols);
runtime.command_output_display.open_command_block = 0; runtime.markTranscriptContentDirty(); const review_during_live_command = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expectEqual(review_still_cached, review_during_live_command); runtime.command_output_display.open_command_block = null;
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.maxxing_mode, runtime.has_committed_frame, ).?.bytes.ptr); const rebuilt_review = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expectEqual(@as(?u16, null), rebuilt_review.measurement_cols);}
const CacheBuildProbe = struct { polls: usize = 0, pending_after: ?usize = null,
fn pending(context: *anyopaque) bool { const self: *CacheBuildProbe = @ptrCast(@alignCast(context)); self.polls += 1; return if (self.pending_after) |limit| self.polls >= limit else false; }};
fn cacheTestRuntime(depth: transcript_presentation.Depth, cols: u16) TranscriptRuntime { var layout = testLayoutWithRows(8); layout.cols = cols; return .{ .layout = layout, .owned_top_row = 1, .full_transcript = .{ .depth = depth }, };}
fn appendCacheTestEntries( runtime: *TranscriptRuntime, alloc: Allocator, count: usize,) !void { try runtime.entries.ensureUnusedCapacity(alloc, count); for (0..count) |_| { const bytes = try alloc.dupe(u8, "retained\n"); runtime.entries.appendAssumeCapacity(.{ .raw_bytes = .{ .id = runtime.next_entry_id, .bytes = bytes, } }); runtime.next_entry_id +%= 1; } runtime.markTranscriptContentDirty();}
fn renderCacheTail( alloc: Allocator, runtime: *TranscriptRuntime, projection: *full_transcript_screen.Projection, rows: u16,) ![]u8 { const measurement = try full_transcript_screen.measureProjectionInterruptible( alloc, projection, null, runtime.layout.cols, null, ); return full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, projection, null, runtime.layout.cols, rows, measurement.total_rows -| rows, null, );}
test "full transcript cache reuses retained entries across append and width changes" { const alloc = std.testing.allocator; for ([_]transcript_presentation.Depth{ .review, .full }) |depth| { var runtime = cacheTestRuntime(depth, 40); defer runtime.deinit(alloc);
try appendCacheTestEntries(&runtime, alloc, 4_500); _ = try runtime.cachedFullTranscriptProjection(alloc, null); if (depth == .review) { runtime.layout.cols = 41; var resize_probe: CacheBuildProbe = .{}; var resize_checkpoint = build_checkpoint.BuildCheckpoint.init( &resize_probe, CacheBuildProbe.pending, ); _ = try runtime.cachedFullTranscriptProjectionInterruptible( alloc, null, &resize_checkpoint, ); try std.testing.expectEqual(@as(usize, 2), resize_probe.polls); runtime.layout.cols = 40; } _ = try runtime.appendRawTranscriptEntryClassified( alloc, "APPENDED_TAIL\n", .unknown_raw, );
var probe: CacheBuildProbe = .{}; var checkpoint = build_checkpoint.BuildCheckpoint.init(&probe, CacheBuildProbe.pending); const projection = try runtime.cachedFullTranscriptProjectionInterruptible( alloc, null, &checkpoint, );
try std.testing.expectEqual(@as(usize, 0), probe.polls); const tail = try renderCacheTail(alloc, &runtime, projection, 2); defer alloc.free(tail); try std.testing.expect(std.mem.find(u8, tail, "APPENDED_TAIL") != null); }}
test "review cache retains block spacing across a hidden tail" { const alloc = std.testing.allocator; var runtime = cacheTestRuntime(.review, 40); defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "BEFORE\n", .unknown_raw); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "HIDDEN\n", .command_output); _ = try runtime.cachedFullTranscriptProjection(alloc, null); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "AFTER\n", .unknown_raw);
const incremental = try runtime.cachedFullTranscriptProjection(alloc, null); const incremental_tail = try renderCacheTail(alloc, &runtime, incremental, 16); defer alloc.free(incremental_tail); var rebuilt = try runtime.buildFullTranscriptProjection(alloc, null); defer rebuilt.deinit(alloc); const rebuilt_tail = try renderCacheTail(alloc, &runtime, &rebuilt, 16); defer alloc.free(rebuilt_tail);
try std.testing.expectEqualStrings(rebuilt_tail, incremental_tail);}
test "full transcript cache updates only the streamed assistant tail" { const alloc = std.testing.allocator; var runtime = cacheTestRuntime(.review, 40); defer runtime.deinit(alloc); try appendCacheTestEntries(&runtime, alloc, 4_500); var metrics: Metrics = .{}; _ = try runtime.streamAssistantChunk(alloc, &metrics, "assistant"); _ = try runtime.cachedFullTranscriptProjection(alloc, null); _ = try runtime.streamAssistantChunk(alloc, &metrics, " tail");
var probe: CacheBuildProbe = .{}; var checkpoint = build_checkpoint.BuildCheckpoint.init(&probe, CacheBuildProbe.pending); const projection = try runtime.cachedFullTranscriptProjectionInterruptible( alloc, null, &checkpoint, );
try std.testing.expectEqual(@as(usize, 0), probe.polls); const tail = try renderCacheTail(alloc, &runtime, projection, 2); defer alloc.free(tail); try std.testing.expect(std.mem.find(u8, tail, "assistant tail") != null);}
test "command output admission invalidates cached prefixes when reservation trims retention" { const alloc = std.testing.allocator; var runtime = cacheTestRuntime(.full, 40); defer runtime.deinit(alloc);
const payload = try alloc.alloc(u8, 256); defer alloc.free(payload); @memset(payload, 'x'); for (0..8) |_| { _ = try runtime.appendRawTranscriptEntryClassified( alloc, payload, .unknown_raw, ); } runtime.full_transcript.depth = .review; _ = try runtime.cachedFullTranscriptProjection(alloc, null); runtime.full_transcript.depth = .full; _ = try runtime.cachedFullTranscriptProjection(alloc, null); const entry_count_before = runtime.entries.items.len; runtime.max_retained_transcript_bytes = 512;
var metrics: Metrics = .{}; try runtime.writeCommandOutputChunk( alloc, &metrics, .{}, .stdout, "new output\n", true, );
try std.testing.expect(runtime.entries.items.len < entry_count_before + 1); try std.testing.expect( runtime.full_transcript_projection_cache.full.entries[0].?.dirty == .all, ); try std.testing.expect( runtime.full_transcript_projection_cache.review.entries[0].?.dirty == .all, );}
test "command and lifecycle updates reuse measured prefix and publish atomically" { const alloc = std.testing.allocator; var runtime = cacheTestRuntime(.full, 40); defer runtime.deinit(alloc); try appendCacheTestEntries(&runtime, alloc, 4_500);
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, "command", true, ); const command_entry_id = runtime.command_output_blocks.items[0].lines.items[0].entry_id.?; const initial = try runtime.cachedFullTranscriptProjection(alloc, null); _ = try full_transcript_screen.measureProjectionInterruptible(alloc, initial, null, 40, null);
const continuation = try alloc.alloc(u8, 16 * 1024); defer alloc.free(continuation); @memset(continuation, 'x'); try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, continuation, true, ); const dirty_after_append = switch (runtime.full_transcript_projection_cache.full.entries[0].?.dirty) { .entry => |entry| entry.id, .clean, .all => return error.TestExpectedExactCommandDirtyEntry, }; try std.testing.expectEqual(command_entry_id, dirty_after_append);
const updated = try runtime.cachedFullTranscriptProjection(alloc, null); const prefix_before = updated.measurement_prefix orelse return error.TestExpectedMeasurementPrefix; const checkpoints_before = updated.measured_segment_checkpoints.items.len; var probe: CacheBuildProbe = .{ .pending_after = 1 }; var checkpoint = build_checkpoint.BuildCheckpoint.init(&probe, CacheBuildProbe.pending); try std.testing.expectError( error.InputPending, full_transcript_screen.measureProjectionInterruptible( alloc, updated, null, 40, &checkpoint, ), ); try std.testing.expectEqual(@as(?u16, null), updated.measurement_cols); try std.testing.expectEqual( prefix_before.segment_count, updated.measurement_prefix.?.segment_count, ); try std.testing.expectEqual( checkpoints_before, updated.measured_segment_checkpoints.items.len, ); for (0..2) |fail_index| { var failing = std.testing.FailingAllocator.init(alloc, .{ .fail_index = fail_index }); try std.testing.expectError( error.OutOfMemory, full_transcript_screen.measureProjectionInterruptible( failing.allocator(), updated, null, 40, null, ), ); try std.testing.expectEqual( checkpoints_before, updated.measured_segment_checkpoints.items.len, ); }
const resumed = try full_transcript_screen.measureProjectionInterruptible(alloc, updated, null, 40, null); var rebuilt = try runtime.buildFullTranscriptProjection(alloc, null); defer rebuilt.deinit(alloc); const baseline = try full_transcript_screen.measureProjectionInterruptible(alloc, &rebuilt, null, 40, null); try std.testing.expectEqual(baseline.total_rows, resumed.total_rows); try std.testing.expectEqual(baseline.anchor_row, resumed.anchor_row); try std.testing.expectEqualSlices( transcript_presentation.ItemRow, baseline.item_rows, resumed.item_rows, );
try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); const dirty_after_close = switch (runtime.full_transcript_projection_cache.full.entries[0].?.dirty) { .entry => |entry| entry.id, .clean, .all => return error.TestExpectedExactCommandDirtyEntry, }; try std.testing.expectEqual(command_entry_id, dirty_after_close); const closed = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expect(closed.measurement_prefix != null); const closed_measurement = try full_transcript_screen.measureProjectionInterruptible(alloc, closed, null, 40, null); var rebuilt_closed = try runtime.buildFullTranscriptProjection(alloc, null); defer rebuilt_closed.deinit(alloc); const closed_baseline = try full_transcript_screen.measureProjectionInterruptible( alloc, &rebuilt_closed, null, 40, null, ); try std.testing.expectEqual(closed_baseline.total_rows, closed_measurement.total_rows); try std.testing.expectEqualSlices( transcript_presentation.ItemRow, closed_baseline.item_rows, closed_measurement.item_rows, );
const status_entry_id = try runtime.appendRawTranscriptEntryClassified( alloc, "● Running command\n", .tool_status, ); runtime.entries.items[runtime.entries.items.len - 1].raw_bytes.lifecycle_pinned = true; const before_status_update = try runtime.cachedFullTranscriptProjection(alloc, null); _ = try full_transcript_screen.measureProjectionInterruptible(alloc, before_status_update, null, 40, null); try std.testing.expect(try transcript_store.replacePinnedToolStatusAtomic( &runtime, alloc, status_entry_id, "● Ran command\n", )); const dirty_after_status = switch (runtime.full_transcript_projection_cache.full.entries[0].?.dirty) { .entry => |entry| entry.id, .clean, .all => return error.TestExpectedExactLifecycleDirtyEntry, }; try std.testing.expectEqual(status_entry_id, dirty_after_status); const updated_status = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expect(updated_status.measurement_prefix != null); _ = try full_transcript_screen.measureProjectionInterruptible(alloc, updated_status, null, 40, null);
try transcript_store.clearLifecyclePinsAtomic(&runtime, alloc, &.{status_entry_id}); const dirty_after_cleanup = switch (runtime.full_transcript_projection_cache.full.entries[0].?.dirty) { .entry => |entry| entry.id, .clean, .all => return error.TestExpectedExactLifecycleDirtyEntry, }; try std.testing.expectEqual(status_entry_id, dirty_after_cleanup); const cleaned_status = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expect(cleaned_status.measurement_prefix != null); _ = try full_transcript_screen.measureProjectionInterruptible(alloc, cleaned_status, null, 40, null);}
test "full transcript cache rebuilds a changed tool group without its old prefix" { const alloc = std.testing.allocator; var runtime = cacheTestRuntime(.review, 80); defer runtime.deinit(alloc); try appendCacheTestEntries(&runtime, alloc, 8); var tool_ids: [3]u32 = undefined; for (&tool_ids, 0..) |*entry_id, index| { entry_id.* = try runtime.appendRawTranscriptEntryClassified( alloc, "● Read file.zig\n", .tool_status, ); try runtime.tool_details.append(alloc, .{ .entry_id = entry_id.*, .tool_name = try std.fmt.allocPrint(alloc, "read_{d}", .{index}), .activity_kind = .read, .outcome = .completed, .presentation_group_id = .{ .turn_id = 1, .anchor_step_id = 1 }, }); } _ = try runtime.cachedFullTranscriptProjection(alloc, null);
runtime.tool_details.items[runtime.tool_details.items.len - 1].outcome = .failed; runtime.markTranscriptContentDirtyFrom(tool_ids[2]); const projection = try runtime.cachedFullTranscriptProjection(alloc, null); const tail = try renderCacheTail(alloc, &runtime, projection, 6); defer alloc.free(tail); try std.testing.expect(std.mem.find(u8, tail, "1 failed") != null);}
test "full transcript cancellation preserves the prior cache and viewport for retry" { const alloc = std.testing.allocator; var runtime = cacheTestRuntime(.review, 40); defer runtime.deinit(alloc); try appendCacheTestEntries(&runtime, alloc, 5_000); const previous = try runtime.cachedFullTranscriptProjection(alloc, null); try std.testing.expect(&runtime.full_transcript_projection_cache.review.entries[0].?.projection == previous); runtime.full_transcript.scroll_rows = 7; runtime.full_transcript.follow_tail = false; runtime.full_transcript.bookmark_entry_id = 12; runtime.full_transcript.bookmark_intra_row = 2; const viewport_before = runtime.snapshotFullTranscriptViewport();
try appendCacheTestEntries(&runtime, alloc, 5_000); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "CANCEL_RETRY_TAIL\n", .unknown_raw, ); const Probe = struct { fn pending(_: *anyopaque) bool { return true; } }; var context: u8 = 0; var checkpoint = build_checkpoint.BuildCheckpoint.init(&context, Probe.pending); try std.testing.expectError( error.InputPending, runtime.cachedFullTranscriptProjectionInterruptible( alloc, null, &checkpoint, ), );
try std.testing.expect(&runtime.full_transcript_projection_cache.review.entries[0].?.projection == previous); try std.testing.expect(runtime.full_transcript_projection_cache.review.find( runtime.full_transcript_review_revision, runtime.layout.cols, null, ) == null); try std.testing.expect(std.meta.eql( viewport_before, runtime.snapshotFullTranscriptViewport(), )); const retry = try runtime.cachedFullTranscriptProjection(alloc, null); const tail = try renderCacheTail(alloc, &runtime, retry, 2); defer alloc.free(tail); try std.testing.expect(std.mem.find(u8, tail, "CANCEL_RETRY_TAIL") != null);}
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 cached sources and projections" { 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 = .review }, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "old session transcript\n", .unknown_raw, );
var compact = try runtime.cachedTranscriptSource(alloc); compact.deinit(alloc); _ = try runtime.cachedFullTranscriptProjection(alloc, null); const content_revision = runtime.full_transcript_content_revision; const review_revision = runtime.full_transcript_review_revision; try std.testing.expect(runtime.compact_transcript_source_cache.find( content_revision, runtime.layout.cols, runtime.maxxing_mode, runtime.has_committed_frame, ) != null); try std.testing.expect(runtime.full_transcript_projection_cache.review.find( review_revision, runtime.layout.cols, null, ) != null);
runtime.clearTranscript(alloc);
try std.testing.expect(runtime.compact_transcript_source_cache.find( content_revision, runtime.layout.cols, runtime.maxxing_mode, runtime.has_committed_frame, ) == null); try std.testing.expect(runtime.full_transcript_projection_cache.review.find( review_revision, runtime.layout.cols, null, ) == 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);}
test "startup resume view release preserves later structured notices" { 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 resume_entry_id = try runtime.appendRawTranscriptEntryClassified( alloc, "cached resume view\n", .unknown_raw, ); _ = try runtime.appendSemanticNotice(alloc, .{ .topic = "recording", .tone = .warning, .body = "terminal capture is active", }); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "cached resume view") != null); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "terminal capture is active") != null);
try std.testing.expect(try runtime.releaseStartupResumeViewEntry(alloc, resume_entry_id));
try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .semantic_notice); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "cached resume view") == null); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "terminal capture is active") != null); try std.testing.expect(!try runtime.releaseStartupResumeViewEntry(alloc, resume_entry_id));}
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 = CacheBuildProbe{ .pending_after = 1 }; var cancelled = build_checkpoint.BuildCheckpoint.init( &cancelled_probe, CacheBuildProbe.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 = CacheBuildProbe{}; var build = build_checkpoint.BuildCheckpoint.init(&build_probe, CacheBuildProbe.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 = CacheBuildProbe{ .pending_after = 2 }; var resize = build_checkpoint.BuildCheckpoint.init(&resize_probe, CacheBuildProbe.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 = CacheBuildProbe{}; var reuse = build_checkpoint.BuildCheckpoint.init(&reuse_probe, CacheBuildProbe.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 = .review, .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.source.deinit(alloc); defer staged.prepared.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, .maxxing_mode = .legacy, }; 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.expectEqualStrings("● Ran printf done", compact.bytes);
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(.review); _ = 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.source.deinit(alloc); defer staged.prepared.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.changed_resized, runtime.fullTranscriptPrimaryRestore());}
test "full transcript primary restore consumes the pending settled resize" { 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.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);}
test "full transcript recovery starts at closest visible entry before a hidden anchor" { const provenance = [_]transcript_blocks.LineProvenance{ .{ .entry = .{ .entry_id = 10, .entry_class = .unknown_raw } }, .{ .entry = .{ .entry_id = 10, .entry_class = .unknown_raw } }, .block_separator, .{ .entry = .{ .entry_id = 14, .entry_class = .tool_status } }, .{ .entry = .{ .entry_id = 14, .entry_class = .tool_status } }, .{ .entry = .{ .entry_id = 22, .entry_class = .assistant_turn } }, };
try std.testing.expectEqual( @as(?usize, 3), TranscriptRuntime.fullTranscriptRecoveryPredecessorStartLine(&provenance, 18), ); try std.testing.expectEqual( @as(?usize, 0), TranscriptRuntime.fullTranscriptRecoveryPredecessorStartLine(&provenance, 10), ); try std.testing.expectEqual( @as(?usize, null), TranscriptRuntime.fullTranscriptRecoveryPredecessorStartLine(&provenance, 9), );}
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 and shows one neutral exit row" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .maxxing_mode = .legacy, .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 false") != null); try std.testing.expectEqual(@as(usize, 1), 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, "Ran printf lines; exit 7") != null); try std.testing.expect(std.mem.find(u8, source.bytes, "Failed") == null);}
test "compact run command header stays on one row after resize" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .maxxing_mode = .legacy, .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, 1), status_line_count); try std.testing.expectEqual(status_line.? + 1, output_line.?);
var lines = std.mem.splitScalar(u8, source.bytes, '\n'); var line_index: usize = 0; var rendered_status: ?[]const u8 = null; while (lines.next()) |line| : (line_index += 1) { if (line_index == status_line.?) { rendered_status = line; break; } } const status = rendered_status orelse return error.TestExpectedStatus; try std.testing.expect(display_width.visibleWidthIgnoringAnsi(status) <= cols); try std.testing.expect(std.mem.find(u8, status, "…") != 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, }, );
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.?);}
test "historical deferred 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, "Context updated: 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 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 reuses replay block and owns private sidebands" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .maxxing_mode = .legacy, .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, 6), std.mem.count(u8, source.bytes, "│ ")); 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 extends the live transcript source" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .maxxing_mode = .legacy, .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.expect(std.mem.indexOf(u8, live.bytes, "STREAM 001") != null);
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, "│ … 75 lines more (ctrl o to view)") != 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, "Ran stream") orelse return error.MissingCommandStatus; const output_pos = std.mem.indexOf(u8, completed.bytes, "STREAM 001") orelse return error.MissingCommandOutput; try std.testing.expect(status_pos < output_pos);
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, }, .maxxing_mode = .legacy, }; 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.expectEqualStrings( "│ kept\n│ … 1 line more (ctrl o to view)", source.bytes, );}
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 live transcript position" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .maxxing_mode = .legacy, .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, "Ran first") orelse return error.MissingFirstStatus; const first_output = std.mem.indexOf(u8, compact.bytes, "FIRST_OUTPUT") orelse return error.MissingFirstOutput; const second_status = std.mem.indexOf(u8, compact.bytes, "Ran second") orelse return error.MissingSecondStatus; const second_output = std.mem.indexOf(u8, compact.bytes, "SECOND_OUTPUT") orelse return error.MissingSecondOutput;
try std.testing.expect(first_status < second_status); try std.testing.expect(second_status < first_output); try std.testing.expect(second_status < second_output);
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);}
pub const PaintTestMode = enum { none, mutate_backing_after_snapshot,};
const PaintTestModeField = if (@import("builtin").is_test) PaintTestMode else void;const default_max_retained_transcript_bytes: usize = 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,
shimmer_row: ?u16 = null, shimmer_over_footer: bool = false, shimmer_label_hash: u64 = 0,
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, changed_resized, exact, resized,};
const ProjectionCacheDirty = union(enum) { clean, all, entry: struct { id: u32, index: usize, },
fn markEntry(self: *ProjectionCacheDirty, entry_id: u32, entry_index: usize) void { self.* = switch (self.*) { .clean => .{ .entry = .{ .id = entry_id, .index = entry_index } }, .all => .all, .entry => |current| if (entry_index < current.index) .{ .entry = .{ .id = entry_id, .index = entry_index } } else .{ .entry = current }, }; }};
test "projection cache dirtiness follows transcript position instead of entry id" { var dirty: ProjectionCacheDirty = .clean; dirty.markEntry(1, 3); dirty.markEntry(9, 1); const earliest = switch (dirty) { .entry => |entry| entry, .clean, .all => return error.TestExpectedExactDirtyEntry, }; try std.testing.expectEqual(@as(u32, 9), earliest.id); try std.testing.expectEqual(@as(usize, 1), earliest.index);}
const CachedFullTranscriptProjection = struct { projection: full_transcript_screen.Projection, content_revision: u64, entry_count: usize, prefix_last_entry_id: ?u32, cols: u16, resolver: ?full_transcript_screen.FullDiffResolver, dirty: ProjectionCacheDirty = .clean,
fn matches( self: *const CachedFullTranscriptProjection, content_revision: u64, cols: u16, resolver: ?full_transcript_screen.FullDiffResolver, ) bool { return self.content_revision == content_revision and self.cols == cols and sameFullDiffResolver(self.resolver, resolver); }};
const FullTranscriptProjectionCache = struct { review: ProjectionCacheSet = .{}, full: ProjectionCacheSet = .{}, relationships: FullTranscriptRelationshipCache = .{},
fn deinit(self: *FullTranscriptProjectionCache, alloc: Allocator) void { self.review.deinit(alloc); self.full.deinit(alloc); self.relationships.deinit(alloc); self.* = .{}; }};
const CachedFullTranscriptRelationships = struct { projection: tool_group_projection.Projection, content_revision: u64, entry_count: usize, prefix_last_entry_id: ?u32, dirty: ProjectionCacheDirty = .clean,};
fn entryPrefixBoundaryId(entries: []const TranscriptEntry, entry_count: usize) ?u32 { if (entry_count == 0 or entry_count > entries.len) return null; return entries[entry_count - 1].id();}
fn cachedEntryPrefixMatches( entries: []const TranscriptEntry, entry_count: usize, prefix_last_entry_id: ?u32,) bool { if (entry_count > entries.len) return false; return entryPrefixBoundaryId(entries, entry_count) == prefix_last_entry_id;}
test "cached entry prefix rejects retention and lifecycle reposition" { const original = [_]TranscriptEntry{ .{ .raw_bytes = .{ .id = 1, .bytes = "one" } }, .{ .raw_bytes = .{ .id = 2, .bytes = "two" } }, }; const retained = [_]TranscriptEntry{ .{ .raw_bytes = .{ .id = 2, .bytes = "two" } }, .{ .raw_bytes = .{ .id = 3, .bytes = "three" } }, }; const repositioned = [_]TranscriptEntry{ .{ .raw_bytes = .{ .id = 2, .bytes = "two" } }, .{ .raw_bytes = .{ .id = 1, .bytes = "one" } }, };
const boundary = entryPrefixBoundaryId(&original, original.len); try std.testing.expect(cachedEntryPrefixMatches(&original, original.len, boundary)); try std.testing.expect(!cachedEntryPrefixMatches(&retained, original.len, boundary)); try std.testing.expect(!cachedEntryPrefixMatches(&repositioned, original.len, boundary));}
const FullTranscriptRelationshipCache = struct { cached: ?CachedFullTranscriptRelationships = null,
fn deinit(self: *FullTranscriptRelationshipCache, alloc: Allocator) void { if (self.cached) |*cached| cached.projection.deinit(alloc); self.* = .{}; }
fn markDirtyFrom( self: *FullTranscriptRelationshipCache, entry_id: u32, entry_index: usize, ) void { const cached = if (self.cached) |*value| value else return; cached.dirty.markEntry(entry_id, entry_index); }
fn markDirtyAll(self: *FullTranscriptRelationshipCache) void { const cached = if (self.cached) |*value| value else return; cached.dirty = .all; }};
const CachedCompactTranscriptSource = struct { source: TranscriptPreparationSource, content_revision: u64, cols: u16, maxxing_mode: presentation_mode.MaxxingMode, 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, maxxing_mode: presentation_mode.MaxxingMode, has_committed_frame: bool, ) ?*TranscriptPreparationSource { for (&self.entries) |*entry| { if (entry.*) |*cached| { if (cached.content_revision == content_revision and cached.cols == cols and cached.maxxing_mode == maxxing_mode 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; }};
const ProjectionCacheSet = struct { const capacity = 4;
entries: [capacity]?CachedFullTranscriptProjection = .{null} ** capacity, next_replacement: usize = 0,
fn deinit(self: *ProjectionCacheSet, alloc: Allocator) void { for (&self.entries) |*entry| { if (entry.*) |*cached| cached.projection.deinit(alloc); } self.* = .{}; }
fn markDirtyFrom(self: *ProjectionCacheSet, entry_id: u32, entry_index: usize) void { for (&self.entries) |*entry| { if (entry.*) |*cached| { cached.dirty.markEntry(entry_id, entry_index); } } }
fn markDirtyAll(self: *ProjectionCacheSet) void { for (&self.entries) |*entry| { if (entry.*) |*cached| { cached.dirty = .all; } } }
fn find( self: *ProjectionCacheSet, content_revision: u64, cols: u16, resolver: ?full_transcript_screen.FullDiffResolver, ) ?*full_transcript_screen.Projection { for (&self.entries) |*entry| { if (entry.*) |*cached| { if (cached.matches( content_revision, cols, resolver, )) return &cached.projection; } } return null; }
fn incrementalCandidate( self: *ProjectionCacheSet, cols: u16, resolver: ?full_transcript_screen.FullDiffResolver, ) ?*CachedFullTranscriptProjection { for (&self.entries) |*entry| { if (entry.*) |*cached| { if (cached.cols != cols or !sameFullDiffResolver(cached.resolver, resolver)) continue; switch (cached.dirty) { .entry => return cached, .clean, .all => {}, } } } return null; }
fn insert( self: *ProjectionCacheSet, alloc: Allocator, replacement: CachedFullTranscriptProjection, ) *full_transcript_screen.Projection { 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.projection.deinit(alloc); self.entries[index] = replacement; return &self.entries[index].?.projection; }};
fn sameFullDiffResolver( lhs: ?full_transcript_screen.FullDiffResolver, rhs: ?full_transcript_screen.FullDiffResolver,) bool { if (lhs == null or rhs == null) return lhs == null and rhs == null; return lhs.?.context == rhs.?.context and lhs.?.full_for_marker == rhs.?.full_for_marker and lhs.?.has_full_for_lifecycle == rhs.?.has_full_for_lifecycle;}
pub const TranscriptRuntime = struct { stdout_file: std.Io.File = std.Io.File.stdout(), 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 = .{}, maxxing_mode: presentation_mode.MaxxingMode = presentation_mode.MaxxingMode.default, /// 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, /// 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_primary_recovery_entry_id: ?u32 = null, full_transcript_projection_cache: FullTranscriptProjectionCache = .{}, full_transcript_content_revision: u64 = 0, full_transcript_review_revision: u64 = 0, compact_transcript_source_cache: CompactTranscriptSourceCache = .{}, /// 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, 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, /// 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, paint_test_mode: PaintTestModeField = if (@import("builtin").is_test) .none else {}, /// 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_row: u16 = 1, 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 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.compact_transcript_source_cache.deinit(alloc); self.full_transcript_projection_cache.deinit(alloc); self.lifecycle_state.deinit(alloc); for (self.tool_details.items) |*detail| detail.deinit(alloc); self.tool_details.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(); }
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); return transcript_store.clearTranscript(self, alloc); }
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); 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, ); 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), )) return error.MissingLifecycleTranscriptEntry; record.phase = .terminal; 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 replace_result = if (preserve_normal_buffer_anchor) transcript_store.replacePinnedToolStatusesPreservingNormalBufferAnchorAtomic( self, alloc, updates, missing_detail_count, ) else transcript_store.replacePinnedToolStatusesAtomic( self, alloc, updates, missing_detail_count, ); replace_result catch |err| { traceLifecycleFailure("turn_finished", finished.turn_id, err); return err; }; const fallback_outcome: ?types.ToolOutcomeKind = switch (finished.outcome) { .completed => .completed, .interrupted => .cancelled, .failed => .failed, .paused => null, }; 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; detail.fallback_disposition = lifecycleFallbackDisposition( finished.outcome, fallback.was_provisional, ); } 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]; }
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 command_artifact_handle = if (!deferred and activity_kind == .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) .denied else if (result.status == .success or (activity_kind == .command and result.command_process_presentation != null)) .completed else .failed, if (deferred) null else result.output, if (deferred) null else .{ .output_handle = result.output_handle, .preview = 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, }, command_artifact_handle, if (deferred) null else command_output_entry_id, ); }
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; }
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, ); }
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, ); 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, ) !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, ); 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, ) !PendingCommandProcessEntry { var pending: PendingCommandProcessEntry = .{ .entry_id = self.next_entry_id }; 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(); }
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.full_transcript_projection_cache.deinit(alloc); self.clearToolDetails(alloc); self.full_transcript = self.full_transcript.closed(); }
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 releaseStartupResumeViewEntry( self: *TranscriptRuntime, alloc: Allocator, entry_id: u32, ) !bool { return transcript_store.releaseStartupResumeViewEntry(self, alloc, entry_id); }
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 }); defer alloc.free(entry); const entry_id = try transcript_store.appendRawTranscriptEntryClassified(self, alloc, entry, .turn_summary); 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 { return transcript_store.streamAssistantChunk(self, alloc, metrics, text); }
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) return false; if (current == .inline_mode and depth != .inline_mode) { 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; try self.captureFullTranscriptAnchor(alloc); } self.full_transcript = self.full_transcript.with_depth(depth); if (depth == .inline_mode) { self.full_transcript_open_content_revision = null; self.full_transcript_open_cols = 0; self.full_transcript_open_rows = 0; self.full_transcript_primary_recovery_entry_id = null; } 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 (open_revision != self.full_transcript_content_revision) { return if (needs_repair) .changed_resized else .changed; } return if (needs_repair) .resized else .exact; }
pub fn prepareRestoredPrimaryTranscriptRecovery( self: *TranscriptRuntime, alloc: Allocator, ) !?[]u8 { const anchor_entry_id = self.full_transcript_primary_recovery_entry_id orelse { debug_trace.logf( "full_transcript_cache", "primary recovery unavailable reason=missing_entry_id revision={d}", .{self.full_transcript_content_revision}, ); return null; }; var source = try self.prepareTranscriptSourceWithFocusedEntry( alloc, anchor_entry_id, ); defer source.deinit(alloc); try source.ensureLineIndex(alloc); const start_line = source.tracked_entry_start_line orelse fullTranscriptRecoveryPredecessorStartLine( source.line_provenance, anchor_entry_id, ) orelse { debug_trace.logf( "full_transcript_cache", "primary recovery unavailable reason=no_visible_predecessor entry_id={d} revision={d}", .{ anchor_entry_id, self.full_transcript_content_revision }, ); return null; }; if (start_line >= source.transcript_visible_lines.len or source.transcript_visible_lines.len != source.transcript_line_visual_rows.len) { debug_trace.logf( "full_transcript_cache", "primary recovery unavailable reason=line_index entry_id={d} start_line={d} visible_lines={d} visual_rows={d}", .{ anchor_entry_id, start_line, source.transcript_visible_lines.len, source.transcript_line_visual_rows.len, }, ); return null; }
const replay = try transcript_painter.prepareResetReplayDocument( self, alloc, source.bytes, source.transcript_visible_lines[start_line..], source.transcript_line_visual_rows[start_line..], source.hard_lines_end_with_newline, self.layout.rows -| self.layout.content_bottom, ); defer if (replay.len > 0) alloc.free(replay);
const clear_visible = "\x1b[0m\x1b[2J\x1b[H"; const recovery = try alloc.alloc(u8, clear_visible.len + replay.len); @memcpy(recovery[0..clear_visible.len], clear_visible); @memcpy(recovery[clear_visible.len..], replay); debug_trace.logf( "full_transcript_cache", "primary recovery prepared entry_id={d} start_line={d} total_lines={d} bytes={d}", .{ anchor_entry_id, start_line, source.transcript_visible_lines.len, recovery.len }, ); return recovery; }
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 = false; self.resize_history_row_delta = null; self.pending_resize_observation = null; self.render_requests.acknowledgeSettledResizeCommit(); self.invalidateTranscriptAnchor("full transcript restored resized primary"); 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 fallback_recovery_entry_id = self.primaryRecoveryFallbackEntryId(); self.full_transcript_primary_recovery_entry_id = fallback_recovery_entry_id; 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_primary_recovery_entry_id = source_entry_id orelse fallback_recovery_entry_id; self.full_transcript = self.full_transcript.capture_anchor(anchor_entry_id); }
fn primaryRecoveryFallbackEntryId(self: *const TranscriptRuntime) ?u32 { if (self.entries.items.len == 0) return null; const retained_entries = @min( self.entries.items.len, @max(@as(usize, self.layout.rows), 1), ); return self.entries.items[self.entries.items.len - retained_entries].id(); }
fn fullTranscriptRecoveryPredecessorStartLine( provenance: []const transcript_blocks.LineProvenance, target_entry_id: u32, ) ?usize { var candidate_entry_id: ?u32 = null; var candidate_start_line: ?usize = null; for (provenance, 0..) |source, line_index| { const entry_id = switch (source) { .entry => |entry| entry.entry_id, .folded_command_output => |output| output.entry_id orelse continue, else => continue, }; if (entry_id > target_entry_id) continue; if (candidate_entry_id == null or entry_id > candidate_entry_id.?) { candidate_entry_id = entry_id; candidate_start_line = line_index; } } return candidate_start_line; }
pub fn fullTranscriptActive(self: *const TranscriptRuntime) bool { return self.full_transcript.depth.active(); }
pub fn transcriptPresentationDepth( self: *const TranscriptRuntime, ) transcript_presentation.Depth { return self.full_transcript.depth; }
pub fn snapshotFullTranscriptViewport( self: *const TranscriptRuntime, ) transcript_presentation.Snapshot { return self.full_transcript.snapshot(); }
pub fn restoreFullTranscriptViewport( self: *TranscriptRuntime, snapshot: transcript_presentation.Snapshot, ) void { self.full_transcript = .from_snapshot(snapshot); 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(); }
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 { 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); 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 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; self.paint_trace.shimmer_row = null; self.paint_trace.shimmer_over_footer = false; self.paint_trace.shimmer_label_hash = 0; }
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, .layout_id = anchor.layout_id, }, .recovering => |receipt| .{ .state = .recovering, .visual_offset = receipt.accepted_visual_offset, .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 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 { const diagnostic = self.transcriptCommitDiagnostic(); if (diagnostic.state != .invalid) { 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 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 { 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, }, ); 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, }, ); 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 = true, .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 { 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, ); 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)), )); }
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 { self.visual_offset = visual_offset; if (visual_offset == self.total_visual_rows) { try transcript_painter.reprojectPreparedTranscriptForVisualOffset( alloc, 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, 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; 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; } };
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 { 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, 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, )) { return error.InvalidTranscriptTransition; } if (semanticProgressNeedsStaging( target, scroll_facts, accepted_semantic_progress_rows, )) { try target.stagePreparedProjection( alloc, self.layout, prepared, target_layout.transcript_area, scroll_facts.source_visual_offset + accepted_semantic_progress_rows, ); } if (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, target_layout.transcript_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 !target_layout.transcript_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. var projection_layout = self.layout; projection_layout.content_bottom = target_layout.transcript_area.bottom; try target.stagePreparedProjection( alloc, projection_layout, prepared, target_layout.transcript_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| { var projection_layout = self.layout; projection_layout.content_bottom = target_layout.transcript_area.bottom; try target.stagePreparedProjection( alloc, projection_layout, prepared, target_layout.transcript_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 (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, target_layout.transcript_area, target.visual_offset, ); } else { try transcript_painter.reprojectPreparedTranscriptForVisualOffset( alloc, self.layout, prepared, target_layout.transcript_area, target.visual_offset, ); target.usePreparedProjection(prepared); } } }, .invalid => if (semanticProgressNeedsStaging( target, scroll_facts, accepted_semantic_progress_rows, )) { try target.stagePreparedProjection( alloc, self.layout, prepared, target_layout.transcript_area, accepted_semantic_progress_rows, ); }, } 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, 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 (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 (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 and self.committed_frame_layout.terminal_cols == self.layout.cols and self.committed_frame_layout.terminal_rows == self.layout.rows) { return .{ .flow_len = 0, .visual_offset = 0, .visual_rows = 0, .cursor_row = prepared.projectionArea().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 = anchor.flow.len, .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 { 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, 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 (!target_layout.transcript_area.isEmpty() and target.selection.last_visible_row > target_layout.transcript_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, target_layout.transcript_area.top, target_layout.transcript_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 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_pending_resume) { 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; } }
const target_flow = source.bytes; if (!borrows_pending_resume) source.bytes = &.{}; errdefer if (!borrows_pending_resume and target_flow.len > 0) alloc.free(target_flow);
const folded_summary_indices = source.folded_summary_indices; if (!borrows_pending_resume) source.folded_summary_indices = &.{}; errdefer if (!borrows_pending_resume 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_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 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); 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 { document_append_bytes = try transcript_painter.prepareTranscriptDocumentAppendBytes( alloc, target_flow, self.layout.cols, base.flow_len, projected_flow_end.?, target.projection_staged, ); } 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, .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; const materialized = resolveMaterializedEndpoint( target, source_endpoint_cursor_row, source_endpoint_cursor_col, append_base, projected_flow_end, target_flow.len, target_layout.transcript_area, ); 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, .target_flow_owned = !borrows_pending_resume, .presentation_resume_bytes = presentation_resume_bytes, .presentation_pending_wrap = presentation_pending_wrap, .presentation_valid = presentation_valid, .target_cache = target_cache, .target_cache_unchanged = borrows_pending_resume, .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 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, ); }
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; }
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, .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, .history_catchup_pending = transition.history_catchup_pending, } }; transition.row_provenance = &.{};
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) { 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, .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 = &.{}; 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, ); }
/// 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 { 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 { 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 { 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.maxxing_mode, 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, .maxxing_mode = self.maxxing_mode, .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 { 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 { source: TranscriptPreparationSource, prepared: transcript_painter.PreparedTranscriptSurfacePaint, };
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 cachedFullTranscriptProjection( self: *TranscriptRuntime, alloc: Allocator, full_diff_resolver: ?full_transcript_screen.FullDiffResolver, ) !*full_transcript_screen.Projection { return self.cachedFullTranscriptProjectionInterruptible( alloc, full_diff_resolver, null, ) catch |err| switch (err) { error.InputPending => unreachable, else => |other| return other, }; }
pub fn cachedFullTranscriptProjectionInterruptible( self: *TranscriptRuntime, alloc: Allocator, full_diff_resolver: ?full_transcript_screen.FullDiffResolver, checkpoint: ?*build_checkpoint.BuildCheckpoint, ) !*full_transcript_screen.Projection { self.prepareFullTranscriptProjectionLayout(); const cache = switch (self.full_transcript.depth) { .review, .inline_mode => &self.full_transcript_projection_cache.review, .full => &self.full_transcript_projection_cache.full, }; const content_revision = switch (self.full_transcript.depth) { .review, .inline_mode => self.full_transcript_review_revision, .full => self.full_transcript_content_revision, }; if (cache.find( content_revision, self.layout.cols, full_diff_resolver, )) |projection| { debug_trace.logf( "full_transcript_cache", "hit depth={s} revision={d} cols={d} measured_cols={any}", .{ @tagName(self.full_transcript.depth), content_revision, self.layout.cols, projection.measurement_cols }, ); return projection; }
debug_trace.logf( "full_transcript_cache", "miss depth={s} revision={d} cols={d}", .{ @tagName(self.full_transcript.depth), content_revision, self.layout.cols }, );
if (cache.incrementalCandidate(self.layout.cols, full_diff_resolver)) |cached| { const dirty_entry = switch (cached.dirty) { .entry => |entry| entry, .clean, .all => unreachable, }; if (cachedEntryPrefixMatches( self.entries.items, cached.entry_count, cached.prefix_last_entry_id, )) { if (tool_group_projection.incrementalRebuildStart( self.entries.items, self.tool_details.items, dirty_entry.index, )) |relationship_start| { var start_index = relationship_start; if (start_index > 0) { const previous_id = self.entries.items[start_index - 1].id(); const context_id = cached.projection.retainedContextEntryId(previous_id); start_index = if (context_id) |entry_id| tool_group_projection.incrementalRebuildStart( self.entries.items, self.tool_details.items, self.transcriptEntryIndex(entry_id) orelse start_index - 1, ) orelse start_index - 1 else 0; } if (start_index < cached.entry_count) { var suffix = try self.buildFullTranscriptProjectionUncachedFrom( alloc, full_diff_resolver, null, start_index, checkpoint, ); defer suffix.deinit(alloc); if (try cached.projection.replaceFromEntry( alloc, self.entries.items[start_index].id(), &suffix, )) { cached.content_revision = content_revision; cached.entry_count = self.entries.items.len; cached.prefix_last_entry_id = entryPrefixBoundaryId( self.entries.items, self.entries.items.len, ); cached.dirty = .clean; debug_trace.logf( "full_transcript_cache", "append depth={s} revision={d} start_entry_id={d} entries={d}", .{ @tagName(self.full_transcript.depth), content_revision, self.entries.items[start_index].id(), self.entries.items.len - start_index, }, ); return &cached.projection; } } } } cached.dirty = .all; }
var replacement = try self.buildFullTranscriptProjectionUncached( alloc, full_diff_resolver, null, checkpoint, ); errdefer replacement.deinit(alloc); return cache.insert(alloc, .{ .projection = replacement, .content_revision = content_revision, .entry_count = self.entries.items.len, .prefix_last_entry_id = entryPrefixBoundaryId( self.entries.items, self.entries.items.len, ), .cols = self.layout.cols, .resolver = full_diff_resolver, }); }
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 { const relationships = try self.cachedFullTranscriptRelationshipsInterruptible( alloc, checkpoint, ); var entry_actions = try tool_group_projection.materializeExpandedRelationshipsRangeInterruptible( alloc, relationships, start_index, self.layout.cols, .{ .marker_style = user_message_card.minimalMarkerStyle(), .text_style = ui_render.statusline_style, .reset_style = "\x1b[0m", }, checkpoint, ); defer entry_actions.deinit(alloc); return full_transcript_screen.buildProjectionForDepthWithEntryActionsInterruptible( 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, switch (self.full_transcript.depth) { .review => .review, .full => .full, .inline_mode => .review, }, full_diff_resolver, entry_actions.entry_actions.items, checkpoint, ); }
fn cachedFullTranscriptRelationshipsInterruptible( self: *TranscriptRuntime, alloc: Allocator, checkpoint: ?*build_checkpoint.BuildCheckpoint, ) !*tool_group_projection.Projection { const cache = &self.full_transcript_projection_cache.relationships; if (cache.cached) |*cached| { switch (cached.dirty) { .clean => if (cached.content_revision == self.full_transcript_content_revision) { debug_trace.logf( "full_transcript_cache", "relationships_hit revision={d} entries={d}", .{ cached.content_revision, cached.entry_count }, ); return &cached.projection; }, .entry => |entry| { if (cachedEntryPrefixMatches( self.entries.items, cached.entry_count, cached.prefix_last_entry_id, )) { if (tool_group_projection.incrementalRebuildStart( self.entries.items, self.tool_details.items, entry.index, )) |start_index| { if (start_index <= cached.projection.entry_actions.items.len) { var suffix = try tool_group_projection.buildExpandedRelationshipsInterruptible( alloc, self.entries.items[start_index..], self.tool_details.items, checkpoint, ); defer suffix.deinit(alloc); try cached.projection.replaceSuffix(alloc, start_index, &suffix); cached.content_revision = self.full_transcript_content_revision; cached.entry_count = self.entries.items.len; cached.prefix_last_entry_id = entryPrefixBoundaryId( self.entries.items, self.entries.items.len, ); cached.dirty = .clean; debug_trace.logf( "full_transcript_cache", "relationships_append revision={d} start_index={d} entries={d}", .{ cached.content_revision, start_index, cached.entry_count }, ); return &cached.projection; } } } }, .all => {}, } }
var replacement = try tool_group_projection.buildExpandedRelationshipsInterruptible( alloc, self.entries.items, self.tool_details.items, checkpoint, ); errdefer replacement.deinit(alloc); if (cache.cached) |*cached| cached.projection.deinit(alloc); cache.cached = .{ .projection = replacement, .content_revision = self.full_transcript_content_revision, .entry_count = self.entries.items.len, .prefix_last_entry_id = entryPrefixBoundaryId( self.entries.items, self.entries.items.len, ), }; debug_trace.logf( "full_transcript_cache", "relationships_rebuild revision={d} entries={d}", .{ self.full_transcript_content_revision, self.entries.items.len }, ); return &cache.cached.?.projection; }
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 { 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 .{ .source = source, .prepared = prepared }; }
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; }
test "full transcript depth changes preserve tail and history viewport intent" { const alloc = std.testing.allocator; const review_measurement = full_transcript_screen.ProjectionMeasurement{ .total_rows = 13, .anchor_row = null, .item_rows = &.{ .{ .entry_id = 10, .row = 0 }, .{ .entry_id = 20, .row = 6 }, }, }; const full_measurement = full_transcript_screen.ProjectionMeasurement{ .total_rows = 30, .anchor_row = null, .item_rows = &.{ .{ .entry_id = 10, .row = 0 }, .{ .entry_id = 20, .row = 12 }, .{ .entry_id = 30, .row = 24 }, }, };
var tail = TranscriptRuntime{ .full_transcript = .{ .depth = .review } }; defer tail.deinit(alloc); try std.testing.expectEqual( @as(u32, 8), selectProjectionViewportOffset(&tail, review_measurement, 5), ); try std.testing.expect(try tail.setTranscriptPresentationDepth(alloc, .full)); try std.testing.expectEqual( @as(u32, 25), selectProjectionViewportOffset(&tail, full_measurement, 5), ); try std.testing.expect(tail.full_transcript.follow_tail);
var history = TranscriptRuntime{ .full_transcript = .{ .depth = .review } }; defer history.deinit(alloc); _ = selectProjectionViewportOffset(&history, review_measurement, 5); history.full_transcript = history.full_transcript.scroll(.up, 2); try std.testing.expectEqual( @as(u32, 6), selectProjectionViewportOffset(&history, review_measurement, 5), ); try std.testing.expect(try history.setTranscriptPresentationDepth(alloc, .full)); try std.testing.expectEqual( @as(u32, 12), selectProjectionViewportOffset(&history, full_measurement, 5), ); try std.testing.expect(!history.full_transcript.follow_tail); }
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); }
pub fn markTranscriptContentDirty(self: *TranscriptRuntime) void { self.full_transcript_content_revision +%= 1; self.full_transcript_projection_cache.relationships.markDirtyAll(); self.full_transcript_projection_cache.full.markDirtyAll(); if (self.command_output_display.open_command_block == null) { self.full_transcript_review_revision +%= 1; self.full_transcript_projection_cache.review.markDirtyAll(); } debug_trace.logf( "full_transcript_cache", "content_dirty content_revision={d} review_revision={d} command_open={s}", .{ self.full_transcript_content_revision, self.full_transcript_review_revision, if (self.command_output_display.open_command_block == null) "false" else "true", }, ); self.markTranscriptDirty(); }
pub fn markTranscriptStructureDirty(self: *TranscriptRuntime) void { self.full_transcript_content_revision +%= 1; self.full_transcript_review_revision +%= 1; self.full_transcript_projection_cache.relationships.markDirtyAll(); self.full_transcript_projection_cache.full.markDirtyAll(); self.full_transcript_projection_cache.review.markDirtyAll(); debug_trace.logf( "full_transcript_cache", "structure_dirty content_revision={d} review_revision={d}", .{ self.full_transcript_content_revision, self.full_transcript_review_revision }, ); self.markTranscriptDirty(); }
fn transcriptEntryIndex(self: *const TranscriptRuntime, entry_id: u32) ?usize { var index = self.entries.items.len; while (index > 0) { index -= 1; if (self.entries.items[index].id() == entry_id) return index; } return null; }
pub fn markTranscriptContentDirtyFrom( self: *TranscriptRuntime, entry_id: u32, ) void { const entry_index = self.transcriptEntryIndex(entry_id) orelse { self.markTranscriptContentDirty(); return; }; self.full_transcript_content_revision +%= 1; self.full_transcript_projection_cache.relationships.markDirtyFrom(entry_id, entry_index); self.full_transcript_projection_cache.full.markDirtyFrom(entry_id, entry_index); if (self.command_output_display.open_command_block == null) { self.full_transcript_review_revision +%= 1; self.full_transcript_projection_cache.review.markDirtyFrom(entry_id, entry_index); } debug_trace.logf( "full_transcript_cache", "content_dirty_from entry_id={d} content_revision={d} review_revision={d} command_open={s}", .{ entry_id, self.full_transcript_content_revision, self.full_transcript_review_revision, if (self.command_output_display.open_command_block == null) "false" else "true", }, ); self.markTranscriptDirty(); }
pub fn markTranscriptCommandOutputDirty(self: *TranscriptRuntime) void { self.full_transcript_content_revision +%= 1; self.full_transcript_projection_cache.relationships.markDirtyAll(); self.full_transcript_projection_cache.full.markDirtyAll(); debug_trace.logf( "full_transcript_cache", "command_output_dirty content_revision={d} review_revision={d}", .{ self.full_transcript_content_revision, self.full_transcript_review_revision }, ); self.markTranscriptDirty(); }
pub fn markTranscriptCommandOutputDirtyFrom( self: *TranscriptRuntime, entry_id: u32, ) void { const entry_index = self.transcriptEntryIndex(entry_id) orelse { self.markTranscriptCommandOutputDirty(); return; }; self.full_transcript_content_revision +%= 1; self.full_transcript_projection_cache.relationships.markDirtyFrom(entry_id, entry_index); self.full_transcript_projection_cache.full.markDirtyFrom(entry_id, entry_index); debug_trace.logf( "full_transcript_cache", "command_output_dirty_from entry_id={d} content_revision={d} review_revision={d}", .{ entry_id, self.full_transcript_content_revision, self.full_transcript_review_revision, }, ); self.markTranscriptDirty(); }
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; }};
fn frameBandsEqual(a: render_engine.paint_plan.FrameBand, b: render_engine.paint_plan.FrameBand) bool { return a.top == b.top and a.bottom == b.bottom and a.owner == b.owner;}
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 { const planned = transition.materialized; 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| anchor.visual_offset, .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 retains bounded recovery progress" { const alloc = std.testing.allocator; const layout = invalidationTestLayout(); var runtime = TranscriptRuntime{ .layout = layout, .owned_top_row = 1, .committed_frame_layout = .{ .terminal_rows = layout.rows, .terminal_cols = layout.cols, }, }; 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, facts, false, source.bytes, facts.target_visual_offset, )) orelse return error.TestExpectedResumeAppendBase; try std.testing.expectEqual(@as(usize, 0), append_base.flow_len);}
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, .footer_reservation_source = .none, .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 "transcript runtime test default matches production presentation mode" { const runtime = TranscriptRuntime{}; try std.testing.expectEqual(presentation_mode.MaxxingMode.default, runtime.maxxing_mode);}
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, }, .footer_reservation_source = .none, .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" { const alloc = std.testing.allocator; const target_flow = "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"; 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, }, .footer_reservation_source = .none, .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()); 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, 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, 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");}