Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

<!-- v1.20.1 -->

[#863]: https://github.com/certinia/debug-log-analyzer/issues/863

<!-- v1.20.0 -->

[#605]: https://github.com/certinia/debug-log-analyzer/issues/605
Expand Down
2 changes: 1 addition & 1 deletion lana/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
35 changes: 35 additions & 0 deletions log-viewer/src/features/timeline/__tests__/minimap-density.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down