dev3000 0.0.113 → 0.0.115

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 (197) hide show
  1. package/dist/cli.js +32 -2
  2. package/dist/cli.js.map +1 -1
  3. package/dist/dev-environment.d.ts.map +1 -1
  4. package/dist/dev-environment.js +25 -4
  5. package/dist/dev-environment.js.map +1 -1
  6. package/dist/src/tui-interface-impl.tsx +175 -127
  7. package/dist/tui-interface-impl.d.ts.map +1 -1
  8. package/dist/tui-interface-impl.js +113 -74
  9. package/dist/tui-interface-impl.js.map +1 -1
  10. package/mcp-server/.next/BUILD_ID +1 -1
  11. package/mcp-server/.next/build-manifest.json +2 -2
  12. package/mcp-server/.next/fallback-build-manifest.json +2 -2
  13. package/mcp-server/.next/next-minimal-server.js.nft.json +1 -1
  14. package/mcp-server/.next/next-server.js.nft.json +1 -1
  15. package/mcp-server/.next/prerender-manifest.json +3 -3
  16. package/mcp-server/.next/required-server-files.json +4 -4
  17. package/mcp-server/.next/server/app/.well-known/workflow/v1/flow/route.js +2 -2
  18. package/mcp-server/.next/server/app/.well-known/workflow/v1/flow/route.js.nft.json +1 -1
  19. package/mcp-server/.next/server/app/.well-known/workflow/v1/step/route.js +2 -2
  20. package/mcp-server/.next/server/app/.well-known/workflow/v1/step/route.js.nft.json +1 -1
  21. package/mcp-server/.next/server/app/.well-known/workflow/v1/webhook/[token]/route.js +2 -2
  22. package/mcp-server/.next/server/app/.well-known/workflow/v1/webhook/[token]/route.js.nft.json +1 -1
  23. package/mcp-server/.next/server/app/_global-error/page.js.nft.json +1 -1
  24. package/mcp-server/.next/server/app/_global-error.html +2 -2
  25. package/mcp-server/.next/server/app/_global-error.rsc +1 -1
  26. package/mcp-server/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  27. package/mcp-server/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  28. package/mcp-server/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  29. package/mcp-server/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  30. package/mcp-server/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  31. package/mcp-server/.next/server/app/_not-found/page.js.nft.json +1 -1
  32. package/mcp-server/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  33. package/mcp-server/.next/server/app/_not-found.html +1 -1
  34. package/mcp-server/.next/server/app/_not-found.rsc +2 -2
  35. package/mcp-server/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  36. package/mcp-server/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  37. package/mcp-server/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  38. package/mcp-server/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  39. package/mcp-server/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  40. package/mcp-server/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  41. package/mcp-server/.next/server/app/api/auth/authorize/route.js.nft.json +1 -1
  42. package/mcp-server/.next/server/app/api/auth/callback/route.js.nft.json +1 -1
  43. package/mcp-server/.next/server/app/api/auth/signout/route.js.nft.json +1 -1
  44. package/mcp-server/.next/server/app/api/auth/token/route.js.nft.json +1 -1
  45. package/mcp-server/.next/server/app/api/cloud/check-pr/route.js +2 -2
  46. package/mcp-server/.next/server/app/api/cloud/check-pr/route.js.nft.json +1 -1
  47. package/mcp-server/.next/server/app/api/cloud/fix-workflow/health/route.js.nft.json +1 -1
  48. package/mcp-server/.next/server/app/api/cloud/fix-workflow/route.js +2 -2
  49. package/mcp-server/.next/server/app/api/cloud/fix-workflow/route.js.nft.json +1 -1
  50. package/mcp-server/.next/server/app/api/cloud/start-fix/route.js +2 -2
  51. package/mcp-server/.next/server/app/api/cloud/start-fix/route.js.nft.json +1 -1
  52. package/mcp-server/.next/server/app/api/integration/webhook/route.js.nft.json +1 -1
  53. package/mcp-server/.next/server/app/api/jank/[session]/route.js.nft.json +1 -1
  54. package/mcp-server/.next/server/app/api/logs/append/route.js.nft.json +1 -1
  55. package/mcp-server/.next/server/app/api/logs/head/route.js.nft.json +1 -1
  56. package/mcp-server/.next/server/app/api/logs/list/route.js.nft.json +1 -1
  57. package/mcp-server/.next/server/app/api/logs/rotate/route.js.nft.json +1 -1
  58. package/mcp-server/.next/server/app/api/logs/stream/route.js.nft.json +1 -1
  59. package/mcp-server/.next/server/app/api/logs/tail/route.js.nft.json +1 -1
  60. package/mcp-server/.next/server/app/api/orchestrator/route.js.nft.json +1 -1
  61. package/mcp-server/.next/server/app/api/projects/[projectId]/bypass-token/route.js.nft.json +1 -1
  62. package/mcp-server/.next/server/app/api/projects/branches/route.js.nft.json +1 -1
  63. package/mcp-server/.next/server/app/api/projects/check-protection/route.js.nft.json +1 -1
  64. package/mcp-server/.next/server/app/api/projects/route.js.nft.json +1 -1
  65. package/mcp-server/.next/server/app/api/screenshots/[filename]/route.js.nft.json +1 -1
  66. package/mcp-server/.next/server/app/api/screenshots/list/route.js.nft.json +1 -1
  67. package/mcp-server/.next/server/app/api/teams/route.js.nft.json +1 -1
  68. package/mcp-server/.next/server/app/api/tools/route.js.nft.json +1 -1
  69. package/mcp-server/.next/server/app/api/workflows/route.js.nft.json +1 -1
  70. package/mcp-server/.next/server/app/auth/error/page.js.nft.json +1 -1
  71. package/mcp-server/.next/server/app/auth/error/page_client-reference-manifest.js +1 -1
  72. package/mcp-server/.next/server/app/auth/error.html +1 -1
  73. package/mcp-server/.next/server/app/auth/error.rsc +2 -2
  74. package/mcp-server/.next/server/app/auth/error.segments/_full.segment.rsc +2 -2
  75. package/mcp-server/.next/server/app/auth/error.segments/_head.segment.rsc +1 -1
  76. package/mcp-server/.next/server/app/auth/error.segments/_index.segment.rsc +2 -2
  77. package/mcp-server/.next/server/app/auth/error.segments/_tree.segment.rsc +2 -2
  78. package/mcp-server/.next/server/app/auth/error.segments/auth/error/__PAGE__.segment.rsc +1 -1
  79. package/mcp-server/.next/server/app/auth/error.segments/auth/error.segment.rsc +1 -1
  80. package/mcp-server/.next/server/app/auth/error.segments/auth.segment.rsc +1 -1
  81. package/mcp-server/.next/server/app/index.html +1 -1
  82. package/mcp-server/.next/server/app/index.rsc +3 -3
  83. package/mcp-server/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  84. package/mcp-server/.next/server/app/index.segments/_full.segment.rsc +3 -3
  85. package/mcp-server/.next/server/app/index.segments/_head.segment.rsc +1 -1
  86. package/mcp-server/.next/server/app/index.segments/_index.segment.rsc +2 -2
  87. package/mcp-server/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  88. package/mcp-server/.next/server/app/logs/page.js.nft.json +1 -1
  89. package/mcp-server/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  90. package/mcp-server/.next/server/app/mcp/route.js.nft.json +1 -1
  91. package/mcp-server/.next/server/app/page.js.nft.json +1 -1
  92. package/mcp-server/.next/server/app/page_client-reference-manifest.js +1 -1
  93. package/mcp-server/.next/server/app/signin/page.js.nft.json +1 -1
  94. package/mcp-server/.next/server/app/signin/page_client-reference-manifest.js +1 -1
  95. package/mcp-server/.next/server/app/video/[session]/page.js.nft.json +1 -1
  96. package/mcp-server/.next/server/app/video/[session]/page_client-reference-manifest.js +1 -1
  97. package/mcp-server/.next/server/app/workflows/[id]/report/page.js.nft.json +1 -1
  98. package/mcp-server/.next/server/app/workflows/[id]/report/page_client-reference-manifest.js +1 -1
  99. package/mcp-server/.next/server/app/workflows/new/page.js.nft.json +1 -1
  100. package/mcp-server/.next/server/app/workflows/new/page_client-reference-manifest.js +1 -1
  101. package/mcp-server/.next/server/app/workflows/page.js.nft.json +1 -1
  102. package/mcp-server/.next/server/app/workflows/page_client-reference-manifest.js +1 -1
  103. package/mcp-server/.next/server/chunks/[root-of-the-server]__157de66b._.js +101 -36
  104. package/mcp-server/.next/server/chunks/[root-of-the-server]__157de66b._.js.map +1 -1
  105. package/mcp-server/.next/server/chunks/{[root-of-the-server]__f99bd75f._.js → [root-of-the-server]__2920484d._.js} +2 -2
  106. package/mcp-server/.next/server/chunks/{[root-of-the-server]__f99bd75f._.js.map → [root-of-the-server]__2920484d._.js.map} +1 -1
  107. package/mcp-server/.next/server/chunks/{[root-of-the-server]__d6a224bc._.js → [root-of-the-server]__71b44a42._.js} +14 -14
  108. package/mcp-server/.next/server/chunks/[root-of-the-server]__71b44a42._.js.map +1 -0
  109. package/mcp-server/.next/server/chunks/[root-of-the-server]__730a8fd0._.js +1 -1
  110. package/mcp-server/.next/server/chunks/[root-of-the-server]__730a8fd0._.js.map +1 -1
  111. package/mcp-server/.next/server/chunks/{[root-of-the-server]__c86876f8._.js → [root-of-the-server]__76031cfc._.js} +2 -2
  112. package/mcp-server/.next/server/chunks/{[root-of-the-server]__c86876f8._.js.map → [root-of-the-server]__76031cfc._.js.map} +1 -1
  113. package/mcp-server/.next/server/chunks/{[root-of-the-server]__c5e6aa23._.js → [root-of-the-server]__832ec618._.js} +2 -2
  114. package/mcp-server/.next/server/chunks/{[root-of-the-server]__c5e6aa23._.js.map → [root-of-the-server]__832ec618._.js.map} +1 -1
  115. package/mcp-server/.next/server/chunks/{[root-of-the-server]__41aa7b20._.js → [root-of-the-server]__b194d4eb._.js} +13 -13
  116. package/mcp-server/.next/server/chunks/[root-of-the-server]__b194d4eb._.js.map +1 -0
  117. package/mcp-server/.next/server/chunks/[root-of-the-server]__c1681338._.js +3 -0
  118. package/mcp-server/.next/server/chunks/[root-of-the-server]__c1681338._.js.map +1 -0
  119. package/mcp-server/.next/server/chunks/{[root-of-the-server]__d37c38d3._.js → [root-of-the-server]__e6808c21._.js} +2 -2
  120. package/mcp-server/.next/server/chunks/{[root-of-the-server]__d37c38d3._.js.map → [root-of-the-server]__e6808c21._.js.map} +1 -1
  121. package/mcp-server/.next/server/chunks/{[root-of-the-server]__446f0436._.js → [root-of-the-server]__ec6a1335._.js} +2 -2
  122. package/mcp-server/.next/server/chunks/[root-of-the-server]__ec6a1335._.js.map +1 -0
  123. package/mcp-server/.next/server/chunks/bee4f_next_dist_esm_build_templates_app-route_1ece9366.js +5 -5
  124. package/mcp-server/.next/server/chunks/bee4f_next_dist_esm_build_templates_app-route_1ece9366.js.map +1 -1
  125. package/mcp-server/.next/server/chunks/mcp-server_app_api_cloud_fix-workflow_steps_ts_b65f3271._.js +30 -16
  126. package/mcp-server/.next/server/chunks/mcp-server_app_api_cloud_fix-workflow_steps_ts_b65f3271._.js.map +1 -1
  127. package/mcp-server/.next/server/chunks/node_modules__pnpm_85ddbe9c._.js +2 -2
  128. package/mcp-server/.next/server/chunks/node_modules__pnpm_85ddbe9c._.js.map +1 -1
  129. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__2e44f0db._.js +3 -0
  130. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__2e44f0db._.js.map +1 -0
  131. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__3585c949._.js +3 -0
  132. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__3585c949._.js.map +1 -0
  133. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__477c3bbb._.js +3 -0
  134. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__477c3bbb._.js.map +1 -0
  135. package/mcp-server/.next/server/chunks/ssr/{[root-of-the-server]__570677dc._.js → [root-of-the-server]__880839a0._.js} +2 -2
  136. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__880839a0._.js.map +1 -0
  137. package/mcp-server/.next/server/chunks/ssr/_41b8f993._.js +3 -0
  138. package/mcp-server/.next/server/chunks/ssr/_41b8f993._.js.map +1 -0
  139. package/mcp-server/.next/server/chunks/ssr/_9ba0ef29._.js +3 -0
  140. package/mcp-server/.next/server/chunks/ssr/_9ba0ef29._.js.map +1 -0
  141. package/mcp-server/.next/server/chunks/ssr/_cd4dc25e._.js.map +1 -1
  142. package/mcp-server/.next/server/chunks/ssr/mcp-server_app_workflows_new_new-workflow-client_tsx_1312c046._.js +2 -2
  143. package/mcp-server/.next/server/chunks/ssr/mcp-server_app_workflows_new_new-workflow-client_tsx_1312c046._.js.map +1 -1
  144. package/mcp-server/.next/server/chunks/ssr/mcp-server_app_workflows_workflows-client_tsx_268cfd4a._.js +7 -0
  145. package/mcp-server/.next/server/chunks/ssr/mcp-server_app_workflows_workflows-client_tsx_268cfd4a._.js.map +1 -0
  146. package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_961f21c4._.js +3 -0
  147. package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_961f21c4._.js.map +1 -0
  148. package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_a82244bf._.js +3 -0
  149. package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_a82244bf._.js.map +1 -0
  150. package/mcp-server/.next/server/chunks/ssr/{node_modules__pnpm_07527699._.js → node_modules__pnpm_eb98e511._.js} +2 -2
  151. package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_eb98e511._.js.map +1 -0
  152. package/mcp-server/.next/server/middleware.js.nft.json +1 -1
  153. package/mcp-server/.next/server/server-reference-manifest.js +1 -1
  154. package/mcp-server/.next/server/server-reference-manifest.json +1 -1
  155. package/mcp-server/.next/static/chunks/000849a6a897f531.css +1 -0
  156. package/mcp-server/.next/static/chunks/048cee2510ddb1a0.js +1 -0
  157. package/mcp-server/.next/static/chunks/0622bd0e093adee7.js +3 -0
  158. package/mcp-server/.next/static/chunks/{46f60efee5f19794.js → 16359f64918a93f3.js} +1 -1
  159. package/mcp-server/.next/static/chunks/1851a3e70d7efc10.js +1 -0
  160. package/mcp-server/.next/static/chunks/{cc6addc4bb10fa11.js → 2ad16eeb719786f1.js} +1 -1
  161. package/mcp-server/.next/static/chunks/57feca7a4e06545e.js +7 -0
  162. package/mcp-server/.next/static/chunks/93db5737a327ab0c.js +6 -0
  163. package/mcp-server/.next/static/chunks/9fd3c715ecfb4d05.js +1 -0
  164. package/mcp-server/.next/static/chunks/b4b1ec6435790587.js +1 -0
  165. package/mcp-server/.next/static/chunks/cfe150cb2048b7e8.js +1 -0
  166. package/mcp-server/app/api/cloud/fix-workflow/steps.ts +267 -28
  167. package/mcp-server/app/api/cloud/fix-workflow/workflow.ts +16 -8
  168. package/mcp-server/app/api/cloud/start-fix/route.ts +2 -2
  169. package/mcp-server/app/api/workflows/route.ts +45 -1
  170. package/mcp-server/app/workflows/workflows-client.tsx +259 -100
  171. package/mcp-server/package.json +1 -1
  172. package/package.json +3 -3
  173. package/src/tui-interface-impl.tsx +175 -127
  174. package/mcp-server/.next/server/chunks/[root-of-the-server]__41aa7b20._.js.map +0 -1
  175. package/mcp-server/.next/server/chunks/[root-of-the-server]__446f0436._.js.map +0 -1
  176. package/mcp-server/.next/server/chunks/[root-of-the-server]__c508da18._.js +0 -3
  177. package/mcp-server/.next/server/chunks/[root-of-the-server]__c508da18._.js.map +0 -1
  178. package/mcp-server/.next/server/chunks/[root-of-the-server]__d6a224bc._.js.map +0 -1
  179. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__0ff05d72._.js +0 -3
  180. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__0ff05d72._.js.map +0 -1
  181. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__27cc5956._.js +0 -3
  182. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__27cc5956._.js.map +0 -1
  183. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__570677dc._.js.map +0 -1
  184. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__ef510343._.js +0 -3
  185. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__ef510343._.js.map +0 -1
  186. package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_07527699._.js.map +0 -1
  187. package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_7cc36047._.js +0 -3
  188. package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_7cc36047._.js.map +0 -1
  189. package/mcp-server/.next/static/chunks/07848f6bd2a7e5f6.js +0 -3
  190. package/mcp-server/.next/static/chunks/637a66565f27572f.js +0 -6
  191. package/mcp-server/.next/static/chunks/aed4fb5252a4bc95.js +0 -3
  192. package/mcp-server/.next/static/chunks/e8d521464b0c96ca.css +0 -1
  193. package/mcp-server/.next/static/chunks/ff53279afa939907.js +0 -1
  194. package/mcp-server/.next/static/chunks/ffa2ecb6845be49c.js +0 -1
  195. /package/mcp-server/.next/static/{UcmWUkU-l9iLeWRnSUybj → 5zfTZk2QSS7WLdL1K8Q5I}/_buildManifest.js +0 -0
  196. /package/mcp-server/.next/static/{UcmWUkU-l9iLeWRnSUybj → 5zfTZk2QSS7WLdL1K8Q5I}/_clientMiddlewareManifest.json +0 -0
  197. /package/mcp-server/.next/static/{UcmWUkU-l9iLeWRnSUybj → 5zfTZk2QSS7WLdL1K8Q5I}/_ssgManifest.js +0 -0
@@ -2,7 +2,7 @@ import chalk from "chalk"
2
2
  import { createReadStream, unwatchFile, watchFile } from "fs"
3
3
  import { Box, render, Text, useInput, useStdout } from "ink"
4
4
  import Spinner from "ink-spinner"
5
- import { useEffect, useRef, useState } from "react"
5
+ import { memo, useEffect, useRef, useState } from "react"
6
6
  import type { Readable } from "stream"
7
7
  import { LOG_COLORS } from "./constants/log-colors.js"
8
8
 
@@ -29,6 +29,117 @@ const COMPACT_LOGO = "d3k"
29
29
  // Full ASCII logo lines as array for easier rendering
30
30
  const FULL_LOGO = [" ▐▌▄▄▄▄ █ ▄ ", " ▐▌ █ █▄▀ ", "▗▞▀▜▌▀▀▀█ █ ▀▄ ", "▝▚▄▟▌▄▄▄█ █ █ "]
31
31
 
32
+ // Type colors map - defined outside component to avoid recreation
33
+ const TYPE_COLORS: Record<string, string> = {
34
+ NETWORK: LOG_COLORS.NETWORK,
35
+ ERROR: LOG_COLORS.ERROR,
36
+ WARNING: LOG_COLORS.WARNING,
37
+ INFO: LOG_COLORS.INFO,
38
+ LOG: LOG_COLORS.LOG,
39
+ DEBUG: LOG_COLORS.DEBUG,
40
+ SCREENSHOT: LOG_COLORS.SCREENSHOT,
41
+ DOM: LOG_COLORS.DOM,
42
+ CDP: LOG_COLORS.CDP,
43
+ CHROME: LOG_COLORS.CHROME,
44
+ CRASH: LOG_COLORS.CRASH,
45
+ REPLAY: LOG_COLORS.REPLAY,
46
+ NAVIGATION: LOG_COLORS.NAVIGATION,
47
+ INTERACTION: LOG_COLORS.INTERACTION,
48
+ GET: LOG_COLORS.SERVER,
49
+ POST: LOG_COLORS.SERVER,
50
+ PUT: LOG_COLORS.SERVER,
51
+ DELETE: LOG_COLORS.SERVER,
52
+ PATCH: LOG_COLORS.SERVER,
53
+ HEAD: LOG_COLORS.SERVER,
54
+ OPTIONS: LOG_COLORS.SERVER
55
+ }
56
+
57
+ // Memoized log line component to prevent re-parsing on every render
58
+ const LogLine = memo(
59
+ ({ log, isCompact, isVeryCompact }: { log: LogEntry; isCompact: boolean; isVeryCompact: boolean }) => {
60
+ // Parse log line to colorize different parts
61
+ const parts = log.content.match(/^\[(.*?)\] \[(.*?)\] (?:\[(.*?)\] )?(.*)$/)
62
+
63
+ if (parts) {
64
+ let [, timestamp, source, type, message] = parts
65
+
66
+ // Extract HTTP method from SERVER logs as a secondary tag
67
+ if (source === "SERVER" && !type && message) {
68
+ const methodMatch = message.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s/)
69
+ if (methodMatch) {
70
+ type = methodMatch[1]
71
+ message = message.slice(type.length + 1) // Remove method from message
72
+ }
73
+ }
74
+
75
+ // Replace warning emoji in ERROR/WARNING messages for consistent terminal rendering
76
+ if (message && (type === "ERROR" || type === "WARNING")) {
77
+ message = message.replace(/⚠/g, "[!]")
78
+ }
79
+
80
+ // In very compact mode, simplify the output
81
+ if (isVeryCompact) {
82
+ const shortSource = source === "BROWSER" ? "B" : "S"
83
+ const shortType = type ? type.split(".")[0].charAt(0) : ""
84
+ return (
85
+ <Text wrap="truncate-end">
86
+ <Text dimColor>[{shortSource}]</Text>
87
+ {shortType && <Text dimColor>[{shortType}]</Text>}
88
+ <Text> {message}</Text>
89
+ </Text>
90
+ )
91
+ }
92
+
93
+ // Use shared color constants
94
+ const sourceColor = source === "BROWSER" ? LOG_COLORS.BROWSER : LOG_COLORS.SERVER
95
+
96
+ // In compact mode, skip padding
97
+ if (isCompact) {
98
+ return (
99
+ <Text wrap="truncate-end">
100
+ <Text dimColor>[{timestamp}]</Text>
101
+ <Text> </Text>
102
+ <Text color={sourceColor} bold>
103
+ [{source.charAt(0)}]
104
+ </Text>
105
+ {type && (
106
+ <>
107
+ <Text> </Text>
108
+ <Text color={TYPE_COLORS[type] || "#A0A0A0"}>[{type}]</Text>
109
+ </>
110
+ )}
111
+ <Text> {message}</Text>
112
+ </Text>
113
+ )
114
+ }
115
+
116
+ // Normal mode with minimal padding
117
+ return (
118
+ <Text wrap="truncate-end">
119
+ <Text dimColor>[{timestamp}]</Text>
120
+ <Text> </Text>
121
+ <Text color={sourceColor} bold>
122
+ [{source}]
123
+ </Text>
124
+ {type ? (
125
+ <>
126
+ <Text> </Text>
127
+ <Text color={TYPE_COLORS[type] || "#A0A0A0"}>[{type}]</Text>
128
+ <Text> </Text>
129
+ </>
130
+ ) : (
131
+ <Text> </Text>
132
+ )}
133
+ <Text>{message}</Text>
134
+ </Text>
135
+ )
136
+ }
137
+
138
+ // Fallback for unparsed lines
139
+ return <Text wrap="truncate-end">{log.content}</Text>
140
+ }
141
+ )
142
+
32
143
  const TUIApp = ({
33
144
  appPort: initialAppPort,
34
145
  mcpPort,
@@ -155,19 +266,19 @@ const TUIApp = ({
155
266
  useEffect(() => {
156
267
  let logStream: Readable | undefined
157
268
  let buffer = ""
269
+ let pendingLogs: LogEntry[] = []
270
+ let flushTimeout: NodeJS.Timeout | null = null
158
271
 
159
- const appendLog = (line: string) => {
160
- if (NEXTJS_MCP_404_REGEX.test(line)) {
161
- return
162
- }
272
+ // Batch log updates to prevent excessive renders
273
+ const flushPendingLogs = () => {
274
+ if (pendingLogs.length === 0) return
163
275
 
164
- const newLog: LogEntry = {
165
- id: logIdCounter.current++,
166
- content: line
167
- }
276
+ const logsToAdd = pendingLogs
277
+ pendingLogs = []
278
+ flushTimeout = null
168
279
 
169
280
  setLogs((prevLogs) => {
170
- const updated = [...prevLogs, newLog]
281
+ const updated = [...prevLogs, ...logsToAdd]
171
282
  // Keep only last N logs to prevent memory issues
172
283
  if (updated.length > maxLogs) {
173
284
  return updated.slice(-maxLogs)
@@ -176,12 +287,32 @@ const TUIApp = ({
176
287
  })
177
288
 
178
289
  // Auto-scroll to bottom only if user is already at the bottom
179
- // Otherwise, increment scroll offset by 1, accounting for the appended log and max scroll offset
290
+ // Otherwise, increment scroll offset by count of new logs
180
291
  setScrollOffset((currentOffset) => {
181
- return currentOffset === 0 ? 0 : Math.min(maxScrollOffsetRef.current, currentOffset + 1)
292
+ return currentOffset === 0 ? 0 : Math.min(maxScrollOffsetRef.current, currentOffset + logsToAdd.length)
182
293
  })
183
294
  }
184
295
 
296
+ const appendLog = (line: string) => {
297
+ if (NEXTJS_MCP_404_REGEX.test(line)) {
298
+ return
299
+ }
300
+
301
+ const newLog: LogEntry = {
302
+ id: logIdCounter.current++,
303
+ content: line
304
+ }
305
+
306
+ pendingLogs.push(newLog)
307
+
308
+ // Debounce: flush after 50ms of no new logs
309
+ // Terminal synchronized updates prevent flicker, so we can be more responsive
310
+ if (flushTimeout) {
311
+ clearTimeout(flushTimeout)
312
+ }
313
+ flushTimeout = setTimeout(flushPendingLogs, 50)
314
+ }
315
+
185
316
  // Create a read stream for the log file
186
317
  logStream = createReadStream(logFile, {
187
318
  encoding: "utf8",
@@ -236,6 +367,9 @@ const TUIApp = ({
236
367
  if (logStream) {
237
368
  logStream.destroy()
238
369
  }
370
+ if (flushTimeout) {
371
+ clearTimeout(flushTimeout)
372
+ }
239
373
  unwatchFile(logFile)
240
374
  }
241
375
  }, [logFile])
@@ -376,121 +510,9 @@ const TUIApp = ({
376
510
  {visibleLogs.length === 0 ? (
377
511
  <Text dimColor>Waiting for logs...</Text>
378
512
  ) : (
379
- visibleLogs.map((log) => {
380
- // Parse log line to colorize different parts
381
- const parts = log.content.match(/^\[(.*?)\] \[(.*?)\] (?:\[(.*?)\] )?(.*)$/)
382
-
383
- if (parts) {
384
- let [, timestamp, source, type, message] = parts
385
-
386
- // Extract HTTP method from SERVER logs as a secondary tag
387
- if (source === "SERVER" && !type && message) {
388
- const methodMatch = message.match(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s/)
389
- if (methodMatch) {
390
- type = methodMatch[1]
391
- message = message.slice(type.length + 1) // Remove method from message
392
- }
393
- }
394
-
395
- // Replace warning emoji in ERROR/WARNING messages for consistent terminal rendering
396
- if (message && (type === "ERROR" || type === "WARNING")) {
397
- message = message.replace(/⚠/g, "[!]")
398
- }
399
-
400
- // In very compact mode, simplify the output
401
- if (isVeryCompact) {
402
- const shortSource = source === "BROWSER" ? "B" : "S"
403
- const shortType = type ? type.split(".")[0].charAt(0) : ""
404
- return (
405
- <Text key={log.id} wrap="truncate-end">
406
- <Text dimColor>[{shortSource}]</Text>
407
- {shortType && <Text dimColor>[{shortType}]</Text>}
408
- <Text> {message}</Text>
409
- </Text>
410
- )
411
- }
412
-
413
- // Use shared color constants
414
- const sourceColor = source === "BROWSER" ? LOG_COLORS.BROWSER : LOG_COLORS.SERVER
415
- const typeColors: Record<string, string> = {
416
- NETWORK: LOG_COLORS.NETWORK,
417
- ERROR: LOG_COLORS.ERROR,
418
- WARNING: LOG_COLORS.WARNING,
419
- INFO: LOG_COLORS.INFO,
420
- LOG: LOG_COLORS.LOG,
421
- DEBUG: LOG_COLORS.DEBUG,
422
- SCREENSHOT: LOG_COLORS.SCREENSHOT,
423
- DOM: LOG_COLORS.DOM,
424
- CDP: LOG_COLORS.CDP,
425
- CHROME: LOG_COLORS.CHROME,
426
- CRASH: LOG_COLORS.CRASH,
427
- REPLAY: LOG_COLORS.REPLAY,
428
- NAVIGATION: LOG_COLORS.NAVIGATION,
429
- INTERACTION: LOG_COLORS.INTERACTION,
430
- GET: LOG_COLORS.SERVER,
431
- POST: LOG_COLORS.SERVER,
432
- PUT: LOG_COLORS.SERVER,
433
- DELETE: LOG_COLORS.SERVER,
434
- PATCH: LOG_COLORS.SERVER,
435
- HEAD: LOG_COLORS.SERVER,
436
- OPTIONS: LOG_COLORS.SERVER
437
- }
438
-
439
- // In compact mode, skip padding
440
- if (isCompact) {
441
- return (
442
- <Text key={log.id} wrap="truncate-end">
443
- <Text dimColor>[{timestamp}]</Text>
444
- <Text> </Text>
445
- <Text color={sourceColor} bold>
446
- [{source.charAt(0)}]
447
- </Text>
448
- {type && (
449
- <>
450
- <Text> </Text>
451
- <Text color={typeColors[type] || "#A0A0A0"}>[{type}]</Text>
452
- </>
453
- )}
454
- <Text> {message}</Text>
455
- </Text>
456
- )
457
- }
458
-
459
- // Normal mode with minimal padding
460
- // Single space after source
461
- const sourceSpacing = ""
462
-
463
- // Single space after type
464
- const typeSpacing = ""
465
-
466
- return (
467
- <Text key={log.id} wrap="truncate-end">
468
- <Text dimColor>[{timestamp}]</Text>
469
- <Text> </Text>
470
- <Text color={sourceColor} bold>
471
- [{source}]
472
- </Text>
473
- {type ? (
474
- <>
475
- <Text>{sourceSpacing} </Text>
476
- <Text color={typeColors[type] || "#A0A0A0"}>[{type}]</Text>
477
- <Text>{typeSpacing} </Text>
478
- </>
479
- ) : (
480
- <Text> </Text>
481
- )}
482
- <Text>{message}</Text>
483
- </Text>
484
- )
485
- }
486
-
487
- // Fallback for unparsed lines
488
- return (
489
- <Text key={log.id} wrap="truncate-end">
490
- {log.content}
491
- </Text>
492
- )
493
- })
513
+ visibleLogs.map((log) => (
514
+ <LogLine key={log.id} log={log} isCompact={isCompact} isVeryCompact={isVeryCompact} />
515
+ ))
494
516
  )}
495
517
  </Box>
496
518
 
@@ -524,6 +546,32 @@ export async function runTUI(options: TUIOptions): Promise<{
524
546
  let statusUpdater: ((status: string | null) => void) | null = null
525
547
  let appPortUpdater: ((port: string) => void) | null = null
526
548
 
549
+ // Wrap stdout.write to add synchronized update escape sequences
550
+ // This tells the terminal to buffer all output until the end marker
551
+ // Supported by iTerm2, Kitty, WezTerm, and other modern terminals
552
+ const originalWrite = process.stdout.write.bind(process.stdout)
553
+ const syncStart = "\x1b[?2026h" // Begin synchronized update (DECSM 2026)
554
+ const syncEnd = "\x1b[?2026l" // End synchronized update (DECRM 2026)
555
+
556
+ process.stdout.write = ((
557
+ chunk: string | Uint8Array,
558
+ encodingOrCb?: BufferEncoding | ((err?: Error | null) => void),
559
+ cb?: (err?: Error | null) => void
560
+ ): boolean => {
561
+ if (typeof chunk === "string" && chunk.length > 0) {
562
+ // Wrap output in synchronized update markers to prevent partial renders
563
+ const wrapped = syncStart + chunk + syncEnd
564
+ if (typeof encodingOrCb === "function") {
565
+ return originalWrite(wrapped, encodingOrCb)
566
+ }
567
+ return originalWrite(wrapped, encodingOrCb, cb)
568
+ }
569
+ if (typeof encodingOrCb === "function") {
570
+ return originalWrite(chunk, encodingOrCb)
571
+ }
572
+ return originalWrite(chunk, encodingOrCb, cb)
573
+ }) as typeof process.stdout.write
574
+
527
575
  const app = render(
528
576
  <TUIApp
529
577
  {...options}