{# ECharts-side chart-readiness wrapper. Templates that emit
chart containers + echarts.init() use fsEChartsInit(el, opts) instead of echarts.init(el, opts). The wrapper: - calls fsBeaconAcquire() before init - delegates to echarts.init(el, opts) - registers chart.on('finished', fsBeaconRelease) once: ECharts can fire 'finished' multiple times (after setOption, resize, theme change), but we only release ONCE — first paint is sufficient for PDF capture; later updates don't add new pending work from the beacon's perspective - if echarts.init throws (missing library, bad element), the wrapper releases the acquire (fail-closed) so the beacon doesn't strand the counter and the PDF doesn't hang CALLER RESPONSIBILITY: fsEChartsInit acquires the beacon before echarts.init returns. If the INITIAL chart.setOption(...) call throws, the chart never paints, the 'finished' listener registered below never fires, and the acquire strands — so the caller MUST wrap that initial call in try/catch and call window.fsBeaconRelease() in the catch handler. The wrapper can't see post-return throws, and a stranded acquire forces PDF rendering through the full 30s BEACON_TIMEOUT. DO NOT wrap subsequent chart-method calls (setTheme, resize, dispatchAction, additional setOption calls after first paint) in release-on-throw. Once the chart paints, 'finished' fires and fsBeaconRelease has already been called; a second release from a catch block prematurely flips fsReportReady when other charts are still pending. The beacon coordinator's clamp-to-0 logic catches single-chart over-release but not multi-chart cases. Let subsequent throws propagate to the caller's regular error handling. See dev/template_migration.md § Pattern 2 for the canonical pattern. fsBeaconAcquire/Release are provided by _ready_beacon.html, the shared coordinator that owns window.fsReportReady. This wrapper does NOT own that flag; the beacon does. Chart.js uses the same beacon via _chart_ready.html. #} {% include "_ready_beacon.html" %}