railwatch 0.6.0 → 0.7.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 (364) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +42 -26
  3. data/CHANGELOG.md +41 -0
  4. data/README.md +40 -29
  5. data/app/frontend/components/app-content.tsx +26 -0
  6. data/app/frontend/components/app-logo-icon.tsx +23 -0
  7. data/app/frontend/components/app-logo.tsx +14 -0
  8. data/app/frontend/components/app-shell.tsx +38 -0
  9. data/app/frontend/components/app-sidebar-header.tsx +54 -0
  10. data/app/frontend/components/app-sidebar.tsx +94 -0
  11. data/app/frontend/components/app-wordmark.tsx +29 -0
  12. data/app/frontend/components/breadcrumbs.tsx +46 -0
  13. data/app/frontend/components/icon.tsx +16 -0
  14. data/app/frontend/components/input-error.tsx +18 -0
  15. data/app/frontend/components/nav-footer.tsx +46 -0
  16. data/app/frontend/components/nav-main.tsx +41 -0
  17. data/app/frontend/components/nav-user.tsx +50 -0
  18. data/app/frontend/components/railwatch/chart-hover.tsx +34 -0
  19. data/app/frontend/components/railwatch/chart-panel.tsx +170 -0
  20. data/app/frontend/components/railwatch/code.tsx +28 -0
  21. data/app/frontend/components/railwatch/command-palette.tsx +132 -0
  22. data/app/frontend/components/railwatch/copy-block.tsx +52 -0
  23. data/app/frontend/components/railwatch/copy-id.tsx +49 -0
  24. data/app/frontend/components/railwatch/cursor-load-more.tsx +39 -0
  25. data/app/frontend/components/railwatch/data-table.tsx +195 -0
  26. data/app/frontend/components/railwatch/digit-roll.tsx +312 -0
  27. data/app/frontend/components/railwatch/empty-state.tsx +159 -0
  28. data/app/frontend/components/railwatch/env-nav.tsx +130 -0
  29. data/app/frontend/components/railwatch/env-switcher.tsx +145 -0
  30. data/app/frontend/components/railwatch/filter-bar.tsx +153 -0
  31. data/app/frontend/components/railwatch/flamegraph.tsx +324 -0
  32. data/app/frontend/components/railwatch/frames.tsx +295 -0
  33. data/app/frontend/components/railwatch/json-viewer.tsx +159 -0
  34. data/app/frontend/components/railwatch/live-dot.tsx +78 -0
  35. data/app/frontend/components/railwatch/live-toggle.tsx +76 -0
  36. data/app/frontend/components/railwatch/metric-chart.tsx +112 -0
  37. data/app/frontend/components/railwatch/mobile-page-switcher.tsx +89 -0
  38. data/app/frontend/components/railwatch/onboarding.tsx +91 -0
  39. data/app/frontend/components/railwatch/origin-identity.tsx +84 -0
  40. data/app/frontend/components/railwatch/page-header.tsx +42 -0
  41. data/app/frontend/components/railwatch/percentile-picker.tsx +18 -0
  42. data/app/frontend/components/railwatch/relative-time.tsx +18 -0
  43. data/app/frontend/components/railwatch/saved-views.tsx +232 -0
  44. data/app/frontend/components/railwatch/segmented.tsx +64 -0
  45. data/app/frontend/components/railwatch/series-chart.tsx +400 -0
  46. data/app/frontend/components/railwatch/shortcuts-dialog.tsx +89 -0
  47. data/app/frontend/components/railwatch/signal-toast.tsx +121 -0
  48. data/app/frontend/components/railwatch/sort-header.tsx +37 -0
  49. data/app/frontend/components/railwatch/source-link.tsx +57 -0
  50. data/app/frontend/components/railwatch/sparkline-cell.tsx +14 -0
  51. data/app/frontend/components/railwatch/sql-block.tsx +116 -0
  52. data/app/frontend/components/railwatch/stat.tsx +173 -0
  53. data/app/frontend/components/railwatch/status-badge.tsx +79 -0
  54. data/app/frontend/components/railwatch/step-picker.tsx +57 -0
  55. data/app/frontend/components/railwatch/timeline.tsx +405 -0
  56. data/app/frontend/components/railwatch/utilisation-bar.tsx +44 -0
  57. data/app/frontend/components/railwatch/window-picker.tsx +147 -0
  58. data/app/frontend/components/ui/avatar.tsx +51 -0
  59. data/app/frontend/components/ui/badge.tsx +46 -0
  60. data/app/frontend/components/ui/breadcrumb.tsx +109 -0
  61. data/app/frontend/components/ui/button.tsx +60 -0
  62. data/app/frontend/components/ui/card.tsx +92 -0
  63. data/app/frontend/components/ui/chart.tsx +374 -0
  64. data/app/frontend/components/ui/checkbox.tsx +32 -0
  65. data/app/frontend/components/ui/collapsible.tsx +31 -0
  66. data/app/frontend/components/ui/command.tsx +182 -0
  67. data/app/frontend/components/ui/dialog.tsx +158 -0
  68. data/app/frontend/components/ui/dropdown-menu.tsx +255 -0
  69. data/app/frontend/components/ui/hover-card.tsx +42 -0
  70. data/app/frontend/components/ui/input.tsx +21 -0
  71. data/app/frontend/components/ui/label.tsx +24 -0
  72. data/app/frontend/components/ui/popover.tsx +87 -0
  73. data/app/frontend/components/ui/select.tsx +185 -0
  74. data/app/frontend/components/ui/separator.tsx +28 -0
  75. data/app/frontend/components/ui/sheet.tsx +137 -0
  76. data/app/frontend/components/ui/sidebar.tsx +726 -0
  77. data/app/frontend/components/ui/skeleton.tsx +13 -0
  78. data/app/frontend/components/ui/sonner.tsx +41 -0
  79. data/app/frontend/components/ui/table.tsx +114 -0
  80. data/app/frontend/components/ui/tabs.tsx +91 -0
  81. data/app/frontend/components/ui/textarea.tsx +18 -0
  82. data/app/frontend/components/ui/tooltip.tsx +61 -0
  83. data/app/frontend/components/user-info.tsx +32 -0
  84. data/app/frontend/components/user-menu-content.tsx +94 -0
  85. data/app/frontend/entrypoints/application.css +244 -0
  86. data/app/frontend/entrypoints/inertia.tsx +95 -0
  87. data/app/frontend/fonts/CommitMono-Bold.woff2 +0 -0
  88. data/app/frontend/fonts/CommitMono-Regular.woff2 +0 -0
  89. data/app/frontend/fonts/Roboto-Black.woff2 +0 -0
  90. data/app/frontend/fonts/Roboto-Bold.woff2 +0 -0
  91. data/app/frontend/fonts/Roboto-Medium.woff2 +0 -0
  92. data/app/frontend/fonts/Roboto-Regular.woff2 +0 -0
  93. data/app/frontend/hooks/use-appearance.tsx +67 -0
  94. data/app/frontend/hooks/use-clipboard.ts +32 -0
  95. data/app/frontend/hooks/use-flash.tsx +23 -0
  96. data/app/frontend/hooks/use-hotkeys.ts +63 -0
  97. data/app/frontend/hooks/use-initials.tsx +15 -0
  98. data/app/frontend/hooks/use-live.ts +179 -0
  99. data/app/frontend/hooks/use-mobile-navigation.ts +8 -0
  100. data/app/frontend/hooks/use-mobile.ts +21 -0
  101. data/app/frontend/hooks/use-percentile.ts +24 -0
  102. data/app/frontend/hooks/use-reduced-motion.ts +25 -0
  103. data/app/frontend/hooks/use-row-nav.ts +106 -0
  104. data/app/frontend/hooks/use-step.ts +27 -0
  105. data/app/frontend/hooks/use-ticker.ts +12 -0
  106. data/app/frontend/hooks/use-window.ts +58 -0
  107. data/app/frontend/layouts/app/app-sidebar-layout.tsx +107 -0
  108. data/app/frontend/layouts/app-layout.tsx +21 -0
  109. data/app/frontend/layouts/env-layout.tsx +53 -0
  110. data/app/frontend/layouts/persistent-layout.tsx +18 -0
  111. data/app/frontend/lib/cable.ts +10 -0
  112. data/app/frontend/lib/env-pages.ts +236 -0
  113. data/app/frontend/lib/execution-path.ts +55 -0
  114. data/app/frontend/lib/explain.ts +50 -0
  115. data/app/frontend/lib/filter.ts +25 -0
  116. data/app/frontend/lib/flamegraph.ts +155 -0
  117. data/app/frontend/lib/format.ts +71 -0
  118. data/app/frontend/lib/keyboard.ts +18 -0
  119. data/app/frontend/lib/source-link.ts +217 -0
  120. data/app/frontend/lib/utils.ts +6 -0
  121. data/app/frontend/pages/alerts/index.tsx +171 -0
  122. data/app/frontend/pages/broadcasts/index.tsx +171 -0
  123. data/app/frontend/pages/cache_events/index.tsx +127 -0
  124. data/app/frontend/pages/commands/index.tsx +143 -0
  125. data/app/frontend/pages/deploys/index.tsx +94 -0
  126. data/app/frontend/pages/deploys/show.tsx +309 -0
  127. data/app/frontend/pages/deprecations/index.tsx +176 -0
  128. data/app/frontend/pages/exceptions/index.tsx +271 -0
  129. data/app/frontend/pages/executions/show.tsx +821 -0
  130. data/app/frontend/pages/issues/index.tsx +399 -0
  131. data/app/frontend/pages/issues/show.tsx +1367 -0
  132. data/app/frontend/pages/jobs/index.tsx +381 -0
  133. data/app/frontend/pages/jobs/klass.tsx +215 -0
  134. data/app/frontend/pages/llm_calls/index.tsx +664 -0
  135. data/app/frontend/pages/logs/index.tsx +303 -0
  136. data/app/frontend/pages/mails/index.tsx +218 -0
  137. data/app/frontend/pages/notifications/index.tsx +161 -0
  138. data/app/frontend/pages/outgoing_requests/index.tsx +309 -0
  139. data/app/frontend/pages/overview/show.tsx +333 -0
  140. data/app/frontend/pages/people/index.tsx +102 -0
  141. data/app/frontend/pages/people/show.tsx +149 -0
  142. data/app/frontend/pages/processes/index.tsx +335 -0
  143. data/app/frontend/pages/profiles/index.tsx +110 -0
  144. data/app/frontend/pages/profiles/show.tsx +105 -0
  145. data/app/frontend/pages/queries/index.tsx +492 -0
  146. data/app/frontend/pages/queries/show.tsx +276 -0
  147. data/app/frontend/pages/releases/index.tsx +190 -0
  148. data/app/frontend/pages/releases/release-health.tsx +68 -0
  149. data/app/frontend/pages/releases/release-path.ts +25 -0
  150. data/app/frontend/pages/releases/show.tsx +262 -0
  151. data/app/frontend/pages/requests/index.tsx +235 -0
  152. data/app/frontend/pages/requests/route.tsx +159 -0
  153. data/app/frontend/pages/scheduled_tasks/index.tsx +220 -0
  154. data/app/frontend/pages/spans/index.tsx +229 -0
  155. data/app/frontend/pages/spans/show.tsx +220 -0
  156. data/app/frontend/pages/storage_ops/index.tsx +150 -0
  157. data/app/frontend/pages/tenants/index.tsx +236 -0
  158. data/app/frontend/pages/tenants/show.tsx +477 -0
  159. data/app/frontend/pages/tenants/tenant-path.ts +26 -0
  160. data/app/frontend/pages/thresholds/index.tsx +414 -0
  161. data/app/frontend/pages/traces/show.tsx +208 -0
  162. data/app/frontend/pages/transactions/index.tsx +220 -0
  163. data/app/frontend/pages/view_renders/index.tsx +241 -0
  164. data/app/frontend/pages/visits/index.tsx +321 -0
  165. data/app/frontend/public/icon.png +0 -0
  166. data/app/frontend/public/icon.svg +5 -0
  167. data/app/frontend/routes/index.d.ts +2136 -0
  168. data/app/frontend/routes/index.js +2112 -0
  169. data/app/frontend/types/globals.d.ts +9 -0
  170. data/app/frontend/types/index.ts +288 -0
  171. data/app/frontend/types/vite-env.d.ts +9 -0
  172. data/app/jobs/railwatch/rollup_job.rb +3 -1
  173. data/app/models/railwatch/ingest/rollup_absorber.rb +1 -1
  174. data/docs/ai-and-mcp.md +9 -3
  175. data/docs/configuration.md +68 -27
  176. data/docs/embedded.md +76 -43
  177. data/docs/faq.md +22 -10
  178. data/docs/getting-started.md +116 -41
  179. data/docs/records.md +13 -10
  180. data/docs/replacing-nightwatch.md +16 -14
  181. data/docs/replacing-sentry.md +19 -10
  182. data/docs/security.md +24 -3
  183. data/docs/self-hosting.md +9 -1
  184. data/docs/testing.md +14 -4
  185. data/docs/troubleshooting.md +62 -23
  186. data/lib/generators/railwatch/install/install_generator.rb +5 -4
  187. data/lib/generators/railwatch/install/templates/initializer.rb.tt +2 -2
  188. data/lib/railwatch/version.rb +1 -1
  189. data/lib/railwatch.rb +8 -3
  190. data/llms.txt +21 -18
  191. data/public/railwatch/assets/app-layout-LvxN9xSS.js +41 -0
  192. data/public/railwatch/assets/application-Cc5v_Vk9.css +1 -0
  193. data/public/railwatch/assets/{arrow-up-EmePsTKP.js → arrow-up-DG01k9AL.js} +1 -1
  194. data/public/railwatch/assets/{braces-iZ7MEfYP.js → braces-C3w7pfxS.js} +1 -1
  195. data/public/railwatch/assets/{card-DqdEEKcJ.js → card-Dpwlr0uY.js} +1 -1
  196. data/public/railwatch/assets/{chart-C4k7Ub9W.js → chart-DJHv3R7W.js} +1 -1
  197. data/public/railwatch/assets/{chart-hover-2yZG3w2u.js → chart-hover-CEH-DtXO.js} +1 -1
  198. data/public/railwatch/assets/{chart-panel-CONkZcn9.js → chart-panel-CUdIzJvk.js} +1 -1
  199. data/public/railwatch/assets/code-BRZprwun.js +1 -0
  200. data/public/railwatch/assets/copy-id-CwhGGiwF.js +1 -0
  201. data/public/railwatch/assets/cursor-load-more-uF1_955Q.js +1 -0
  202. data/public/railwatch/assets/data-table-DYi-N0hm.js +1 -0
  203. data/public/railwatch/assets/env-layout-HdcOaPPX.js +1 -0
  204. data/public/railwatch/assets/{execution-path-DWovhgLi.js → execution-path-CElXXFO_.js} +1 -1
  205. data/public/railwatch/assets/filter-bar-DEv0G_34.js +1 -0
  206. data/public/railwatch/assets/{flamegraph-BNbNIuKT.js → flamegraph-SKD9yxW9.js} +2 -2
  207. data/public/railwatch/assets/frames-CUpyJQDy.js +1 -0
  208. data/public/railwatch/assets/index-2W-ppwap.js +1 -0
  209. data/public/railwatch/assets/index-74ocuU5d.js +1 -0
  210. data/public/railwatch/assets/{index-Zkt4W_CM.js → index-B8NA62Lz.js} +2 -2
  211. data/public/railwatch/assets/index-BBCEkLqp.js +1 -0
  212. data/public/railwatch/assets/index-BDcglJzX.js +1 -0
  213. data/public/railwatch/assets/index-B_KFb9LS.js +1 -0
  214. data/public/railwatch/assets/index-BqK_VKom.js +1 -0
  215. data/public/railwatch/assets/index-BxCXBHP2.js +1 -0
  216. data/public/railwatch/assets/index-C1jc1OoH.js +1 -0
  217. data/public/railwatch/assets/index-C7zREldp.js +1 -0
  218. data/public/railwatch/assets/index-CEetm4oC.js +1 -0
  219. data/public/railwatch/assets/index-CIaZkokF.js +1 -0
  220. data/public/railwatch/assets/index-CNH85TZp.js +1 -0
  221. data/public/railwatch/assets/index-Ce4SB5AB.js +1 -0
  222. data/public/railwatch/assets/index-CiRMSYMn.js +1 -0
  223. data/public/railwatch/assets/index-CjxXgOs-.js +1 -0
  224. data/public/railwatch/assets/index-ClL-PYEV.js +1 -0
  225. data/public/railwatch/assets/index-CteZ6fop.js +1 -0
  226. data/public/railwatch/assets/index-CvqfEExO.js +1 -0
  227. data/public/railwatch/assets/index-CxTLT-Qh.js +1 -0
  228. data/public/railwatch/assets/index-DJd3OUSd.js +1 -0
  229. data/public/railwatch/assets/index-DPD-rLp6.js +1 -0
  230. data/public/railwatch/assets/index-DYK_dps4.js +1 -0
  231. data/public/railwatch/assets/index-DrayARrI.js +1 -0
  232. data/public/railwatch/assets/index-HRXGir-0.js +1 -0
  233. data/public/railwatch/assets/index-UJOUALr_.js +1 -0
  234. data/public/railwatch/assets/index-d-3J7gQ5.js +1 -0
  235. data/public/railwatch/assets/index-oQrvBpOm.js +1 -0
  236. data/public/railwatch/assets/inertia-FoqYZAk8.js +97 -0
  237. data/public/railwatch/assets/json-viewer-CxqMojEm.js +1 -0
  238. data/public/railwatch/assets/klass-DEF-Vat5.js +1 -0
  239. data/public/railwatch/assets/origin-identity-Y6gvfQVR.js +1 -0
  240. data/public/railwatch/assets/{percentile-picker-BxIVblBw.js → percentile-picker-E2aUdLxx.js} +1 -1
  241. data/public/railwatch/assets/relative-time-DcJbkKlN.js +1 -0
  242. data/public/railwatch/assets/{release-health-Cb1tMOOO.js → release-health-C8WSCDKD.js} +1 -1
  243. data/public/railwatch/assets/route-qO5vX1p-.js +1 -0
  244. data/public/railwatch/assets/{segmented-C4oWE73o.js → segmented-BWee8uc9.js} +1 -1
  245. data/public/railwatch/assets/select-G7rNBzv0.js +1 -0
  246. data/public/railwatch/assets/series-chart-C5nXIUc5.js +1 -0
  247. data/public/railwatch/assets/show-BEWisLQw.js +1 -0
  248. data/public/railwatch/assets/show-BEa9PGu1.js +2 -0
  249. data/public/railwatch/assets/{show-gtwKsTsl.js → show-BiT_Aloc.js} +3 -3
  250. data/public/railwatch/assets/show-BpCO49d3.js +1 -0
  251. data/public/railwatch/assets/show-CH7yxt7R.js +1 -0
  252. data/public/railwatch/assets/show-CTO8Lvd2.js +1 -0
  253. data/public/railwatch/assets/show-DnYsDMUt.js +2 -0
  254. data/public/railwatch/assets/show-DyzHM527.js +1 -0
  255. data/public/railwatch/assets/show-Nfpxs8su.js +1 -0
  256. data/public/railwatch/assets/show-ddjUMo9G.js +1 -0
  257. data/public/railwatch/assets/show-ksjbSzDH.js +1 -0
  258. data/public/railwatch/assets/{sort-header-CPM6fhXv.js → sort-header-uOq5VlrB.js} +1 -1
  259. data/public/railwatch/assets/{sparkline-cell-CXCxXPWt.js → sparkline-cell-DgGij0cu.js} +1 -1
  260. data/public/railwatch/assets/stat-BTzNKTm2.js +1 -0
  261. data/public/railwatch/assets/status-badge-BDkOXa-C.js +1 -0
  262. data/public/railwatch/assets/{tenant-path-C4aM_cO0.js → tenant-path-DU0ezKSU.js} +1 -1
  263. data/public/railwatch/assets/{use-clipboard-C1ApSwzA.js → use-clipboard-DoENmlWY.js} +1 -1
  264. data/public/railwatch/assets/{use-live-DWwLg1Nj.js → use-live-CE_fN5_6.js} +1 -1
  265. data/public/railwatch/manifest.json +612 -1591
  266. metadata +242 -117
  267. data/public/railwatch/assets/app-layout-2xAKndVD.js +0 -1
  268. data/public/railwatch/assets/app-wordmark-Bt6XqURo.js +0 -1
  269. data/public/railwatch/assets/appearance-DyCfeh9o.js +0 -1
  270. data/public/railwatch/assets/application-B7h1MIhi.css +0 -1
  271. data/public/railwatch/assets/auth-layout-BuwogWMZ.js +0 -1
  272. data/public/railwatch/assets/badge-Dv7I-ebP.js +0 -1
  273. data/public/railwatch/assets/checkbox-DKbThbxr.js +0 -1
  274. data/public/railwatch/assets/code-CYahYQHZ.js +0 -1
  275. data/public/railwatch/assets/copy-DaQMJWoT.js +0 -1
  276. data/public/railwatch/assets/copy-block-CbJum9s8.js +0 -1
  277. data/public/railwatch/assets/copy-id-JmvLfkmx.js +0 -1
  278. data/public/railwatch/assets/cursor-load-more-C1ylbqqL.js +0 -1
  279. data/public/railwatch/assets/data-table-CVKR2wkq.js +0 -1
  280. data/public/railwatch/assets/edit-BXF5oNYA.js +0 -1
  281. data/public/railwatch/assets/edit-BwSEVrTG.js +0 -8
  282. data/public/railwatch/assets/edit-D5BRDwLT.js +0 -1
  283. data/public/railwatch/assets/empty-state-Bn48Gplm.js +0 -1
  284. data/public/railwatch/assets/env-layout-CKLL8Ds7.js +0 -1
  285. data/public/railwatch/assets/filter-bar-xcbJC93z.js +0 -1
  286. data/public/railwatch/assets/frames-Bk39xfJG.js +0 -1
  287. data/public/railwatch/assets/google-sign-in-button-B5as2ps1.js +0 -1
  288. data/public/railwatch/assets/index-Atp1FTgX.js +0 -1
  289. data/public/railwatch/assets/index-B-FXAo1w.js +0 -1
  290. data/public/railwatch/assets/index-B0xgzHuN.js +0 -1
  291. data/public/railwatch/assets/index-BDVShWMs.js +0 -1
  292. data/public/railwatch/assets/index-BRvRkfob.js +0 -1
  293. data/public/railwatch/assets/index-BVWapDeB.js +0 -1
  294. data/public/railwatch/assets/index-Bd4YpOIO.js +0 -1
  295. data/public/railwatch/assets/index-BeQw69GM.js +0 -1
  296. data/public/railwatch/assets/index-BfitxVBv.js +0 -1
  297. data/public/railwatch/assets/index-C-EwH4lO.js +0 -1
  298. data/public/railwatch/assets/index-C8JIdb3_.js +0 -1
  299. data/public/railwatch/assets/index-CBFCtJAR.js +0 -1
  300. data/public/railwatch/assets/index-CDXlITyG.js +0 -1
  301. data/public/railwatch/assets/index-COd9lT84.js +0 -1
  302. data/public/railwatch/assets/index-C_hQmVTP.js +0 -1
  303. data/public/railwatch/assets/index-CcennT28.js +0 -1
  304. data/public/railwatch/assets/index-CiaeS-n7.js +0 -1
  305. data/public/railwatch/assets/index-CrA7jBTK.js +0 -1
  306. data/public/railwatch/assets/index-D0mKj3Hg.js +0 -1
  307. data/public/railwatch/assets/index-DV5MdmpY.js +0 -1
  308. data/public/railwatch/assets/index-DaE-1xhx.js +0 -1
  309. data/public/railwatch/assets/index-DdVg9LKX.js +0 -1
  310. data/public/railwatch/assets/index-DeLtMWou.js +0 -1
  311. data/public/railwatch/assets/index-DiQ4mqT_.js +0 -1
  312. data/public/railwatch/assets/index-LKgpCzoR.js +0 -1
  313. data/public/railwatch/assets/index-S0NVaLj9.js +0 -1
  314. data/public/railwatch/assets/index-Vj93BTR5.js +0 -1
  315. data/public/railwatch/assets/index-XjXcV-EO.js +0 -1
  316. data/public/railwatch/assets/index-aPCZX-pW.js +0 -1
  317. data/public/railwatch/assets/index-cTzBycCg.js +0 -1
  318. data/public/railwatch/assets/index-dpG-19ZN.js +0 -1
  319. data/public/railwatch/assets/index-gCvuyG1l.js +0 -1
  320. data/public/railwatch/assets/index-o1xqGvLo.js +0 -1
  321. data/public/railwatch/assets/index-w_skgMmK.js +0 -1
  322. data/public/railwatch/assets/inertia-CqnzqPVD.js +0 -97
  323. data/public/railwatch/assets/input-error-ByT28UdP.js +0 -1
  324. data/public/railwatch/assets/json-viewer-Bnu9RsJL.js +0 -1
  325. data/public/railwatch/assets/klass-nLiBV-Az.js +0 -1
  326. data/public/railwatch/assets/label-1nSzpSB6.js +0 -1
  327. data/public/railwatch/assets/layout-D7lE1Doo.js +0 -1
  328. data/public/railwatch/assets/live-dot-UdzOfzJb.js +0 -41
  329. data/public/railwatch/assets/nav-BRCgp2w3.js +0 -1
  330. data/public/railwatch/assets/new-C60G72DR.js +0 -1
  331. data/public/railwatch/assets/new-COyDKOOb.js +0 -1
  332. data/public/railwatch/assets/new-Cd9zPNJ8.js +0 -1
  333. data/public/railwatch/assets/new-DMJGKjmH.js +0 -1
  334. data/public/railwatch/assets/new-Omvf-c9s.js +0 -1
  335. data/public/railwatch/assets/new-vjRzERL3.js +0 -1
  336. data/public/railwatch/assets/onboarding-gUjJQc-6.js +0 -1
  337. data/public/railwatch/assets/origin-identity-M1YUPYS0.js +0 -1
  338. data/public/railwatch/assets/relative-time-DQ4kjowZ.js +0 -1
  339. data/public/railwatch/assets/route-BJkoEm7r.js +0 -1
  340. data/public/railwatch/assets/select-ocdQJdQo.js +0 -1
  341. data/public/railwatch/assets/separator-Ct2UcZ2x.js +0 -1
  342. data/public/railwatch/assets/series-chart-D6upsrSn.js +0 -1
  343. data/public/railwatch/assets/show-B4bwI9x2.js +0 -2
  344. data/public/railwatch/assets/show-BAjfddEn.js +0 -6
  345. data/public/railwatch/assets/show-BLJOyLb_.js +0 -1
  346. data/public/railwatch/assets/show-BoVyMQS6.js +0 -1
  347. data/public/railwatch/assets/show-Bsu1rvnK.js +0 -1
  348. data/public/railwatch/assets/show-C6OiJ6Hr.js +0 -1
  349. data/public/railwatch/assets/show-C8TkpEgm.js +0 -1
  350. data/public/railwatch/assets/show-CBFBiBY2.js +0 -1
  351. data/public/railwatch/assets/show-CEhPVMMT.js +0 -1
  352. data/public/railwatch/assets/show-CV-lG2b6.js +0 -2
  353. data/public/railwatch/assets/show-CX_pab9S.js +0 -2
  354. data/public/railwatch/assets/show-D6lgzL6n.js +0 -1
  355. data/public/railwatch/assets/show-DDVsv-uS.js +0 -1
  356. data/public/railwatch/assets/show-Dwiq-8hA.js +0 -1
  357. data/public/railwatch/assets/show-eKazflRI.js +0 -1
  358. data/public/railwatch/assets/show-yEOrk6Pt.js +0 -1
  359. data/public/railwatch/assets/stat-BNEfE8X1.js +0 -1
  360. data/public/railwatch/assets/status-badge-D0aylrM5.js +0 -1
  361. data/public/railwatch/assets/text-link-CP4lqUi6.js +0 -1
  362. data/public/railwatch/assets/textarea-DzJo2ds4.js +0 -1
  363. data/public/railwatch/assets/timeline-BNcJ9PMc.js +0 -1
  364. data/public/railwatch/assets/transition-BX2M1iaq.js +0 -5
@@ -0,0 +1,9 @@
1
+ import type { FlashData, SharedProps } from "@/types"
2
+
3
+ declare module "@inertiajs/core" {
4
+ export interface InertiaConfig {
5
+ sharedPageProps: SharedProps
6
+ flashDataType: FlashData
7
+ errorValueType: string[]
8
+ }
9
+ }
@@ -0,0 +1,288 @@
1
+ import type { LucideIcon } from "lucide-react"
2
+
3
+ export interface Auth {
4
+ user: User
5
+ session: Pick<Session, "id" | "recently_authenticated">
6
+ }
7
+
8
+ export interface BreadcrumbItem {
9
+ title: string
10
+ href: string
11
+ }
12
+
13
+ export interface NavItem {
14
+ title: string
15
+ href: string
16
+ icon?: LucideIcon | null
17
+ isActive?: boolean
18
+ }
19
+
20
+ export interface FlashData {
21
+ alert?: string
22
+ warning?: string
23
+ notice?: string
24
+ }
25
+
26
+ export interface AccountSummary {
27
+ id: number
28
+ name: string
29
+ slug: string
30
+ plan: string
31
+ }
32
+
33
+ export interface EnvironmentSummary {
34
+ id: number
35
+ name: string
36
+ slug: string
37
+ last_seen_at: string | null
38
+ paused: boolean
39
+ }
40
+
41
+ export interface ApplicationSummary {
42
+ id: number
43
+ name: string
44
+ slug: string
45
+ issue_prefix: string
46
+ environments: EnvironmentSummary[]
47
+ }
48
+
49
+ export interface EnvironmentContext {
50
+ id: number
51
+ name: string
52
+ slug: string
53
+ application_id: number
54
+ application_name: string
55
+ issue_prefix: string
56
+ last_seen_at: string | null
57
+ paused: boolean
58
+ token_prefix: string
59
+ repository_url: string | null
60
+ default_branch: string
61
+ }
62
+
63
+ export type Window = "1h" | "6h" | "24h" | "7d" | "30d" | "custom"
64
+
65
+ // Chart bucket width; the server offers the subset that fits the window.
66
+ export type Step = "1m" | "5m" | "15m" | "1h" | "6h" | "1d"
67
+
68
+ export interface WindowRange {
69
+ from: string
70
+ to: string
71
+ }
72
+
73
+ export type TelemetryTimeParams =
74
+ | { window: Exclude<Window, "custom">; from?: never; to?: never }
75
+ | { window?: never; from: string; to: string }
76
+
77
+ export interface CursorMeta {
78
+ limit: number
79
+ next_cursor: string | null
80
+ has_more: boolean
81
+ }
82
+
83
+ export interface SharedProps {
84
+ auth: Auth
85
+ account: AccountSummary | null
86
+ accounts: { id: number; name: string }[]
87
+ applications: ApplicationSummary[]
88
+ environment?: EnvironmentContext
89
+ telemetry_freshness?: {
90
+ received_at: string | null
91
+ aggregated_at?: string | null
92
+ processing_lag_seconds: number | null
93
+ }
94
+ window?: Window
95
+ range?: WindowRange
96
+ step?: Step
97
+ steps?: Step[]
98
+ flash: FlashData
99
+ google_oauth: boolean
100
+ embedded?: boolean
101
+ saved_views?: SavedView[]
102
+ [key: string]: unknown
103
+ }
104
+
105
+ export interface User {
106
+ id: number
107
+ name: string
108
+ email: string
109
+ provider: string | null
110
+ avatar?: string
111
+ verified: boolean
112
+ editor: string
113
+ editor_root: string | null
114
+ created_at: string
115
+ updated_at: string
116
+ [key: string]: unknown
117
+ }
118
+
119
+ export interface Session {
120
+ id: string
121
+ user_agent: string
122
+ ip_address: string
123
+ created_at: string
124
+ recently_authenticated: boolean
125
+ }
126
+
127
+ // Telemetry shapes shared by pages
128
+
129
+ export type Percentile = "p50" | "p95" | "p99"
130
+
131
+ // One chart bucket. Durations are null where nothing ran, so a latency
132
+ // line breaks over a quiet bucket instead of dropping to zero.
133
+ export interface SeriesPoint {
134
+ t: string
135
+ count: number
136
+ errors: number
137
+ client_errors: number
138
+ avg: number | null
139
+ p50: number | null
140
+ p95: number | null
141
+ p99: number | null
142
+ }
143
+
144
+ export interface GroupRow {
145
+ group_hash: string
146
+ name: string
147
+ count: number
148
+ errors: number
149
+ client_errors: number
150
+ avg: number
151
+ p50: number
152
+ p95: number
153
+ p99: number
154
+ max: number
155
+ sparkline: number[]
156
+ }
157
+
158
+ export interface Summary {
159
+ count: number
160
+ errors: number
161
+ client_errors: number
162
+ avg: number
163
+ p50: number
164
+ p95: number
165
+ p99: number
166
+ max: number
167
+ }
168
+
169
+ export interface SummaryWithDelta {
170
+ current: Summary
171
+ previous: Summary
172
+ }
173
+
174
+ // Release health: crash-free rates are null, not 0, when there were no
175
+ // sessions (or no users) to divide by.
176
+ export interface ReleaseHealthSummary {
177
+ sessions: number
178
+ users: number
179
+ crash_free_sessions: number | null
180
+ crash_free_users: number | null
181
+ errored: number | null
182
+ avg_duration_ms: number | null
183
+ }
184
+
185
+ export interface SessionStatusPoint {
186
+ t: string
187
+ sessions: number
188
+ ok: number
189
+ errored: number
190
+ crashed: number
191
+ }
192
+
193
+ export interface DeployMarker {
194
+ deploy: string
195
+ ref: string
196
+ at: string
197
+ }
198
+
199
+ export interface IssueRow {
200
+ id: number
201
+ key: string
202
+ title: string
203
+ kind: "exception" | "performance"
204
+ status: "open" | "resolved" | "ignored" | "merged"
205
+ priority: "low" | "normal" | "high" | "urgent"
206
+ culprit?: string | null
207
+ occurrences: number
208
+ affected_users: number
209
+ first_seen_at?: string
210
+ last_seen_at: string
211
+ assignee?: { id: number; name: string } | null
212
+ fingerprint_source?: string | null
213
+ source?: string | null
214
+ application?: { id: number; name: string }
215
+ environment?: { id: number; name: string }
216
+ }
217
+
218
+ export interface ExecutionRow {
219
+ execution_id: string
220
+ name: string
221
+ kind?: string
222
+ status?: number | null
223
+ outcome?: string | null
224
+ duration: number
225
+ occurred_at: string
226
+ user_ref?: string | null
227
+ tenant?: string | null
228
+ exception_preview?: string | null
229
+ inertia_component?: string | null
230
+ queries?: number
231
+ deploy?: string | null
232
+ }
233
+
234
+ export interface Frame {
235
+ file: string
236
+ line: number
237
+ function: string
238
+ in_app: boolean
239
+ code?: Record<string, string>
240
+ }
241
+
242
+ export interface ExceptionDetail {
243
+ id: number
244
+ class_name: string
245
+ message: string
246
+ handled: boolean
247
+ severity?: string
248
+ source?: string
249
+ file?: string | null
250
+ line?: number | null
251
+ frames: Frame[]
252
+ cause?: { class: string; message: string } | null
253
+ locals?: Record<string, string> | null
254
+ context?: string | null
255
+ occurred_at?: string
256
+ execution_id?: string | null
257
+ execution_source?: string | null
258
+ execution_preview?: string | null
259
+ deploy?: string | null
260
+ group_hash?: string
261
+ }
262
+
263
+ export interface TimelineEntry {
264
+ type: string
265
+ id: number
266
+ offset: number
267
+ duration: number | null
268
+ label: string
269
+ stage: string | null
270
+ sql?: string | null
271
+ source?: string | null
272
+ detail?: Record<string, string | number | boolean | null>
273
+ }
274
+
275
+ // A named filter on one environment page, shared through every env page's
276
+ // props. `url` is built server-side from the page's route helper.
277
+ export interface SavedView {
278
+ id: number
279
+ name: string
280
+ page: string
281
+ query: string | null
282
+ window: string | null
283
+ params: Record<string, string>
284
+ pinned: boolean
285
+ shared: boolean
286
+ mine: boolean
287
+ url: string
288
+ }
@@ -0,0 +1,9 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_APP_NAME: string
5
+ }
6
+
7
+ interface ImportMeta {
8
+ readonly env: ImportMetaEnv
9
+ }
@@ -118,7 +118,9 @@ module Railwatch
118
118
  cache_read_tokens: group.sum { |r| r.cache_read_tokens.to_i }, cache_write_tokens: group.sum { |r| r.cache_write_tokens.to_i },
119
119
  cost_nanos: group.sum { |r| r.cost_nanos.to_i },
120
120
  priced: group.count { |r| r.cost_nanos },
121
- unpriced: group.count { |r| r.cost_nanos.nil? },
121
+ # A call that failed before the provider answered used no tokens,
122
+ # so there is nothing to price; only an answered call can be unpriced.
123
+ unpriced: group.count { |r| r.cost_nanos.nil? && r.status != "failed" },
122
124
  # A cut-off answer is not an error and will never show in the error
123
125
  # rate, so it needs counting on its own or it stays invisible.
124
126
  truncated: group.count { |r| r.finish_reason == "max_tokens" },
@@ -160,7 +160,7 @@ module Railwatch
160
160
  extra["cache_write_tokens"] += row[:cache_write_tokens].to_i
161
161
  extra["cost_nanos"] += row[:cost_nanos].to_i
162
162
  extra["priced"] += 1 if row[:cost_nanos]
163
- extra["unpriced"] += 1 if row[:cost_nanos].nil?
163
+ extra["unpriced"] += 1 if row[:cost_nanos].nil? && row[:status] != "failed"
164
164
  extra["truncated"] += 1 if row[:finish_reason] == "max_tokens"
165
165
  extra["with_attachments"] += 1 if row[:attachments].to_i.positive?
166
166
  end
data/docs/ai-and-mcp.md CHANGED
@@ -9,6 +9,10 @@ crash-free rates per release. It can also write — resolve an issue, set a
9
9
  priority, leave a comment — and every write is signed with your user and the
10
10
  agent's name in the issue's activity feed.
11
11
 
12
+ MCP is a Railwatch Cloud feature. The embedded dashboard at `/railwatch`
13
+ does not serve it; an embedded install gets it by
14
+ [exporting to the cloud](embedded.md#three-ways-to-run-it).
15
+
12
16
  The endpoint is `<your ingest host>/mcp`. For the hosted platform that's
13
17
  `https://railwatch.rebulk.com/mcp`; if you self-host, it is your own host (see
14
18
  [`self-hosting.md`](self-hosting.md)). The gem knows which one you're on:
@@ -145,9 +149,9 @@ durations are milliseconds.** Every `window` argument takes `1h`, `6h`,
145
149
 
146
150
  | Tool | Arguments | Returns |
147
151
  |---|---|---|
148
- | `list_applications` | — | applications, their account, and their environments. Start here — everything else takes the `application_slug` and `environment` this returns. |
152
+ | `list_applications` | — | applications, their account, and their environments. Start here — everything else takes the `application_slug` and `environment` this returns (plus `account_slug`/`application_id` when two accounts share a slug). |
149
153
  | `list_issues` | `application_slug?`, `environment?`, `status?` (`open`/`resolved`/`ignored`, default `open`), `limit?` | issues with key, title, kind, status, priority, occurrence and affected-user counts, culprit, first/last seen. |
150
- | `get_issue` | `key` | one issue plus the sample occurrence: exception class, message, stack frames, and the execution it happened inside. |
154
+ | `get_issue` | `key` or `issue_id` | one issue. For an exception, the sample and latest occurrences with stored stack frames, cause, locals, parsed context, browser breadcrumbs, and the execution it happened inside. For a performance or anomaly issue, the metric, limit or baseline, representative records, trend, and deploy comparison. |
151
155
  | `update_issue` | `key`, `status?`, `priority?`, `assignee_email?`, `agent?` | the updated issue. Writes an activity entry attributed to your user and `agent`. |
152
156
  | `add_comment` | `key`, `body`, `agent?` | the created comment, attributed the same way. |
153
157
  | `list_slow_routes` | `application_slug`, `environment`, `window?` | the 20 slowest routes by p95, each with `group_hash`, count, errors, `p95_ms`. |
@@ -157,6 +161,7 @@ durations are milliseconds.** Every `window` argument takes `1h`, `6h`,
157
161
  | `explain_query` | `application_slug`, `environment`, `group_hash`, `window?` | the stored query plan for a query group, with the SQL and the sample's duration. `explain` is null unless the app sets `RAILWATCH_CAPTURE_QUERY_EXPLAIN`. `sql` is the normalized shape unless the app also sets `RAILWATCH_CAPTURE_SQL_VALUES`. |
158
162
  | `get_profile` | `application_slug`, `environment`, `profile_id?`, `execution_id?`, `limit?` | the hottest frames of a stack profile — self and total samples, each with a percentage. |
159
163
  | `search_logs` | `application_slug`, `environment`, `q`, `level?`, `limit?` | matching log lines, each with the `execution_id` to expand with `get_execution`. |
164
+ | `search_telemetry` | `application_slug`, `environment`, `resource` (`jobs`/`exceptions`/`logs`/`queries`), `q?`, `window?`, `cursor?`, `limit?` | raw rows of that resource with cursor pagination (`meta.next_cursor`, `meta.has_more`). `q` takes `after:`, `before:`, `user:`, `tenant:`, `deploy:`, plus per-resource keys such as `class:`, `queue:`, `level:`. |
160
165
  | `list_tenants` | `application_slug`, `environment`, `window?`, `q?` | your app's own tenants (whatever it passes to `Railwatch.context(tenant:)`) with request, error, job, exception, and user counts. |
161
166
  | `recent_deploys` | `application_slug`, `environment` | the 20 most recent deploys with ref, name, time, and link. |
162
167
  | `release_health` | `application_slug`, `environment`, `window?` | crash-free session rate, crash-free user rate, and adoption per release. |
@@ -220,7 +225,8 @@ Copy either into your own app's repo to give its agent the same context.
220
225
 
221
226
  ## See also
222
227
 
223
- - [`getting-started.md`](getting-started.md) — install, token, first request.
228
+ - [`getting-started.md`](getting-started.md) — install, the cloud token,
229
+ first request.
224
230
  - [`self-hosting.md`](self-hosting.md) — pointing the gem, and this endpoint,
225
231
  at your own platform.
226
232
  - [`troubleshooting.md`](troubleshooting.md) — when something isn't
@@ -2,18 +2,17 @@
2
2
 
3
3
  Everything below lives on `Railwatch::Configuration`, in
4
4
  `lib/railwatch/configuration.rb`. Set it via
5
- `Railwatch.configure { |c| ... }` in `config/initializers/railwatch.rb`.
6
- That file is created by
7
- `bin/rails generate railwatch:install`. Most settings have a `RAILWATCH_*`
8
- env var default; the tables below show which. Explicit values set in the
9
- initializer always win over the env var.
5
+ `Railwatch.configure { |c| ... }` in `config/initializers/railwatch.rb`,
6
+ which `bin/rails generate railwatch:install` creates. Most settings have a
7
+ `RAILWATCH_*` env var default; the tables below show which. Explicit
8
+ values set in the initializer always win over the env var.
10
9
 
11
10
  ## Core
12
11
 
13
12
  | Attribute | Env var | Default | Meaning |
14
13
  |---|---|---|---|
15
- | `enabled` | `RAILWATCH_ENABLED` | `true` | Master switch. `Railwatch.enabled?` is also `false` whenever `token` is blank, so setting only `RAILWATCH_TOKEN` is enough to turn Railwatch on. |
16
- | `token` | `RAILWATCH_TOKEN` | nil | Bearer token for `/ingest`. Required. |
14
+ | `enabled` | `RAILWATCH_ENABLED` | `true` | Master switch. With `transport = :http`, `Railwatch.enabled?` is also `false` whenever `token` is blank, so setting only `RAILWATCH_TOKEN` is enough to turn a cloud install on. An embedded install needs no token. |
15
+ | `token` | `RAILWATCH_TOKEN` | nil | Bearer token for `/ingest`. Required with `transport = :http` and for [export](#export-to-railwatch-cloud). |
17
16
  | `ingest_url` | `RAILWATCH_INGEST_URL` | `https://railwatch.rebulk.com` | Platform base URL. Point at a self-hosted instance to override. |
18
17
  | `allow_http` | `RAILWATCH_ALLOW_HTTP` | `false` | Permit a non-loopback plain HTTP ingest URL. HTTPS is required by default; `localhost`, `127.0.0.1`, and `::1` remain available for local self-hosted development. |
19
18
  | `deploy` | `RAILWATCH_DEPLOY` | auto-detected (order below), then nil | Version tag stamped on every record and used by `railwatch:deploy`. Full 40-character SHAs are shortened to 12 characters. |
@@ -26,7 +25,8 @@ initializer always win over the env var.
26
25
  | `beacon_rate_limit` | `RAILWATCH_BEACON_RATE_LIMIT` | `120` | Beacon POSTs accepted per client IP per minute before `POST /railwatch/beacon` answers 429. The beacon is unauthenticated and keeps every browser error it is sent, so this is what stops a script from spending the app's event quota. Counted in the app's cache store; `0` turns it off. |
27
26
 
28
27
  `Railwatch.enabled?` delegates to `config.enabled?`, which is `@enabled &&
29
- token.present?`. There is no separate "is configured" check elsewhere.
28
+ (local? || token.present?)`. There is no separate "is configured" check
29
+ elsewhere.
30
30
 
31
31
  Deploy detection stops at the first value found: `RAILWATCH_DEPLOY`,
32
32
  `KAMAL_VERSION`, `GIT_REV`, `GIT_SHA`, `SOURCE_VERSION`,
@@ -569,7 +569,7 @@ Rake tasks and Solid Queue jobs are never interactive.
569
569
 
570
570
  | Attribute | Env var | Default | Meaning |
571
571
  |---|---|---|---|
572
- | `capture_exception_source` | `RAILWATCH_CAPTURE_EXCEPTION_SOURCE_CODE` | `true` | Send source snippet lines surrounding each in-application exception frame to Railwatch Cloud. This is on by default for crash context; disable it when source disclosure is outside the application's telemetry policy. |
572
+ | `capture_exception_source` | `RAILWATCH_CAPTURE_EXCEPTION_SOURCE_CODE` | `true` | Record source snippet lines surrounding each in-application exception frame (sent to Railwatch Cloud when the install reports there or exports). This is on by default for crash context; disable it when source disclosure is outside the application's telemetry policy. |
573
573
  | `capture_exception_locals` | `RAILWATCH_CAPTURE_EXCEPTION_LOCALS` | `false` | Snapshot the raising frame's local variables (up to 25, values truncated to 200 chars, run through the same filter as request params) onto each exception, like Sentry's locals panel. Installs a `TracePoint(:raise)`; opt in per environment. |
574
574
  | `capture_request_payload` | `RAILWATCH_CAPTURE_REQUEST_PAYLOAD` | `false` | Capture (redacted) request params — only for a request that raised, never otherwise. |
575
575
  | `capture_job_arguments` | `RAILWATCH_CAPTURE_JOB_ARGUMENTS` | `false` | Add the job's real arguments (`job.serialize["arguments"]`) to each `job_attempt`/`scheduled_task` record, capped at 8 KiB of JSON. Hash arguments run through the same filter as request params. Off by default because job arguments routinely carry PII; `arguments_preview` (argument *shapes* only) is always on regardless. |
@@ -884,7 +884,7 @@ ignore block's building blocks, and are nestable.
884
884
  ## Embedded mode
885
885
 
886
886
  ```ruby
887
- c.transport = :local # RAILWATCH_TRANSPORT; default "http"
887
+ c.transport = :local # RAILWATCH_TRANSPORT; runtime default "http", but the installer writes :local
888
888
  c.issue_prefix = "SHOP" # RAILWATCH_ISSUE_PREFIX; default from the app name
889
889
  c.repository_url = "..." # RAILWATCH_REPOSITORY_URL
890
890
  c.retention_days = 7 # RAILWATCH_RETENTION_DAYS
@@ -892,8 +892,9 @@ c.http_basic_auth_enabled = true # RAILWATCH_HTTP_BASIC_AUTH_ENABLED; on, a
892
892
  c.http_basic_auth_user = "ops" # RAILWATCH_HTTP_BASIC_AUTH_USER, or credentials railwatch.http_basic_auth_user
893
893
  c.http_basic_auth_password = "..." # RAILWATCH_HTTP_BASIC_AUTH_PASSWORD, or credentials railwatch.http_basic_auth_password
894
894
  c.base_controller_class = "AdminController" # RAILWATCH_BASE_CONTROLLER_CLASS; default ActionController::Base
895
- c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN; public on purpose
895
+ c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN; default false, true makes it public on purpose
896
896
  c.dashboard_user = ->(request) { { id:, name:, email: } or nil }
897
+ c.writer_socket = "tmp/sockets/railwatch-writer.sock" # RAILWATCH_WRITER_SOCKET
897
898
  ```
898
899
 
899
900
  With `transport = :local` the reporter writes each batch into the app's
@@ -905,25 +906,48 @@ railwatch:authentication:configure` has written credentials; a host with
905
906
  its own admin auth turns Basic off and sets `base_controller_class` or a
906
907
  routes constraint. Full walkthrough: [Embedded mode](embedded.md).
907
908
 
909
+ ### Export to Railwatch Cloud
910
+
911
+ An embedded install can also mirror every record to Railwatch Cloud.
912
+ Off unless `export_enabled` is set; a configured token alone sends
913
+ nothing. Only meaningful with `transport = :local`.
914
+
915
+ | Attribute | Env var | Default | Meaning |
916
+ |---|---|---|---|
917
+ | `export_enabled` | `RAILWATCH_EXPORT_ENABLED` | `false` | Mirror every record to the cloud as well as storing it locally. |
918
+ | `export_token` | `RAILWATCH_EXPORT_TOKEN` | `token` | Cloud environment token to send with. |
919
+ | `export_url` | `RAILWATCH_EXPORT_URL` | `{ingest_url}/ingest` | Where to send. |
920
+ | `export_max_bytes` | `RAILWATCH_EXPORT_MAX_BYTES` | `268435456` (256 MiB) | Queue ceiling in bytes. At capacity new work is refused and counted, not swapped for old. |
921
+ | `export_max_deliveries` | `RAILWATCH_EXPORT_MAX_DELIVERIES` | `100000` | Queue ceiling in deliveries. |
922
+ | `export_max_age` | `RAILWATCH_EXPORT_MAX_AGE_SECONDS` | `86400` | How long a queued delivery is kept. At most seven days, past which the receiver no longer recognises it. |
923
+
924
+ `railwatch:doctor` fails when export is enabled and cannot work, and
925
+ `railwatch:export:status` shows the queue. See
926
+ [Embedded mode](embedded.md#three-ways-to-run-it).
927
+
908
928
  ## Rake tasks
909
929
 
910
930
  Ship with the gem via Rails::Engine's default `lib/tasks` convention, in
911
931
  `lib/tasks/railwatch_tasks.rake`:
912
932
 
913
- - **`railwatch:status`** pings `{ingest_url}/ingest/ping` with the
914
- configured token. It aborts if `RAILWATCH_TOKEN` is unset or the ping
915
- fails.
916
- - **`railwatch:doctor`** prints a ✓/✗ checklist of the whole install:
917
- token, ingest URL, `GET /ingest/ping`, `Railwatch::Middleware::Request`
933
+ - **`railwatch:status`** embedded: prints that telemetry is stored in the
934
+ app. Cloud: pings `{ingest_url}/ingest/ping` with the configured token,
935
+ and aborts if `RAILWATCH_TOKEN` is unset or the ping fails.
936
+ - **`railwatch:doctor`** prints a ✓/✗ checklist of the whole install.
937
+ Embedded, it checks both databases and their migrations, telemetry
938
+ disk mode, the maintenance clock, the writer process, dashboard access,
939
+ and export when enabled. Cloud, it checks the token, where the token is
940
+ stored, the ingest URL and its transport security, and
941
+ `GET /ingest/ping`. Then, either way: `Railwatch::Middleware::Request`
918
942
  in the middleware stack, the mounted engine's beacon route,
919
- `config.deploy` and its environment, `REVISION`, Git, or initializer
920
- source, sample rates, ignored record types, the Kamal `post-deploy`
921
- hook, `app/frontend/lib/railwatch.ts`, and whether `railwatch/rspec` or
922
- `railwatch/minitest` is required by the test helper. The last five are
923
- informational. It exits non-zero only when the token is missing or the
924
- ping fails.
925
- - **`railwatch:deploy[ref,name,url]`** POSTs `{deploy, ref, name, url,
926
- server, timestamp, performer, destination, service, commits}` to
943
+ `config.deploy` and its source, sample rates, ignored record types, the
944
+ Kamal `post-deploy` hook, the browser client and whether an entrypoint
945
+ calls it, the profiler backend, and the test matchers. It exits
946
+ non-zero only on the lines marked fatal in
947
+ [Troubleshooting](troubleshooting.md).
948
+ - **`railwatch:deploy[ref,name,url]`** embedded: writes the deploy marker
949
+ to the app's `railwatch` database. Cloud: POSTs `{deploy, ref, name,
950
+ url, server, timestamp, performer, destination, service, commits}` to
927
951
  `{ingest_url}/ingest/deploys`. `deploy` comes from `config.deploy`. It
928
952
  aborts if that's unset. `ref` defaults to `git rev-parse HEAD` when not
929
953
  passed. `performer`/`destination`/`service` come from `KAMAL_PERFORMER`,
@@ -931,13 +955,30 @@ Ship with the gem via Rails::Engine's default `lib/tasks` convention, in
931
955
  `{sha, author, message, at}` objects, newest first, from `git log`. It
932
956
  is empty inside an app container, which has no `.git`. That is why the
933
957
  hook below posts from the deployer instead.
958
+ - **`railwatch:authentication:configure`** writes the embedded
959
+ dashboard's HTTP Basic credentials to the current environment's Rails
960
+ credentials.
961
+ - **`railwatch:export:status`**, **`railwatch:export:rebind`**,
962
+ **`railwatch:export:discard`** show the export queue, clear a
963
+ credential block (abandoning work queued under the old token), and
964
+ abandon everything queued.
965
+ - **`railwatch:vacuum:status`** and **`railwatch:vacuum`** report and
966
+ reclaim the telemetry database's disk
967
+ ([Embedded mode](embedded.md#giving-the-disk-back)).
968
+ - **`railwatch:token`** and **`railwatch:mcp`** print where to create a
969
+ cloud ingest token and paste-ready MCP client configuration.
970
+ - **`railwatch:sourcemaps[directory,delete]`** uploads browser source
971
+ maps ([Source maps](source-maps.md)).
934
972
 
935
973
  ## Kamal integration
936
974
 
937
975
  `bin/rails generate railwatch:install` writes `.kamal/hooks/post-deploy`,
938
- but only if `config/deploy.yml` already exists. It no-ops when
939
- `RAILWATCH_TOKEN` isn't set, and never fails a deploy. Every network call
940
- ends in `|| true`.
976
+ but only if `config/deploy.yml` already exists. With
977
+ `RAILWATCH_TRANSPORT=local` in the deployer's environment it runs
978
+ `bin/rails railwatch:deploy[$KAMAL_VERSION]` in the primary container,
979
+ which records the marker in the embedded database, and stops. Otherwise
980
+ it no-ops when `RAILWATCH_TOKEN` isn't set. It never fails a deploy.
981
+ Every network call ends in `|| true`.
941
982
 
942
983
  The hook runs on the **deployer machine**, not in a container, which is
943
984
  the whole point. That's where the git history lives and where Kamal