diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b7fc94c..bcfde01c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.20.1] 2026-07-23 + +### Fixed + +- 🪟 **Timeline on Windows**: fixed the Flame Chart failing to load due to fractional display scaling (125% / 150% / 175%) - zoom, pan and keyboard navigation all appeared unresponsive ([#863]). + ## [1.20.0] 2026-06-18 ### Added @@ -485,6 +491,10 @@ Skipped due to adopting odd numbering for pre releases and even number for relea - Add explorer menu item. - Provide more information when selecting log to download. + + +[#863]: https://github.com/certinia/debug-log-analyzer/issues/863 + [#605]: https://github.com/certinia/debug-log-analyzer/issues/605 diff --git a/lana/package.json b/lana/package.json index 88dc4dd0..b6421753 100644 --- a/lana/package.json +++ b/lana/package.json @@ -1,7 +1,7 @@ { "name": "lana", "displayName": "Apex Log Analyzer", - "version": "1.20.0", + "version": "1.20.1", "description": "Salesforce Apex Debug Log Analyzer: Blazing-fast VS Code extension for Salesforce. Visualize and debug Apex logs with interactive flame charts, dynamic call trees, and detailed SOQL/DML breakdowns. Identify performance bottlenecks, gain deep transaction insights and optimize slow Apex.", "keywords": [ "analysis", diff --git a/log-viewer/src/features/timeline/__tests__/minimap-density.test.ts b/log-viewer/src/features/timeline/__tests__/minimap-density.test.ts index 53dbdb3f..d033ed4a 100644 --- a/log-viewer/src/features/timeline/__tests__/minimap-density.test.ts +++ b/log-viewer/src/features/timeline/__tests__/minimap-density.test.ts @@ -196,4 +196,39 @@ describe('MinimapDensityQuery', () => { expect(result.buckets).toHaveLength(0); }); }); + + describe('fractional bucket count (Windows fractional DPI scaling)', () => { + // Regression: getBoundingClientRect().width is non-integer under Windows + // 125%/150% display scaling; a fractional/NaN count previously threw + // "Invalid array length" and aborted timeline init. + const rects = [createRect('Method', 0, 1000, 0)]; + + it('floors a fractional bucket count instead of throwing (segment tree path)', () => { + const rectsByCategory = buildRectsByCategory(rects); + const segmentTree = new TemporalSegmentTree(rectsByCategory); + const query = new MinimapDensityQuery(rectsByCategory, 1000, 0, segmentTree); + + const fractional = query.query(10.6); + const floored = query.query(10); + + expect(fractional.buckets).toHaveLength(10); + expect(fractional.buckets).toEqual(floored.buckets); + }); + + it('floors a fractional bucket count instead of throwing (fallback path)', () => { + const rectsByCategory = buildRectsByCategory(rects); + const query = new MinimapDensityQuery(rectsByCategory, 1000, 0); + + expect(() => query.query(1536.6667)).not.toThrow(); + expect(query.query(1536.6667).buckets).toHaveLength(1536); + }); + + it('treats a non-finite bucket count as empty instead of throwing', () => { + const rectsByCategory = buildRectsByCategory(rects); + const query = new MinimapDensityQuery(rectsByCategory, 1000, 0); + + expect(() => query.query(Number.NaN)).not.toThrow(); + expect(query.query(Number.NaN).buckets).toHaveLength(0); + }); + }); }); diff --git a/log-viewer/src/features/timeline/optimised/minimap/MinimapDensityQuery.ts b/log-viewer/src/features/timeline/optimised/minimap/MinimapDensityQuery.ts index 26ecf89f..a2b3ea9b 100644 --- a/log-viewer/src/features/timeline/optimised/minimap/MinimapDensityQuery.ts +++ b/log-viewer/src/features/timeline/optimised/minimap/MinimapDensityQuery.ts @@ -164,6 +164,14 @@ export class MinimapDensityQuery { * @returns MinimapDensityData for rendering */ public query(bucketCount: number): MinimapDensityData { + // bucketCount is the display width from getBoundingClientRect(), which is + // fractional under non-integer OS display scaling (Windows 125%/150% DPI). + // A fractional/NaN count crashes the Array/typed-array constructors below + // with "Invalid array length" (the `<= 0` guards do not catch it), which + // aborts timeline init and leaves interaction handlers unwired. Normalise + // here, the single entry point feeding both compute paths and the cache. + bucketCount = Number.isFinite(bucketCount) ? Math.floor(bucketCount) : 0; + // Fast path: exact match in cache const cached = this.densityCache.get(bucketCount); if (cached) {