Lattice Grid Buy a licence

developer guide

Data Grid Diagnostics and Devtools

The grid can explain itself: the configuration it actually resolved, where a displayed value came from, and where the time is going. That turns "it is not showing what I expect" into an answer rather than a hunt.

Developer guideSources and pushdown › Data Grid Diagnostics and Devtools

Diagnostics and devtools

Data grids fail in ways that are hard to diagnose from outside. This is the grid saying what it is actually doing.

Asserting on DOM writes

const before = grid.diagnostics.renders().dom.cellWrites;
await doTheThing();
expect(grid.diagnostics.renders().dom.cellWrites - before).toBeLessThan(200);

The API came first and the panel second, on purpose. Instrumentation written behind a UI gets shaped by the layout: it reports what is convenient to display rather than what is true, and it cannot be tested. An API that stands on its own can be asserted against, and the assertion above is not one most grids can support.

Most of it already existed inside the grid. The renderer had counted cell writes, row updates and paints all along; the column store could already report a real byte footprint per column, summing backing array, presence bitset and dictionary. Neither was reachable from the public API. Exposing what a system already knows is usually a better first move than measuring something new.

Warnings are mostly collection, not detection. The grid has 160 places that warn once per cause, each already carrying a stable de-duplication key, which is exactly the stable identifier a support conversation needs. They went to the console and nowhere else. The console interleaves with your own logging, does not survive a reload, and cannot be asked what it has already complained about. They are now kept as records too.

Every warning names values, not just a condition. "Something is slow" is a warning nobody can act on. Each carries the specific numbers, a stable id, and is dismissible for the session but not permanently, a permanently dismissible warning is one nobody sees again after the person who dismissed it leaves.

The checks are tested for silence as much as for detection. A clean grid must raise nothing. A checker that cries wolf is one developers learn to ignore, and then it is worth less than no checker at all. The accessibility checks found two false positives in themselves during development: first comparing aria-rowcount against the row count when ARIA counts header rows too, then counting header row *elements*, of which a single-level header has three because the header is built once per pinned region.

Instrumentation must not change what it measures. Counters are integers incremented where the work already happened. Render phases are four performance.now() marks around existing sections. Timings are sampled, a bounded window of recent operations, and every report names which of its figures are sampled, because a number whose provenance is unclear is worse than no number. Paint wait is the browser's and is deliberately not claimed.

Render causes are captured, not inferred. By the time a paint runs, several distinct causes have collapsed into the same dirty flags, so working backwards gives a plausible answer rather than a true one. The renderer records the structural reason when the invalidation arrives; the semantic one (filter, sort, data) is only knowable from the event that preceded it, so the DOM layer supplies that and the structural reason is the fallback.

Providers are wrapped where they are installed, not at each call site, so one added later is instrumented by construction. The wrapper returns exactly what the original returned and re-throws exactly what it threw. Failures are kept, because a provider failure is usually swallowed by the host application's own error handling before a developer sees it, and "the grid is behaving strangely" is where that conversation otherwise starts.

The heat overlay uses two colours because there are two findings. A cell written with a value it did not hold is work the grid had to do. A cell rewritten with the value already in it is work it did not. Only the second is waste, and a counter alone will never tell you where it is. The overlay records a baseline when switched on, because otherwise the first paint has nothing to compare against and tints nothing, which reads as broken rather than as empty.

The module imports nothing. Not the grid, not a shared helper. The bundler inlines whatever a module imports: the framework adapters stay small because createGrid is handed to them, while the web component carries the grid with it because it imports it. A devtools module that imported anything from core would put the whole grid inside a bundle whose entire promise is that deployments not using it pay nothing.

The support bundle carries no row data. Configuration, query state, timing, warnings, provider statistics, version and environment, and nothing from your data. Stated as a guarantee because a bundle that had to be inspected for confidential values before sending is a bundle that never gets attached to the ticket.

It observes and never mutates. A configuration editor in a debug panel is tempting and would create a second path into grid state that has to be kept correct forever. No telemetry: nothing leaves the browser unless you export a bundle yourself.