@dudousxd/nestjs-telescope-ui 2.0.0 → 3.0.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 (266) hide show
  1. package/CHANGELOG.md +771 -0
  2. package/README.md +35 -0
  3. package/dist/client/console-url.d.ts +11 -0
  4. package/dist/client/console-url.d.ts.map +1 -0
  5. package/dist/client/console-url.js +21 -0
  6. package/dist/client/console-url.js.map +1 -0
  7. package/dist/client/index.d.ts +3 -2
  8. package/dist/client/index.d.ts.map +1 -1
  9. package/dist/client/index.js +4 -1
  10. package/dist/client/index.js.map +1 -1
  11. package/dist/client/telescope-client.d.ts +82 -11
  12. package/dist/client/telescope-client.d.ts.map +1 -1
  13. package/dist/client/telescope-client.js +101 -54
  14. package/dist/client/telescope-client.js.map +1 -1
  15. package/dist/client/types.d.ts +431 -30
  16. package/dist/client/types.d.ts.map +1 -1
  17. package/dist/react/components/batch-timeline.js +2 -2
  18. package/dist/react/components/batch-timeline.js.map +1 -1
  19. package/dist/react/components/cache-badge.d.ts +14 -6
  20. package/dist/react/components/cache-badge.d.ts.map +1 -1
  21. package/dist/react/components/cache-badge.js +26 -12
  22. package/dist/react/components/cache-badge.js.map +1 -1
  23. package/dist/react/components/charts/area-chart-card.d.ts +11 -3
  24. package/dist/react/components/charts/area-chart-card.d.ts.map +1 -1
  25. package/dist/react/components/charts/area-chart-card.js +33 -6
  26. package/dist/react/components/charts/area-chart-card.js.map +1 -1
  27. package/dist/react/components/charts/bar-chart-card.d.ts +18 -7
  28. package/dist/react/components/charts/bar-chart-card.d.ts.map +1 -1
  29. package/dist/react/components/charts/bar-chart-card.js +18 -9
  30. package/dist/react/components/charts/bar-chart-card.js.map +1 -1
  31. package/dist/react/components/charts/breakdown-card.d.ts +17 -0
  32. package/dist/react/components/charts/breakdown-card.d.ts.map +1 -0
  33. package/dist/react/components/charts/breakdown-card.js +43 -0
  34. package/dist/react/components/charts/breakdown-card.js.map +1 -0
  35. package/dist/react/components/charts/chart-card.d.ts +7 -0
  36. package/dist/react/components/charts/chart-card.d.ts.map +1 -1
  37. package/dist/react/components/charts/chart-card.js +10 -3
  38. package/dist/react/components/charts/chart-card.js.map +1 -1
  39. package/dist/react/components/charts/chart-selection.d.ts +44 -0
  40. package/dist/react/components/charts/chart-selection.d.ts.map +1 -0
  41. package/dist/react/components/charts/chart-selection.js +36 -0
  42. package/dist/react/components/charts/chart-selection.js.map +1 -0
  43. package/dist/react/components/charts/chart-theme.d.ts +62 -19
  44. package/dist/react/components/charts/chart-theme.d.ts.map +1 -1
  45. package/dist/react/components/charts/chart-theme.js +72 -13
  46. package/dist/react/components/charts/chart-theme.js.map +1 -1
  47. package/dist/react/components/charts/distribution-chart-card.d.ts +17 -0
  48. package/dist/react/components/charts/distribution-chart-card.d.ts.map +1 -0
  49. package/dist/react/components/charts/distribution-chart-card.js +46 -0
  50. package/dist/react/components/charts/distribution-chart-card.js.map +1 -0
  51. package/dist/react/components/charts/gauge-card.d.ts +12 -0
  52. package/dist/react/components/charts/gauge-card.d.ts.map +1 -0
  53. package/dist/react/components/charts/gauge-card.js +17 -0
  54. package/dist/react/components/charts/gauge-card.js.map +1 -0
  55. package/dist/react/components/charts/index.d.ts +1 -0
  56. package/dist/react/components/charts/index.d.ts.map +1 -1
  57. package/dist/react/components/charts/index.js +1 -0
  58. package/dist/react/components/charts/index.js.map +1 -1
  59. package/dist/react/components/charts/stacked-area-chart-card.d.ts +17 -3
  60. package/dist/react/components/charts/stacked-area-chart-card.d.ts.map +1 -1
  61. package/dist/react/components/charts/stacked-area-chart-card.js +31 -8
  62. package/dist/react/components/charts/stacked-area-chart-card.js.map +1 -1
  63. package/dist/react/components/entries-table.d.ts.map +1 -1
  64. package/dist/react/components/entries-table.js +17 -3
  65. package/dist/react/components/entries-table.js.map +1 -1
  66. package/dist/react/components/entry-detail.d.ts +2 -0
  67. package/dist/react/components/entry-detail.d.ts.map +1 -1
  68. package/dist/react/components/entry-detail.js +186 -20
  69. package/dist/react/components/entry-detail.js.map +1 -1
  70. package/dist/react/components/entry-insights.d.ts.map +1 -1
  71. package/dist/react/components/entry-insights.js +28 -17
  72. package/dist/react/components/entry-insights.js.map +1 -1
  73. package/dist/react/components/entry-types.d.ts +29 -0
  74. package/dist/react/components/entry-types.d.ts.map +1 -1
  75. package/dist/react/components/entry-types.js +60 -0
  76. package/dist/react/components/entry-types.js.map +1 -1
  77. package/dist/react/components/export-json-toolbar.js +2 -2
  78. package/dist/react/components/export-json-toolbar.js.map +1 -1
  79. package/dist/react/components/extensions/panel-column-menu.d.ts +19 -0
  80. package/dist/react/components/extensions/panel-column-menu.d.ts.map +1 -0
  81. package/dist/react/components/extensions/panel-column-menu.js +47 -0
  82. package/dist/react/components/extensions/panel-column-menu.js.map +1 -0
  83. package/dist/react/components/extensions/panel-renderer.d.ts +46 -0
  84. package/dist/react/components/extensions/panel-renderer.d.ts.map +1 -0
  85. package/dist/react/components/extensions/panel-renderer.js +122 -0
  86. package/dist/react/components/extensions/panel-renderer.js.map +1 -0
  87. package/dist/react/components/extensions/panel-table-header.d.ts +18 -0
  88. package/dist/react/components/extensions/panel-table-header.d.ts.map +1 -0
  89. package/dist/react/components/extensions/panel-table-header.js +82 -0
  90. package/dist/react/components/extensions/panel-table-header.js.map +1 -0
  91. package/dist/react/components/extensions/panel-table.d.ts +27 -0
  92. package/dist/react/components/extensions/panel-table.d.ts.map +1 -0
  93. package/dist/react/components/extensions/panel-table.js +87 -0
  94. package/dist/react/components/extensions/panel-table.js.map +1 -0
  95. package/dist/react/components/extensions/stat-card.d.ts +18 -0
  96. package/dist/react/components/extensions/stat-card.d.ts.map +1 -0
  97. package/dist/react/components/extensions/stat-card.js +44 -0
  98. package/dist/react/components/extensions/stat-card.js.map +1 -0
  99. package/dist/react/components/extensions/table-columns.d.ts +32 -0
  100. package/dist/react/components/extensions/table-columns.d.ts.map +1 -0
  101. package/dist/react/components/extensions/table-columns.js +51 -0
  102. package/dist/react/components/extensions/table-columns.js.map +1 -0
  103. package/dist/react/components/extensions/table-features.d.ts +47 -0
  104. package/dist/react/components/extensions/table-features.d.ts.map +1 -0
  105. package/dist/react/components/extensions/table-features.js +45 -0
  106. package/dist/react/components/extensions/table-features.js.map +1 -0
  107. package/dist/react/components/extensions/table-query.d.ts +53 -0
  108. package/dist/react/components/extensions/table-query.d.ts.map +1 -0
  109. package/dist/react/components/extensions/table-query.js +51 -0
  110. package/dist/react/components/extensions/table-query.js.map +1 -0
  111. package/dist/react/components/flamegraph-view.d.ts +18 -0
  112. package/dist/react/components/flamegraph-view.d.ts.map +1 -0
  113. package/dist/react/components/flamegraph-view.js +48 -0
  114. package/dist/react/components/flamegraph-view.js.map +1 -0
  115. package/dist/react/components/flamegraph.d.ts +34 -0
  116. package/dist/react/components/flamegraph.d.ts.map +1 -0
  117. package/dist/react/components/flamegraph.js +54 -0
  118. package/dist/react/components/flamegraph.js.map +1 -0
  119. package/dist/react/components/inertia-badge.d.ts +33 -0
  120. package/dist/react/components/inertia-badge.d.ts.map +1 -0
  121. package/dist/react/components/inertia-badge.js +72 -0
  122. package/dist/react/components/inertia-badge.js.map +1 -0
  123. package/dist/react/components/inertia-body.d.ts +11 -0
  124. package/dist/react/components/inertia-body.d.ts.map +1 -0
  125. package/dist/react/components/inertia-body.js +127 -0
  126. package/dist/react/components/inertia-body.js.map +1 -0
  127. package/dist/react/components/official-event.d.ts +16 -0
  128. package/dist/react/components/official-event.d.ts.map +1 -0
  129. package/dist/react/components/official-event.js +88 -0
  130. package/dist/react/components/official-event.js.map +1 -0
  131. package/dist/react/components/pulse-panel.d.ts.map +1 -1
  132. package/dist/react/components/pulse-panel.js +6 -6
  133. package/dist/react/components/pulse-panel.js.map +1 -1
  134. package/dist/react/components/queues/EnqueueJobForm.d.ts.map +1 -1
  135. package/dist/react/components/queues/EnqueueJobForm.js +2 -1
  136. package/dist/react/components/queues/EnqueueJobForm.js.map +1 -1
  137. package/dist/react/components/queues/JobActions.d.ts.map +1 -1
  138. package/dist/react/components/queues/JobActions.js +11 -9
  139. package/dist/react/components/queues/JobActions.js.map +1 -1
  140. package/dist/react/components/queues/JobDetailDrawer.d.ts.map +1 -1
  141. package/dist/react/components/queues/JobDetailDrawer.js +8 -13
  142. package/dist/react/components/queues/JobDetailDrawer.js.map +1 -1
  143. package/dist/react/components/queues/JobTable.d.ts.map +1 -1
  144. package/dist/react/components/queues/JobTable.js +0 -0
  145. package/dist/react/components/queues/JobTable.js.map +1 -1
  146. package/dist/react/components/queues/QueueList.d.ts.map +1 -1
  147. package/dist/react/components/queues/QueueList.js +6 -6
  148. package/dist/react/components/queues/QueueList.js.map +1 -1
  149. package/dist/react/components/queues/QueueStateTabs.d.ts.map +1 -1
  150. package/dist/react/components/queues/QueueStateTabs.js +9 -7
  151. package/dist/react/components/queues/QueueStateTabs.js.map +1 -1
  152. package/dist/react/components/queues/queue-format.d.ts +5 -1
  153. package/dist/react/components/queues/queue-format.d.ts.map +1 -1
  154. package/dist/react/components/queues/queue-format.js +11 -7
  155. package/dist/react/components/queues/queue-format.js.map +1 -1
  156. package/dist/react/components/queues-panel.js +1 -1
  157. package/dist/react/components/queues-panel.js.map +1 -1
  158. package/dist/react/components/request-timeline.d.ts.map +1 -1
  159. package/dist/react/components/request-timeline.js +2 -2
  160. package/dist/react/components/request-timeline.js.map +1 -1
  161. package/dist/react/components/retention-indicator.d.ts.map +1 -1
  162. package/dist/react/components/retention-indicator.js +1 -1
  163. package/dist/react/components/retention-indicator.js.map +1 -1
  164. package/dist/react/components/sparkline.d.ts.map +1 -1
  165. package/dist/react/components/sparkline.js +1 -1
  166. package/dist/react/components/sparkline.js.map +1 -1
  167. package/dist/react/components/tag-autocomplete.d.ts +21 -4
  168. package/dist/react/components/tag-autocomplete.d.ts.map +1 -1
  169. package/dist/react/components/tag-autocomplete.js +57 -29
  170. package/dist/react/components/tag-autocomplete.js.map +1 -1
  171. package/dist/react/components/to-csv.d.ts +25 -0
  172. package/dist/react/components/to-csv.d.ts.map +1 -0
  173. package/dist/react/components/to-csv.js +78 -0
  174. package/dist/react/components/to-csv.js.map +1 -0
  175. package/dist/react/components/user-tag.d.ts +15 -0
  176. package/dist/react/components/user-tag.d.ts.map +1 -1
  177. package/dist/react/components/user-tag.js +19 -0
  178. package/dist/react/components/user-tag.js.map +1 -1
  179. package/dist/react/components/waterfall-view.d.ts +13 -0
  180. package/dist/react/components/waterfall-view.d.ts.map +1 -0
  181. package/dist/react/components/waterfall-view.js +58 -0
  182. package/dist/react/components/waterfall-view.js.map +1 -0
  183. package/dist/react/components/window-select.d.ts.map +1 -1
  184. package/dist/react/components/window-select.js +6 -2
  185. package/dist/react/components/window-select.js.map +1 -1
  186. package/dist/react/index.d.ts +7 -1
  187. package/dist/react/index.d.ts.map +1 -1
  188. package/dist/react/index.js +11 -1
  189. package/dist/react/index.js.map +1 -1
  190. package/dist/react/ui/badge.d.ts +16 -0
  191. package/dist/react/ui/badge.d.ts.map +1 -0
  192. package/dist/react/ui/badge.js +28 -0
  193. package/dist/react/ui/badge.js.map +1 -0
  194. package/dist/react/ui/button.d.ts +17 -0
  195. package/dist/react/ui/button.d.ts.map +1 -0
  196. package/dist/react/ui/button.js +35 -0
  197. package/dist/react/ui/button.js.map +1 -0
  198. package/dist/react/ui/chart.d.ts +116 -0
  199. package/dist/react/ui/chart.d.ts.map +1 -0
  200. package/dist/react/ui/chart.js +191 -0
  201. package/dist/react/ui/chart.js.map +1 -0
  202. package/dist/react/ui/cn.d.ts +8 -0
  203. package/dist/react/ui/cn.d.ts.map +1 -0
  204. package/dist/react/ui/cn.js +11 -0
  205. package/dist/react/ui/cn.js.map +1 -0
  206. package/dist/react/ui/dialog.d.ts +26 -0
  207. package/dist/react/ui/dialog.d.ts.map +1 -0
  208. package/dist/react/ui/dialog.js +40 -0
  209. package/dist/react/ui/dialog.js.map +1 -0
  210. package/dist/react/ui/index.d.ts +24 -0
  211. package/dist/react/ui/index.d.ts.map +1 -0
  212. package/dist/react/ui/index.js +24 -0
  213. package/dist/react/ui/index.js.map +1 -0
  214. package/dist/react/ui/input.d.ts +3 -0
  215. package/dist/react/ui/input.d.ts.map +1 -0
  216. package/dist/react/ui/input.js +8 -0
  217. package/dist/react/ui/input.js.map +1 -0
  218. package/dist/react/ui/select.d.ts +20 -0
  219. package/dist/react/ui/select.d.ts.map +1 -0
  220. package/dist/react/ui/select.js +27 -0
  221. package/dist/react/ui/select.js.map +1 -0
  222. package/dist/react/ui/table.d.ts +27 -0
  223. package/dist/react/ui/table.d.ts.map +1 -0
  224. package/dist/react/ui/table.js +22 -0
  225. package/dist/react/ui/table.js.map +1 -0
  226. package/dist/react/ui/tabs.d.ts +12 -0
  227. package/dist/react/ui/tabs.d.ts.map +1 -0
  228. package/dist/react/ui/tabs.js +20 -0
  229. package/dist/react/ui/tabs.js.map +1 -0
  230. package/dist/react/ui/tooltip.d.ts +10 -0
  231. package/dist/react/ui/tooltip.d.ts.map +1 -0
  232. package/dist/react/ui/tooltip.js +12 -0
  233. package/dist/react/ui/tooltip.js.map +1 -0
  234. package/dist/react/use-telescope-queries.d.ts +129 -130
  235. package/dist/react/use-telescope-queries.d.ts.map +1 -1
  236. package/dist/react/use-telescope-queries.js +146 -1
  237. package/dist/react/use-telescope-queries.js.map +1 -1
  238. package/dist/react/use-telescope-stream.d.ts +10 -0
  239. package/dist/react/use-telescope-stream.d.ts.map +1 -0
  240. package/dist/react/use-telescope-stream.js +40 -0
  241. package/dist/react/use-telescope-stream.js.map +1 -0
  242. package/dist/server/index.d.ts +3 -1
  243. package/dist/server/index.d.ts.map +1 -1
  244. package/dist/server/index.js +5 -0
  245. package/dist/server/index.js.map +1 -1
  246. package/dist/server/telescope-ui.controller.d.ts.map +1 -1
  247. package/dist/server/telescope-ui.controller.js +3 -1
  248. package/dist/server/telescope-ui.controller.js.map +1 -1
  249. package/dist/server/telescope-ui.module.d.ts +28 -1
  250. package/dist/server/telescope-ui.module.d.ts.map +1 -1
  251. package/dist/server/telescope-ui.module.js +48 -4
  252. package/dist/server/telescope-ui.module.js.map +1 -1
  253. package/dist/server/telescope-ui.options.d.ts +63 -0
  254. package/dist/server/telescope-ui.options.d.ts.map +1 -1
  255. package/dist/server/telescope-ui.options.js.map +1 -1
  256. package/dist/spa/assets/index-BfyQ_2IN.css +1 -0
  257. package/dist/spa/assets/index-EBJiTdQF.js +159 -0
  258. package/dist/spa/index.html +2 -2
  259. package/package.json +75 -36
  260. package/skills/telescope-ui-dashboard/SKILL.md +203 -0
  261. package/dist/react/components/type-tabs.d.ts +0 -5
  262. package/dist/react/components/type-tabs.d.ts.map +0 -1
  263. package/dist/react/components/type-tabs.js +0 -8
  264. package/dist/react/components/type-tabs.js.map +0 -1
  265. package/dist/spa/assets/index-CCzTpxj3.css +0 -1
  266. package/dist/spa/assets/index-w0XTI5OM.js +0 -135
package/CHANGELOG.md CHANGED
@@ -1,7 +1,778 @@
1
1
  # @dudousxd/nestjs-telescope-ui
2
2
 
3
+ ## 3.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#183](https://github.com/DavideCarvalho/nestjs-telescope/pull/183) [`1a1d056`](https://github.com/DavideCarvalho/nestjs-telescope/commit/1a1d0560b680349d4e1ce142a61910ddebcb9ae2) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - One `authorize` option decides who may use the console.
8
+
9
+ - `TelescopeModule.forRoot({ authorize })` covers the API, the live stream and the MCP endpoint; `mcpAuthorize` replaces it on MCP alone. `TelescopeUiModule.forRoot({ authorize })` covers the page and its assets, and `TelescopeOtelMetricsModule.forExporter(exporter, { authorize })` covers `GET /metrics`. With `forRootAsync`, `authorize` can come from the factory.
10
+ - `authorize` takes a function `(ctx) => result`, an `@Injectable()` class implementing `ConsoleAuthorizer`, a lazy `() => import(...)`, `{ policy, ability }` for `@nestjs/authorization` (now an optional peer), or a gate name resolved by `@dudousxd/nestjs-authz`.
11
+ - It returns `true`/`false`, `{ allowed }`, `{ allowed: false, reason: 'unauthenticated' }` or `{ redirectTo }`. Denials answer `403 { error: 'forbidden' }` or `401 { error: 'unauthenticated' }`; `redirectTo` gives a 302 for a page navigation and a 401 with `redirectTo` for an API call. A throw denies. A denied page navigation gets an "Access denied" page (403) or a "Sign in required" page (401).
12
+ - New `authorizeByToken({ token })` checks `Authorization: Bearer <token>` in constant time, for coding agents on MCP and Prometheus on `/metrics`. `@UseConsoleAuthorize()` protects controllers you mount yourself with `routes: false`: with no argument it runs `TelescopeModule`'s `authorize`; list it above your authentication guard so that guard runs first. `ConsoleAuthorizeGuard` (or `ConsoleAuthorizeGuard.for({ authorize })`) and `ConsoleAuthorizeFilter` are the guard and the denial filter on their own.
13
+ - The dashboard follows `redirectTo` from a 401 and shows an access-denied screen on a 403. `createTelescopeClient` takes an `onAccessDenied` callback.
14
+
15
+ **Breaking:**
16
+
17
+ - With no `authorize`, the console is open outside production and answers 403 to every request in production, with one warning at boot (also with `routes: false`). The production boot refusal ("refusing to mount undecorated routes with NODE_ENV=production") is removed, and `decorators` no longer count as protection on their own: keep authentication in `decorators` and move the authorization check to `authorize`.
18
+ - `assertConsoleProtected`, `CONSOLE_DECORATORS_EXAMPLE` and `ConsoleProtectionHint` are removed.
19
+ - otel: `requireProtectionInProduction` is removed. `/metrics` follows the console's rule, so it answers 403 in production until you set `authorize`. **If a Prometheus scraper reads `/metrics` without credentials today, it starts getting 403 after this upgrade:** set `authorize: authorizeByToken({ token: process.env.METRICS_TOKEN })` and give the scraper that bearer token, or `authorize: () => true` to keep `/metrics` open when the network already restricts it.
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies [[`1a1d056`](https://github.com/DavideCarvalho/nestjs-telescope/commit/1a1d0560b680349d4e1ce142a61910ddebcb9ae2)]:
24
+ - @dudousxd/nestjs-telescope@3.0.0
25
+
26
+ ## 2.0.1
27
+
28
+ ### Patch Changes
29
+
30
+ - [#179](https://github.com/DavideCarvalho/nestjs-telescope/pull/179) [`3f6437a`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3f6437ae527264cc5acbbd4be7c0b9ab060cfdc9) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Republish the 2.0 release as 2.0.1. The 2.0.0 versions on npm are an old accidental build from 2026-06-05 (deprecated at the time), so `changeset publish` skipped them as "already published" and `^2.0.0` installs that June build. 2.0.1 is the real 2.0: the first-party NestJS integration and route protection release, with no further changes.
31
+
3
32
  ## 2.0.0
4
33
 
34
+ ### Major Changes
35
+
36
+ - [#178](https://github.com/DavideCarvalho/nestjs-telescope/pull/178) [`3222eae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3222eae574429a9807784ba413e8f0e43c3d98d3) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - **Breaking:** the page is now protected only with `@nestjs/authentication` + `@nestjs/authorization`. Add a `decorators` option to `TelescopeUiModule.forRoot` / `forRootAsync` (static top-level on the async variant): the class decorators go on the dashboard controller, which is also marked open to `@nestjs/authorization`'s deny-by-default global guard; a `Can()` passed in `decorators` is still enforced. With `NODE_ENV=production`, mounting the page with no `decorators` (an empty array counts as none) fails the boot (the rule checks that decorators are present, not that they enforce auth); `routes: false` always boots.
37
+
38
+ Removed: the `guards` and `dashboardAuth` options (and `unauthenticatedPage`), the SPA's sign-in screen, sign-out button and `client.auth.*` methods, `meta.auth` in the client types, and the session-minting console launcher (`openTelescopeConsole`, `mintTelescopeConsoleSession`, `telescopeConsoleSessionUrl`, `ConsoleSessionError`, `useOpenTelescopeConsole`, `openTelescopeConsoleMutationOptions`, `<OpenTelescopeConsoleButton>` and the `./react/console` subpath). `telescopeConsoleUrl` stays in `./client`.
39
+
40
+ Migration: `guards: [G]` → `decorators: [UseGuards(G), Can(Policy, 'view')]` (pass the same list to `TelescopeModule`); `dashboardAuth` → your app's own sign-in through `@nestjs/authentication` (the page authenticates from your app's cookie); the launcher → a plain link to `telescopeConsoleUrl(basePath)`.
41
+
42
+ - [#178](https://github.com/DavideCarvalho/nestjs-telescope/pull/178) [`3222eae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3222eae574429a9807784ba413e8f0e43c3d98d3) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add `routes: false` to `TelescopeUiModule.forRoot` / `forRootAsync` (static top-level on the async variant): no controller is mounted, and `TelescopeUiController` and `TELESCOPE_UI_OPTIONS` are now exported (the module exports the options provider), so you can serve the page from your own subclass protected with `@Can(...)` / `@UseGuards(...)`.
43
+
44
+ ### Minor Changes
45
+
46
+ - [#178](https://github.com/DavideCarvalho/nestjs-telescope/pull/178) [`3222eae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3222eae574429a9807784ba413e8f0e43c3d98d3) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Cards and nav entries for the entries `@dudousxd/nestjs-telescope-official` records from NestJS's first-party channels: `resilience` (Resilience), `workflow` (Nest workflows) and `auth` (Auth). Each card shows the event as a badge (red for a refusal or failure), its subject (the policy and entrypoint, the workflow and step, or the user / the denied `Policy.ability`), every scalar field the entry carries, the error, and the workflow output or status. The entries table labels them by event and subject, and labels `mail` entries `[status] subject`.
47
+
48
+ ### Patch Changes
49
+
50
+ - Updated dependencies [[`3222eae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3222eae574429a9807784ba413e8f0e43c3d98d3), [`3222eae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3222eae574429a9807784ba413e8f0e43c3d98d3), [`3222eae`](https://github.com/DavideCarvalho/nestjs-telescope/commit/3222eae574429a9807784ba413e8f0e43c3d98d3)]:
51
+ - @dudousxd/nestjs-telescope@2.0.0
52
+
53
+ ## 1.26.0
54
+
55
+ ### Minor Changes
56
+
57
+ - [#175](https://github.com/DavideCarvalho/nestjs-telescope/pull/175) [`a884482`](https://github.com/DavideCarvalho/nestjs-telescope/commit/a884482066c847c7dfbb24945f50c72ba5b92d51) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - AI usage and agent run timings are first-class.
58
+
59
+ - **AI screen** (`#/ai`): spend over the window (total, platform and estimated shares, by feature, model and top payers, over time), agent runs with a queue / model / tools bar, steps, tool calls, cost, model and status, the slowest and most expensive runs, a per-step breakdown with links to the trace waterfall, and the calls nobody priced. Backed by `GET <path>/api/ai`, summarized from the `aviary:agent:*` entries `@dudousxd/nestjs-agent` 1.38.0+ (core 0.60.0+) records. An app on an older agent lib gets an empty state that says so.
60
+ - **Metrics** `aviary_ai_cost_usd_total`, `aviary_ai_tokens_total`, `aviary_ai_audio_seconds_total`, `aviary_ai_requests_total` (counters, as Prometheus names them) and `aviary_agent_run_duration_ms`, `aviary_agent_run_queue_ms`, `aviary_agent_run_model_ms`, `aviary_agent_run_tools_ms`, `aviary_agent_tool_duration_ms` (histograms), read straight from the diagnostics channels. No actor, run or thread attributes.
61
+ - `aiMetrics: { prometheus: true }` serves them at `GET <path>/api/metrics` (Prometheus text format) behind the API gate. Off by default.
62
+ - `TelescopeAiMetricsExporter` (`-otel`) pushes them over OTLP/HTTP from a private `MeterProvider`, with its own endpoint. `@opentelemetry/sdk-metrics`, `@opentelemetry/exporter-metrics-otlp-http` and `@opentelemetry/resources` are optional, lazily loaded peers.
63
+ - An importable Grafana dashboard ships at `@dudousxd/nestjs-telescope-otel/grafana/aviary-ai-dashboard.json`.
64
+ - `StackedAreaChartCard` takes an optional `colors` map.
65
+
66
+ ## 1.25.0
67
+
68
+ ### Minor Changes
69
+
70
+ - [#165](https://github.com/DavideCarvalho/nestjs-telescope/pull/165) [`6a1ecea`](https://github.com/DavideCarvalho/nestjs-telescope/commit/6a1ecea013ea550167c362b69fcc74f27ec1c749) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `Page` gains an optional `partial` flag, and the dashboard says when a listing is incomplete
71
+
72
+ A store sets `partial: true` on a `get()` page when it answered from a subset of its data — a tiered store whose cold tier could not be read returns its hot entries this way instead of failing. The flag is additive (absent means complete), passes through `GET /entries`, `GET /profiles` and the MCP `list_entries` tool, and the entries list shows a non-blocking notice that older entries are unavailable while the rows it did get stay usable.
73
+
74
+ ## 1.24.1
75
+
76
+ ### Patch Changes
77
+
78
+ - fix(deps): update dependency @base-ui/react to v1.8.0 ([#138](https://github.com/DavideCarvalho/nestjs-telescope/issues/138))
79
+
80
+ ## 1.24.0
81
+
82
+ ### Minor Changes
83
+
84
+ - [`8d9f88f`](https://github.com/DavideCarvalho/nestjs-telescope/commit/8d9f88f0ef9653cf3e6cafdf47ceceecc4d2abef) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - The tag filter is a picker over what exists — searched and paged on the server
85
+
86
+ It was a text box with suggestions bolted on: it showed nothing until you typed, asked the server for
87
+ a **prefix** match, and then re-filtered and sliced the answer to ten in the browser. So it could
88
+ only suggest tags whose first characters you already knew, and the ten it kept were the ten it
89
+ happened to fetch.
90
+
91
+ - **Focus opens it.** The list is the answer to "what can I filter by", and requiring a first
92
+ character asks an operator to guess that answer before seeing it.
93
+ - **`search` matches anywhere in the tag** and runs on the server, before the bound. A picker that
94
+ narrowed its own fetched page could only ever find what the bound already let through — and on a
95
+ deployment with real tag cardinality, the bound is precisely what hides the value being looked for.
96
+ - **The list pages as it scrolls**, so the bound is a window rather than a ceiling.
97
+ - Typing something the list does not offer still applies as typed. A bounded list has to stay
98
+ escapable.
99
+
100
+ `GET /tags` gains `search`, `limit` and `offset`; `StorageProvider.tags` gains an optional `TagQuery`
101
+ and now promises an order (count descending, ties alphabetical) — an unordered listing cannot be
102
+ paged, because page two would be cut from a different arrangement of the same rows. A provider that
103
+ ignores the query stays correct: the controller re-applies the page, so a picker never renders more
104
+ than it asked for.
105
+
106
+ ## 1.23.1
107
+
108
+ ### Patch Changes
109
+
110
+ - [`80392f0`](https://github.com/DavideCarvalho/nestjs-telescope/commit/80392f0e4d038b50b5c8703275cfd91e3328064b) - Add NestJS 12 to the supported peer range.
111
+
112
+ NestJS 12.0.1 shipped the framework as pure ESM and raised its floor to Node >= 20.19. Telescope's
113
+ packages are already `"type": "module"`, so the ESM shift is a non-event here — nothing needed
114
+ porting, and every watcher, storage provider and dashboard route behaves identically on 11 and 12.
115
+
116
+ The declared peer ranges (`@nestjs/common`/`@nestjs/core` at `>=10.0.0`, `>=11.0.0` for the UI
117
+ package, and the satellite peers `@nestjs/bullmq`, `@nestjs/cache-manager`, `@nestjs/event-emitter`,
118
+ `@nestjs/schedule`) already admitted a 12.x resolution, so no range changed — what changed is that
119
+ the claim is now tested. Every package's dev and test matrix moved to the 12.x line, along with the
120
+ example app and the memory-soak harness, and the whole suite builds, typechecks and passes there.
121
+
122
+ Moving the example app and the soak harness matters for more than tidiness: leaving them on
123
+ `@nestjs/platform-express@11` left a second `@nestjs/platform-express` major in the workspace, and
124
+ pnpm's peer resolution then produced two distinct `@nestjs/core@12` instances. `tsc` correctly
125
+ rejected the resulting `ModuleRef` from one instance where the other was expected. That is a
126
+ workspace resolution artifact rather than a NestJS 12 incompatibility, but it is exactly the shape
127
+ of breakage a consumer sees when they upgrade half a dependency graph.
128
+
129
+ No behaviour change — peer ranges only affect what a package manager warns about on install.
130
+
131
+ ## 1.23.0
132
+
133
+ ### Minor Changes
134
+
135
+ - [#82](https://github.com/DavideCarvalho/nestjs-telescope/pull/82) [`d245d70`](https://github.com/DavideCarvalho/nestjs-telescope/commit/d245d7093fdb748fa398d6dd1c8b24d4110cbfbb) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Let a dashboard section declare `cols: 1` — the full-width row.
136
+
137
+ A section renders as a fixed `grid-cols-N` grid, one panel per cell, with no `colSpan`, and `cols`
138
+ only accepted `2 | 3 | 4`. So the widest panel a dashboard has — a table with ten or more columns —
139
+ could not be given a row of its own. Declared in the narrowest grid available it still got half the
140
+ viewport, scrolled sideways inside its own card, and left the cell beside it empty.
141
+
142
+ That is not hypothetical: `@dudousxd/nestjs-durable-telescope` ships its eleven-column worker table
143
+ as the only panel of a `cols: 2` section, which on a 1418px viewport rendered a 1406px table into a
144
+ 575px card with a hole next to it. There was no way to express the fix.
145
+
146
+ Additive: `cols` gains `1`, existing values behave identically, and the grid needs no new responsive
147
+ class for it — it is already `grid-cols-1` at every breakpoint, so a full-width row is the absence
148
+ of an override rather than the presence of one.
149
+
150
+ ## 1.22.1
151
+
152
+ ### Patch Changes
153
+
154
+ - [#80](https://github.com/DavideCarvalho/nestjs-telescope/pull/80) [`1378075`](https://github.com/DavideCarvalho/nestjs-telescope/commit/137807549d64889222d9119534b8e8be4d5c7651) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Stop a provider that ignores `query.page` from freezing a paged table's pager.
155
+
156
+ `PagerControls` read its current page off the **response**, and its handlers computed `page ± 1`
157
+ from that. When a provider answers with a pinned page — as `@dudousxd/nestjs-agent-telescope`'s
158
+ did, reading `?page=2` as a string and returning page 1 for every request — the result was not a
159
+ stale number but a dead control: the first click moves the caller's state to 2 and refetches, the
160
+ response still says 1, so the second click asks for 2 again, the state does not change, React skips
161
+ the re-render, and neither button does anything again short of a page reload. Prev never re-enables.
162
+
163
+ `PanelView` now takes an optional `requestedPage` — the page the caller has asked for — and prefers
164
+ it over the response's. `ExtensionDashboardPage` passes its own paging state, so every click keeps
165
+ advancing and the worst a broken provider can do is fail to change the rows: visible, and
166
+ recoverable with Prev. Panels whose provider honours paging are unaffected, since the two values
167
+ agree; a caller that passes no `requestedPage` still reads the page off the response exactly as
168
+ before.
169
+
170
+ ## 1.22.0
171
+
172
+ ### Minor Changes
173
+
174
+ - [#78](https://github.com/DavideCarvalho/nestjs-telescope/pull/78) [`5832622`](https://github.com/DavideCarvalho/nestjs-telescope/commit/58326225e99fa134997d85e5e81952d7d505ad1c) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Rebuild the chart cards on a vendored shadcn chart layer, and give them a legend, a brush and
175
+ drill-down.
176
+
177
+ Every chart in the console — the Overview throughput and by-type areas, the queues bars, the entry
178
+ insights, and every chart-shaped extension panel — was hand-rolled Recharts: each card restated its
179
+ own grid stroke, axis stroke, tick style and the three `contentStyle`/`labelStyle`/`itemStyle`
180
+ objects that make up Recharts' single line of tooltip text. Series colours lived in two places, and
181
+ chrome colours in seven.
182
+
183
+ - **`src/react/ui/chart.tsx`** joins the vendored primitives: `ChartContainer`, `ChartTooltip`,
184
+ `ChartTooltipContent`, `ChartLegend`, `ChartLegendContent` and the `ChartConfig` type, retuned to
185
+ the Aviary tokens. Two departures from upstream shadcn, both deliberate: chart chrome is styled by
186
+ selecting Recharts' own class names off the container (a stylesheet rule beats an SVG presentation
187
+ attribute), and the per-series `--color-<key>` custom properties are set inline instead of through
188
+ shadcn's `dangerouslySetInnerHTML` style tag — a config that can come from an extension-authored
189
+ dashboard spec should not reach an HTML-injection-shaped API.
190
+ - **`chart-theme.ts` is now the only place a series colour is decided**, and the chrome constants it
191
+ used to export are deprecated rather than removed. Breakdown segments resolve through the same
192
+ entry-type hues as everything else, so an `exception` segment is no longer amber in a donut and red
193
+ in the chart beside it.
194
+ - **Rich hover cards** everywhere: label row, one row per series with its own swatch, configured name
195
+ and formatted value. Everything read out of a Recharts payload goes through runtime guards — a
196
+ provider can return anything, and a tooltip rendering `[object Object]` is a bug nobody notices
197
+ until it is in a screenshot.
198
+ - **Clickable legends** on the multi-series charts: an entry toggles its series, which restacks the
199
+ rest. With six entry types stacked, switching the big ones off is the only way to read the small
200
+ ones. The entries are the vendored `Button` with `aria-pressed`, not bare `<button>`s.
201
+ - **Brush on the timeseries charts**, so a long window can be narrowed in place. It appears once a
202
+ series passes 24 points and can be forced either way with `brush`; below that it is a scrollbar for
203
+ a page that already fits.
204
+ - **Drill-down.** A chart-shaped panel that declares `drilldown: { param }` becomes clickable: the
205
+ click sets one dashboard-wide selection, every panel re-resolves with that param merged onto its
206
+ own `data.query`, and a chip in the header says what is filtered (clicking the same item again, or
207
+ the chip's Clear, undoes it). A panel that declares no `drilldown` gets no click handler at all, so
208
+ every dashboard shipping today behaves exactly as before. The cards themselves take a typed
209
+ `onSelect(selection)` and know nothing about dashboards; `BarChartCard.onBarClick` still fires and
210
+ is deprecated in favour of it.
211
+ - Two bugs the rewrite surfaced: a `gauge` panel drew a full half-dial whatever it was reporting
212
+ (with one datum and no explicit polar domain, Recharts scales the arc to the value itself), and a
213
+ `distribution` panel's percentile chips pushed its chart out of the card by exactly their own
214
+ height.
215
+
216
+ - [#77](https://github.com/DavideCarvalho/nestjs-telescope/pull/77) [`b330bfc`](https://github.com/DavideCarvalho/nestjs-telescope/commit/b330bfce9b025dc5bfe1c695eaaeeea80b007f82) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Rebuild the extension-dashboard `table` panel on TanStack Table v9, and give it server-side sort
217
+ and filter.
218
+
219
+ A table panel could only ever be read. Sorting it was not an oversight so much as an unanswered
220
+ question: the panel holds the one page its provider returned, so a sort added in the browser would
221
+ have ordered 50 rows out of 50,000 and presented the result as "the slowest runs" — a control that
222
+ is wrong precisely when someone is relying on it. The answer here is that the choice goes back to
223
+ the provider, which is the only party that can see the whole result set.
224
+
225
+ **The new SPI** (additive; existing panels and providers are untouched). A column opts in:
226
+
227
+ ```ts
228
+ columns: [
229
+ { key: "runId", label: "Run", filterable: true },
230
+ { key: "duration", label: "Duration", sortable: true },
231
+ { key: "worker", label: "Worker", hideable: true },
232
+ ];
233
+ ```
234
+
235
+ - `sortable` — the header becomes a button cycling ascending → descending → unsorted, and the
236
+ provider is re-resolved with `sort=<key>` + `dir=asc|desc` (both absent when unsorted).
237
+ - `filterable` — a filter box appears under the header, committed on Enter or blur, sent as
238
+ `filter.<key>=<text>`. Namespaced because a panel scoped to `{ status: 'running' }` carrying a
239
+ filterable `status` column is an ordinary combination, and unprefixed one would silently
240
+ overwrite the other.
241
+ - `hideable` — the column joins the panel's **Columns** menu. Display-only; it never reaches the
242
+ provider, and the menu is absent entirely when no column opts in.
243
+
244
+ Providers read the state with the new `readTableQuery(query)` from `@dudousxd/nestjs-telescope`,
245
+ which returns `{ page?, limit?, sort?: { key, dir }, filters }`. Reading it by hand is the trap it
246
+ exists to close: every value arrives as a **string** off the URL, so `query.page > 1` is silently
247
+ `false` for `'2'`, and `?page=banana` reaches a `LIMIT` clause as `NaN`. It also drops an emptied
248
+ filter box (`filter.status=` means _no_ filter, not "match the empty string") and reads an
249
+ unrecognized direction as ascending.
250
+
251
+ Backward compatibility is the point, not a footnote: a provider that ignores the new params behaves
252
+ exactly as before, and a panel whose columns declare none of the three flags renders the identical
253
+ table — no header buttons, no filter row, no column menu, and the byte-identical query on the wire.
254
+ The query builder returns the panel's own `data.query` _by identity_ when there is nothing to merge,
255
+ so existing dashboards do not even move to a new React Query cache key.
256
+
257
+ - **v9's explicit feature model.** `table-features.ts` is the package's only module that imports
258
+ `@tanstack/react-table`; everything else consumes the hook, `flexRender` and the pre-bound types
259
+ through it. v9 installs a feature's state and instance methods only when it is registered, and an
260
+ unregistered feature is not an error — the method is simply gone, which at the call site is
261
+ indistinguishable from "removed in v9". A contract spec asserts every API the table actually calls.
262
+ - **No client row models are registered**, deliberately. `createSortedRowModel` and friends would
263
+ re-do server-side work over the single page in hand, which is the exact failure this change exists
264
+ to avoid. `rowPaginationFeature` and `columnSizingFeature` are left out too — the pager is driven
265
+ by what the provider returned, and column sizing would default every column of every dashboard to
266
+ a fixed 150px.
267
+ - **A paged table's rows now scroll under a pinned header**, capped at 28rem. A 50-row page was
268
+ otherwise a ~1,500px card that pushed every panel below it off screen, and by row 40 the column
269
+ labels were long gone. A short page never reaches the cap and is unchanged.
270
+ - The vendored `Table` primitive gains `containerClassName`. That wrapper div is already a scroll
271
+ container, so it is the element a sticky header positions against — a `max-h-*` applied anywhere
272
+ further out gives a scrolling ancestor the sticky cells cannot see.
273
+ - `@tanstack/react-table` is declared as an optional peer dependency, alongside `recharts` and
274
+ `@tanstack/react-query`: the bundled SPA carries its own copy, so only a host composing these
275
+ React components itself needs to install it.
276
+
277
+ ## 1.21.1
278
+
279
+ ### Patch Changes
280
+
281
+ - [#75](https://github.com/DavideCarvalho/nestjs-telescope/pull/75) [`f5ba9bf`](https://github.com/DavideCarvalho/nestjs-telescope/commit/f5ba9bf29c16b55244cf05e41f12e31a202919cd) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Stop an extension dashboard's tables from painting outside their cards.
282
+
283
+ A `table` panel was the one surface in the console still rendering hand-rolled `<table>` markup:
284
+ it never moved onto the vendored shadcn `Table` when that primitive landed, so it also never got
285
+ the scroll container the primitive brings. That matters because a section lays its panels out as
286
+ a `grid-cols-N` of equal cells, and a card is routinely narrower than the table inside it — a
287
+ 7-column table in a `cols: 3` section. `w-full` is only a _preferred_ width: `table-layout: auto`
288
+ still lays the table out at its min-content width, and with nothing clipping it the remaining
289
+ columns painted over the neighbouring panel.
290
+
291
+ Measured on flip's `durable.workflows` dashboard: a 379px card holding a 633px "Worker health"
292
+ table (its `STATUS` column landed on top of the panel to its right), and a 575px card holding a
293
+ 1154px "Workers" table. `agent.overview` had the same break — `COST (USD)` and `LAST ACTIVITY`
294
+ rendered outside their cards.
295
+
296
+ - Both table variants, paged and not, now render through `Table`/`TableHeader`/`TableBody`/
297
+ `TableRow`/`TableHead`/`TableCell`, so a table too wide for its card scrolls inside it and
298
+ every panel keeps the same row height, header casing and divider colour as the rest of the app.
299
+ - Cells are `whitespace-nowrap`. Wrapping is what turned durable's "Workers" panel into
300
+ three-line rows; with a scroller in place, a long worker id belongs on one line behind a
301
+ scrollbar.
302
+ - The pager's prev/next are the vendored `Button` (`outline`/`xs`) rather than bare `<button>`s.
303
+
304
+ ## 1.21.0
305
+
306
+ ### Minor Changes
307
+
308
+ - [#73](https://github.com/DavideCarvalho/nestjs-telescope/pull/73) [`5203512`](https://github.com/DavideCarvalho/nestjs-telescope/commit/520351225a9f0f754e11cc7ef3e28a17d4499d11) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Adopt the Aviary console design tokens and a vendored shadcn-on-Base-UI primitive layer.
309
+
310
+ The dashboard was authored against hardcoded Tailwind `zinc`/`emerald` utilities — the only
311
+ Aviary console with no token vocabulary at all. It now declares the canonical tokens from
312
+ `AVIARY-UI.md` (`--bg`, `--panel`, `--panel-2`, `--line`, `--line-soft`, `--text`, `--muted`,
313
+ `--good`, `--warn`, `--bad`, `--live`, `--accent`) and consumes them through Tailwind semantic
314
+ colours, so it reads as a sibling of the agent, durable and media consoles rather than a stranger.
315
+
316
+ - **Light mode actually works.** It was a block of `.light .bg-zinc-900 { … }` overrides that
317
+ covered only the utilities someone had remembered to list, leaving mid-grey panels and
318
+ unreadable status text. The tokens are now declared twice — dark under `:root`, light under
319
+ `.light` — so every token-based surface, border, label and status flips with the theme.
320
+ - **Telescope's accent is magenta** (`#e879f9`). Its de-facto accent was emerald-400, which is
321
+ byte-identical to `--good`: the same hue meant both "healthy" and "interactive", side by side
322
+ on the Overview page. Magenta clears `--good`, `--warn`, `--bad`, `--live`, agent's violet and
323
+ media's cyan.
324
+ - **Vendored shadcn primitives** under `src/react/ui/` on Base UI — `Button`, `Badge`, `Input`,
325
+ `Table`, `Select`, `Tabs`, `Tooltip`, `Dialog` — wired so shadcn's semantic classes resolve to
326
+ the Aviary tokens. Note that shadcn's `accent` is a _hover surface_, so the brand hue is
327
+ exposed as `brand`/`--accent` and never as `bg-accent`.
328
+ - The command palette and the queue job drawer are now one `Dialog` implementation instead of
329
+ two hand-rolled overlays, and the two native `<select>` popups (which the OS drew, unthemed)
330
+ are now themable listboxes.
331
+
332
+ `cacheBadge()` and `inertiaBadges()` gain a `variant` field naming the semantic `Badge` variant.
333
+ `className` is unchanged in shape and still returned, so existing callers keep working; prefer
334
+ `variant` in new code.
335
+
336
+ Also fixes a long-standing packaging bug this work's bundle check surfaced: the `./react` barrel
337
+ reaches `recharts`, `react-router-dom` and `reflect-metadata`, none of which this package declared
338
+ — the exact failure that once broke a host's client build. They are now declared as optional peer
339
+ dependencies. `@base-ui-components/react`, `class-variance-authority`, `clsx` and `tailwind-merge`
340
+ are `dependencies`, because this package publishes React source that hosts bundle. A new
341
+ `published-graph.spec.ts` bundles each published entry with esbuild and asserts every package it
342
+ reaches is declared, so this cannot regress silently.
343
+
344
+ ### Patch Changes
345
+
346
+ - [#71](https://github.com/DavideCarvalho/nestjs-telescope/pull/71) [`68e4052`](https://github.com/DavideCarvalho/nestjs-telescope/commit/68e40523200c699dae3a376752b8604d3d68ef1f) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Bound the React peer ranges to the majors this package is actually built and tested against.
347
+
348
+ `react` and `react-dom` were declared `>=18.0.0` and `@tanstack/react-query` `>=5.0.0` — open-ended
349
+ ranges that claim support for every future major. React 20 and Query 6 do not exist yet and
350
+ certainly are not tested here, so the declaration was a promise the package cannot keep; a consumer
351
+ on a future major would get no warning and a runtime surprise instead.
352
+
353
+ Now `^18.0.0 || ^19.0.0` and `^5.0.0`. Both majors are real: the package builds and tests against
354
+ React 18, and its `./react/console` launcher is consumed from a React 19 host.
355
+
356
+ No behaviour change — peer ranges only affect what a package manager warns about on install.
357
+
358
+ ## 1.20.1
359
+
360
+ ### Patch Changes
361
+
362
+ - [#69](https://github.com/DavideCarvalho/nestjs-telescope/pull/69) [`acdfacb`](https://github.com/DavideCarvalho/nestjs-telescope/commit/acdfacba22495b6517bf89d7e50a25d3032c1933) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Clear the console launcher's pending state when the page is restored from the back/forward cache.
363
+
364
+ `useOpenTelescopeConsole` deliberately keeps `isPending` set after a successful mint so the button
365
+ does not flicker back to idle on a page that is navigating away. With bfcache the page does not die:
366
+ pressing Back restored the launcher with its React state intact, leaving a permanent "Opening…"
367
+ spinner on a button that could never be clicked again. The hook now listens for `pageshow` and
368
+ resets only when `event.persisted` is true, so a fresh load and a mint that is still genuinely in
369
+ flight both keep the original behaviour.
370
+
371
+ ## 1.20.0
372
+
373
+ ### Minor Changes
374
+
375
+ - [#67](https://github.com/DavideCarvalho/nestjs-telescope/pull/67) [`87c8732`](https://github.com/DavideCarvalho/nestjs-telescope/commit/87c8732006a46eeea50d1dd1536c8a9794a13ae5) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Ship the console launcher on its own subpath: `@dudousxd/nestjs-telescope-ui/react/console`.
376
+
377
+ The launcher was only reachable from the `./react` barrel, which re-exports the whole dashboard
378
+ component set and therefore pulls in `recharts` and `react-router-dom`. Neither is declared by this
379
+ package, and a bundler resolves re-exported modules even when nothing references them — so a host
380
+ that wanted nothing but the launcher button got a hard build failure on a missing dependency
381
+ instead of tree-shaking it away.
382
+
383
+ `./react/console` re-exports the hook, the button and the headless client primitives, and nothing
384
+ else. Its entire dependency footprint is `react`, guarded by a test that walks the import graph.
385
+
386
+ `./react` is unchanged and still exports the launcher, so nothing breaks.
387
+
388
+ ## 1.19.0
389
+
390
+ ### Minor Changes
391
+
392
+ - [#65](https://github.com/DavideCarvalho/nestjs-telescope/pull/65) [`be97836`](https://github.com/DavideCarvalho/nestjs-telescope/commit/be978365f716cbe9e120b4024abb5d0ff6129d3d) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - **React tier for the console launcher: `useOpenTelescopeConsole` and `<OpenTelescopeConsoleButton>`, exported from `@dudousxd/nestjs-telescope-ui/react`.**
393
+
394
+ The headless `openTelescopeConsole` gave hosts the mint-then-navigate call; every host then wrote the
395
+ same three lines of React around it — an `isPending` flag, an error slot, and a button that has to
396
+ remember not to fire twice. Three tiers now, pick the one that fits and drop a level when it stops
397
+ fitting:
398
+
399
+ ```tsx
400
+ import {
401
+ OpenTelescopeConsoleButton,
402
+ useOpenTelescopeConsole,
403
+ openTelescopeConsoleMutationOptions,
404
+ } from "@dudousxd/nestjs-telescope-ui/react";
405
+
406
+ // 1. drop-in
407
+ <OpenTelescopeConsoleButton
408
+ className="btn btn-primary"
409
+ headers={authHeaders}
410
+ />;
411
+
412
+ // 2. state only, your markup
413
+ const { open, isPending, error, reset } = useOpenTelescopeConsole({
414
+ headers: authHeaders,
415
+ });
416
+
417
+ // 3. openTelescopeConsole(...) from `/client` — no React at all
418
+ ```
419
+
420
+ - `open()` **never rejects**; the refusal lands in `error` as a `ConsoleSessionError` (with its
421
+ `status` and `url`). It deliberately does not clear `isPending` on success, because the navigation
422
+ is already underway and flipping back to idle flickers "ready to click again" on a page that is
423
+ leaving.
424
+ - `<OpenTelescopeConsoleButton>` is **unstyled on purpose** — a bare `<button>` that forwards
425
+ `className`/`style`/every other button prop, so it inherits the host's design system instead of
426
+ importing CSS that fights it. It renders inside the host app, not inside Telescope's own bundle.
427
+ It disables itself and sets `aria-busy` while in flight, and renders the refusal as
428
+ `<p role="alert">` by default: a launcher that silently does nothing reads as broken rather than
429
+ forbidden. `renderError` substitutes that node; `renderError={null}` opts out entirely.
430
+ - `openTelescopeConsoleMutationOptions()` returns the `{ mutationKey, mutationFn }` shape
431
+ `useMutation` takes, so a host already on TanStack Query gets the launcher in its cache, devtools
432
+ and error handling with no adapter — and this package still never imports `@tanstack/react-query`.
433
+
434
+ React stays an optional peer dependency: a host that only mounts the NestJS module pulls none of
435
+ this in. Additive only; nothing existing changes.
436
+
437
+ ## 1.18.0
438
+
439
+ ### Minor Changes
440
+
441
+ - [#63](https://github.com/DavideCarvalho/nestjs-telescope/pull/63) [`2d0e76e`](https://github.com/DavideCarvalho/nestjs-telescope/commit/2d0e76ee52e3a556c24b567fdb4b9e6cef25c067) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - **Headless console launcher: `openTelescopeConsole` / `mintTelescopeConsoleSession` / `telescopeConsoleSessionUrl`, exported from `@dudousxd/nestjs-telescope-ui/client`.**
442
+
443
+ The console is entered from the HOST's app: a browser navigation to it carries no identity, so
444
+ something inside the host has to mint the Mode A session cookie first (an XHR that _does_ carry the
445
+ host's auth), then navigate. Every host was writing that by hand, which meant hardcoding two things
446
+ this package owns:
447
+
448
+ - **the session endpoint's path** — `<path>/api/auth/session`. Nothing tells a host when that moves; the break
449
+ only shows up as a runtime 404 after a version bump.
450
+ - **`redirect: 'manual'`** — and this one is a real trap. `fetch` follows redirects by default, so a
451
+ host whose auth layer rewrites a 401 into a sign-in redirect gets a resolved 200 against the
452
+ sign-in HTML. `response.ok` reads true, the caller navigates, and the user lands in a console with
453
+ no session — indistinguishable from a permissions bug. The helper detects the redirect (browser
454
+ opaque response _and_ Node/undici 3xx) and throws a message naming the likely cause.
455
+
456
+ ```ts
457
+ import { openTelescopeConsole } from "@dudousxd/nestjs-telescope-ui/client";
458
+
459
+ await openTelescopeConsole({
460
+ headers: () => ({ Authorization: `Bearer ${token()}` }),
461
+ });
462
+ ```
463
+
464
+ No UI: the host owns the button, the page and the copy. `headers` accepts a sync or async function
465
+ so a refreshing token is read at call time rather than captured at wiring time. `fetch` and
466
+ `navigate` are injectable (tests, routers, non-browser callers). A refused mint throws
467
+ `ConsoleSessionError` (carrying `status` and `url`) and **does not navigate** — a denied user gets a
468
+ real error instead of the console's "no session" page.
469
+
470
+ Additive only: nothing existing changes.
471
+
472
+ ## 1.17.0
473
+
474
+ ### Minor Changes
475
+
476
+ - [#61](https://github.com/DavideCarvalho/nestjs-telescope/pull/61) [`c3ee743`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c3ee743fd7c51b34dcc2b349d07659ce9d5d7cbe) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - **`dashboardAuth.unauthenticatedPage` — hosts can now render the dashboard's unauthenticated page themselves.**
477
+
478
+ Under Mode A, a visitor navigating straight to `/telescope` with no cookie got the SPA shell, which
479
+ then rendered the built-in auth screen: _"open this console from your application."_ Deliberately
480
+ generic, because the library cannot know who hosts it — it can't name the host's launcher, link to
481
+ it, or look like the rest of the host's product.
482
+
483
+ ```ts
484
+ const dashboardAuth = {
485
+ secret: process.env.TELESCOPE_AUTH_SECRET,
486
+ session: (request) => resolveAdmin(request),
487
+ unauthenticatedPage: ({ request, response, basePath }) => {
488
+ (response as Response)
489
+ .status(401)
490
+ .render("console-locked", { returnTo: basePath });
491
+ },
492
+ };
493
+
494
+ TelescopeModule.forRoot({ enabled: true, dashboardAuth });
495
+ TelescopeUiModule.forRoot({ dashboardAuth }); // <- also here; see below
496
+ ```
497
+
498
+ Telescope's auth screen is a React component inside the published bundle — there is no
499
+ server-rendered page to replace. So the hook gates the **SPA shell route**: the session is checked
500
+ before the shell is served, which also means the bundle no longer loads at all for a visitor with no
501
+ session. Hashed assets stay ungated; they carry no data.
502
+
503
+ **`TelescopeUiModule` gains a `dashboardAuth` option, and it must be given the same config.**
504
+ `TelescopeModule` (core) gates the JSON API; `TelescopeUiModule` serves the HTML, so it is the only
505
+ one that can replace that page — the same independence the two modules' `guards` options already
506
+ have. Setting `unauthenticatedPage` on core alone changes nothing a visitor sees. Only `secret`, the
507
+ configured modes and `unauthenticatedPage` are read there; minting and renewal stay core's job.
508
+
509
+ **Mode-A-only by design.** With `login` configured the hook is ignored: under Mode B the login form
510
+ the visitor needs is _inside_ the bundle this page would replace, so gating the shell would lock a
511
+ Mode B host out of its own dashboard.
512
+
513
+ Fail-closed by construction: it only runs when the request carries no valid session cookie (a cookie
514
+ signed with another secret does not count), and every data route stays behind `TelescopeGuard`
515
+ regardless. A hook that throws, or returns without writing, logs one warning and serves the SPA
516
+ rather than hanging the request or turning a navigation into a `500`.
517
+
518
+ The UI shell route became non-passthrough `@Res()` so the host can own the response; its
519
+ `Content-Type`/`Cache-Control` moved from decorators into a new `sendHtml` helper, unchanged. Core
520
+ additionally exports `sendHtml` / `responseAlreadyWritten`.
521
+
522
+ Fully backward compatible — omit the option and the shell is served exactly as before, with no
523
+ session check on that route at all.
524
+
525
+ ## 1.16.1
526
+
527
+ ### Patch Changes
528
+
529
+ - [`c07d42a`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c07d42a756e77b54ec92db4249ec060d9556e21c) - Docs + regression tests confirming `dashboardAuth`'s `login` hook already receives the submitted password verbatim end-to-end — including an empty string, since the built-in login screen never marks the field `required` and the auth controller only checks it's a string, not a non-empty one. No code path was blocking empty passwords; this closes the gap for hosts whose `login` hook gates on username alone (e.g. email must be an active admin) and deliberately ignores the password. Documented the pass-through in the `dashboardAuth` reference and added tests asserting: the hook is called with `''`, a hook rejecting an empty password still uniform-fails with `401`, and a hook accepting one mints the session.
530
+
531
+ ## 1.16.0
532
+
533
+ ### Minor Changes
534
+
535
+ - [`46f6bd4`](https://github.com/DavideCarvalho/nestjs-telescope/commit/46f6bd491c962f694ed41066f47da4da29671c3f) - First-class `guards` (+ `imports`) options for the Telescope console, mirroring `@dudousxd/nestjs-agent`'s dashboard module: `TelescopeModule.forRoot`/`forRootAsync` and `TelescopeUiModule.forRoot`/`forRootAsync` now accept `guards: Array<Type<CanActivate> | CanActivate>` fronting the console's controllers, plus `imports` resolving a class guard's own dependencies.
536
+
537
+ This closes the auth seam for hosts with header-only auth: a full-page navigation to the dashboard carries no `Authorization` header, so there was previously no way to hang a cookie/session guard on the page itself. Pass the SAME `guards` (and `imports`) to both modules — they're independent options in separate packages. On core, `guards` **appends** to (never replaces) the existing `TelescopeGuard` gate (`authorizer` / `dashboardAuth` / dev-open-prod-closed default); on `-ui`, `TelescopeUiController` had no guard of its own, so it's a plain replace. See the new "Securing the console with your own guards" docs section.
538
+
539
+ ## 1.15.0
540
+
541
+ ### Minor Changes
542
+
543
+ - [`dbaf8d2`](https://github.com/DavideCarvalho/nestjs-telescope/commit/dbaf8d2c00c08bc03c8df874cabc0c48741d712f) - Ext-dashboard tables paginate: a table panel may declare `paged: true` — the renderer adds
544
+ prev/next + "Page X of Y" and re-resolves the provider with `query.page`/`query.limit`, expecting
545
+ `{ rows, total, page, limit }`. Non-paged tables are byte-identical. `LinkSpec` documents the two
546
+ href conventions (in-app hash routes — including the trace view at `#/traces/{traceId}` — vs host
547
+ console paths); hash hrefs already navigate in-app.
548
+
549
+ ## 1.14.0
550
+
551
+ ### Minor Changes
552
+
553
+ - [#33](https://github.com/DavideCarvalho/nestjs-telescope/pull/33) [`996c9d4`](https://github.com/DavideCarvalho/nestjs-telescope/commit/996c9d442eb16f581acf67aca9ead84ce3bbc6c1) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Make cache observability cache-agnostic and richer. `CacheContent` keeps its
554
+ universal core (`operation`/`key`/`hit`) and gains optional, vendor-neutral
555
+ dimensions any cache can map onto: `store`, `tier` (e.g. l1/l2), `stale`
556
+ (stale-while-revalidate / BentoCache grace), `ttlMs`, and a `metadata` escape
557
+ hatch. `operation` widens to include `delete`/`clear`. The cache watcher's
558
+ `{ instrument }` event carries these through (and surfaces store/tier/stale as
559
+ tags), and the auto-patch path now records the set TTL and wraps `del`/`delete`
560
+ so deletes are their own operation instead of being invisible or mislabelled.
561
+ The stats endpoint splits hits/misses by tier and counts stale hits + deletes,
562
+ and the Cache insights view renders per-tier hits, stale hits and delete counts;
563
+ the cache badge labels DEL/CLEAR and flags stale (amber) + tier. Simple caches
564
+ that don't supply the optional fields are unchanged.
565
+
566
+ ## 1.13.0
567
+
568
+ ### Minor Changes
569
+
570
+ - [#31](https://github.com/DavideCarvalho/nestjs-telescope/pull/31) [`53c944f`](https://github.com/DavideCarvalho/nestjs-telescope/commit/53c944f8dd66540813c1ea45fa751b192a0946b3) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Cross-link entries and harden extension empty states. The entry detail view gains
571
+ a "Related" panel — jump to every entry of the same family, to the originating
572
+ request a non-request entry was recorded under, or to the queue console for a job.
573
+ Extension dashboard `table` and `topN` panels now render an explicit "No data in
574
+ this window" state instead of a blank/broken-looking panel.
575
+
576
+ - [#31](https://github.com/DavideCarvalho/nestjs-telescope/pull/31) [`6e66ec3`](https://github.com/DavideCarvalho/nestjs-telescope/commit/6e66ec355487c488c8290a13acb99b619895f28d) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add Prunes and Exports screens. The pruner now records every prune run (trigger,
577
+ duration, total deleted and real per-type deletions) in a bounded in-memory ring
578
+ exposed at `GET /prunes` alongside the resolved retention config and the predicted
579
+ next run; a new Prunes page surfaces that activity with per-type chips and a gated
580
+ "Prune now". A new Exports page exports filtered entries (by type / window /
581
+ search) to JSON or CSV entirely client-side, with a session export history and a
582
+ dependency-free CSV serializer.
583
+
584
+ - [#31](https://github.com/DavideCarvalho/nestjs-telescope/pull/31) [`bdc2198`](https://github.com/DavideCarvalho/nestjs-telescope/commit/bdc21987bc90eae6fc95995c99d3de46f0f4fc89) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Surface whether a scheduled cron is currently active. `ScheduledTask` gains a
585
+ `running: boolean | null` field; the `@nestjs/schedule` watcher reads the
586
+ `CronJob.running` flag (null for intervals/timeouts, which expose no state), and
587
+ the Schedules console renders an Active/Stopped badge plus an active/stopped
588
+ summary so a registered-but-stopped cron is obvious at a glance.
589
+
590
+ ## 1.12.1
591
+
592
+ ### Patch Changes
593
+
594
+ - [#29](https://github.com/DavideCarvalho/nestjs-telescope/pull/29) [`a79c3fa`](https://github.com/DavideCarvalho/nestjs-telescope/commit/a79c3fa6f77c8d02a4150325f082d66d9fdc9227) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Ship TanStack Intent agent skills (SKILL.md) inside the package.
595
+
596
+ ## 1.12.0
597
+
598
+ ### Minor Changes
599
+
600
+ - [`74a6563`](https://github.com/DavideCarvalho/nestjs-telescope/commit/74a6563557521edec413b0691a011f9568e140cc) - Surface the Inertia v3 render metadata in the entry detail UI: new **Prepend** and **Rescued** prop rows, a **Once cache** section (cache key → prop, with expiry), a **Scroll** section showing the infinite-scroll cursor (`prev ← current → next`, reset), and **except-once** chips on the partial-reload panel. All fields are read defensively, so older diagnostic payloads still render.
601
+
602
+ ## 1.11.0
603
+
604
+ ### Minor Changes
605
+
606
+ - [#17](https://github.com/DavideCarvalho/nestjs-telescope/pull/17) [`76228a3`](https://github.com/DavideCarvalho/nestjs-telescope/commit/76228a3a33e4e6d649cd8343646472feff97ed16) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - On-demand CPU flamegraph profiling. A new strictly-opt-in `profiling` option
607
+ (OFF by default) captures V8 CPU profiles around sampled or manually-triggered
608
+ requests via Node's `inspector`, aggregates them into a self/total frame tree,
609
+ and persists them as `cpu_profile` entries correlated to their request batch.
610
+ The dashboard gains a Profiles tab with a dependency-light flamegraph renderer,
611
+ a hot-functions table, and an "arm next N requests" trigger. New headless API
612
+ routes: `GET /profiles`, `GET /profiles/:id`, `GET /profiles/status`, and
613
+ `POST /profiles/arm` (gated by the default-deny mutation guard). When profiling
614
+ is disabled there is ZERO inspector usage and no request-path cost beyond a
615
+ single boolean check.
616
+
617
+ - [#17](https://github.com/DavideCarvalho/nestjs-telescope/pull/17) [`76228a3`](https://github.com/DavideCarvalho/nestjs-telescope/commit/76228a3a33e4e6d649cd8343646472feff97ed16) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Ecosystem improvements across the telescope packages.
618
+
619
+ - **Nested trace / batch waterfall view.** Traces and batches now render as a
620
+ nested waterfall, showing child spans nested under their parents with relative
621
+ offsets and durations so the critical path of a request is visible at a glance.
622
+ - **Pulse-style outlier & rollup cards.** New overview cards surface the slowest
623
+ endpoints, slowest queries, slowest jobs, and slowest outbound calls, plus
624
+ load-by-user and CPU/memory history, in the spirit of Laravel Pulse rollups.
625
+ - **Metric-threshold alert rules.** Alerts can now fire on metric thresholds
626
+ (p95 / p99 latency, cache-hit ratio) in addition to existing rules, with
627
+ shared-storage exception deduplication so the same exception is not alerted
628
+ multiple times across replicas.
629
+ - **Improved N+1 detection.** Detection now recognizes the 1+N loop pattern and
630
+ is duration-weighted, reducing false positives and prioritizing the queries
631
+ that actually cost time.
632
+ - **On-demand CPU flamegraph profiling.** A strictly-opt-in `profiling` option
633
+ (OFF by default) captures V8 CPU profiles via Node's `inspector`, aggregates
634
+ them into a self/total frame tree, and persists them as `cpu_profile` entries
635
+ correlated to their request batch. The dashboard gains a Profiles tab with a
636
+ dependency-light flamegraph renderer, a hot-functions table, and an
637
+ "arm next N requests" trigger. When profiling is disabled there is zero
638
+ inspector usage and no request-path cost beyond a single boolean check.
639
+ - **`TelescopeUiModule.forRootAsync`.** The UI module now mirrors
640
+ `TelescopeModule.forRootAsync`, resolving dashboard options via DI.
641
+ - **Realtime dashboards / SSE live-tail.** Dashboards update in realtime over an
642
+ `@Sse` invalidation stream (falling back to polling) with a LIVE indicator.
643
+ - **Packaging hygiene.** `sideEffects` is now declared on each publishable
644
+ package to enable safe tree-shaking for downstream bundlers.
645
+ - **Independent versioning.** The changeset `fixed` group was removed so packages
646
+ version independently going forward.
647
+
648
+ - [#17](https://github.com/DavideCarvalho/nestjs-telescope/pull/17) [`76228a3`](https://github.com/DavideCarvalho/nestjs-telescope/commit/76228a3a33e4e6d649cd8343646472feff97ed16) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Packaging hygiene + `TelescopeUiModule.forRootAsync`.
649
+
650
+ - `TelescopeUiModule` now exposes `forRootAsync({ imports, inject, useFactory, path })`, mirroring `TelescopeModule.forRootAsync`: dashboard options (`assetsDir`) are resolved via DI. As with the core module, the mount `path` is bound at module-build time and is passed statically on the config object (the static value also drives the serve-time asset-base rewrite, so it always matches the route).
651
+ - Added `"sideEffects"` to each publishable package to enable safe tree-shaking for downstream bundlers:
652
+ - `false` on the pure watcher / helper / provider packages (ai, bullmq, cache, events, logs, mail, mikro-orm, mikro-orm-watcher, otel, prisma, redis, redis-watcher, sqs, typeorm, testing) — their entrypoints only re-export classes/types with no module-load side effects.
653
+ - `["./dist/schedule.watcher.js"]` on schedule, which carries a load-bearing bare `import 'reflect-metadata'`.
654
+ - `["./dist/server/*.js", "**/*.css"]` on ui, whose server module/controller emit decorator metadata at load and whose bundled SPA ships CSS.
655
+ - core is left unmarked (treated as side-effecting) because its decorator metadata emit spans many directories.
656
+
657
+ ## 1.10.0
658
+
659
+ ## 1.9.1
660
+
661
+ ## 1.9.0
662
+
663
+ ## 1.8.0
664
+
665
+ ## 1.7.1
666
+
667
+ ### Patch Changes
668
+
669
+ - [`895ebf2`](https://github.com/DavideCarvalho/nestjs-telescope/commit/895ebf2adb0342a3c648f5c22b27df94328a08da) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add a **Workflows** nav tab for durable-workflow entries. The dashboard now knows the `durable`
670
+ entry type (recorded by `@dudousxd/nestjs-durable-telescope`'s watcher), so when a host registers
671
+ that watcher the sidebar shows a "Workflows" tab listing every workflow run/step lifecycle event,
672
+ tagged by `workflow:<name>` / `kind:<local|remote|sleep|signal>`. Without the watcher registered the
673
+ tab stays hidden, like every other watcher-driven nav item.
674
+
675
+ ## 1.7.0
676
+
677
+ ### Minor Changes
678
+
679
+ - [`4a610c3`](https://github.com/DavideCarvalho/nestjs-telescope/commit/4a610c3a3b71d1daa4180da0e92a88a2d7b10472) - Add the Inertia debug panel. The dashboard now renders a rich detail view for
680
+ `inertia` entries (`InertiaBody`): rendered component header with status/partial
681
+ badges, a red version-mismatch (409) callout, the partial-reload Kept/Excluded
682
+ columns, prop classification chips (shared/final/optional/once/merge/deep-merge
683
+ with `matchPropsOn` annotations), deferred groups, the resolved-props tree
684
+ (showing the Recorder's redaction/truncation markers verbatim), history flags and
685
+ page size. Adds `InertiaBadge` (409 / partial / deferred / size chips), an
686
+ `inertia` list summary, and an `Inertia` nav tab that self-hides until the
687
+ `InertiaWatcher` is installed.
688
+
689
+ ### Patch Changes
690
+
691
+ - [`10c76f1`](https://github.com/DavideCarvalho/nestjs-telescope/commit/10c76f1c0d9c1034e5bffca2d9775cd16e553200) - Inertia panel polish: log on record failure (was silently swallowed) and warn once on
692
+ an unsupported diagnostic version; collapse `InertiaContent` to
693
+ `Omit<InertiaRenderDiagnostic, …>` with a destructure-rest copy; make the
694
+ `isInertiaDiagnostic` guard honest about what it proves; render the Inertia badge in
695
+ the entries table (parity with the cache badge); data-drive the props rows. Adds a
696
+ committed cross-repo wire-contract fixture (`inertia-render.v1.json`) so producer
697
+ drift fails a test.
698
+
699
+ ## 1.6.0
700
+
701
+ ## 1.5.0
702
+
703
+ ### Minor Changes
704
+
705
+ - [`9e1f318`](https://github.com/DavideCarvalho/nestjs-telescope/commit/9e1f318ba876f3aaf066168b6f8feaa8e20ea5c6) - Fix Slack "Open in Telescope" deep links, and surface cached AI diagnoses on the exception detail page.
706
+
707
+ **Slack deep link landed on an empty page.** The "Open in Telescope" button built `<dashboard>#/entries/<type>/<id>`, but `#/entries/<type>` matches the SPA's type-scoped **list** route (`#/entries/:type`) — so the trailing id was ignored and the recipient saw an empty filtered list instead of the entry. The link now points at the real entry-**detail** route, `#/entries/view/:id`, which renders the same `EntryPage` for both `exception` and `client_exception` entries.
708
+
709
+ **Auto-mode diagnosis was invisible on the detail page.** In `auto` mode the AI diagnosis is computed and cached per family at first-seen (and may already have been sent to Slack), yet the exception detail page still showed a bare **Diagnose with AI** button as if nothing existed. Now:
710
+
711
+ - **Core** adds a read-only `GET <telescope>/api/exceptions/:id/diagnosis` behind the same dashboard read guard as the POST. It serves `{ markdown, cached: true }` from the per-family cache when present and `204 No Content` when absent. It is strictly side-effect-free — it **never** builds context or calls the diagnoser, so a read costs no model tokens.
712
+ - **UI** `DiagnosePanel` fetches that GET on mount (only when `meta.ai.enabled`). A cached diagnosis renders immediately with the **cached** badge and a **Re-run** (force) action; the **Diagnose with AI** button only appears for families that have not been diagnosed yet. The mount fetch shows a subtle "checking" line rather than flashing the button and then swapping it.
713
+
714
+ ## 1.4.0
715
+
716
+ ### Minor Changes
717
+
718
+ - [`7878ccc`](https://github.com/DavideCarvalho/nestjs-telescope/commit/7878ccc8ca912fd5fc4102c22a5b1c26331443d7) - AI-powered exception diagnosis.
719
+
720
+ New package **`@dudousxd/nestjs-telescope-ai`**: `createAiSdkDiagnoser({ model })` implements core's `ExceptionDiagnoser` SPI using the Vercel AI SDK (`ai` is a peer dependency; the model is provider-agnostic — Bedrock / OpenAI / Anthropic / any AI-SDK `LanguageModel`). It turns a captured exception (class, message, stack), its sibling request (route/method/status/duration), and the request's recent **redacted** SQL into a markdown triage report — probable cause, where to look, a suggested fix, and a confidence rating — bounded by `maxOutputTokens` (default 1024) and a hard 30s timeout.
721
+
722
+ Core gains an `ai` option (`{ diagnoser, mode? }`, shape defined in core so core stays AI-SDK-free):
723
+
724
+ - **On-demand** (default): `POST <telescope>/api/exceptions/:id/diagnose` (behind the normal dashboard read guard) returns `{ markdown, cached }`. Results are cached per error family (bounded, 24h TTL); `?force=true` bypasses. 404 when AI is off or the entry isn't an exception; 502 (safe message) when the diagnoser fails.
725
+ - **Auto** (`mode: 'auto'`): the first time a new exception family is seen, Telescope runs a fire-and-forget diagnosis on the flush path (never blocking capture) and caches it; a firing `new-exception` alert briefly awaits it and attaches it (Slack renders a "Probable cause (AI)" section). `meta.ai` advertises `{ enabled, mode }`.
726
+
727
+ The UI adds a **Diagnose with AI** button on exception / client_exception detail pages (visible when `meta.ai.enabled`), rendering the markdown with loading + error states and a **cached** badge with a re-run (force) action.
728
+
729
+ - [`bc3a0df`](https://github.com/DavideCarvalho/nestjs-telescope/commit/bc3a0df784f31a82753725d9c5e86d75380fee13) - Client error ingestion — Telescope as the frontend error reporter.
730
+
731
+ A new **public** endpoint, `POST <telescope>/api/client-errors`, lets browsers report errors directly to Telescope instead of a hand-rolled reporter. Reports are recorded as `client_exception` entries through the normal pipeline, so they compose with everything: the `new-exception` alert, per-type prune/archive, and the dashboard.
732
+
733
+ - **Opt-in & ungated.** Configure via `clientErrors: { enabled, maxBodyBytes?, rateLimit?, authorize? }`. Disabled by default (a public surface is opt-in) — while off the endpoint returns 404. It carries no dashboard guard (ordinary users' browsers hit it).
734
+ - **Untrusted-body validation.** Dependency-free structural validation: `message` required, every other field optional with type checks + length caps (message ≤ 2 KB, stack/componentStack ≤ 16 KB, url/userAgent ≤ 2 KB). Invalid → 400 with no echo of the payload. Over the byte cap → 413.
735
+ - **Per-IP rate limit.** A bounded in-memory token bucket (default 60/min, ~10k IPs with oldest-IP eviction). Over the limit → 429. Per-pod best-effort (the effective limit is `perMinute × pods` in a multi-replica deployment).
736
+ - **`authorize` hook.** Runs first; `false` → 403 (a throw is a fail-closed denial). Lets hosts validate a session cookie/header.
737
+ - **Composes.** Records `type: client_exception` with a family hash from name + message + top stack frame (the server exception interceptor now uses the same scheme, so both sources group identically), the `failed` / `client` / `user:<id>` tags, and the captured client IP. The `new-exception` rule now fires for `client_exception` too, using the browser URL and user-agent in place of a server route.
738
+ - **Dashboard.** A new watcher-driven **Client errors** tab (shown only when `clientErrors` is enabled) with a detail view rendering message, stack, component stack, URL, and user-agent.
739
+
740
+ See the new recipe: _Reporting frontend errors_.
741
+
742
+ ## 1.3.0
743
+
744
+ ### Minor Changes
745
+
746
+ - [`7773e0a`](https://github.com/DavideCarvalho/nestjs-telescope/commit/7773e0ad120d2678a07e9eebc4631a19c5c8e381) - Hide the Traces page from the dashboard nav when the host has no `traceContext`.
747
+
748
+ Entries only get a `trace_id` when the host wires a `traceContext` provider, so an app without one had a permanently empty Traces page sitting as a dead nav item. The dashboard now hides that link — mirroring the watcher-driven nav, which already hides entry types whose watcher isn't registered.
749
+
750
+ - Core: `GET /api/meta` now returns `tracesEnabled: boolean` (whether a `traceContext` provider was configured).
751
+ - UI: the **Traces** nav item is hidden when `meta.tracesEnabled === false`. Undefined meta (loading, or an older server predating the field) still shows it — no flash-of-hidden-nav and backward-compatible. The `#/traces` route is untouched, so a direct visit still resolves.
752
+
753
+ - [`8f9b65e`](https://github.com/DavideCarvalho/nestjs-telescope/commit/8f9b65e1b2ec1df664bc173cc76072c6fffdbc59) - Pulse "Slow request hotspots" now require a real slowness threshold.
754
+
755
+ Previously the slow-route (and slow-outgoing-HTTP) hotspots were a pure top-N p99 ranking with no floor, so a quiet host surfaced fast routes like `/health` at 18ms as "hotspots" — a false alarm. A route now only counts as a hotspot when its **p99 ≥ `slowRouteMs`** (default `1000`ms, matching the `slow` request-tag threshold and the `HttpClientWatcher` `slowMs` default).
756
+
757
+ - Configure via `pulse.slowRouteMs` on `TelescopeModule.forRoot({ pulse: { slowRouteMs } })`.
758
+ - The Overview "Slow requests" stat is now labeled **Slow routes** and means "routes over the slow p99 threshold".
759
+ - The Pulse panel shows a friendly empty state ("No routes over the slow threshold") when nothing qualifies.
760
+ - The "Slowest" entries ranking and N+1 hotspots are unchanged — only slow-ROUTE hotspots gained the threshold.
761
+
762
+ ## 1.2.1
763
+
764
+ ## 1.2.0
765
+
766
+ ### Minor Changes
767
+
768
+ - [`0ba0c24`](https://github.com/DavideCarvalho/nestjs-telescope/commit/0ba0c24ac68c34ad75fe51775dc7089e308c3126) - Three dashboard improvements:
769
+
770
+ - **Criticality-first Overview.** The Overview is reorganized so operational triage sits above the fold: a focused stat row (Requests, **Error rate** = exceptions ÷ requests over the window — red above 5%, Failed jobs, and **Slow requests** = routes over their p99 budget), then Recent failures, a newly-mounted **N+1 query hotspots** card, Slowest, and a **Queues needing attention** card that lists only queues with failed jobs or a large pending backlog (and says "All queues healthy" when clean). Trends and Telescope self-health (throughput, by-type, server, health, retention) move below. All figures derive from data already served by `/pulse` and `/queues` — no new endpoints.
771
+ - **Watcher-driven navigation.** The sidebar Watchers list renders only types whose watcher is registered (from `meta.watchers`); `request` and `exception` always show, direct URLs to hidden types still work, and everything shows while `meta` is loading (no flash of hidden nav).
772
+ - **Dedicated User filter.** The entries filter bar gains a User combobox over the `user:` tag namespace (bare ids + counts); selecting one applies the `tag=user:<id>` filter but renders as a `User: <id>` chip, and a `user:` tag arriving via `?tag=` (the entries-table pivot links) is recognized and shown in the User control instead of the generic tag chip.
773
+
774
+ ## 1.1.0
775
+
5
776
  ### Minor Changes
6
777
 
7
778
  - [`8d91d59`](https://github.com/DavideCarvalho/nestjs-telescope/commit/8d91d59563d731e9125bef65a38651c96ec06e0b) - Gate the dashboard SPA behind `dashboardAuth` when the host enables it.