dev3000 0.0.66 → 0.0.67

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 (212) hide show
  1. package/dist/cdp-monitor.d.ts +4 -1
  2. package/dist/cdp-monitor.d.ts.map +1 -1
  3. package/dist/cdp-monitor.js +35 -1
  4. package/dist/cdp-monitor.js.map +1 -1
  5. package/dist/cli.js +10 -5
  6. package/dist/cli.js.map +1 -1
  7. package/dist/dev-environment.d.ts +2 -0
  8. package/dist/dev-environment.d.ts.map +1 -1
  9. package/dist/dev-environment.js +44 -27
  10. package/dist/dev-environment.js.map +1 -1
  11. package/dist/services/parsers/log-parsers/base.d.ts +1 -1
  12. package/dist/services/parsers/log-parsers/base.d.ts.map +1 -1
  13. package/dist/src/tui-interface-impl.tsx +163 -48
  14. package/dist/tui-interface-impl.d.ts.map +1 -1
  15. package/dist/tui-interface-impl.js +72 -14
  16. package/dist/tui-interface-impl.js.map +1 -1
  17. package/dist/utils/project-name.d.ts +18 -0
  18. package/dist/utils/project-name.d.ts.map +1 -0
  19. package/dist/utils/project-name.js +114 -0
  20. package/dist/utils/project-name.js.map +1 -0
  21. package/dist/utils/timestamp.d.ts +8 -0
  22. package/dist/utils/timestamp.d.ts.map +1 -0
  23. package/dist/utils/timestamp.js +18 -0
  24. package/dist/utils/timestamp.js.map +1 -0
  25. package/mcp-server/.next/BUILD_ID +1 -1
  26. package/mcp-server/.next/app-build-manifest.json +8 -6
  27. package/mcp-server/.next/build-manifest.json +2 -2
  28. package/mcp-server/.next/cache/.tsbuildinfo +1 -1
  29. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000005.sst +0 -0
  30. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000006.sst +0 -0
  31. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000007.sst +0 -0
  32. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000008.sst +0 -0
  33. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000009.meta +0 -0
  34. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000010.meta +0 -0
  35. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000011.meta +0 -0
  36. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000013.meta +0 -0
  37. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000014.sst +0 -0
  38. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000016.sst +0 -0
  39. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000017.meta +0 -0
  40. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000018.meta +0 -0
  41. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000021.sst +0 -0
  42. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000022.sst +0 -0
  43. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000023.meta +0 -0
  44. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000025.meta +0 -0
  45. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000027.sst +0 -0
  46. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000028.sst +0 -0
  47. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000029.sst +0 -0
  48. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000030.sst +0 -0
  49. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000031.meta +0 -0
  50. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000032.meta +0 -0
  51. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000034.meta +0 -0
  52. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000035.meta +0 -0
  53. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000037.sst +0 -0
  54. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000038.sst +0 -0
  55. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000039.sst +0 -0
  56. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000040.sst +0 -0
  57. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000041.meta +0 -0
  58. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000042.meta +0 -0
  59. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000044.meta +0 -0
  60. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000045.meta +0 -0
  61. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000047.sst +0 -0
  62. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000048.sst +0 -0
  63. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000049.sst +0 -0
  64. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000050.sst +0 -0
  65. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000051.meta +0 -0
  66. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000052.meta +0 -0
  67. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000054.meta +0 -0
  68. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000055.meta +0 -0
  69. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000057.sst +0 -0
  70. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000058.sst +0 -0
  71. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000059.sst +0 -0
  72. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000060.sst +0 -0
  73. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000061.meta +0 -0
  74. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000062.meta +0 -0
  75. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000064.meta +0 -0
  76. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000065.meta +0 -0
  77. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000066.sst +0 -0
  78. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000067.meta +0 -0
  79. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000069.sst +0 -0
  80. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000070.sst +0 -0
  81. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000071.sst +0 -0
  82. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000072.sst +0 -0
  83. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000073.sst +0 -0
  84. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000074.meta +0 -0
  85. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000075.meta +0 -0
  86. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000076.meta +0 -0
  87. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000077.meta +0 -0
  88. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000078.meta +0 -0
  89. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000079.sst +0 -0
  90. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000080.sst +0 -0
  91. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000081.sst +0 -0
  92. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000082.sst +0 -0
  93. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000083.sst +0 -0
  94. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000084.meta +0 -0
  95. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000085.meta +0 -0
  96. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000086.meta +0 -0
  97. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000087.meta +0 -0
  98. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000088.meta +0 -0
  99. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000089.sst +0 -0
  100. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000090.sst +0 -0
  101. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000091.sst +0 -0
  102. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000092.sst +0 -0
  103. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000093.sst +0 -0
  104. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000094.meta +0 -0
  105. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000095.meta +0 -0
  106. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000096.meta +0 -0
  107. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000097.meta +0 -0
  108. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000098.meta +0 -0
  109. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000099.sst +0 -0
  110. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000100.sst +0 -0
  111. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000101.sst +0 -0
  112. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000102.meta +0 -0
  113. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000103.meta +0 -0
  114. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000104.meta +0 -0
  115. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000105.sst +0 -0
  116. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000106.sst +0 -0
  117. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000107.sst +0 -0
  118. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000108.meta +0 -0
  119. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000109.meta +0 -0
  120. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/00000110.meta +0 -0
  121. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/CURRENT +0 -0
  122. package/mcp-server/.next/cache/turbopack/v15.5.1-canary.28-7-gede8d1b86/LOG +230 -0
  123. package/mcp-server/.next/fallback-build-manifest.json +2 -2
  124. package/mcp-server/.next/package.json +3 -1
  125. package/mcp-server/.next/required-server-files.json +1 -0
  126. package/mcp-server/.next/server/app/_global-error.html +2 -2
  127. package/mcp-server/.next/server/app/_global-error.rsc +1 -1
  128. package/mcp-server/.next/server/app/_not-found/page/app-build-manifest.json +1 -1
  129. package/mcp-server/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  130. package/mcp-server/.next/server/app/_not-found.html +1 -1
  131. package/mcp-server/.next/server/app/_not-found.rsc +2 -2
  132. package/mcp-server/.next/server/app/index.html +1 -1
  133. package/mcp-server/.next/server/app/index.rsc +3 -3
  134. package/mcp-server/.next/server/app/logs/page/app-build-manifest.json +4 -3
  135. package/mcp-server/.next/server/app/logs/page.js.nft.json +1 -1
  136. package/mcp-server/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  137. package/mcp-server/.next/server/app/mcp/route.js.nft.json +1 -1
  138. package/mcp-server/.next/server/app/page/app-build-manifest.json +3 -2
  139. package/mcp-server/.next/server/app/page.js.nft.json +1 -1
  140. package/mcp-server/.next/server/app/page_client-reference-manifest.js +1 -1
  141. package/mcp-server/.next/server/chunks/[root-of-the-server]__1b561deb._.js +1 -1
  142. package/mcp-server/.next/server/chunks/[root-of-the-server]__1b561deb._.js.map +1 -1
  143. package/mcp-server/.next/server/chunks/[root-of-the-server]__38e8baae._.js +7 -8
  144. package/mcp-server/.next/server/chunks/[root-of-the-server]__38e8baae._.js.map +1 -1
  145. package/mcp-server/.next/server/chunks/ssr/{node_modules__pnpm_4f58b96e._.js → _188bfe33._.js} +2 -2
  146. package/mcp-server/.next/server/chunks/ssr/_188bfe33._.js.map +1 -0
  147. package/mcp-server/.next/server/chunks/ssr/_9d670a6b._.js +2 -2
  148. package/mcp-server/.next/server/chunks/ssr/_9d670a6b._.js.map +1 -1
  149. package/mcp-server/.next/server/chunks/ssr/_d858c4cd._.js +1 -1
  150. package/mcp-server/.next/server/chunks/ssr/_d858c4cd._.js.map +1 -1
  151. package/mcp-server/.next/server/chunks/ssr/_dae9c1d5._.js +1 -1
  152. package/mcp-server/.next/server/chunks/ssr/_dae9c1d5._.js.map +1 -1
  153. package/mcp-server/.next/server/chunks/ssr/{node_modules__pnpm_87fb6266._.js → _f03e80a8._.js} +2 -2
  154. package/mcp-server/.next/server/chunks/ssr/_f03e80a8._.js.map +1 -0
  155. package/mcp-server/.next/server/chunks/ssr/mcp-server_app_layout_tsx_afa41767._.js +1 -1
  156. package/mcp-server/.next/server/chunks/ssr/mcp-server_app_layout_tsx_afa41767._.js.map +1 -1
  157. package/mcp-server/.next/server/pages/404.html +1 -1
  158. package/mcp-server/.next/server/pages/500.html +2 -2
  159. package/mcp-server/.next/static/chunks/5a5edc75ee7e7de4.js +1 -0
  160. package/mcp-server/.next/static/chunks/65b18bf1ede9811a.css +1 -0
  161. package/mcp-server/.next/static/chunks/909033014621484e.js +1 -0
  162. package/mcp-server/.next/static/chunks/c5f8464bc8083ee7.js +1 -0
  163. package/mcp-server/.next/trace +1 -1
  164. package/mcp-server/app/api/tools/route.ts +22 -5
  165. package/mcp-server/app/layout.tsx +4 -2
  166. package/mcp-server/app/logs/LogsClient.infinite-loop.test.tsx +127 -0
  167. package/mcp-server/app/logs/LogsClient.tsx +318 -201
  168. package/mcp-server/app/logs/page.tsx +19 -3
  169. package/mcp-server/app/logs/utils.ts +15 -3
  170. package/mcp-server/app/mcp/route.ts +75 -509
  171. package/mcp-server/app/mcp/tools.ts +747 -0
  172. package/mcp-server/app/page.tsx +244 -169
  173. package/mcp-server/next.config.ts +1 -1
  174. package/mcp-server/package.json +9 -1
  175. package/mcp-server/public/favicon-16.svg +4 -0
  176. package/mcp-server/public/favicon-180.png +0 -0
  177. package/mcp-server/public/favicon-64.svg +4 -0
  178. package/mcp-server/public/favicon-preview.html +67 -0
  179. package/mcp-server/public/favicon.ico +0 -0
  180. package/mcp-server/public/favicon.svg +4 -0
  181. package/mcp-server/public/screenshots/test.txt +1 -0
  182. package/package.json +3 -4
  183. package/src/tui-interface-impl.tsx +163 -48
  184. package/mcp-server/.next/build/chunks/[root-of-the-server]__25374c4f._.js +0 -496
  185. package/mcp-server/.next/build/chunks/[root-of-the-server]__25374c4f._.js.map +0 -11
  186. package/mcp-server/.next/build/chunks/[root-of-the-server]__4718a9dd._.js +0 -408
  187. package/mcp-server/.next/build/chunks/[root-of-the-server]__4718a9dd._.js.map +0 -7
  188. package/mcp-server/.next/build/chunks/[root-of-the-server]__c438ef56._.js +0 -205
  189. package/mcp-server/.next/build/chunks/[root-of-the-server]__c438ef56._.js.map +0 -8
  190. package/mcp-server/.next/build/chunks/[root-of-the-server]__c7ae8543._.js +0 -496
  191. package/mcp-server/.next/build/chunks/[root-of-the-server]__c7ae8543._.js.map +0 -11
  192. package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_d723d216._.js +0 -13
  193. package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_postcss_ts_d723d216._.js.map +0 -5
  194. package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_webpack-loaders_ts_5a40237e._.js +0 -12
  195. package/mcp-server/.next/build/chunks/[turbopack-node]_transforms_webpack-loaders_ts_5a40237e._.js.map +0 -5
  196. package/mcp-server/.next/build/chunks/[turbopack]_runtime.js +0 -770
  197. package/mcp-server/.next/build/chunks/[turbopack]_runtime.js.map +0 -10
  198. package/mcp-server/.next/build/chunks/node_modules__pnpm_806d01c0._.js +0 -6759
  199. package/mcp-server/.next/build/chunks/node_modules__pnpm_806d01c0._.js.map +0 -47
  200. package/mcp-server/.next/postcss.js +0 -6
  201. package/mcp-server/.next/postcss.js.map +0 -5
  202. package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_4f58b96e._.js.map +0 -1
  203. package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_87fb6266._.js.map +0 -1
  204. package/mcp-server/.next/static/chunks/11f1b53bdf7a9af0.js +0 -1
  205. package/mcp-server/.next/static/chunks/3d4ea64f6384f2c6.js +0 -1
  206. package/mcp-server/.next/static/chunks/50335dad5c51aab8.js +0 -1
  207. package/mcp-server/.next/static/chunks/bdd0789390bc312f.css +0 -1
  208. package/mcp-server/.next/webpack-loaders.js +0 -6
  209. package/mcp-server/.next/webpack-loaders.js.map +0 -5
  210. /package/mcp-server/.next/static/{mZfouQw6OHfahPQayuVeY → KrGcHKj--hSqNUOXmnA4A}/_buildManifest.js +0 -0
  211. /package/mcp-server/.next/static/{mZfouQw6OHfahPQayuVeY → KrGcHKj--hSqNUOXmnA4A}/_clientMiddlewareManifest.json +0 -0
  212. /package/mcp-server/.next/static/{mZfouQw6OHfahPQayuVeY → KrGcHKj--hSqNUOXmnA4A}/_ssgManifest.js +0 -0
@@ -3,6 +3,8 @@
3
3
  import Image from "next/image"
4
4
  import { useRouter, useSearchParams } from "next/navigation"
5
5
  import { useCallback, useEffect, useMemo, useRef, useState } from "react"
6
+ import { DarkModeToggle } from "@/components/dark-mode-toggle"
7
+ import { useDarkMode } from "@/hooks/use-dark-mode"
6
8
  import type { LogEntry, LogFile, LogListResponse, LogsApiResponse } from "@/types"
7
9
  import { getTextColor, LOG_COLORS } from "../../../src/constants/log-colors"
8
10
 
@@ -29,51 +31,6 @@ interface ReplayEvent {
29
31
 
30
32
  import { parseLogEntries } from "./utils"
31
33
 
32
- // Hook for dark mode with system preference detection
33
- function useDarkMode() {
34
- const [darkMode, setDarkMode] = useState<boolean>(() => {
35
- if (typeof window !== "undefined") {
36
- // Check localStorage first
37
- const saved = localStorage.getItem("dev3000-dark-mode")
38
- if (saved !== null) {
39
- return JSON.parse(saved)
40
- }
41
- // Default to system preference
42
- return window.matchMedia("(prefers-color-scheme: dark)").matches
43
- }
44
- return false
45
- })
46
-
47
- useEffect(() => {
48
- // Save to localStorage
49
- localStorage.setItem("dev3000-dark-mode", JSON.stringify(darkMode))
50
-
51
- // Apply dark class to document
52
- if (darkMode) {
53
- document.documentElement.classList.add("dark")
54
- } else {
55
- document.documentElement.classList.remove("dark")
56
- }
57
- }, [darkMode])
58
-
59
- // Listen for system theme changes
60
- useEffect(() => {
61
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
62
- const handler = (e: MediaQueryListEvent) => {
63
- // Only update if no explicit choice has been made
64
- const saved = localStorage.getItem("dev3000-dark-mode")
65
- if (saved === null) {
66
- setDarkMode(e.matches)
67
- }
68
- }
69
-
70
- mediaQuery.addEventListener("change", handler)
71
- return () => mediaQuery.removeEventListener("change", handler)
72
- }, [])
73
-
74
- return [darkMode, setDarkMode] as const
75
- }
76
-
77
34
  // Keep this for backwards compatibility, but it's not used anymore
78
35
  function _parseLogLine(line: string): LogEntry | null {
79
36
  const match = line.match(/^\[([^\]]+)\] \[([^\]]+)\] (.*)$/s)
@@ -125,7 +82,7 @@ function URLRenderer({ url, maxLength = 60 }: { url: string; maxLength?: number
125
82
  <button
126
83
  type="button"
127
84
  onClick={() => setIsExpanded(false)}
128
- className="ml-2 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 px-1 py-0.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700"
85
+ className="ml-2 text-xs text-muted-foreground hover:text-foreground px-1 py-0.5 rounded hover:bg-muted"
129
86
  >
130
87
  [collapse]
131
88
  </button>
@@ -143,7 +100,7 @@ function URLRenderer({ url, maxLength = 60 }: { url: string; maxLength?: number
143
100
  <button
144
101
  type="button"
145
102
  onClick={() => setIsExpanded(true)}
146
- className="ml-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 px-1 py-0.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700"
103
+ className="ml-1 text-xs text-muted-foreground hover:text-foreground px-1 py-0.5 rounded hover:bg-muted"
147
104
  >
148
105
  [expand]
149
106
  </button>
@@ -285,7 +242,7 @@ function ObjectRenderer({ content }: { content: string }) {
285
242
 
286
243
  {isExpanded && (
287
244
  <div className="mt-1 ml-4 border-l-2 border-gray-200 pl-3">
288
- <pre className="font-mono text-sm text-gray-700 whitespace-pre-wrap">{JSON.stringify(obj, null, 2)}</pre>
245
+ <pre className="font-mono text-sm text-foreground whitespace-pre-wrap">{JSON.stringify(obj, null, 2)}</pre>
289
246
  </div>
290
247
  )}
291
248
  </div>
@@ -296,103 +253,90 @@ function ObjectRenderer({ content }: { content: string }) {
296
253
  }
297
254
  }
298
255
 
299
- function LogEntryComponent({ entry }: { entry: LogEntry }) {
256
+ function LogEntryComponent({ entry, darkMode }: { entry: LogEntry; darkMode: boolean }) {
300
257
  // Parse log type from message patterns - using shared TUI colors
301
258
  const parseLogType = (message: string) => {
302
259
  if (message.includes("[INTERACTION]"))
303
260
  return {
304
261
  type: "INTERACTION",
305
- color: "bg-purple-50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800",
306
262
  backgroundColor: LOG_COLORS.CONSOLE_DEBUG,
307
263
  textColor: getTextColor(LOG_COLORS.CONSOLE_DEBUG)
308
264
  }
309
265
  if (message.includes("[CONSOLE ERROR]"))
310
266
  return {
311
267
  type: "CONSOLE ERROR",
312
- color: "bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",
313
268
  backgroundColor: LOG_COLORS.CONSOLE_ERROR,
314
269
  textColor: getTextColor(LOG_COLORS.CONSOLE_ERROR)
315
270
  }
316
271
  if (message.includes("[CONSOLE WARN]"))
317
272
  return {
318
273
  type: "CONSOLE WARN",
319
- color: "bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800",
320
274
  backgroundColor: LOG_COLORS.CONSOLE_WARN,
321
275
  textColor: getTextColor(LOG_COLORS.CONSOLE_WARN)
322
276
  }
323
277
  if (message.includes("[CONSOLE INFO]"))
324
278
  return {
325
279
  type: "CONSOLE INFO",
326
- color: "bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800",
327
280
  backgroundColor: LOG_COLORS.CONSOLE_INFO,
328
281
  textColor: getTextColor(LOG_COLORS.CONSOLE_INFO)
329
282
  }
330
283
  if (message.includes("[CONSOLE LOG]"))
331
284
  return {
332
285
  type: "CONSOLE LOG",
333
- color: "bg-gray-50 dark:bg-gray-800/50 border-gray-200 dark:border-gray-700",
334
286
  backgroundColor: LOG_COLORS.CONSOLE_LOG,
335
287
  textColor: getTextColor(LOG_COLORS.CONSOLE_LOG)
336
288
  }
337
289
  if (message.includes("[CONSOLE DEBUG]"))
338
290
  return {
339
291
  type: "CONSOLE DEBUG",
340
- color: "bg-purple-50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800",
341
292
  backgroundColor: LOG_COLORS.CONSOLE_DEBUG,
342
293
  textColor: getTextColor(LOG_COLORS.CONSOLE_DEBUG)
343
294
  }
344
295
  if (message.includes("[SCREENSHOT]"))
345
296
  return {
346
297
  type: "SCREENSHOT",
347
- color: "bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800",
348
298
  backgroundColor: LOG_COLORS.SCREENSHOT,
349
299
  textColor: getTextColor(LOG_COLORS.SCREENSHOT)
350
300
  }
351
301
  if (message.includes("[NAVIGATION]") || message.includes("[PAGE]"))
352
302
  return {
353
303
  type: "PAGE",
354
- color: "bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800",
355
304
  backgroundColor: LOG_COLORS.PAGE,
356
305
  textColor: getTextColor(LOG_COLORS.PAGE)
357
306
  }
358
307
  if (message.includes("[DOM]"))
359
308
  return {
360
309
  type: "DOM",
361
- color: "bg-purple-50 dark:bg-purple-900/20 border-purple-200 dark:border-purple-800",
362
310
  backgroundColor: LOG_COLORS.DOM,
363
311
  textColor: getTextColor(LOG_COLORS.DOM)
364
312
  }
365
313
  if (message.includes("[CDP]"))
366
314
  return {
367
315
  type: "CDP",
368
- color: "bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800",
369
316
  backgroundColor: LOG_COLORS.CDP,
370
317
  textColor: getTextColor(LOG_COLORS.CDP)
371
318
  }
372
319
  if (message.includes("[NETWORK]"))
373
320
  return {
374
321
  type: "NETWORK",
375
- color: "bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800",
376
322
  backgroundColor: LOG_COLORS.NETWORK,
377
323
  textColor: getTextColor(LOG_COLORS.NETWORK)
378
324
  }
379
325
  if (message.includes("[ERROR]") || message.includes("[PAGE ERROR]") || message.includes("[NETWORK ERROR]"))
380
326
  return {
381
327
  type: "ERROR",
382
- color: "bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",
383
328
  backgroundColor: LOG_COLORS.ERROR,
384
329
  textColor: getTextColor(LOG_COLORS.ERROR)
385
330
  }
386
331
  if (message.includes("[CRITICAL ERROR]"))
387
332
  return {
388
333
  type: "CRITICAL ERROR",
389
- color: "bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",
390
334
  backgroundColor: LOG_COLORS.CRITICAL_ERROR,
391
335
  textColor: getTextColor(LOG_COLORS.CRITICAL_ERROR)
392
336
  }
393
337
  return {
394
338
  type: "DEFAULT",
395
- color: "border-gray-200 dark:border-gray-700",
339
+ color: "bg-gray-50 dark:bg-gray-800/50 border-gray-200 dark:border-gray-700",
396
340
  backgroundColor: LOG_COLORS.DEFAULT,
397
341
  textColor: getTextColor(LOG_COLORS.DEFAULT)
398
342
  }
@@ -493,12 +437,90 @@ function LogEntryComponent({ entry }: { entry: LogEntry }) {
493
437
  return parts.length > 0 ? parts : message
494
438
  }
495
439
 
440
+ // Determine the background color based on log type and dark mode
441
+ const getBackgroundStyle = () => {
442
+ const type = logTypeInfo.type
443
+ if (darkMode) {
444
+ // Dark mode colors
445
+ switch (type) {
446
+ case "INTERACTION":
447
+ case "CONSOLE DEBUG":
448
+ case "DOM":
449
+ return { backgroundColor: "rgba(88, 28, 135, 0.3)" } // purple-950/30
450
+ case "CONSOLE ERROR":
451
+ case "ERROR":
452
+ case "CRITICAL ERROR":
453
+ return { backgroundColor: "rgba(127, 29, 29, 0.3)" } // red-950/30
454
+ case "CONSOLE WARN":
455
+ case "CDP":
456
+ case "NETWORK":
457
+ return { backgroundColor: "rgba(133, 77, 14, 0.3)" } // yellow-950/30
458
+ case "CONSOLE INFO":
459
+ case "SCREENSHOT":
460
+ return { backgroundColor: "rgba(30, 58, 138, 0.3)" } // blue-950/30
461
+ case "PAGE":
462
+ return { backgroundColor: "rgba(20, 83, 45, 0.3)" } // green-950/30
463
+ default:
464
+ return { backgroundColor: "rgba(17, 24, 39, 0.3)" } // gray-900/30
465
+ }
466
+ } else {
467
+ // Light mode colors
468
+ switch (type) {
469
+ case "INTERACTION":
470
+ case "CONSOLE DEBUG":
471
+ case "DOM":
472
+ return { backgroundColor: "rgba(250, 245, 255, 0.5)" } // purple-50/50
473
+ case "CONSOLE ERROR":
474
+ case "ERROR":
475
+ case "CRITICAL ERROR":
476
+ return { backgroundColor: "rgba(254, 242, 242, 0.5)" } // red-50/50
477
+ case "CONSOLE WARN":
478
+ case "CDP":
479
+ case "NETWORK":
480
+ return { backgroundColor: "rgba(254, 252, 232, 0.5)" } // yellow-50/50
481
+ case "CONSOLE INFO":
482
+ case "SCREENSHOT":
483
+ return { backgroundColor: "rgba(239, 246, 255, 0.5)" } // blue-50/50
484
+ case "PAGE":
485
+ return { backgroundColor: "rgba(240, 253, 244, 0.5)" } // green-50/50
486
+ default:
487
+ return { backgroundColor: "transparent" }
488
+ }
489
+ }
490
+ }
491
+
492
+ // Determine the border class based on log type
493
+ const getBorderClass = () => {
494
+ const type = logTypeInfo.type
495
+ switch (type) {
496
+ case "INTERACTION":
497
+ case "CONSOLE DEBUG":
498
+ case "DOM":
499
+ return "border-purple-200 dark:border-purple-800"
500
+ case "CONSOLE ERROR":
501
+ case "ERROR":
502
+ case "CRITICAL ERROR":
503
+ return "border-red-200 dark:border-red-800"
504
+ case "CONSOLE WARN":
505
+ case "CDP":
506
+ case "NETWORK":
507
+ return "border-yellow-200 dark:border-yellow-800"
508
+ case "CONSOLE INFO":
509
+ case "SCREENSHOT":
510
+ return "border-blue-200 dark:border-blue-800"
511
+ case "PAGE":
512
+ return "border-green-200 dark:border-green-800"
513
+ default:
514
+ return "border-gray-200 dark:border-gray-700"
515
+ }
516
+ }
517
+
496
518
  return (
497
- <div className={`border-l-4 ${logTypeInfo.color} pl-4 py-2`}>
519
+ <div className={`border-l-4 ${getBorderClass()} pl-4 py-2`} style={getBackgroundStyle()}>
498
520
  {/* Table-like layout using CSS Grid */}
499
521
  <div className="grid grid-cols-[auto_auto_1fr] gap-3 items-start">
500
522
  {/* Column 1: Timestamp */}
501
- <div className="text-xs text-gray-500 dark:text-gray-400 font-mono whitespace-nowrap pt-1">
523
+ <div className="text-xs text-muted-foreground font-mono whitespace-nowrap pt-1">
502
524
  {new Date(entry.timestamp).toLocaleTimeString()}
503
525
  </div>
504
526
 
@@ -514,13 +536,13 @@ function LogEntryComponent({ entry }: { entry: LogEntry }) {
514
536
  </div>
515
537
 
516
538
  {/* Column 3: Message content with user agent info */}
517
- <div className="font-mono text-sm min-w-0 text-gray-900 dark:text-gray-100">
539
+ <div className="font-mono text-sm min-w-0 text-foreground">
518
540
  <div className="flex flex-wrap items-start gap-2">
519
541
  <div className="flex-1 min-w-0">{renderMessage(entry.message)}</div>
520
542
  {/* User Agent and Tab Identifier Pills */}
521
543
  <div className="flex items-center gap-1 flex-shrink-0">
522
544
  {entry.tabIdentifier && (
523
- <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300">
545
+ <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-muted text-muted-foreground">
524
546
  {entry.tabIdentifier}
525
547
  </span>
526
548
  )}
@@ -803,6 +825,10 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
803
825
  const [showReplayPreview, setShowReplayPreview] = useState(false)
804
826
  const [replayEvents, setReplayEvents] = useState<ReplayEvent[]>([])
805
827
  const [isRotatingLog, setIsRotatingLog] = useState(false)
828
+ const [retryCount, setRetryCount] = useState(0)
829
+ const [maxRetries] = useState(5)
830
+ const [lastFailedUrl, setLastFailedUrl] = useState<string | null>(null)
831
+ const [hasLoadedInitial, setHasLoadedInitial] = useState(false)
806
832
  const [filters, setFilters] = useState({
807
833
  browser: true,
808
834
  server: true,
@@ -826,9 +852,12 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
826
852
  setAvailableLogs(data.files)
827
853
  setCurrentLogFile(data.currentFile)
828
854
  setProjectName(data.projectName)
855
+ return true
829
856
  }
857
+ return false
830
858
  } catch (error) {
831
859
  console.error("Error loading available logs:", error)
860
+ return false
832
861
  }
833
862
  }, [])
834
863
 
@@ -861,11 +890,28 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
861
890
  ? `/api/logs/tail?lines=50&logPath=${encodeURIComponent(logPath)}`
862
891
  : `/api/logs/tail?lines=50`
863
892
 
893
+ // Check if we've exceeded retry limit for this specific URL
894
+ if (lastFailedUrl === apiUrl && retryCount >= maxRetries) {
895
+ console.error(`Maximum retry attempts (${maxRetries}) reached for ${apiUrl}. Stopping polling.`)
896
+ // Clear the polling interval to stop further attempts
897
+ if (pollIntervalRef.current) {
898
+ clearInterval(pollIntervalRef.current)
899
+ pollIntervalRef.current = null
900
+ }
901
+ return
902
+ }
903
+
864
904
  const response = await fetch(apiUrl)
865
905
  if (!response.ok) {
866
906
  throw new Error(`HTTP ${response.status}: ${response.statusText}`)
867
907
  }
868
908
 
909
+ // Reset retry count on successful fetch
910
+ if (lastFailedUrl === apiUrl || retryCount > 0) {
911
+ setRetryCount(0)
912
+ setLastFailedUrl(null)
913
+ }
914
+
869
915
  const data: LogsApiResponse = await response.json()
870
916
 
871
917
  if (!data.logs) {
@@ -906,13 +952,55 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
906
952
  }
907
953
  } catch (error) {
908
954
  console.error("Error polling logs:", error)
909
- // Don't spam console on network errors during polling
955
+
956
+ // Build the failed URL for tracking
957
+ const requestedFile = searchParams.get("file")
958
+ let logPath = ""
959
+ if (requestedFile && availableLogs.length > 0) {
960
+ const foundFile = availableLogs.find((f) => f.name === requestedFile)
961
+ logPath = foundFile?.path || currentLogFile
962
+ } else {
963
+ logPath = currentLogFile
964
+ }
965
+ const failedUrl = logPath
966
+ ? `/api/logs/tail?lines=50&logPath=${encodeURIComponent(logPath)}`
967
+ : `/api/logs/tail?lines=50`
968
+
969
+ // Increment retry count if it's the same URL, otherwise reset
970
+ if (lastFailedUrl === failedUrl) {
971
+ const newRetryCount = retryCount + 1
972
+ setRetryCount(newRetryCount)
973
+
974
+ if (newRetryCount >= maxRetries) {
975
+ console.error(`Maximum retry attempts (${maxRetries}) reached for ${failedUrl}. Stopping polling.`)
976
+ // Clear the polling interval to stop further attempts
977
+ if (pollIntervalRef.current) {
978
+ clearInterval(pollIntervalRef.current)
979
+ pollIntervalRef.current = null
980
+ }
981
+ }
982
+ } else {
983
+ setLastFailedUrl(failedUrl)
984
+ setRetryCount(1)
985
+ }
910
986
  }
911
- }, [mode, isAtBottom, searchParams, availableLogs, currentLogFile, lastLogCount, logBuffer, logs])
987
+ }, [
988
+ mode,
989
+ isAtBottom,
990
+ searchParams,
991
+ availableLogs,
992
+ currentLogFile,
993
+ lastLogCount,
994
+ logBuffer,
995
+ logs,
996
+ retryCount,
997
+ lastFailedUrl,
998
+ maxRetries
999
+ ])
912
1000
 
913
1001
  // Start/stop polling based on mode (always poll in tail mode, but buffer when not at bottom)
914
1002
  useEffect(() => {
915
- if (mode === "tail") {
1003
+ if (mode === "tail" && retryCount < maxRetries) {
916
1004
  pollIntervalRef.current = setInterval(pollForNewLogs, 3000) // Poll every 3 seconds
917
1005
  return () => {
918
1006
  if (pollIntervalRef.current) {
@@ -924,16 +1012,25 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
924
1012
  clearInterval(pollIntervalRef.current)
925
1013
  }
926
1014
  }
927
- }, [mode, pollForNewLogs]) // Removed isAtBottom - now always poll in tail mode
1015
+ }, [mode, pollForNewLogs, retryCount, maxRetries]) // Removed isAtBottom - now always poll in tail mode
928
1016
 
929
1017
  // Handle returning to live mode - ONLY flush buffer when user explicitly clicks "Live" button
930
1018
  // This effect is removed to prevent race conditions - buffer flushing now happens only on explicit user action
931
1019
 
932
1020
  const loadInitialLogs = useCallback(async () => {
1021
+ if (retryCount >= maxRetries) {
1022
+ console.log("Max retries reached, not attempting to load")
1023
+ return
1024
+ }
1025
+
933
1026
  setIsInitialLoading(true)
934
1027
 
935
1028
  // Load available logs list first
936
- await loadAvailableLogs()
1029
+ const logsListLoaded = await loadAvailableLogs()
1030
+ if (!logsListLoaded) {
1031
+ setIsInitialLoading(false)
1032
+ return
1033
+ }
937
1034
 
938
1035
  try {
939
1036
  // Determine which log file to load
@@ -954,11 +1051,25 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
954
1051
  ? `/api/logs/${mode}?lines=1000&logPath=${encodeURIComponent(logPath)}`
955
1052
  : `/api/logs/${mode}?lines=1000`
956
1053
 
1054
+ // Check if we've exceeded retry limit for this specific URL
1055
+ if (lastFailedUrl === apiUrl && retryCount >= maxRetries) {
1056
+ console.error(`Maximum retry attempts (${maxRetries}) reached for ${apiUrl}. Not attempting to load.`)
1057
+ setLogs([])
1058
+ setIsInitialLoading(false)
1059
+ return
1060
+ }
1061
+
957
1062
  const response = await fetch(apiUrl)
958
1063
  if (!response.ok) {
959
1064
  throw new Error(`HTTP ${response.status}: ${response.statusText}`)
960
1065
  }
961
1066
 
1067
+ // Reset retry count on successful fetch
1068
+ if (lastFailedUrl === apiUrl || retryCount > 0) {
1069
+ setRetryCount(0)
1070
+ setLastFailedUrl(null)
1071
+ }
1072
+
962
1073
  const data: LogsApiResponse = await response.json()
963
1074
 
964
1075
  if (!data.logs) {
@@ -991,17 +1102,46 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
991
1102
  }
992
1103
  } catch (error) {
993
1104
  console.error("Error loading logs:", error)
1105
+
1106
+ // Build the failed URL for tracking
1107
+ const requestedFile = searchParams.get("file")
1108
+ let logPath = ""
1109
+ if (requestedFile && availableLogs.length > 0) {
1110
+ const foundFile = availableLogs.find((f) => f.name === requestedFile)
1111
+ logPath = foundFile?.path || currentLogFile
1112
+ } else {
1113
+ logPath = currentLogFile
1114
+ }
1115
+ const failedUrl = logPath
1116
+ ? `/api/logs/${mode}?lines=1000&logPath=${encodeURIComponent(logPath)}`
1117
+ : `/api/logs/${mode}?lines=1000`
1118
+
1119
+ // Track retry attempts
1120
+ if (lastFailedUrl === failedUrl) {
1121
+ const newRetryCount = retryCount + 1
1122
+ setRetryCount(newRetryCount)
1123
+
1124
+ if (newRetryCount >= maxRetries) {
1125
+ console.error(`Maximum retry attempts (${maxRetries}) reached for initial load of ${failedUrl}.`)
1126
+ }
1127
+ } else {
1128
+ setLastFailedUrl(failedUrl)
1129
+ setRetryCount(1)
1130
+ }
1131
+
994
1132
  setLogs([])
1133
+ } finally {
1134
+ setIsInitialLoading(false)
995
1135
  }
996
- }, [loadAvailableLogs, searchParams, availableLogs, currentLogFile, mode])
1136
+ }, [loadAvailableLogs, searchParams, availableLogs, currentLogFile, mode, retryCount, lastFailedUrl, maxRetries])
997
1137
 
998
1138
  useEffect(() => {
999
- // Only load logs if we don't have initial data or mode actually changed
1000
- const _currentMode = searchParams.get("mode") || "tail"
1139
+ // Only load logs if we don't have initial data and haven't tried loading yet
1001
1140
  const hasInitialData = initialData?.logs && initialData?.logs.length > 0
1002
1141
 
1003
- if (!hasInitialData && !logs.length) {
1142
+ if (!hasLoadedInitial && !hasInitialData && !logs.length) {
1004
1143
  // No server-side data and no client data - load fresh
1144
+ setHasLoadedInitial(true)
1005
1145
  loadInitialLogs()
1006
1146
  } else if (hasInitialData && logs.length === 0) {
1007
1147
  // We have server-side data but client state is empty - use server data
@@ -1022,22 +1162,10 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1022
1162
  setTimeout(scrollToBottom, 100)
1023
1163
  setTimeout(scrollToBottom, 300)
1024
1164
  }
1025
- } else if (mode === "tail" && isAtBottom) {
1026
- // Set up polling timer for new logs if we're in tail mode
1027
- setIsInitialLoading(false)
1028
- pollIntervalRef.current = setInterval(() => {
1029
- pollForNewLogs()
1030
- }, 3000)
1031
1165
  }
1032
- }, [
1033
- mode,
1034
- initialData?.logs,
1035
- isAtBottom, // No server-side data and no client data - load fresh
1036
- loadInitialLogs,
1037
- logs.length,
1038
- pollForNewLogs,
1039
- searchParams.get
1040
- ]) // Only depend on mode to avoid infinite loops
1166
+ }, [mode, initialData, logs.length, hasLoadedInitial, loadInitialLogs]) // eslint-disable-line react-hooks/exhaustive-deps
1167
+ // CRITICAL: loadInitialLogs must NEVER be added to dependencies - it will cause infinite loops
1168
+ // The linter will complain but this is intentional. DO NOT "FIX" THIS.
1041
1169
 
1042
1170
  // Separate effect to handle scrolling after logs are rendered
1043
1171
  useEffect(() => {
@@ -1291,17 +1419,15 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1291
1419
  }, [logs, filters, userAgentFilters, availableUserAgents])
1292
1420
 
1293
1421
  return (
1294
- <div className="h-screen bg-gray-50 dark:bg-gray-900 flex flex-col transition-colors">
1422
+ <div className="h-screen bg-background text-foreground flex flex-col transition-colors">
1295
1423
  {/* Header - Fixed */}
1296
- <div className="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700 flex-none z-10">
1424
+ <div className="bg-card shadow-sm border-b border-border flex-none z-10">
1297
1425
  <div className="max-w-7xl mx-auto px-4 py-3">
1298
1426
  <div className="flex items-center justify-between">
1299
1427
  <div className="flex items-center gap-2 sm:gap-4">
1300
1428
  <div className="flex items-center gap-1">
1301
- <h1 className="text-xl sm:text-2xl font-bold text-gray-900 dark:text-white whitespace-nowrap">
1302
- dev3000
1303
- </h1>
1304
- <span className="text-xs text-gray-400 dark:text-gray-500 whitespace-nowrap">(v{version})</span>
1429
+ <h1 className="text-xl sm:text-2xl font-bold whitespace-nowrap">dev3000</h1>
1430
+ <span className="text-xs text-muted-foreground whitespace-nowrap">(v{version})</span>
1305
1431
  </div>
1306
1432
  {/* Log File Selector */}
1307
1433
  {availableLogs.length > 1 ? (
@@ -1309,19 +1435,13 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1309
1435
  <button
1310
1436
  type="button"
1311
1437
  onClick={() => setShowLogSelector(!showLogSelector)}
1312
- className="flex items-center gap-2 px-3 py-1 text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-700 rounded-md transition-colors"
1438
+ className="flex items-center gap-2 px-3 py-1 text-sm text-muted-foreground hover:text-foreground hover:bg-accent rounded-md transition-colors"
1313
1439
  >
1314
1440
  <span className="font-mono text-xs whitespace-nowrap">
1315
1441
  {isInitialLoading && !currentLogFile ? (
1316
- <div
1317
- className="h-4 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"
1318
- style={{ width: "220px" }}
1319
- />
1442
+ <div className="h-4 bg-muted rounded animate-pulse" style={{ width: "220px" }} />
1320
1443
  ) : currentLogFile ? (
1321
- // Show basename for all files
1322
- currentLogFile
1323
- .split("/")
1324
- .pop()
1444
+ "Current"
1325
1445
  ) : (
1326
1446
  "No log file"
1327
1447
  )}
@@ -1337,9 +1457,9 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1337
1457
  </button>
1338
1458
  {/* Dropdown */}
1339
1459
  {showLogSelector && availableLogs.length > 1 && (
1340
- <div className="absolute top-full left-0 mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-md shadow-lg z-20 min-w-80">
1460
+ <div className="absolute top-full left-0 mt-1 bg-card border border-border rounded-md shadow-lg z-20 min-w-80">
1341
1461
  <div className="py-1 max-h-60 overflow-y-auto">
1342
- <div className="px-3 py-2 text-xs font-medium text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-600">
1462
+ <div className="px-3 py-2 text-xs font-medium text-muted-foreground border-b border-border">
1343
1463
  {projectName} logs ({availableLogs.length})
1344
1464
  </div>
1345
1465
  {availableLogs.map((logFile) => (
@@ -1350,13 +1470,13 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1350
1470
  setShowLogSelector(false)
1351
1471
  router.push(`/logs?file=${encodeURIComponent(logFile.name)}&mode=${mode}`)
1352
1472
  }}
1353
- className={`w-full text-left px-3 py-2 text-sm hover:bg-gray-50 flex items-center justify-between ${
1354
- logFile.isCurrent ? "bg-blue-50 text-blue-900" : "text-gray-700"
1473
+ className={`w-full text-left px-3 py-2 text-sm hover:bg-accent flex items-center justify-between ${
1474
+ logFile.isCurrent ? "bg-primary/10 text-primary" : "text-foreground"
1355
1475
  }`}
1356
1476
  >
1357
1477
  <div className="flex flex-col">
1358
1478
  <span className="font-mono text-xs">{logFile.name}</span>
1359
- <span className="text-xs text-gray-500">
1479
+ <span className="text-xs text-muted-foreground">
1360
1480
  {new Date(logFile.mtime).toLocaleString()} • {Math.round(logFile.size / 1024)}KB
1361
1481
  </span>
1362
1482
  </div>
@@ -1384,8 +1504,29 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1384
1504
  </div>
1385
1505
  )}
1386
1506
 
1387
- {/* Entries count */}
1388
- {logs.length > 0 && <span className="text-sm text-gray-500 hidden sm:inline">{logs.length} entries</span>}
1507
+ {/* Entries count with clear button */}
1508
+ {logs.length > 0 && (
1509
+ <div className="flex items-center gap-1 hidden sm:flex">
1510
+ <span className="text-sm text-muted-foreground">{logs.length} entries</span>
1511
+ {currentLogFile && !isInitialLoading && (
1512
+ <button
1513
+ type="button"
1514
+ onClick={handleRotateLog}
1515
+ disabled={isRotatingLog}
1516
+ className="p-0.5 text-muted-foreground hover:text-foreground hover:bg-accent rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
1517
+ title="Clear logs (rotate current log to archive and start fresh)"
1518
+ >
1519
+ {isRotatingLog ? (
1520
+ <div className="w-3 h-3 border border-current border-t-transparent rounded-full animate-spin" />
1521
+ ) : (
1522
+ <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1523
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
1524
+ </svg>
1525
+ )}
1526
+ </button>
1527
+ )}
1528
+ </div>
1529
+ )}
1389
1530
 
1390
1531
  {/* Buffered logs indicator */}
1391
1532
  {logBuffer.length > 0 && !isAtBottom && (
@@ -1393,19 +1534,6 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1393
1534
  +{logBuffer.length} buffered
1394
1535
  </span>
1395
1536
  )}
1396
-
1397
- {/* Clear button - always visible when we have a current log file */}
1398
- {currentLogFile && !isInitialLoading && (
1399
- <button
1400
- type="button"
1401
- onClick={handleRotateLog}
1402
- disabled={isRotatingLog}
1403
- className="px-2 py-1 text-xs bg-orange-100 text-orange-700 hover:bg-orange-200 rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
1404
- title="Clear logs (rotate current log to archive and start fresh)"
1405
- >
1406
- {isRotatingLog ? "..." : "Clear"}
1407
- </button>
1408
- )}
1409
1537
  </div>
1410
1538
 
1411
1539
  {/* Mode Toggle */}
@@ -1472,7 +1600,7 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1472
1600
  ? "bg-blue-100 text-blue-800"
1473
1601
  : event.type === "SCROLL"
1474
1602
  ? "bg-green-100 text-green-800"
1475
- : "bg-gray-100 text-gray-700"
1603
+ : "bg-muted text-muted-foreground"
1476
1604
  }`}
1477
1605
  >
1478
1606
  {event.type}
@@ -1501,7 +1629,7 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1501
1629
  <button
1502
1630
  type="button"
1503
1631
  onClick={() => setShowFilters(!showFilters)}
1504
- className="flex items-center gap-1 px-3 py-1 rounded text-sm font-medium transition-colors whitespace-nowrap bg-gray-100 text-gray-700 hover:bg-gray-200"
1632
+ className="flex items-center gap-1 px-3 py-1 rounded text-sm font-medium transition-colors whitespace-nowrap bg-muted text-muted-foreground hover:bg-muted/80"
1505
1633
  >
1506
1634
  <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1507
1635
  <path
@@ -1515,14 +1643,14 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1515
1643
  </button>
1516
1644
  {/* Filter Dropdown */}
1517
1645
  {showFilters && (
1518
- <div className="absolute top-full right-0 mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md shadow-lg z-20 min-w-48">
1646
+ <div className="absolute top-full right-0 mt-1 bg-card border border-border rounded-md shadow-lg z-20 min-w-48">
1519
1647
  <div className="py-2">
1520
- <div className="px-3 py-2 text-xs font-medium text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-700">
1648
+ <div className="px-3 py-2 text-xs font-medium text-muted-foreground border-b border-border">
1521
1649
  Log Types
1522
1650
  </div>
1523
1651
 
1524
1652
  {/* Server Logs */}
1525
- <label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer">
1653
+ <label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer">
1526
1654
  <div className="flex items-center gap-2">
1527
1655
  <input
1528
1656
  type="checkbox"
@@ -1533,17 +1661,17 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1533
1661
  server: e.target.checked
1534
1662
  }))
1535
1663
  }
1536
- className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
1664
+ className="rounded border-input text-primary focus:ring-ring"
1537
1665
  />
1538
- <span className="text-gray-900 dark:text-gray-100">Server</span>
1666
+ <span className="text-foreground">Server</span>
1539
1667
  </div>
1540
- <span className="text-xs text-gray-400">
1668
+ <span className="text-xs text-muted-foreground">
1541
1669
  {logs.filter((l) => l.source === "SERVER").length}
1542
1670
  </span>
1543
1671
  </label>
1544
1672
 
1545
1673
  {/* Browser Logs */}
1546
- <label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer">
1674
+ <label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer">
1547
1675
  <div className="flex items-center gap-2">
1548
1676
  <input
1549
1677
  type="checkbox"
@@ -1554,18 +1682,18 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1554
1682
  browser: e.target.checked
1555
1683
  }))
1556
1684
  }
1557
- className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
1685
+ className="rounded border-input text-primary focus:ring-ring"
1558
1686
  />
1559
- <span className="text-gray-900 dark:text-gray-100">Browser</span>
1687
+ <span className="text-foreground">Browser</span>
1560
1688
  </div>
1561
- <span className="text-xs text-gray-400">
1689
+ <span className="text-xs text-muted-foreground">
1562
1690
  {logs.filter((l) => l.source === "BROWSER").length}
1563
1691
  </span>
1564
1692
  </label>
1565
1693
 
1566
1694
  {/* User Agent Sub-filters */}
1567
1695
  {availableUserAgents.length > 1 && filters.browser && (
1568
- <div className="ml-6 border-l border-gray-200 dark:border-gray-600 pl-2">
1696
+ <div className="ml-6 border-l border-border pl-2">
1569
1697
  {availableUserAgents.map((ua) => {
1570
1698
  const shortUA = ua.includes("Chrome")
1571
1699
  ? "Chrome"
@@ -1579,7 +1707,7 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1579
1707
  return (
1580
1708
  <label
1581
1709
  key={ua}
1582
- className="flex items-center justify-between px-2 py-1 text-xs hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer"
1710
+ className="flex items-center justify-between px-2 py-1 text-xs hover:bg-accent cursor-pointer"
1583
1711
  >
1584
1712
  <div className="flex items-center gap-2">
1585
1713
  <input
@@ -1591,11 +1719,11 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1591
1719
  [ua]: e.target.checked
1592
1720
  }))
1593
1721
  }
1594
- className="rounded border-gray-300 text-blue-600 focus:ring-blue-500 w-3 h-3"
1722
+ className="rounded border-input text-primary focus:ring-ring w-3 h-3"
1595
1723
  />
1596
- <span className="text-gray-700 dark:text-gray-300">{shortUA}</span>
1724
+ <span className="text-foreground">{shortUA}</span>
1597
1725
  </div>
1598
- <span className="text-xs text-gray-400">
1726
+ <span className="text-xs text-muted-foreground">
1599
1727
  {logs.filter((l) => l.source === "BROWSER" && l.userAgent === ua).length}
1600
1728
  </span>
1601
1729
  </label>
@@ -1605,7 +1733,7 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1605
1733
  )}
1606
1734
 
1607
1735
  {/* Interaction Logs */}
1608
- <label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer">
1736
+ <label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer">
1609
1737
  <div className="flex items-center gap-2">
1610
1738
  <input
1611
1739
  type="checkbox"
@@ -1616,17 +1744,17 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1616
1744
  interaction: e.target.checked
1617
1745
  }))
1618
1746
  }
1619
- className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
1747
+ className="rounded border-input text-primary focus:ring-ring"
1620
1748
  />
1621
- <span className="text-gray-900 dark:text-gray-100">Interaction</span>
1749
+ <span className="text-foreground">Interaction</span>
1622
1750
  </div>
1623
- <span className="text-xs text-gray-400">
1751
+ <span className="text-xs text-muted-foreground">
1624
1752
  {logs.filter((l) => l.message.includes("[INTERACTION]")).length}
1625
1753
  </span>
1626
1754
  </label>
1627
1755
 
1628
1756
  {/* Screenshot Logs */}
1629
- <label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer">
1757
+ <label className="flex items-center justify-between px-3 py-2 text-sm hover:bg-accent cursor-pointer">
1630
1758
  <div className="flex items-center gap-2">
1631
1759
  <input
1632
1760
  type="checkbox"
@@ -1637,11 +1765,11 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1637
1765
  screenshot: e.target.checked
1638
1766
  }))
1639
1767
  }
1640
- className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
1768
+ className="rounded border-input text-primary focus:ring-ring"
1641
1769
  />
1642
- <span className="text-gray-900 dark:text-gray-100">Screenshot</span>
1770
+ <span className="text-foreground">Screenshot</span>
1643
1771
  </div>
1644
- <span className="text-xs text-gray-400">
1772
+ <span className="text-xs text-muted-foreground">
1645
1773
  {logs.filter((l) => l.message.includes("[SCREENSHOT]")).length}
1646
1774
  </span>
1647
1775
  </label>
@@ -1649,7 +1777,8 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1649
1777
  </div>
1650
1778
  )}
1651
1779
  </div>
1652
- <div className="flex items-center bg-gray-100 rounded-md p-1">
1780
+ {/* Head/Tail toggle - commented out for now as it's too noisy */}
1781
+ {/* <div className="flex items-center bg-muted rounded-md p-1">
1653
1782
  <button
1654
1783
  type="button"
1655
1784
  onClick={() => {
@@ -1661,7 +1790,9 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1661
1790
  }
1662
1791
  }}
1663
1792
  className={`px-2 sm:px-3 py-1 rounded text-xs sm:text-sm font-medium transition-colors whitespace-nowrap ${
1664
- mode === "head" ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"
1793
+ mode === "head"
1794
+ ? "bg-background text-foreground shadow-sm"
1795
+ : "text-muted-foreground hover:text-foreground"
1665
1796
  }`}
1666
1797
  >
1667
1798
  Head
@@ -1677,41 +1808,16 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1677
1808
  }
1678
1809
  }}
1679
1810
  className={`px-2 sm:px-3 py-1 rounded text-xs sm:text-sm font-medium transition-colors whitespace-nowrap ${
1680
- mode === "tail" ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"
1811
+ mode === "tail"
1812
+ ? "bg-background text-foreground shadow-sm"
1813
+ : "text-muted-foreground hover:text-foreground"
1681
1814
  }`}
1682
1815
  >
1683
1816
  Tail
1684
1817
  </button>
1685
- </div>
1818
+ </div> */}
1686
1819
  {/* Dark Mode Toggle - moved to last item */}
1687
- <button
1688
- type="button"
1689
- onClick={() => setDarkMode(!darkMode)}
1690
- className="p-2 rounded-md text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ml-2"
1691
- title={darkMode ? "Switch to light mode" : "Switch to dark mode"}
1692
- >
1693
- {darkMode ? (
1694
- // Sun icon for light mode
1695
- <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1696
- <path
1697
- strokeLinecap="round"
1698
- strokeLinejoin="round"
1699
- strokeWidth={2}
1700
- d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
1701
- />
1702
- </svg>
1703
- ) : (
1704
- // Moon icon for dark mode
1705
- <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1706
- <path
1707
- strokeLinecap="round"
1708
- strokeLinejoin="round"
1709
- strokeWidth={2}
1710
- d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
1711
- />
1712
- </svg>
1713
- )}
1714
- </button>
1820
+ <DarkModeToggle darkMode={darkMode} setDarkMode={setDarkMode} className="ml-2" />
1715
1821
  </div>
1716
1822
  </div>
1717
1823
  </div>
@@ -1722,27 +1828,45 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1722
1828
  <div ref={containerRef} className="max-w-7xl mx-auto px-4 py-6 h-full overflow-y-auto" onScroll={handleScroll}>
1723
1829
  {isInitialLoading ? (
1724
1830
  <div className="text-center py-12">
1725
- <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
1726
- <div className="text-gray-500 text-sm mt-4">Loading logs...</div>
1831
+ <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
1832
+ <div className="text-muted-foreground text-sm mt-4">Loading logs...</div>
1727
1833
  </div>
1728
1834
  ) : logs.length === 0 ? (
1729
1835
  <div className="text-center py-12">
1730
- <div className="text-gray-400 dark:text-gray-500 text-lg">📝 No logs yet</div>
1731
- <div className="text-gray-500 dark:text-gray-400 text-sm mt-2">
1732
- Logs will appear here as your development server runs
1836
+ <div className="text-muted-foreground text-lg">📝 No logs yet</div>
1837
+ <div className="text-muted-foreground text-sm mt-2">
1838
+ {retryCount >= maxRetries ? (
1839
+ <>
1840
+ <p className="text-red-500 mb-2">Failed to load logs after {maxRetries} attempts.</p>
1841
+ <button
1842
+ type="button"
1843
+ onClick={() => {
1844
+ setRetryCount(0)
1845
+ setLastFailedUrl(null)
1846
+ setHasLoadedInitial(false)
1847
+ loadInitialLogs()
1848
+ }}
1849
+ className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
1850
+ >
1851
+ Retry
1852
+ </button>
1853
+ </>
1854
+ ) : (
1855
+ "Logs will appear here as your development server runs"
1856
+ )}
1733
1857
  </div>
1734
1858
  </div>
1735
1859
  ) : filteredLogs.length === 0 ? (
1736
1860
  <div className="text-center py-12">
1737
- <div className="text-gray-400 dark:text-gray-500 text-lg">🔍 No logs match current filters</div>
1738
- <div className="text-gray-500 dark:text-gray-400 text-sm mt-2">
1861
+ <div className="text-muted-foreground text-lg">🔍 No logs match current filters</div>
1862
+ <div className="text-muted-foreground text-sm mt-2">
1739
1863
  Try adjusting your filter settings to see more logs
1740
1864
  </div>
1741
1865
  </div>
1742
1866
  ) : (
1743
1867
  <div className="space-y-1 pb-4">
1744
1868
  {filteredLogs.map((entry, index) => (
1745
- <LogEntryComponent key={`${entry.timestamp}-${index}`} entry={entry} />
1869
+ <LogEntryComponent key={`${entry.timestamp}-${index}`} entry={entry} darkMode={darkMode} />
1746
1870
  ))}
1747
1871
  <div ref={bottomRef} />
1748
1872
  </div>
@@ -1751,23 +1875,16 @@ export default function LogsClient({ version, initialData }: LogsClientProps) {
1751
1875
  </div>
1752
1876
 
1753
1877
  {/* Footer - Fixed */}
1754
- <div className="border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 flex-none">
1878
+ <div className="border-t border-border bg-muted/50 flex-none">
1755
1879
  <div className="max-w-7xl mx-auto px-4 py-3 flex items-center justify-between">
1756
1880
  <div className="flex items-center gap-3">
1757
1881
  {lastFetched && (
1758
- <span className="text-xs text-gray-400 dark:text-gray-500 font-mono">
1882
+ <span className="text-xs text-muted-foreground font-mono">
1759
1883
  Last updated {lastFetched.toLocaleTimeString()}
1760
1884
  </span>
1761
1885
  )}
1762
1886
  {currentLogFile && (
1763
- <a
1764
- href={`file://${currentLogFile}`}
1765
- target="_blank"
1766
- rel="noopener noreferrer"
1767
- className="text-xs text-blue-600 hover:text-blue-800 flex items-center gap-1"
1768
- >
1769
- Raw Log ↗
1770
- </a>
1887
+ <span className="text-xs text-muted-foreground font-mono break-all">{currentLogFile}</span>
1771
1888
  )}
1772
1889
  </div>
1773
1890