@pasko70/pibo 1.11.3 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/dist/apps/chat/data/chat-data-mappers.js +18 -6
  2. package/dist/apps/{context-files-ui/assets/dist-BOiGdrG-.js → chat-ui/assets/dist-B2Pozq2c.js} +1 -1
  3. package/dist/apps/chat-ui/assets/dist-B5PTVnox.js +1 -0
  4. package/dist/apps/chat-ui/assets/dist-B5ZAfwf5.js +1 -0
  5. package/dist/apps/{context-files-ui/assets/dist-TF5iO5ML.js → chat-ui/assets/dist-Bgv5Fm3v.js} +1 -1
  6. package/dist/apps/chat-ui/assets/dist-BkUONE3N.js +1 -0
  7. package/dist/apps/{context-files-ui/assets/dist-TbjAhles.js → chat-ui/assets/dist-Ci4lft6y.js} +1 -1
  8. package/dist/apps/{context-files-ui/assets/dist-DPG9jOQ-.js → chat-ui/assets/dist-CkLit_da.js} +1 -1
  9. package/dist/apps/chat-ui/assets/{dist-SVPsM5Oi.js → dist-D1KyNDg5.js} +2 -2
  10. package/dist/apps/{context-files-ui/assets/dist-vnd7PMu3.js → chat-ui/assets/dist-D1U9ck8z.js} +1 -1
  11. package/dist/apps/{context-files-ui/assets/dist-kmsvtHkH.js → chat-ui/assets/dist-D4qbF8wy.js} +1 -1
  12. package/dist/apps/chat-ui/assets/dist-DKoyMSNr.js +1 -0
  13. package/dist/apps/chat-ui/assets/dist-DLMjd26T.js +1 -0
  14. package/dist/apps/{context-files-ui/assets/dist-DK01b7x4.js → chat-ui/assets/dist-DYdgxFZX.js} +1 -1
  15. package/dist/apps/{context-files-ui/assets/dist-ZsBbt0uY.js → chat-ui/assets/dist-DiPvo4rq.js} +1 -1
  16. package/dist/apps/chat-ui/assets/dist-DotDq_kD.js +1 -0
  17. package/dist/apps/{context-files-ui/assets/dist-DqYw0LH7.js → chat-ui/assets/dist-DxJWicji.js} +1 -1
  18. package/dist/apps/chat-ui/assets/dist-QQuDy4KA.js +1 -0
  19. package/dist/apps/{context-files-ui/assets/dist-Dp5AH00p.js → chat-ui/assets/dist-la7ZfDGk.js} +1 -1
  20. package/dist/apps/chat-ui/assets/{dockerfile-CUaqzcqp.js → dockerfile-CJb2sekW.js} +1 -1
  21. package/dist/apps/chat-ui/assets/{factor-C2tAxNob.js → factor-B2EMQZkP.js} +1 -1
  22. package/dist/apps/chat-ui/assets/index-BJoo_GJP.css +1 -0
  23. package/dist/apps/chat-ui/assets/index-CYxPvrxL.js +237 -0
  24. package/dist/apps/chat-ui/assets/{nsis-SMDwKltR.js → nsis-DOo3SJmd.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{pug-Bo3wfL-f.js → pug-QU72BPAa.js} +1 -1
  26. package/dist/apps/chat-ui/index.html +3 -4
  27. package/dist/apps/chat-vscode-web/assets/index-CFSHKXsQ.js +41 -0
  28. package/dist/apps/chat-vscode-web/assets/{index-Bf2JvJ9z.css → index-Dupu1DzQ.css} +1 -1
  29. package/dist/apps/chat-vscode-web/index.html +2 -2
  30. package/dist/apps/{chat-ui/assets/dist-BQmnOdXD.js → context-files-ui/assets/dist-BYcsFgKp.js} +2 -2
  31. package/dist/apps/context-files-ui/assets/dist-Bdudyq9I.js +1 -0
  32. package/dist/apps/context-files-ui/assets/dist-BvIW3V94.js +1 -0
  33. package/dist/apps/context-files-ui/assets/dist-CSLUWBzz.js +1 -0
  34. package/dist/apps/context-files-ui/assets/dist-CT-91DFe.js +1 -0
  35. package/dist/apps/context-files-ui/assets/dist-DcsDivye.js +11 -0
  36. package/dist/apps/context-files-ui/assets/dist-DeozI4lc.js +9 -0
  37. package/dist/apps/context-files-ui/assets/dist-f2E1-leP.js +1 -0
  38. package/dist/apps/context-files-ui/assets/dist-sus90AFR.js +1 -0
  39. package/dist/apps/context-files-ui/assets/dist-uFphVGi8.js +1 -0
  40. package/dist/apps/context-files-ui/assets/index-CQ25ablG.js +202 -0
  41. package/dist/apps/context-files-ui/assets/{index-B92tL3Pp.css → index-CukPnd6A.css} +1 -1
  42. package/dist/apps/context-files-ui/index.html +2 -2
  43. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  44. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.12.0.vsix +0 -0
  45. package/dist/debug/web-render-order-analysis.js +307 -0
  46. package/dist/debug/web-snapshot-browser-scripts.js +10 -1
  47. package/dist/debug/web-streaming-benchmark-analysis.js +12 -1
  48. package/dist/debug/web-streaming-browser-library.js +268 -58
  49. package/dist/debug/web-streaming-report.js +24 -0
  50. package/dist/debug/web.js +15 -4
  51. package/dist/plugins/context-files.js +3 -1
  52. package/dist/tools/cdp-client.js +87 -3
  53. package/package.json +1 -1
  54. package/dist/apps/chat-ui/assets/dist--55mTWk3.js +0 -1
  55. package/dist/apps/chat-ui/assets/dist-1w_WVrcu.js +0 -11
  56. package/dist/apps/chat-ui/assets/dist-BEd6jKzd.js +0 -1
  57. package/dist/apps/chat-ui/assets/dist-BI1eS8pb.js +0 -1
  58. package/dist/apps/chat-ui/assets/dist-BLdgeEs8.js +0 -9
  59. package/dist/apps/chat-ui/assets/dist-BZ39rB1W.js +0 -1
  60. package/dist/apps/chat-ui/assets/dist-BmGSbokp.js +0 -23
  61. package/dist/apps/chat-ui/assets/dist-BsedZGJo.js +0 -1
  62. package/dist/apps/chat-ui/assets/dist-Bwx_CaKF.js +0 -1
  63. package/dist/apps/chat-ui/assets/dist-C5YJOwhH.js +0 -1
  64. package/dist/apps/chat-ui/assets/dist-C5oXtFSP.js +0 -1
  65. package/dist/apps/chat-ui/assets/dist-CKM5j-iy.js +0 -1
  66. package/dist/apps/chat-ui/assets/dist-CRYLB6HZ.js +0 -1
  67. package/dist/apps/chat-ui/assets/dist-CUp9us2q.js +0 -1
  68. package/dist/apps/chat-ui/assets/dist-CZ9T4Cly.js +0 -1
  69. package/dist/apps/chat-ui/assets/dist-CoUOMSbW.js +0 -1
  70. package/dist/apps/chat-ui/assets/dist-DSKQpPaz.js +0 -1
  71. package/dist/apps/chat-ui/assets/dist-Dehi8o5p.js +0 -6
  72. package/dist/apps/chat-ui/assets/dist-Zqt9kNG9.js +0 -1
  73. package/dist/apps/chat-ui/assets/dist-o1kTkdhi.js +0 -1
  74. package/dist/apps/chat-ui/assets/dist-tGfufz3b.js +0 -1
  75. package/dist/apps/chat-ui/assets/index-CYLZe0Y0.css +0 -1
  76. package/dist/apps/chat-ui/assets/index-DT80TM0S.js +0 -173
  77. package/dist/apps/chat-ui/assets/rolldown-runtime-S-ySWqyJ.js +0 -1
  78. package/dist/apps/chat-vscode-web/assets/index-DVlCO7v_.js +0 -41
  79. package/dist/apps/context-files-ui/assets/index-BhAWD3dv.js +0 -202
  80. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.11.3.vsix +0 -0
  81. /package/dist/apps/chat-ui/assets/{apl-BxEMbctQ.js → apl-14BqhLYK.js} +0 -0
  82. /package/dist/apps/chat-ui/assets/{asciiarmor-eFjeGRuZ.js → asciiarmor-WLvjqI7d.js} +0 -0
  83. /package/dist/apps/chat-ui/assets/{asn1-Cfx-0pt9.js → asn1-Bg4FbdCg.js} +0 -0
  84. /package/dist/apps/chat-ui/assets/{asterisk-B5BHTsBr.js → asterisk-B2MO-X4d.js} +0 -0
  85. /package/dist/apps/chat-ui/assets/{brainfuck-CAA2xofr.js → brainfuck-CV9kufud.js} +0 -0
  86. /package/dist/apps/chat-ui/assets/{clike-BoDCOLzN.js → clike-CjmcUpt6.js} +0 -0
  87. /package/dist/apps/chat-ui/assets/{clojure-kVFSWX0o.js → clojure-De3QGfx6.js} +0 -0
  88. /package/dist/apps/chat-ui/assets/{cmake-BUPAhezI.js → cmake-i2VDNaRq.js} +0 -0
  89. /package/dist/apps/chat-ui/assets/{cobol-BfgHZgup.js → cobol-BwO6DF08.js} +0 -0
  90. /package/dist/apps/chat-ui/assets/{coffeescript-G1Pe_1AZ.js → coffeescript-BtivDvRz.js} +0 -0
  91. /package/dist/apps/chat-ui/assets/{commonlisp-B3N0CjHn.js → commonlisp-DFkEQdLP.js} +0 -0
  92. /package/dist/apps/chat-ui/assets/{crystal-C15ndaGM.js → crystal-CU0NkcVL.js} +0 -0
  93. /package/dist/apps/chat-ui/assets/{css-BqOeyZOV.js → css-CbMZAQ1W.js} +0 -0
  94. /package/dist/apps/chat-ui/assets/{cypher-KjIKv-wW.js → cypher-Dke5QOzv.js} +0 -0
  95. /package/dist/apps/chat-ui/assets/{d-BlT5AEC-.js → d-TPqSVaHM.js} +0 -0
  96. /package/dist/apps/chat-ui/assets/{diff-oF98tbqj.js → diff-B365LSoZ.js} +0 -0
  97. /package/dist/apps/chat-ui/assets/{dtd-VCA9yr61.js → dtd-CBMymJ7O.js} +0 -0
  98. /package/dist/apps/chat-ui/assets/{dylan-O-V5Dw5W.js → dylan-08Azy1NK.js} +0 -0
  99. /package/dist/apps/chat-ui/assets/{ebnf-SPmdBkvT.js → ebnf-Dkum_1J8.js} +0 -0
  100. /package/dist/apps/chat-ui/assets/{ecl-ChfCttfI.js → ecl-dhxjHL8y.js} +0 -0
  101. /package/dist/apps/chat-ui/assets/{eiffel-cB9EbjeN.js → eiffel-C446m6dI.js} +0 -0
  102. /package/dist/apps/chat-ui/assets/{elm-BTiVflBU.js → elm-po1E9de0.js} +0 -0
  103. /package/dist/apps/chat-ui/assets/{erlang-egf0TXFm.js → erlang-BD5LkVkR.js} +0 -0
  104. /package/dist/apps/chat-ui/assets/{fcl-BiCjh1tj.js → fcl-DkEOamHd.js} +0 -0
  105. /package/dist/apps/chat-ui/assets/{forth-C4kDGW6u.js → forth-dCg2_HVB.js} +0 -0
  106. /package/dist/apps/chat-ui/assets/{fortran-DzkLaPnd.js → fortran-3ZpXLFh1.js} +0 -0
  107. /package/dist/apps/chat-ui/assets/{gas-DMOxskQF.js → gas-DMmxLZRI.js} +0 -0
  108. /package/dist/apps/chat-ui/assets/{gherkin-BPxBpp-k.js → gherkin-DUSFg2R6.js} +0 -0
  109. /package/dist/apps/chat-ui/assets/{groovy-CkEnc0ZY.js → groovy-Cmduloe1.js} +0 -0
  110. /package/dist/apps/chat-ui/assets/{haskell-Dadpo9-w.js → haskell-Cyr255R9.js} +0 -0
  111. /package/dist/apps/chat-ui/assets/{haxe-CDNZL9MW.js → haxe-BJVKtprM.js} +0 -0
  112. /package/dist/apps/chat-ui/assets/{http-jy9Zxxt3.js → http-DszHFpc-.js} +0 -0
  113. /package/dist/apps/chat-ui/assets/{idl-CGSB6iiy.js → idl-Cati9Nrv.js} +0 -0
  114. /package/dist/apps/chat-ui/assets/{javascript-DSlzRUd8.js → javascript-C5pDVaoP.js} +0 -0
  115. /package/dist/apps/chat-ui/assets/{julia-DTiF9v6R.js → julia-DlqY1cTU.js} +0 -0
  116. /package/dist/apps/chat-ui/assets/{livescript-Ci8Xxna6.js → livescript-C6p8-2W1.js} +0 -0
  117. /package/dist/apps/chat-ui/assets/{lua-eVxIrt9t.js → lua-BxuTHaF7.js} +0 -0
  118. /package/dist/apps/chat-ui/assets/{mathematica-Da3grj36.js → mathematica-nDHZUMJT.js} +0 -0
  119. /package/dist/apps/chat-ui/assets/{mbox-BWBMSafM.js → mbox-DAaR0Hcz.js} +0 -0
  120. /package/dist/apps/chat-ui/assets/{mirc-CtHu9S1B.js → mirc-CCA0FRfl.js} +0 -0
  121. /package/dist/apps/chat-ui/assets/{mllike-wTFlPWXS.js → mllike-Cw3Iccsb.js} +0 -0
  122. /package/dist/apps/chat-ui/assets/{modelica-cKbQ0UXu.js → modelica-Czx80fNR.js} +0 -0
  123. /package/dist/apps/chat-ui/assets/{mscgen-C5pXQCMK.js → mscgen-BmfuTbzG.js} +0 -0
  124. /package/dist/apps/chat-ui/assets/{mumps-CKBHkOQq.js → mumps-Ds_ZaumL.js} +0 -0
  125. /package/dist/apps/chat-ui/assets/{nginx-B00AiQel.js → nginx-D9M2PQ4U.js} +0 -0
  126. /package/dist/apps/chat-ui/assets/{ntriples-B_36EBV0.js → ntriples-DLHGTvHs.js} +0 -0
  127. /package/dist/apps/chat-ui/assets/{octave-BXPjzAif.js → octave-8jj96JiC.js} +0 -0
  128. /package/dist/apps/chat-ui/assets/{oz-Dedpat1M.js → oz-BmMq7iC4.js} +0 -0
  129. /package/dist/apps/chat-ui/assets/{pascal-CMcUg72K.js → pascal-BgMr6XpT.js} +0 -0
  130. /package/dist/apps/chat-ui/assets/{perl-DoVTbnzt.js → perl-D6YJObi_.js} +0 -0
  131. /package/dist/apps/chat-ui/assets/{pig-CULXT1YV.js → pig-nx7uchs8.js} +0 -0
  132. /package/dist/apps/chat-ui/assets/{powershell-Cv5Uhf6b.js → powershell-B4IPkVxR.js} +0 -0
  133. /package/dist/apps/chat-ui/assets/{properties-CQ1C2P-M.js → properties-CoavQdLC.js} +0 -0
  134. /package/dist/apps/chat-ui/assets/{protobuf-SRlvzrl6.js → protobuf-D5EWhcmc.js} +0 -0
  135. /package/dist/apps/chat-ui/assets/{puppet-ClSqCGj6.js → puppet-B_0GFuyD.js} +0 -0
  136. /package/dist/apps/chat-ui/assets/{python-C2_X1GpX.js → python-Cs7sNzy_.js} +0 -0
  137. /package/dist/apps/chat-ui/assets/{q-B_6Gdyzl.js → q-BIB9pPtI.js} +0 -0
  138. /package/dist/apps/chat-ui/assets/{r-BD07NGED.js → r-CN5s4f3o.js} +0 -0
  139. /package/dist/apps/chat-ui/assets/{rpm-CRu5Z2Jl.js → rpm-Ct_Ck8mQ.js} +0 -0
  140. /package/dist/apps/chat-ui/assets/{ruby-dZPnOz8h.js → ruby-BVPradRk.js} +0 -0
  141. /package/dist/apps/chat-ui/assets/{sas-S2t8_sDI.js → sas-DN4j74IF.js} +0 -0
  142. /package/dist/apps/chat-ui/assets/{scheme-WcyFWLW2.js → scheme-CvNgHAnh.js} +0 -0
  143. /package/dist/apps/chat-ui/assets/{shell-DUelnYSh.js → shell-BjiMLAaM.js} +0 -0
  144. /package/dist/apps/chat-ui/assets/{sieve-jDjlVhNh.js → sieve-C2U1O5Z4.js} +0 -0
  145. /package/dist/apps/chat-ui/assets/{simple-mode-CL04yRVp.js → simple-mode-DnMbQuKW.js} +0 -0
  146. /package/dist/apps/chat-ui/assets/{smalltalk-ddWDYy10.js → smalltalk-DkYwYufV.js} +0 -0
  147. /package/dist/apps/chat-ui/assets/{solr-C7rQhLuW.js → solr-BopnSD-v.js} +0 -0
  148. /package/dist/apps/chat-ui/assets/{sparql--fPCCgVd.js → sparql-Cpg3YQfd.js} +0 -0
  149. /package/dist/apps/chat-ui/assets/{spreadsheet-DjthqD5G.js → spreadsheet-kRxtVt9J.js} +0 -0
  150. /package/dist/apps/chat-ui/assets/{sql-D_dieYbr.js → sql-DYI2GwJR.js} +0 -0
  151. /package/dist/apps/chat-ui/assets/{stex-rHcFYcA0.js → stex-BCRT8IJ3.js} +0 -0
  152. /package/dist/apps/chat-ui/assets/{stylus-DxF70RNr.js → stylus-CARoeVE9.js} +0 -0
  153. /package/dist/apps/chat-ui/assets/{swift-T0hDoOb-.js → swift-DKNaLAK6.js} +0 -0
  154. /package/dist/apps/chat-ui/assets/{tcl-Dlzy1mPk.js → tcl-CQyXkgMH.js} +0 -0
  155. /package/dist/apps/chat-ui/assets/{textile-Dy5rFZe5.js → textile-Yg_UWn-t.js} +0 -0
  156. /package/dist/apps/chat-ui/assets/{tiddlywiki-DQP1OymS.js → tiddlywiki-dqVbkfX4.js} +0 -0
  157. /package/dist/apps/chat-ui/assets/{tiki-0vOj3DB2.js → tiki-CC_ic8g0.js} +0 -0
  158. /package/dist/apps/chat-ui/assets/{toml-DEltN0lI.js → toml-B7gGzVkJ.js} +0 -0
  159. /package/dist/apps/chat-ui/assets/{troff-CRbBpEqY.js → troff-Bq3z6oAu.js} +0 -0
  160. /package/dist/apps/chat-ui/assets/{ttcn-M5q1nLKg.js → ttcn-GvRgs2AT.js} +0 -0
  161. /package/dist/apps/chat-ui/assets/{ttcn-cfg-Rmw9NWq9.js → ttcn-cfg-CGCXz7Vo.js} +0 -0
  162. /package/dist/apps/chat-ui/assets/{turtle-BCDWhJK0.js → turtle-CwIpz2yy.js} +0 -0
  163. /package/dist/apps/chat-ui/assets/{vb-QQ7LGlOz.js → vb-BmcHUM2C.js} +0 -0
  164. /package/dist/apps/chat-ui/assets/{vbscript-BiSEdUzz.js → vbscript-Cs9dylre.js} +0 -0
  165. /package/dist/apps/chat-ui/assets/{velocity-CzMg5_6w.js → velocity-CBWU0VCy.js} +0 -0
  166. /package/dist/apps/chat-ui/assets/{verilog-BJOO83gk.js → verilog-DVCHmufx.js} +0 -0
  167. /package/dist/apps/chat-ui/assets/{vhdl-ed4zAZAA.js → vhdl-BGDk4f1d.js} +0 -0
  168. /package/dist/apps/chat-ui/assets/{webidl-Ddy6hbV6.js → webidl-egXQEWlC.js} +0 -0
  169. /package/dist/apps/chat-ui/assets/{xquery-Bw1zwhh6.js → xquery-5f0hn7cg.js} +0 -0
  170. /package/dist/apps/chat-ui/assets/{yacas-DZdTXy3s.js → yacas-DNs9XkmQ.js} +0 -0
  171. /package/dist/apps/chat-ui/assets/{z80-DMG02_YV.js → z80-B7Dfb4UO.js} +0 -0
@@ -40,7 +40,7 @@ function classSummary(element) {
40
40
  }
41
41
  function attrMap(element) {
42
42
  const attrs = {};
43
- const allow = /^(id|role|aria-|data-pibo-|data-testid$|disabled$|checked$|selected$|hidden$|tabindex$|title$)/;
43
+ const allow = /^(id|role|aria-|data-pibo-|data-testid$|data-row-id$|data-row-kind$|data-row-status$|data-trace-node-id$|data-event-id$|data-run-id$|data-order-source$|data-order-stream-id$|data-order-frame-index$|data-span-type$|data-span-status$|data-stable-key$|disabled$|checked$|selected$|hidden$|tabindex$|title$)/;
44
44
  for (const attr of Array.from(element.attributes || [])) {
45
45
  if (!allow.test(attr.name)) continue;
46
46
  if (/token|cookie|authorization|secret|password/i.test(attr.name)) {
@@ -101,8 +101,17 @@ function identityOf(element) {
101
101
  const session = element.getAttribute('data-pibo-session-id');
102
102
  const room = element.getAttribute('data-pibo-room-id');
103
103
  const view = element.getAttribute('data-pibo-view-id');
104
+ const row = element.getAttribute('data-row-id');
105
+ const traceNode = element.getAttribute('data-trace-node-id');
106
+ const eventId = element.getAttribute('data-event-id');
107
+ const runId = element.getAttribute('data-run-id');
104
108
  const testId = element.getAttribute('data-testid');
105
109
  const id = element.id;
110
+ if (debug === 'terminal-row' && row) return { identity: 'terminal-row:' + row, kind: 'pibo-row' };
111
+ if (debug === 'trace-span' && traceNode) return { identity: 'trace-span:' + traceNode, kind: 'pibo-trace-node' };
112
+ if (debug && traceNode) return { identity: debug + ':' + traceNode, kind: 'pibo-trace-node' };
113
+ if (debug && eventId) return { identity: debug + ':event:' + eventId, kind: 'pibo-event' };
114
+ if (debug && runId) return { identity: debug + ':run:' + runId, kind: 'pibo-run' };
106
115
  if (debug && session) return { identity: debug + ':' + session, kind: 'pibo-session' };
107
116
  if (debug && room) return { identity: debug + ':' + room, kind: 'pibo-room' };
108
117
  if (debug && view) return { identity: debug + ':' + view, kind: 'pibo-view' };
@@ -1,5 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { summarizeStreamingSelectedLiveEventSource } from "./web-streaming-report.js";
3
+ import { analyzeStreamingRenderOrderCapture } from "./web-render-order-analysis.js";
3
4
  const LIVE_PIPELINE_MIN_PRESERVATION_RATIO = 0.95;
4
5
  const LIVE_PIPELINE_MIN_FLUSH_RATIO = 0.75;
5
6
  const URL_COMPARISON_MAX_SMOOTHNESS_DROP = 15;
@@ -327,6 +328,12 @@ export function summarizeStreamingBenchmarks(runs) {
327
328
  firstVisibleMs: numericStats(runs.map((run) => run.dom.firstPositiveUpdateMs)),
328
329
  longTaskMaxMs: numericStats(runs.map((run) => run.longTasks.maxMs)),
329
330
  regressionCount: numericStats(runs.map((run) => run.regressions.length)),
331
+ renderOrderDomStateCount: numericStats(runs.map((run) => run.renderOrder?.analysis?.domStateCount)),
332
+ renderOrderTraceSnapshotCount: numericStats(runs.map((run) => run.renderOrder?.analysis?.traceSnapshotCount)),
333
+ renderOrderReorderCount: numericStats(runs.map((run) => run.renderOrder?.analysis?.reorderCount)),
334
+ renderOrderDisappearReappearCount: numericStats(runs.map((run) => run.renderOrder?.analysis?.disappearReappearCount)),
335
+ renderOrderIdentityReplacementCount: numericStats(runs.map((run) => run.renderOrder?.analysis?.identityReplacementCount)),
336
+ renderOrderStateDomMismatchCount: numericStats(runs.map((run) => run.renderOrder?.analysis?.stateDomMismatchCount)),
330
337
  debugEnqueueCount: numericStats(runs.map((run) => streamingDebugDeltaNumber(run, "enqueueCount"))),
331
338
  debugFlushCount: numericStats(runs.map((run) => streamingDebugDeltaNumber(run, "flushCount"))),
332
339
  debugFlushedEventCount: numericStats(runs.map((run) => streamingDebugDeltaNumber(run, "flushedEventCount"))),
@@ -529,7 +536,11 @@ function normalizeStreamingBenchmarkGroupArtifact(value, runs) {
529
536
  };
530
537
  }
531
538
  function normalizeStreamingBenchmarkRun(run) {
532
- const scored = { ...run, regressions: Array.isArray(run.regressions) ? run.regressions : [], warnings: Array.isArray(run.warnings) ? run.warnings : [], score: run.score ?? scoreStreamingBenchmark(run) };
539
+ const renderOrder = analyzeStreamingRenderOrderCapture(run.renderOrder);
540
+ const baseRegressions = Array.isArray(run.regressions) ? run.regressions.filter((regression) => !regression.startsWith("render order ")) : [];
541
+ const regressions = [...new Set([...baseRegressions, ...(renderOrder?.analysis?.regressions ?? [])])];
542
+ const withRenderOrder = { ...run, renderOrder, regressions, warnings: Array.isArray(run.warnings) ? run.warnings : [] };
543
+ const scored = { ...withRenderOrder, score: run.score ?? scoreStreamingBenchmark(withRenderOrder) };
533
544
  const withProviderPreservation = { ...scored, providerPreservation: run.providerPreservation ?? summarizeStreamingProviderPreservation(scored) };
534
545
  const withLivePipeline = { ...withProviderPreservation, livePipeline: run.livePipeline ?? summarizeStreamingLivePipeline(withProviderPreservation) };
535
546
  return { ...withLivePipeline, cadence: run.cadence ?? summarizeStreamingCadence(withLivePipeline) };
@@ -33,8 +33,17 @@ function stats(values) {
33
33
  function fetchWithTimeout(url, init, timeoutMs) {
34
34
  if (typeof AbortController === 'undefined') return fetch(url, init);
35
35
  const controller = new AbortController();
36
+ const externalSignal = init && init.signal;
37
+ const abortFromExternal = () => controller.abort();
38
+ if (externalSignal) {
39
+ if (externalSignal.aborted) controller.abort();
40
+ else externalSignal.addEventListener('abort', abortFromExternal, { once: true });
41
+ }
36
42
  const timer = setTimeout(() => controller.abort(), timeoutMs);
37
- return fetch(url, { ...init, signal: controller.signal }).finally(() => clearTimeout(timer));
43
+ return fetch(url, { ...init, signal: controller.signal }).finally(() => {
44
+ clearTimeout(timer);
45
+ if (externalSignal) externalSignal.removeEventListener('abort', abortFromExternal);
46
+ });
38
47
  }
39
48
  function installOverlayDropSimulation(enabled) {
40
49
  if (!enabled) {
@@ -107,6 +116,8 @@ function createSseProbe(piboSessionId, startedAt) {
107
116
  let buffer = '';
108
117
  let stopped = false;
109
118
  let finished = false;
119
+ let reader;
120
+ let stoppedResult;
110
121
  const controller = typeof AbortController === 'undefined' ? undefined : new AbortController();
111
122
  const decoder = typeof TextDecoder === 'undefined' ? undefined : new TextDecoder();
112
123
  const encoder = typeof TextEncoder === 'undefined' ? undefined : new TextEncoder();
@@ -169,7 +180,7 @@ function createSseProbe(piboSessionId, startedAt) {
169
180
  response.headers.forEach((value, key) => { result.headers[key] = value; });
170
181
  if (!response.body || typeof response.body.getReader !== 'function') throw new Error('ReadableStream unavailable');
171
182
  result.installed = true;
172
- const reader = response.body.getReader();
183
+ reader = response.body.getReader();
173
184
  while (true) {
174
185
  const next = await reader.read();
175
186
  if (next.done) break;
@@ -197,21 +208,33 @@ function createSseProbe(piboSessionId, startedAt) {
197
208
  if (stopped || (error && error.name === 'AbortError')) result.aborted = true;
198
209
  else result.errors.push(String(error && error.message ? error.message : error));
199
210
  } finally {
211
+ reader = undefined;
200
212
  finished = true;
201
213
  finalize();
202
214
  }
203
215
  })();
204
216
  return {
205
- result,
217
+ get result() { return stoppedResult || result; },
206
218
  stop: async () => {
219
+ if (stoppedResult) return stoppedResult;
207
220
  stopped = true;
208
221
  if (controller) controller.abort();
209
- await Promise.race([done.catch(() => {}), new Promise((resolve) => setTimeout(resolve, 2500))]);
222
+ try { if (reader && typeof reader.cancel === 'function') void Promise.resolve(reader.cancel()).catch(() => {}); } catch {}
223
+ let stopTimeout;
224
+ try {
225
+ await Promise.race([
226
+ done.catch(() => {}),
227
+ new Promise((resolve) => { stopTimeout = setTimeout(resolve, 2500); }),
228
+ ]);
229
+ } finally {
230
+ if (stopTimeout !== undefined) clearTimeout(stopTimeout);
231
+ }
210
232
  if (!finished) {
211
233
  result.aborted = true;
212
234
  result.errors.push('stop timeout after abort');
213
235
  }
214
- return finalize();
236
+ stoppedResult = cloneDebugSnapshot(finalize()) || { ...finalize() };
237
+ return stoppedResult;
215
238
  },
216
239
  };
217
240
  }
@@ -235,50 +258,69 @@ function createTraceProbe(piboSessionId, startedAt, intervalMs) {
235
258
  maxAssistantOutputLength: 0,
236
259
  samples: [],
237
260
  };
238
- let sampling = false;
239
- const sample = async () => {
240
- if (sampling) return;
241
- sampling = true;
242
- try {
243
- const t = Math.round(performance.now() - startedAt);
244
- const response = await fetchWithTimeout('/api/chat/trace?piboSessionId=' + encodeURIComponent(piboSessionId) + '&includeRawEvents=true&rawEventsLimit=80', {}, 2500);
245
- if (!response.ok) throw new Error(response.status + ' ' + response.statusText);
246
- const trace = await response.json();
247
- result.fetchCount += 1;
248
- const version = typeof trace.version === 'string' ? trace.version : undefined;
249
- const rawEvents = Array.isArray(trace.rawEvents) ? trace.rawEvents : [];
250
- const assistantOutputLength = maxTraceAssistantOutputLength(trace);
251
- const liveVersion = Boolean(version && version.includes(':live:'));
252
- const sampleResult = {
253
- t,
254
- version,
255
- eventCount: typeof trace.eventCount === 'number' ? trace.eventCount : undefined,
256
- rawEventCount: rawEvents.length,
257
- assistantOutputLength,
258
- liveVersion,
259
- rawEventTypes: countRawTraceEventTypes(rawEvents),
260
- };
261
- result.samples.push(sampleResult);
262
- if (result.samples.length > 80) result.samples.shift();
263
- result.sampleCount = result.samples.length;
264
- result.maxAssistantOutputLength = Math.max(result.maxAssistantOutputLength, assistantOutputLength);
265
- result.finalAssistantOutputLength = assistantOutputLength;
266
- if (typeof sampleResult.eventCount === 'number') {
267
- result.durableEventCountStart ??= sampleResult.eventCount;
268
- result.durableEventCountEnd = sampleResult.eventCount;
269
- }
270
- if (liveVersion) {
271
- result.liveVersionCount += 1;
272
- result.firstLiveVersionMs ??= t;
261
+ let stopped = false;
262
+ let activeController;
263
+ let activeSample;
264
+ const sample = () => {
265
+ if (stopped) return Promise.resolve();
266
+ if (activeSample) return activeSample;
267
+ const controller = typeof AbortController === 'undefined' ? undefined : new AbortController();
268
+ activeController = controller;
269
+ const currentSample = (async () => {
270
+ try {
271
+ const t = Math.round(performance.now() - startedAt);
272
+ const response = await fetchWithTimeout('/api/chat/trace?piboSessionId=' + encodeURIComponent(piboSessionId) + '&includeRawEvents=true&rawEventsLimit=80', { signal: controller && controller.signal }, 2500);
273
+ if (!response.ok) throw new Error(response.status + ' ' + response.statusText);
274
+ const trace = await response.json();
275
+ result.fetchCount += 1;
276
+ const version = typeof trace.version === 'string' ? trace.version : undefined;
277
+ const rawEvents = Array.isArray(trace.rawEvents) ? trace.rawEvents : [];
278
+ const assistantOutputLength = maxTraceAssistantOutputLength(trace);
279
+ const liveVersion = Boolean(version && version.includes(':live:'));
280
+ const sampleResult = {
281
+ t,
282
+ version,
283
+ eventCount: typeof trace.eventCount === 'number' ? trace.eventCount : undefined,
284
+ rawEventCount: rawEvents.length,
285
+ assistantOutputLength,
286
+ liveVersion,
287
+ rawEventTypes: countRawTraceEventTypes(rawEvents),
288
+ };
289
+ result.samples.push(sampleResult);
290
+ if (result.samples.length > 80) result.samples.shift();
291
+ result.sampleCount = result.samples.length;
292
+ result.maxAssistantOutputLength = Math.max(result.maxAssistantOutputLength, assistantOutputLength);
293
+ result.finalAssistantOutputLength = assistantOutputLength;
294
+ if (typeof sampleResult.eventCount === 'number') {
295
+ result.durableEventCountStart ??= sampleResult.eventCount;
296
+ result.durableEventCountEnd = sampleResult.eventCount;
297
+ }
298
+ if (liveVersion) {
299
+ result.liveVersionCount += 1;
300
+ result.firstLiveVersionMs ??= t;
301
+ }
302
+ } catch (error) {
303
+ if (!(stopped && error && error.name === 'AbortError')) result.failedFetchCount += 1;
273
304
  }
274
- } catch {
275
- result.failedFetchCount += 1;
276
- } finally {
277
- sampling = false;
278
- }
305
+ })();
306
+ const trackedSample = currentSample.finally(() => {
307
+ if (activeSample === trackedSample) activeSample = undefined;
308
+ if (activeController === controller) activeController = undefined;
309
+ });
310
+ activeSample = trackedSample;
311
+ return trackedSample;
279
312
  };
280
313
  const timer = setInterval(() => { sample().catch(() => {}); }, intervalMs);
281
- return { result, sample, stop: () => clearInterval(timer) };
314
+ return {
315
+ result,
316
+ sample,
317
+ stop: async () => {
318
+ stopped = true;
319
+ clearInterval(timer);
320
+ if (activeController) activeController.abort();
321
+ if (activeSample) await activeSample.catch(() => {});
322
+ },
323
+ };
282
324
  }
283
325
  function countRawTraceEventTypes(rawEvents) {
284
326
  const counts = {};
@@ -649,6 +691,124 @@ async function waitForAssistantDomSettle(timeoutMs) {
649
691
  }
650
692
  }
651
693
  }
694
+ function startRenderOrderCapture(startedAt) {
695
+ const piboSessionId = document.querySelector('[data-pibo-debug="chat-shell"]')?.getAttribute('data-pibo-session-id')
696
+ || document.querySelector('[data-pibo-selected-session-id]')?.getAttribute('data-pibo-selected-session-id')
697
+ || undefined;
698
+ const capturedSessionIds = new Set(piboSessionId ? [piboSessionId] : []);
699
+ const traceSnapshotsApi = window.__piboTraceSnapshots;
700
+ if (piboSessionId && traceSnapshotsApi && typeof traceSnapshotsApi.clearSnapshots === 'function') {
701
+ try { traceSnapshotsApi.clearSnapshots(piboSessionId); } catch {}
702
+ }
703
+ const domStates = [];
704
+ let omittedDomStates = 0;
705
+ let previousSignature;
706
+ const root = document.querySelector('[data-pibo-debug="chat-shell"]') || document.body;
707
+ const atBottomFor = (element) => {
708
+ let current = element && element.parentElement;
709
+ while (current && current !== document.body) {
710
+ if (current.scrollHeight > current.clientHeight + 2) return current.scrollHeight - current.scrollTop - current.clientHeight <= 180;
711
+ current = current.parentElement;
712
+ }
713
+ return undefined;
714
+ };
715
+ const rowFromElement = (element) => {
716
+ const rect = element.getBoundingClientRect();
717
+ const sourceNodeIds = String(element.getAttribute('data-trace-node-id') || '').split(/\s+/).filter(Boolean);
718
+ const numberAttr = (name) => {
719
+ const raw = element.getAttribute(name);
720
+ if (raw === null || raw === '') return undefined;
721
+ const value = Number(raw);
722
+ return Number.isFinite(value) ? value : undefined;
723
+ };
724
+ return {
725
+ id: element.getAttribute('data-row-id') || element.getAttribute('data-trace-node-id') || '',
726
+ kind: element.getAttribute('data-row-kind') || element.getAttribute('data-span-type') || undefined,
727
+ status: element.getAttribute('data-row-status') || element.getAttribute('data-span-status') || undefined,
728
+ sourceNodeIds,
729
+ eventId: element.getAttribute('data-event-id') || undefined,
730
+ runId: element.getAttribute('data-run-id') || undefined,
731
+ stableKey: element.getAttribute('data-stable-key') || undefined,
732
+ orderSource: element.getAttribute('data-order-source') || undefined,
733
+ orderStreamId: numberAttr('data-order-stream-id'),
734
+ orderStreamFrameIndex: numberAttr('data-order-frame-index'),
735
+ top: Math.round(rect.top * 1000) / 1000,
736
+ left: Math.round(rect.left * 1000) / 1000,
737
+ };
738
+ };
739
+ const capture = (reason) => {
740
+ const shell = document.querySelector('[data-pibo-debug="chat-shell"]');
741
+ const activePiboSessionId = shell?.getAttribute('data-pibo-session-id')
742
+ || document.querySelector('[data-pibo-selected-session-id]')?.getAttribute('data-pibo-selected-session-id')
743
+ || piboSessionId;
744
+ if (activePiboSessionId) capturedSessionIds.add(activePiboSessionId);
745
+ const terminalRows = shell ? Array.from(shell.querySelectorAll('[data-pibo-terminal-row="true"][data-row-id]')) : [];
746
+ const traceRows = shell ? Array.from(shell.querySelectorAll('[data-pibo-debug="trace-span"][data-trace-node-id]')) : [];
747
+ const elements = terminalRows.length ? terminalRows : traceRows;
748
+ const rows = elements.map(rowFromElement).filter((row) => row.id);
749
+ const view = terminalRows.length ? 'compact-terminal' : traceRows.length ? 'trace-timeline' : 'unknown';
750
+ const visualRows = rows.slice().sort((left, right) => (left.top || 0) - (right.top || 0) || (left.left || 0) - (right.left || 0));
751
+ let traceSequence;
752
+ if (activePiboSessionId && traceSnapshotsApi && typeof traceSnapshotsApi.getLatestSequence === 'function') {
753
+ try { traceSequence = traceSnapshotsApi.getLatestSequence(activePiboSessionId); } catch {}
754
+ }
755
+ const state = {
756
+ t: Math.round((performance.now() - startedAt) * 1000) / 1000,
757
+ timestamp: Date.now(),
758
+ traceSequence,
759
+ reason,
760
+ piboSessionId: activePiboSessionId,
761
+ view,
762
+ shellState: shell?.getAttribute('data-pibo-state') || undefined,
763
+ atBottom: rows.length ? atBottomFor(elements[0]) : undefined,
764
+ rows,
765
+ rowIds: rows.map((row) => row.id),
766
+ visualRowIds: visualRows.map((row) => row.id),
767
+ };
768
+ const signature = JSON.stringify({ traceSequence: state.traceSequence, view: state.view, shellState: state.shellState, atBottom: state.atBottom, rows: rows.map((row) => [row.id, row.kind, row.status, row.sourceNodeIds, row.eventId, row.runId, row.stableKey, row.orderSource, row.orderStreamId, row.orderStreamFrameIndex]), visual: state.visualRowIds });
769
+ if (signature === previousSignature) return;
770
+ previousSignature = signature;
771
+ if (domStates.length >= 1000) omittedDomStates += 1;
772
+ else domStates.push(state);
773
+ };
774
+ capture('start');
775
+ const observer = new MutationObserver(() => capture('mutation'));
776
+ if (root) observer.observe(root, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class', 'data-row-id', 'data-row-kind', 'data-row-status', 'data-trace-node-id', 'data-event-id', 'data-run-id', 'data-stable-key', 'data-order-source', 'data-order-stream-id', 'data-order-frame-index', 'data-span-type', 'data-span-status'] });
777
+ const interval = setInterval(() => capture('interval'), 50);
778
+ return {
779
+ stop: () => {
780
+ let traceSnapshots = [];
781
+ try {
782
+ try { capture('stop'); } catch {}
783
+ if (traceSnapshotsApi && typeof traceSnapshotsApi.getSnapshots === 'function') {
784
+ for (const sessionId of capturedSessionIds) {
785
+ try {
786
+ const sessionSnapshots = cloneDebugSnapshot(traceSnapshotsApi.getSnapshots(sessionId)) || [];
787
+ traceSnapshots.push(...sessionSnapshots);
788
+ } catch {}
789
+ }
790
+ }
791
+ return {
792
+ requested: true,
793
+ available: Boolean(root),
794
+ piboSessionId,
795
+ domStates,
796
+ traceSnapshots,
797
+ omittedDomStates,
798
+ warning: traceSnapshotsApi ? undefined : 'window.__piboTraceSnapshots unavailable',
799
+ };
800
+ } finally {
801
+ observer.disconnect();
802
+ clearInterval(interval);
803
+ if (traceSnapshotsApi && typeof traceSnapshotsApi.clearSnapshots === 'function') {
804
+ for (const sessionId of capturedSessionIds) {
805
+ try { traceSnapshotsApi.clearSnapshots(sessionId); } catch {}
806
+ }
807
+ }
808
+ }
809
+ },
810
+ };
811
+ }
652
812
  async function runStreamingBenchmark(options) {
653
813
  const warnings = [];
654
814
  const selectedSessionId = () => document.querySelector('[data-pibo-debug="chat-shell"]')?.getAttribute('data-pibo-session-id')
@@ -657,7 +817,38 @@ async function runStreamingBenchmark(options) {
657
817
  let reset = false;
658
818
  let backendPreludeError;
659
819
  let backendPreludeConfig;
660
- try { localStorage.setItem('pibo.chat.debugStreaming', '1'); } catch (error) { warnings.push('failed to set debugStreaming localStorage: ' + String(error)); }
820
+ let debugStreamingBefore;
821
+ const traceCollectionBefore = window.__piboTraceSnapshotCollectionEnabled;
822
+ try {
823
+ debugStreamingBefore = localStorage.getItem('pibo.chat.debugStreaming');
824
+ localStorage.setItem('pibo.chat.debugStreaming', '1');
825
+ } catch (error) {
826
+ warnings.push('failed to set debugStreaming localStorage: ' + String(error));
827
+ }
828
+ window.__piboTraceSnapshotCollectionEnabled = true;
829
+ const restoreDebugCollection = () => {
830
+ try {
831
+ if (debugStreamingBefore === null || debugStreamingBefore === undefined) localStorage.removeItem('pibo.chat.debugStreaming');
832
+ else localStorage.setItem('pibo.chat.debugStreaming', debugStreamingBefore);
833
+ } catch {}
834
+ if (traceCollectionBefore === undefined) delete window.__piboTraceSnapshotCollectionEnabled;
835
+ else window.__piboTraceSnapshotCollectionEnabled = traceCollectionBefore;
836
+ const piboSessionId = selectedSessionId();
837
+ const snapshots = window.__piboTraceSnapshots;
838
+ if (piboSessionId && snapshots && typeof snapshots.clearSnapshots === 'function') {
839
+ try { snapshots.clearSnapshots(piboSessionId); } catch {}
840
+ }
841
+ };
842
+ let benchmarkObserver;
843
+ let renderOrderCapture;
844
+ let rafHandle;
845
+ let perfObserver;
846
+ let traceProbe;
847
+ let traceSummary;
848
+ let sseProbe;
849
+ let reconnectTimer;
850
+ let fixtureStarted = false;
851
+ try {
661
852
  if (options.startBackendFixture && options.fixturePreludeMessages > 0) {
662
853
  const piboSessionId = selectedSessionId();
663
854
  if (!piboSessionId) {
@@ -689,6 +880,7 @@ async function runStreamingBenchmark(options) {
689
880
  try { window.__piboStreamingDebugReset(); reset = true; } catch (error) { warnings.push('failed to reset __piboStreamingDebug: ' + String(error)); }
690
881
  }
691
882
  const overlayDrop = installOverlayDropSimulation(Boolean(options.simulateOverlayDrop));
883
+ renderOrderCapture = startRenderOrderCapture(startedAt);
692
884
 
693
885
  const debugBefore = cloneDebugSnapshot(window.__piboStreamingDebug);
694
886
  const initialText = selectedAssistantText(fixtureDomInitialTargets, ignorePreludeDomTargets);
@@ -714,13 +906,12 @@ async function runStreamingBenchmark(options) {
714
906
  }
715
907
  currentLength = length;
716
908
  };
717
- const observer = new MutationObserver(sample);
718
- observer.observe(document.body, { childList: true, subtree: true, characterData: true });
909
+ benchmarkObserver = new MutationObserver(sample);
910
+ benchmarkObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
719
911
 
720
912
  const rafGaps = [];
721
913
  let rafCount = 0;
722
914
  let lastRaf;
723
- let rafHandle;
724
915
  const onRaf = (t) => {
725
916
  rafCount += 1;
726
917
  if (lastRaf !== undefined) rafGaps.push(t - lastRaf);
@@ -730,7 +921,6 @@ async function runStreamingBenchmark(options) {
730
921
  rafHandle = requestAnimationFrame(onRaf);
731
922
 
732
923
  const longTasks = [];
733
- let perfObserver;
734
924
  if (typeof PerformanceObserver !== 'undefined') {
735
925
  try {
736
926
  perfObserver = new PerformanceObserver((list) => {
@@ -744,11 +934,8 @@ async function runStreamingBenchmark(options) {
744
934
  warnings.push('PerformanceObserver unavailable');
745
935
  }
746
936
 
747
- let fixtureStarted = false;
748
937
  let fixtureConfig;
749
938
  let backendFixtureError;
750
- let traceProbe;
751
- let sseProbe;
752
939
  if (options.startFixture) {
753
940
  if (typeof window.__piboStreamingFixtureStart === 'function') {
754
941
  try { fixtureConfig = window.__piboStreamingFixtureStart(); fixtureStarted = true; } catch (error) { warnings.push('failed to start streaming fixture: ' + String(error)); }
@@ -757,7 +944,8 @@ async function runStreamingBenchmark(options) {
757
944
  }
758
945
  } else if (options.startBackendFixture) {
759
946
  if (options.simulateReconnect && typeof window.__piboStreamingBenchmarkForceReconnect === 'function') {
760
- setTimeout(() => {
947
+ reconnectTimer = setTimeout(() => {
948
+ reconnectTimer = undefined;
761
949
  try { window.__piboStreamingBenchmarkForceReconnect(); } catch (error) { warnings.push('failed to force EventSource reconnect: ' + String(error)); }
762
950
  }, options.reconnectAtMs || 325);
763
951
  } else if (options.simulateReconnect) {
@@ -800,12 +988,20 @@ async function runStreamingBenchmark(options) {
800
988
  sample();
801
989
  if (traceProbe) {
802
990
  await traceProbe.sample();
803
- traceProbe.stop();
991
+ traceSummary = traceProbe.result;
992
+ await traceProbe.stop();
993
+ traceProbe = undefined;
804
994
  }
805
995
  const sseSummary = sseProbe ? await sseProbe.stop() : undefined;
806
- observer.disconnect();
996
+ sseProbe = undefined;
997
+ benchmarkObserver.disconnect();
998
+ benchmarkObserver = undefined;
999
+ const renderOrder = renderOrderCapture.stop();
1000
+ renderOrderCapture = undefined;
807
1001
  if (rafHandle !== undefined) cancelAnimationFrame(rafHandle);
1002
+ rafHandle = undefined;
808
1003
  try { perfObserver && perfObserver.disconnect(); } catch {}
1004
+ perfObserver = undefined;
809
1005
 
810
1006
  const debugAfter = cloneDebugSnapshot(window.__piboStreamingDebug);
811
1007
  const overlayDropSummary = overlayDrop ? cloneDebugSnapshot(window.__piboStreamingBenchmarkOverlayDrop) : undefined;
@@ -869,7 +1065,6 @@ async function runStreamingBenchmark(options) {
869
1065
  error: backendPreludeError || backendFixtureError,
870
1066
  } : undefined;
871
1067
  const eventSourceSummary = summarizeEventSourceProbe(startedAt, Boolean(options.startBackendFixture), options.reconnectAtMs, Boolean(options.simulateTraceCatchup), options.traceCatchupDropMs);
872
- const traceSummary = traceProbe && traceProbe.result;
873
1068
  const regressions = streamingBenchmarkRegressions({
874
1069
  debugAfter,
875
1070
  debugDelta,
@@ -920,9 +1115,24 @@ async function runStreamingBenchmark(options) {
920
1115
  sse: sseSummary,
921
1116
  trace: traceSummary,
922
1117
  overlayDrop: overlayDropSummary,
1118
+ renderOrder,
923
1119
  regressions,
924
1120
  warnings,
925
1121
  };
1122
+ } finally {
1123
+ try { benchmarkObserver && benchmarkObserver.disconnect(); } catch {}
1124
+ try { renderOrderCapture && renderOrderCapture.stop(); } catch {}
1125
+ if (rafHandle !== undefined) cancelAnimationFrame(rafHandle);
1126
+ if (reconnectTimer !== undefined) clearTimeout(reconnectTimer);
1127
+ try { perfObserver && perfObserver.disconnect(); } catch {}
1128
+ try { if (traceProbe) await traceProbe.stop(); } catch {}
1129
+ try { if (sseProbe) await sseProbe.stop(); } catch {}
1130
+ try { installOverlayDropSimulation(false); } catch {}
1131
+ if (fixtureStarted && options.startFixture && typeof window.__piboStreamingDebugReset === 'function') {
1132
+ try { window.__piboStreamingDebugReset(); } catch {}
1133
+ }
1134
+ restoreDebugCollection();
1135
+ }
926
1136
  }
927
1137
  `;
928
1138
  }
@@ -117,6 +117,7 @@ function streamingBenchmarkCompactRunRows(benchmark) {
117
117
  { section: "compact", metric: "Live trace compute", preservation: liveTraceComputeCount !== undefined ? `count ${jsonShort(liveTraceComputeCount)}, total ${jsonShort(liveTraceComputeTotalMs)}ms` : "n/a", cadenceLatency: liveTraceComputeMaxMs !== undefined ? `max ${jsonShort(liveTraceComputeMaxMs)}ms` : "n/a" },
118
118
  { section: "compact", metric: "Markdown render", preservation: markdownRenderCount !== undefined ? `count ${jsonShort(markdownRenderCount)}, plain ${jsonShort(markdownRenderPlainCount)}, commonmark ${jsonShort(markdownRenderCommonMarkCount)}, gfm ${jsonShort(markdownRenderGfmCount)}, gfmFast ${jsonShort(markdownRenderGfmFastCount)}, full ${jsonShort(markdownRenderFullCount)}` : "n/a", cadenceLatency: markdownRenderMaxMs !== undefined ? `total ${jsonShort(markdownRenderTotalMs)}ms, max ${jsonShort(markdownRenderMaxMs)}ms` : "n/a" },
119
119
  { section: "compact", metric: "DOM", preservation: `positive ${benchmark.dom.positiveUpdateCount}, max jump ${jsonShort(benchmark.dom.positiveCharJumps.max)} chars`, cadenceLatency: `p90 gap ${statP90(benchmark.dom.gapsMs, "ms")}, first visible ${jsonShort(benchmark.dom.firstPositiveUpdateMs)}ms` },
120
+ { section: "compact", metric: "Render order", preservation: renderOrderPreservation(benchmark), cadenceLatency: renderOrderEvidence(benchmark) },
120
121
  { section: "compact", metric: "Score", preservation: `smoothness ${benchmark.score.smoothness}`, cadenceLatency: `regressions ${benchmark.regressions.length}, warnings ${benchmark.warnings.length}` },
121
122
  ];
122
123
  }
@@ -131,6 +132,7 @@ function streamingBenchmarkCompactGroupRows(group) {
131
132
  { section: "compact", metric: "Live trace compute", preservation: `count ${statP50(group.summary.debugLiveTraceComputeCount)}, total ${statP50(group.summary.debugLiveTraceComputeDurationTotalMs, "ms")}`, cadenceLatency: `max ${statP50(group.summary.debugLiveTraceComputeDurationMaxMs, "ms")}` },
132
133
  { section: "compact", metric: "Markdown render", preservation: `count ${statP50(group.summary.debugMarkdownRenderCount)}, plain ${statP50(group.summary.debugMarkdownRenderPlainCount)}, commonmark ${statP50(group.summary.debugMarkdownRenderCommonMarkCount)}, gfm ${statP50(group.summary.debugMarkdownRenderGfmCount)}, gfmFast ${statP50(group.summary.debugMarkdownRenderGfmFastCount)}, full ${statP50(group.summary.debugMarkdownRenderFullCount)}`, cadenceLatency: `total ${statP50(group.summary.debugMarkdownRenderDurationTotalMs, "ms")}, max ${statP50(group.summary.debugMarkdownRenderDurationMaxMs, "ms")}` },
133
134
  { section: "compact", metric: "DOM", preservation: `positive ${statP50(group.summary.domPositiveUpdateCount)}, max jump ${statP50(group.summary.domJumpMaxChars, " chars")}`, cadenceLatency: `p90 gap ${statP50(group.summary.domGapP90Ms, "ms")}, first visible ${statP50(group.summary.firstVisibleMs, "ms")}` },
135
+ { section: "compact", metric: "Render order", preservation: `reorder ${statP50(group.summary.renderOrderReorderCount)}, reappear ${statP50(group.summary.renderOrderDisappearReappearCount)}, identity ${statP50(group.summary.renderOrderIdentityReplacementCount)}, mismatch ${statP50(group.summary.renderOrderStateDomMismatchCount)}`, cadenceLatency: `DOM states ${statP50(group.summary.renderOrderDomStateCount)}, trace snapshots ${statP50(group.summary.renderOrderTraceSnapshotCount)}` },
134
136
  { section: "compact", metric: "Score", preservation: `smoothness ${statP50(group.summary.smoothness)}`, cadenceLatency: `regressions ${statP50(group.summary.regressionCount)}, warnings ${group.warnings.length}` },
135
137
  ];
136
138
  }
@@ -182,6 +184,7 @@ function formatStreamingBenchmarkCompactRun(benchmark, target) {
182
184
  ["Live trace compute", liveTraceComputeCount !== undefined ? `count ${jsonShort(liveTraceComputeCount)}, total ${jsonShort(liveTraceComputeTotalMs)}ms` : "n/a", liveTraceComputeMaxMs !== undefined ? `max ${jsonShort(liveTraceComputeMaxMs)}ms` : "n/a"],
183
185
  ["Markdown render", markdownRenderCount !== undefined ? `count ${jsonShort(markdownRenderCount)}, plain ${jsonShort(markdownRenderPlainCount)}, commonmark ${jsonShort(markdownRenderCommonMarkCount)}, gfm ${jsonShort(markdownRenderGfmCount)}, gfmFast ${jsonShort(markdownRenderGfmFastCount)}, full ${jsonShort(markdownRenderFullCount)}` : "n/a", markdownRenderMaxMs !== undefined ? `total ${jsonShort(markdownRenderTotalMs)}ms, max ${jsonShort(markdownRenderMaxMs)}ms` : "n/a"],
184
186
  ["DOM", `positive ${benchmark.dom.positiveUpdateCount}, max jump ${jsonShort(benchmark.dom.positiveCharJumps.max)} chars`, `p90 gap ${statP90(benchmark.dom.gapsMs, "ms")}, first visible ${jsonShort(benchmark.dom.firstPositiveUpdateMs)}ms`],
187
+ ["Render order", renderOrderPreservation(benchmark), renderOrderEvidence(benchmark)],
185
188
  ["Score", `smoothness ${benchmark.score.smoothness}`, `regressions ${benchmark.regressions.length}, warnings ${benchmark.warnings.length}`],
186
189
  ]),
187
190
  ];
@@ -203,6 +206,7 @@ function formatStreamingBenchmarkCompactGroup(group, target) {
203
206
  ["Live trace compute", `count ${statP50(group.summary.debugLiveTraceComputeCount)}, total ${statP50(group.summary.debugLiveTraceComputeDurationTotalMs, "ms")}`, `max ${statP50(group.summary.debugLiveTraceComputeDurationMaxMs, "ms")}`],
204
207
  ["Markdown render", `count ${statP50(group.summary.debugMarkdownRenderCount)}, plain ${statP50(group.summary.debugMarkdownRenderPlainCount)}, commonmark ${statP50(group.summary.debugMarkdownRenderCommonMarkCount)}, gfm ${statP50(group.summary.debugMarkdownRenderGfmCount)}, gfmFast ${statP50(group.summary.debugMarkdownRenderGfmFastCount)}, full ${statP50(group.summary.debugMarkdownRenderFullCount)}`, `total ${statP50(group.summary.debugMarkdownRenderDurationTotalMs, "ms")}, max ${statP50(group.summary.debugMarkdownRenderDurationMaxMs, "ms")}`],
205
208
  ["DOM", `positive ${statP50(group.summary.domPositiveUpdateCount)}, max jump ${statP50(group.summary.domJumpMaxChars, " chars")}`, `p90 gap ${statP50(group.summary.domGapP90Ms, "ms")}, first visible ${statP50(group.summary.firstVisibleMs, "ms")}`],
209
+ ["Render order", `reorder ${statP50(group.summary.renderOrderReorderCount)}, reappear ${statP50(group.summary.renderOrderDisappearReappearCount)}, identity ${statP50(group.summary.renderOrderIdentityReplacementCount)}, mismatch ${statP50(group.summary.renderOrderStateDomMismatchCount)}`, `DOM states ${statP50(group.summary.renderOrderDomStateCount)}, trace snapshots ${statP50(group.summary.renderOrderTraceSnapshotCount)}`],
206
210
  ["Score", `smoothness ${statP50(group.summary.smoothness)}`, `regressions ${statP50(group.summary.regressionCount)}, warnings ${group.warnings.length}`],
207
211
  ]),
208
212
  ];
@@ -241,6 +245,18 @@ function formatStreamingBenchmarkCompactUrlComparison(comparison, target) {
241
245
  appendCompactBenchmarkNotes(lines, comparison.negativeProfile, comparison.regressions, comparison.warnings);
242
246
  return lines.join("\n");
243
247
  }
248
+ function renderOrderPreservation(benchmark) {
249
+ const analysis = benchmark.renderOrder?.analysis;
250
+ if (!analysis)
251
+ return benchmark.renderOrder?.warning ?? "n/a";
252
+ return `reorder ${analysis.reorderCount}, reappear ${analysis.disappearReappearCount}, identity ${analysis.identityReplacementCount}, mismatch ${analysis.stateDomMismatchCount}`;
253
+ }
254
+ function renderOrderEvidence(benchmark) {
255
+ const analysis = benchmark.renderOrder?.analysis;
256
+ if (!analysis)
257
+ return "n/a";
258
+ return `DOM states ${analysis.domStateCount}, trace snapshots ${analysis.traceSnapshotCount}`;
259
+ }
244
260
  function appendCompactBenchmarkNotes(lines, negativeProfile, regressions, warnings) {
245
261
  if (negativeProfile)
246
262
  lines.push("", `Negative profile: ${negativeProfile}`);
@@ -308,6 +324,7 @@ function formatStreamingBenchmark(benchmark, target) {
308
324
  `dom jumps: ${formatStats(benchmark.dom.positiveCharJumps)} chars`,
309
325
  `raf: count=${benchmark.raf.count}, gaps=${formatStats(benchmark.raf.gapsMs)}`,
310
326
  `longTasks: count=${benchmark.longTasks.count}, max=${benchmark.longTasks.maxMs}ms, total=${benchmark.longTasks.totalMs}ms`,
327
+ `render order: ${renderOrderPreservation(benchmark)}, ${renderOrderEvidence(benchmark)}`,
311
328
  ];
312
329
  if (benchmark.negativeProfile)
313
330
  lines.push(`negative profile: ${benchmark.negativeProfile}`);
@@ -331,6 +348,12 @@ function formatStreamingBenchmark(benchmark, target) {
331
348
  lines.push(`sse: requested=${benchmark.sse.requested} installed=${benchmark.sse.installed} status=${jsonShort(benchmark.sse.status)} firstChunk=${jsonShort(benchmark.sse.firstChunkMs)}ms firstText=${jsonShort(benchmark.sse.firstTextEventMs)}ms chunks=${benchmark.sse.chunkCount} chunkBytes=${formatStats(benchmark.sse.chunkBytes)} chunkGaps=${formatStats(benchmark.sse.chunkGapsMs)} textPerChunk=${formatStats(benchmark.sse.textEventsPerChunk)} text=${benchmark.sse.textEventCount} reasoning=${benchmark.sse.reasoningEventCount} textGaps=${formatStats(benchmark.sse.textEventGapsMs)} transient=${benchmark.sse.transientIdCount} durable=${benchmark.sse.durableIdCount} errors=${benchmark.sse.errors.length}`);
332
349
  if (benchmark.trace)
333
350
  lines.push(`trace: requested=${benchmark.trace.requested} samples=${benchmark.trace.sampleCount} fetches=${benchmark.trace.fetchCount} failed=${benchmark.trace.failedFetchCount} liveVersions=${benchmark.trace.liveVersionCount} firstLive=${jsonShort(benchmark.trace.firstLiveVersionMs)}ms assistantMax=${benchmark.trace.maxAssistantOutputLength} assistantFinal=${jsonShort(benchmark.trace.finalAssistantOutputLength)} durableEvents=${jsonShort(benchmark.trace.durableEventCountStart)}->${jsonShort(benchmark.trace.durableEventCountEnd)} session=${jsonShort(benchmark.trace.piboSessionId)}`);
351
+ if (benchmark.renderOrder?.analysis?.findings.length) {
352
+ lines.push("", "Render order findings:");
353
+ for (const finding of benchmark.renderOrder.analysis.findings.slice(0, 20)) {
354
+ lines.push(`- ${finding.source}/${finding.kind} @ ${new Date(finding.timestamp).toISOString()}: ${finding.detail}`);
355
+ }
356
+ }
334
357
  if (benchmark.regressions.length) {
335
358
  lines.push("", "Regressions:");
336
359
  for (const regression of benchmark.regressions)
@@ -357,6 +380,7 @@ function formatStreamingBenchmarkGroup(group, target) {
357
380
  `dom gaps p50=${formatStats(group.summary.domGapP50Ms)}, p90=${formatStats(group.summary.domGapP90Ms)}, max=${formatStats(group.summary.domGapMaxMs)}`,
358
381
  `dom jumps p90=${formatStats(group.summary.domJumpP90Chars)}, max=${formatStats(group.summary.domJumpMaxChars)} chars`,
359
382
  `firstVisible=${formatStats(group.summary.firstVisibleMs)}, longTaskMax=${formatStats(group.summary.longTaskMaxMs)}`,
383
+ `render order: reorder=${formatStats(group.summary.renderOrderReorderCount)}, reappear=${formatStats(group.summary.renderOrderDisappearReappearCount)}, identity=${formatStats(group.summary.renderOrderIdentityReplacementCount)}, mismatch=${formatStats(group.summary.renderOrderStateDomMismatchCount)}, DOM states=${formatStats(group.summary.renderOrderDomStateCount)}, trace snapshots=${formatStats(group.summary.renderOrderTraceSnapshotCount)}`,
360
384
  ];
361
385
  if (group.negativeProfile)
362
386
  lines.push(`negative profile: ${group.negativeProfile}`);