From a69b84f572048b8e9be4d1b209f861bfa0c9d61f Mon Sep 17 00:00:00 2001 From: Guillem Arias Date: Mon, 11 May 2026 21:52:56 +0200 Subject: [PATCH] fix(goals/chart): redraw on turbo:render so the chart survives morph navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repro: index -> goal show (chart drawn) -> open edit modal in turbo frame -> pick a new icon -> submit. Server responds with turbo_stream.action(:redirect, goal_path). Turbo morphs the show page, wiping the chart container's children, but Stimulus' connect() isn't re-run on the morphed element so _draw never fires again. The ResizeObserver doesn't help — the container's box dimensions are unchanged. Listen for turbo:render and turbo:frame-load on document and re-draw when the container's SVG is missing. Cheap idempotent check (querySelector('svg')) — no-op if the chart is already there. Listeners cleaned up in disconnect(). Verified: same flow now lands on the show page with the chart fully rendered (23 SVG children). --- .../goal_projection_chart_controller.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/javascript/controllers/goal_projection_chart_controller.js b/app/javascript/controllers/goal_projection_chart_controller.js index bd370e54e..22fd59179 100644 --- a/app/javascript/controllers/goal_projection_chart_controller.js +++ b/app/javascript/controllers/goal_projection_chart_controller.js @@ -36,12 +36,26 @@ export default class extends Controller { attributeFilter: ["data-theme"], }); } + // After a Turbo render (eg. after saving the goal from the edit modal + // and redirecting back to show), the chart container can be left empty + // — its children are wiped by the morph but connect() was already + // called and ResizeObserver doesn't fire because the size didn't + // change. Listen for the render event so we redraw when needed. + this._onTurboRender = () => { + if (!this.element.querySelector("svg")) this._draw(); + }; + document.addEventListener("turbo:render", this._onTurboRender); + document.addEventListener("turbo:frame-load", this._onTurboRender); } disconnect() { window.removeEventListener("resize", this._resize); this._observer?.disconnect(); this._themeObserver?.disconnect(); + if (this._onTurboRender) { + document.removeEventListener("turbo:render", this._onTurboRender); + document.removeEventListener("turbo:frame-load", this._onTurboRender); + } } _draw() {