rails-profiler 0.32.3 → 0.33.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97fa343641a35a6bb20283e405ed8955b7039d4da924a7ce4195feeaeffacec6
4
- data.tar.gz: d7f9d92015e47e0f20c2b344e2d297b169fcf5d9b184aad17f8d3c68f109f5f2
3
+ metadata.gz: 4dc81c1f51aaa36f9a01703962763e6526c95ea44a86aec0f6580a46c6239bfe
4
+ data.tar.gz: 1555289065c0d81d6267b21ddb9d3ae2c2c0716e1290f33e30df30590ff4b8c9
5
5
  SHA512:
6
- metadata.gz: cd484fea1576a382bc4a6ecef0a39bad2d25b0197d672c62b41e83fed0ef39dfbaf96e8d6100d9d34ed8fca48d765dacbf97099b8963f505503cfc56058be646
7
- data.tar.gz: de36e884fb0fd828d6d498c93858c73998762ec5823c7be1806897bd66ab36a97e61c2a1aa9d5a854694a6d184495855c812afcc0b6646cfe5318aac0f0dd037
6
+ metadata.gz: 967050b5160cb75653481fd86444696cb81fa84c46aa7faa34064a3c281c0acaa5ec08d4029a8b1aca2525da8ffb5d0033b934025d885a8b53e969faca151293
7
+ data.tar.gz: e63d49ccaf002fb7b4911920fc3e279597451fa276d669f557fde971e13e2855cbc347cc16bf254ff2d995b14ba0c18454e929910326a41d0104ee83db49c327
data/CHANGELOG.md CHANGED
@@ -19,6 +19,38 @@ every commit of every tag interval is accounted for one way or the other.
19
19
 
20
20
  ## [Unreleased]
21
21
 
22
+ ## [0.33.0] - 2026-10-14
23
+
24
+ <!-- stamped -->
25
+
26
+ ### Added
27
+
28
+ - **AJAX:** Requests made by Turbo (Hotwire) for a `<turbo-frame>`, and requests answered with
29
+ Turbo Stream actions, carry a `FRAME` or `STREAM` badge in the AJAX panel of the toolbar and in
30
+ the AJAX tab of the dashboard. The AJAX data of a page in the API gives each request a `turbo`
31
+ field: `frame`, `stream` or `null`. The MCP tool `get_profile_ajax` gives the same kind on a
32
+ `Turbo` line of each Turbo request, and none for a plain fetch. The Request tab of a frame
33
+ request shows its `Turbo-Frame` header.
34
+
35
+ ### Fixed
36
+
37
+ - **AJAX:** A Turbo Drive visit is no longer listed as an AJAX request of the page it leaves,
38
+ and neither is a link Turbo prefetches on hover: they are the next page, with their own
39
+ profile. The requests a page reached by a Drive visit makes are linked to that page instead of
40
+ to the first page of the session.
41
+ - **Toolbar:** After a Turbo Drive visit, the toolbar of the previous page is taken down: it no
42
+ longer keeps checking for updates of the previous profile, and `Alt+P` toggles the toolbar
43
+ once instead of once per page visited.
44
+
45
+ ### Limitations
46
+
47
+ - **Toolbar:** A Turbo 8 page refresh with morphing (`turbo_refreshes_with method: :morph`, or
48
+ a `broadcasts_refreshes` model) is not handled: it empties the toolbar, which keeps checking
49
+ for updates of the profile of the page as first loaded, and the requests made after it stay
50
+ linked to that profile. This predates this release and is left for a later one.
51
+ - **Toolbar:** After going back in the browser history, two toolbar containers are left in the
52
+ page, only one of them visible. This predates this release too.
53
+
22
54
  ## [0.32.3] - 2026-10-14
23
55
 
24
56
  <!-- stamped -->
@@ -3356,6 +3356,10 @@
3356
3356
  };
3357
3357
  return map[method] || "";
3358
3358
  }
3359
+ var TURBO_TITLES = {
3360
+ frame: "Turbo Frame request",
3361
+ stream: "Turbo Stream response"
3362
+ };
3359
3363
  function AjaxPanel({ ajaxData }) {
3360
3364
  return /* @__PURE__ */ u3(k, { children: [
3361
3365
  /* @__PURE__ */ u3("div", { class: "profiler-toolbar-panel-header", children: [
@@ -3373,6 +3377,7 @@
3373
3377
  return /* @__PURE__ */ u3("div", { class: `profiler-ajax-card ${statusCls}`, children: /* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: [
3374
3378
  /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", children: [
3375
3379
  /* @__PURE__ */ u3("span", { class: `badge ${methodBadgeClass(req.method)}`, children: req.method }),
3380
+ req.turbo && /* @__PURE__ */ u3("span", { class: "badge badge-default", title: TURBO_TITLES[req.turbo], children: req.turbo.toUpperCase() }),
3376
3381
  /* @__PURE__ */ u3("span", { class: "profiler-text--xs profiler-text--truncate", children: req.path })
3377
3382
  ] }),
3378
3383
  /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", children: [
@@ -4367,14 +4372,26 @@
4367
4372
  if (!el || !toggleEl) return;
4368
4373
  const token = el.dataset.token;
4369
4374
  if (!token) return;
4375
+ window.__PROFILER_TOOLBAR_TEARDOWN__?.();
4376
+ const listeners = new AbortController();
4377
+ const { signal } = listeners;
4378
+ const teardown = () => {
4379
+ listeners.abort();
4380
+ J(null, el);
4381
+ if (window.__PROFILER_TOOLBAR_TEARDOWN__ === teardown) delete window.__PROFILER_TOOLBAR_TEARDOWN__;
4382
+ };
4383
+ window.__PROFILER_TOOLBAR_TEARDOWN__ = teardown;
4384
+ document.addEventListener("turbo:load", () => {
4385
+ if (!el.isConnected) teardown();
4386
+ }, { signal });
4370
4387
  const themeEls = [el, toggleEl];
4371
4388
  applyTheme(themeEls);
4372
4389
  window.addEventListener("storage", (e3) => {
4373
4390
  if (e3.key === "profiler-theme") applyTheme(themeEls);
4374
- });
4391
+ }, { signal });
4375
4392
  window.addEventListener("profiler:theme-change", ((e3) => {
4376
4393
  if (e3.detail?.theme) themeEls.forEach((elem) => elem.setAttribute("data-theme", e3.detail.theme));
4377
- }));
4394
+ }), { signal });
4378
4395
  let isCollapsed = localStorage.getItem("profiler-toolbar-collapsed") === "true";
4379
4396
  toggleEl.dataset.collapsed = String(isCollapsed);
4380
4397
  if (isCollapsed && !el.style.transform) {
@@ -4406,13 +4423,13 @@
4406
4423
  }, ANIM_MS + 50);
4407
4424
  }
4408
4425
  }
4409
- toggleEl.addEventListener("click", toggleCollapse);
4426
+ toggleEl.addEventListener("click", toggleCollapse, { signal });
4410
4427
  document.addEventListener("keydown", (e3) => {
4411
4428
  if (e3.altKey && e3.key === "p") {
4412
4429
  e3.preventDefault();
4413
4430
  toggleCollapse();
4414
4431
  }
4415
- });
4432
+ }, { signal });
4416
4433
  J(
4417
4434
  /* @__PURE__ */ u3(QueryClientProvider, { client: toolbarQueryClient, children: /* @__PURE__ */ u3(ToolbarMount, { token }) }),
4418
4435
  el
@@ -8173,6 +8173,10 @@
8173
8173
  }
8174
8174
 
8175
8175
  // app/assets/typescript/profiler/components/dashboard/tabs/AjaxTab.tsx
8176
+ var TURBO_TITLES = {
8177
+ frame: "Turbo Frame request",
8178
+ stream: "Turbo Stream response"
8179
+ };
8176
8180
  function AjaxTab({ ajaxData }) {
8177
8181
  if (!ajaxData?.requests?.length) {
8178
8182
  return /* @__PURE__ */ u3("div", { class: "profiler-empty", children: [
@@ -8225,6 +8229,7 @@
8225
8229
  /* @__PURE__ */ u3("div", { class: "profiler-ajax-card__row", children: [
8226
8230
  /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-3", children: [
8227
8231
  /* @__PURE__ */ u3("span", { class: `profiler-ajax-card__method badge-${methodBadge(req.method)}`, children: req.method }),
8232
+ req.turbo && /* @__PURE__ */ u3("span", { class: "badge-default", title: TURBO_TITLES[req.turbo], children: req.turbo.toUpperCase() }),
8228
8233
  /* @__PURE__ */ u3("strong", { class: "profiler-ajax-card__path", children: req.path })
8229
8234
  ] }),
8230
8235
  /* @__PURE__ */ u3("div", { class: "profiler-flex profiler-flex--gap-2", children: [
@@ -105,9 +105,30 @@ module Profiler
105
105
  "method" => profile.method,
106
106
  "status" => profile.status,
107
107
  "duration" => profile.duration,
108
- "started_at" => profile.started_at&.iso8601
108
+ "started_at" => profile.started_at&.iso8601,
109
+ "turbo" => turbo_kind(profile)
109
110
  }
110
111
  end
112
+
113
+ TURBO_STREAM_MIME = "text/vnd.turbo-stream.html"
114
+
115
+ # "frame" for a request a <turbo-frame> made (Turbo sends its id in Turbo-Frame), "stream"
116
+ # for one answered with Turbo Stream actions, nil for any other fetch or XHR.
117
+ def turbo_kind(profile)
118
+ return "frame" if header_value(profile.headers, "Turbo-Frame").to_s != ""
119
+ return "stream" if header_value(profile.response_headers, "Content-Type").to_s.start_with?(TURBO_STREAM_MIME)
120
+
121
+ nil
122
+ end
123
+
124
+ # Header names keep the case they were saved with: Content-Type from Rack 2, content-type
125
+ # from Rack 3.
126
+ def header_value(headers, name)
127
+ return nil unless headers.respond_to?(:each)
128
+
129
+ headers.each { |key, value| return value if key.to_s.casecmp?(name) }
130
+ nil
131
+ end
111
132
  end
112
133
  end
113
134
  end
@@ -61,6 +61,7 @@ module Profiler
61
61
  lines << "- **Path:** #{req['path']}"
62
62
  lines << "- **Status:** #{req['status']}"
63
63
  lines << "- **Duration:** #{req['duration'].round(2)} ms"
64
+ lines << "- **Turbo:** #{req['turbo']}" if req['turbo']
64
65
  lines << "- **Token:** #{req['token']}" if req['token']
65
66
  lines << "- **Started At:** #{req['started_at']}" if req['started_at']
66
67
  end
@@ -346,7 +346,7 @@ module Profiler
346
346
  Accept Accept-Charset Accept-Encoding Accept-Language
347
347
  Authorization Cache-Control Connection Content-Length Content-Type
348
348
  Cookie Host If-Modified-Since If-None-Match Origin
349
- Referer User-Agent
349
+ Referer User-Agent Turbo-Frame
350
350
  ].freeze
351
351
 
352
352
  def extract_headers(env)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Profiler
4
- VERSION = "0.32.3"
4
+ VERSION = "0.33.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.32.3
4
+ version: 0.33.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sébastien Duplessy