Rework assert* functions
diff --git a/ui/src/base/assert.ts b/ui/src/base/assert.ts
index 3ce1523..d0632a0 100644
--- a/ui/src/base/assert.ts
+++ b/ui/src/base/assert.ts
@@ -12,68 +12,83 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Assertion utilities for runtime validation and TypeScript type narrowing.
+// Assertion and check utilities for runtime validation and TypeScript type
+// narrowing.
 //
-// These functions provide fail-fast semantics: if an assertion fails, an
-// exception is thrown immediately. This makes bugs easier to catch and debug
-// by surfacing issues at the point of failure rather than propagating invalid
-// state.
+// Two flavors:
+// - assert*() — void. Narrows the type of the input variable in place for
+//   subsequent code. Use as a standalone statement.
+// - checkExists() — returns the narrowed value. Use in expressions,
+//   assignments, and function arguments.
 //
-// In addition to runtime checks, these assertions help TypeScript narrow types.
-// For example, after calling assertExists(x), TypeScript knows x is non-null.
+// All functions throw immediately on failure (fail-fast).
 
-export function assertExists<A>(
-  value: A | null | undefined,
-  optMsg?: string,
-): A {
+// Throws if |value| is null or undefined. Returns the value with null and
+// undefined stripped from the type, for use in expressions.
+export function checkExists<T>(value: T, msg?: string): NonNullable<T> {
   if (value === null || value === undefined) {
-    throw new Error(optMsg ?? 'Value is null or undefined');
+    throw new Error(msg ?? 'Value is null or undefined');
   }
   return value;
 }
 
-// assertExists trips over NULLs, but in many contexts NULL is a valid SQL value
-// we have to work with.
-export function assertDefined<T>(value: T | undefined, optMsg?: string): T {
-  if (value === undefined) {
-    throw new Error(optMsg ?? 'Value is undefined');
+// Throws if |value| is null or undefined. Narrows the type of |value| to
+// exclude null and undefined for all subsequent code.
+export function assertExists<T>(
+  value: T,
+  msg?: string,
+): asserts value is NonNullable<T> {
+  if (value === null || value === undefined) {
+    throw new Error(msg ?? 'Value is null or undefined');
   }
-  return value;
 }
 
-// Asserts that the value is an instance of the given class. Returns the value
-// with a narrowed type if the assertion passes, otherwise throws an error.
-export function assertIsInstance<T>(
+// Throws if |value| is undefined. Narrows the type of |value| to exclude
+// undefined for all subsequent code. Unlike assertExists(), this permits null.
+export function assertDefined<T>(
+  value: T | undefined,
+  msg?: string,
+): asserts value is T {
+  if (value === undefined) throw new Error(msg ?? 'Value is undefined');
+}
+
+// Throws if |value| is not an instance of |cls|. Narrows the type of |value|
+// to |T| for all subsequent code.
+export function assertInstanceOf<T>(
   value: unknown,
-  clazz: abstract new (...args: never[]) => T,
-  optMsg?: string,
-): T {
-  assertTrue(
-    value instanceof clazz,
-    optMsg ?? `Value is not an instance of ${clazz.name}`,
-  );
-  return value as T;
-}
-
-export function assertTrue(value: boolean, optMsg?: string) {
-  if (!value) {
-    throw new Error(optMsg ?? 'Failed assertion');
+  cls: abstract new (...args: never[]) => T,
+  msg?: string,
+): asserts value is T {
+  if (!(value instanceof cls)) {
+    throw new Error(msg ?? `Value is not instance of '${cls.name}'`);
   }
 }
 
-export function assertFalse(value: boolean, optMsg?: string) {
-  assertTrue(!value, optMsg);
+export function checkInstanceOf<T>(
+  value: unknown,
+  cls: abstract new (...args: never[]) => T,
+  msg?: string,
+): T {
+  if (!(value instanceof cls)) {
+    throw new Error(msg ?? `Value is not instance of '${cls.name}'`);
+  }
+  return value;
 }
 
-// This function serves two purposes.
-// 1) A runtime check - if we are ever called, we throw an exception.
-// This is useful for checking that code we suspect should never be reached is
-// actually never reached.
-// 2) A compile time check where typescript asserts that the value passed can be
-// cast to the "never" type.
-// This is useful for ensuring we exhaustively check union types.
-export function assertUnreachable(value: never, optMsg?: string): never {
+// Throws if |value| is not truthy.
+export function assertTrue(value: boolean, msg?: string) {
+  if (!value) throw new Error(msg ?? 'Value is not truthy');
+}
+
+// Throws if |value| is truthy.
+export function assertFalse(value: boolean, msg?: string) {
+  if (value) throw new Error(msg ?? 'Value is not falsy');
+}
+
+// Throws unconditionally at runtime. At compile time, requires |value| to be
+// of type 'never', ensuring exhaustive checks of union types and enums.
+export function assertUnreachable(value: never, msg?: string): never {
   throw new Error(
-    optMsg ?? `This code should not be reachable ${value as unknown}`,
+    msg ?? `This code should not be reachable ${value as unknown}`,
   );
 }
diff --git a/ui/src/base/assert_unittest.ts b/ui/src/base/assert_unittest.ts
new file mode 100644
index 0000000..1142608
--- /dev/null
+++ b/ui/src/base/assert_unittest.ts
@@ -0,0 +1,199 @@
+// Copyright (C) 2026 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {
+  assertDefined,
+  assertFalse,
+  assertInstanceOf,
+  assertExists,
+  assertTrue,
+  assertUnreachable,
+  checkExists,
+} from './assert';
+
+class Cls {}
+class SubCls extends Cls {}
+class Unrelated {}
+
+function takesString(x: string) {
+  void x;
+}
+
+function takesNull(x: null) {
+  void x;
+}
+
+function takesCls(x: Cls) {
+  void x;
+}
+
+describe('ensureNonNullish', () => {
+  test('non-nullish', () => {
+    expect(() => {
+      const x = 'foo' as string | null | undefined;
+      takesString(checkExists(x));
+    }).not.toThrow();
+  });
+  test('null', () => {
+    expect(() => {
+      const x = null as string | null | undefined;
+      takesString(checkExists(x)); // BOOM
+    }).toThrow();
+  });
+  test('undefined', () => {
+    expect(() => {
+      const x = undefined as string | null | undefined;
+      takesString(checkExists(x)); // BOOM
+    }).toThrow();
+  });
+  test('falsy', () => {
+    expect(() => {
+      checkExists(false);
+      checkExists(0);
+      checkExists('');
+      checkExists(0n);
+    }).not.toThrow();
+  });
+});
+
+describe('assertNonNullish', () => {
+  test('string', () => {
+    expect(() => {
+      const x = 'foo' as string | null | undefined;
+      assertExists(x);
+      takesString(x);
+    }).not.toThrow();
+  });
+  test('null', () => {
+    expect(() => {
+      const x = null as string | null | undefined;
+      assertExists(x); // BOOM
+      takesString(x);
+    }).toThrow();
+  });
+  test('undefined', () => {
+    expect(() => {
+      const x = undefined as string | null | undefined;
+      assertExists(x); // BOOM
+      takesString(x);
+    }).toThrow();
+  });
+  test('falsy', () => {
+    expect(() => {
+      assertExists(false);
+      assertExists(0);
+      assertExists('');
+      assertExists(0n);
+    }).not.toThrow();
+  });
+});
+
+describe('assertDefined', () => {
+  test('defined', () => {
+    expect(() => {
+      const x = 'foo' as string | undefined;
+      assertDefined(x);
+      takesString(x);
+    }).not.toThrow();
+  });
+  test('null', () => {
+    expect(() => {
+      const x = null as null | undefined;
+      assertDefined(x);
+      takesNull(x);
+    }).not.toThrow();
+  });
+  test('undefined', () => {
+    expect(() => {
+      const x = undefined as string | undefined;
+      assertDefined(x); // BOOM
+      takesString(x);
+    }).toThrow();
+  });
+  test('falsy', () => {
+    expect(() => {
+      assertDefined(false);
+      assertDefined(0);
+      assertDefined('');
+      assertDefined(0n);
+    }).not.toThrow();
+  });
+});
+
+describe('assertInstanceOf', () => {
+  test('is instance', () => {
+    expect(() => {
+      const x = new Cls() as unknown;
+      assertInstanceOf(x, Cls);
+      takesCls(x);
+    }).not.toThrow();
+  });
+  test('is subclass', () => {
+    expect(() => {
+      const x = new SubCls() as unknown;
+      assertInstanceOf(x, Cls);
+      takesCls(x);
+    }).not.toThrow();
+  });
+  test('is not instance', () => {
+    expect(() => {
+      const x = new Unrelated() as unknown;
+      assertInstanceOf(x, Cls);
+      takesCls(x);
+    }).toThrow();
+  });
+});
+
+describe('assertTrue', () => {
+  test('true', () => {
+    expect(() => {
+      assertTrue(true);
+    }).not.toThrow();
+  });
+  test('false', () => {
+    expect(() => {
+      assertTrue(false);
+    }).toThrow();
+  });
+});
+
+describe('assertFalse', () => {
+  test('false', () => {
+    expect(() => {
+      assertFalse(false);
+    }).not.toThrow();
+  });
+  test('true', () => {
+    expect(() => {
+      assertFalse(true);
+    }).toThrow();
+  });
+});
+
+describe('assertUnreachable', () => {
+  test('', () => {
+    expect(() => {
+      assertUnreachable(null as never);
+    }).toThrow();
+  });
+  test('', () => {
+    expect(() => {
+      const x = 'foo' as const;
+      if (x === 'foo') {
+        return;
+      }
+      assertUnreachable(x);
+    }).not.toThrow();
+  });
+});
diff --git a/ui/src/base/dom_utils.ts b/ui/src/base/dom_utils.ts
index f418b1f..47dfcd5 100644
--- a/ui/src/base/dom_utils.ts
+++ b/ui/src/base/dom_utils.ts
@@ -67,15 +67,6 @@
   }
 }
 
-// Safely cast an Element to an HTMLElement.
-// Throws if the element is not an HTMLElement.
-export function toHTMLElement(el: Element): HTMLElement {
-  if (!(el instanceof HTMLElement)) {
-    throw new Error('Element is not an HTMLElement');
-  }
-  return el as HTMLElement;
-}
-
 // Return true if EventTarget is or is inside an editable element.
 // Editable elements incluce: <input type="text">, <textarea>, or elements with
 // the |contenteditable| attribute set.
diff --git a/ui/src/base/dom_utils_unittest.ts b/ui/src/base/dom_utils_unittest.ts
index 8b91047..d2f8bc5 100644
--- a/ui/src/base/dom_utils_unittest.ts
+++ b/ui/src/base/dom_utils_unittest.ts
@@ -17,7 +17,6 @@
   elementIsEditable,
   findRef,
   isOrContains,
-  toHTMLElement,
 } from './dom_utils';
 
 describe('isOrContains', () => {
@@ -65,21 +64,6 @@
   });
 });
 
-describe('toHTMLElement', () => {
-  it('should convert a div to an HTMLElement', () => {
-    const divElement: Element = document.createElement('div');
-    expect(toHTMLElement(divElement)).toEqual(divElement);
-  });
-
-  it('should fail to convert an svg element to an HTMLElement', () => {
-    const svgElement = document.createElementNS(
-      'http://www.w3.org/2000/svg',
-      'svg',
-    );
-    expect(() => toHTMLElement(svgElement)).toThrow(Error);
-  });
-});
-
 describe('elementIsEditable', () => {
   test('text input', () => {
     const el = document.createElement('input');
diff --git a/ui/src/base/object_utils.ts b/ui/src/base/object_utils.ts
index a404475..5745b2c 100644
--- a/ui/src/base/object_utils.ts
+++ b/ui/src/base/object_utils.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {assertExists} from './assert';
+import {checkExists} from './assert';
 import {exists} from './utils';
 
 export type PathKey = string | number;
@@ -71,7 +71,7 @@
   const pathClone = [...path];
   let o = obj;
   while (pathClone.length > 1) {
-    const p = assertExists(pathClone.shift());
+    const p = checkExists(pathClone.shift());
     o = o[p];
   }
 
diff --git a/ui/src/components/details/thread_slice_details_tab.ts b/ui/src/components/details/thread_slice_details_tab.ts
index 2838524..c471ee4 100644
--- a/ui/src/components/details/thread_slice_details_tab.ts
+++ b/ui/src/components/details/thread_slice_details_tab.ts
@@ -36,7 +36,7 @@
 import {asSliceSqlId} from '../sql_utils/core_types';
 import {DurationWidget} from '../widgets/duration';
 import {Grid, GridCell, GridHeaderCell} from '../../widgets/grid';
-import {assertIsInstance} from '../../base/assert';
+import {assertInstanceOf} from '../../base/assert';
 import {Trace} from '../../public/trace';
 import {TrackEventDetailsPanel} from '../../public/details_panel';
 import {TrackEventSelection} from '../../public/selection';
@@ -213,10 +213,11 @@
   private readonly attrs: ThreadSliceDetailsPanelAttrs;
 
   constructor(trace: Trace, attrs?: ThreadSliceDetailsPanelAttrs) {
-    // Rationale for the assertIsInstance: ThreadSliceDetailsPanel requires a
+    // Rationale for the assertInstanceOf: ThreadSliceDetailsPanel requires a
     // TraceImpl (because of flows) but here we must take a Trace interface,
     // because this track is exposed to plugins (which see only Trace).
-    this.trace = assertIsInstance(trace, TraceImpl);
+    assertInstanceOf(trace, TraceImpl);
+    this.trace = trace;
     this.attrs = attrs ?? {};
   }
 
diff --git a/ui/src/components/query_flamegraph.ts b/ui/src/components/query_flamegraph.ts
index 05f3a3c..2ff6d4f 100644
--- a/ui/src/components/query_flamegraph.ts
+++ b/ui/src/components/query_flamegraph.ts
@@ -233,9 +233,8 @@
     metrics: ReadonlyArray<QueryFlamegraphMetric>,
     state: FlamegraphState,
   ) {
-    const metric = assertExists(
-      metrics.find((x) => state.selectedMetricName === x.name),
-    );
+    const metric = metrics.find((x) => state.selectedMetricName === x.name);
+    assertExists(metric);
     const engine = this.trace.engine;
     this.queryLimiter.schedule(async () => {
       this.data = undefined;
diff --git a/ui/src/components/tracks/slice_track.ts b/ui/src/components/tracks/slice_track.ts
index 7b49ea4..97ff1de 100644
--- a/ui/src/components/tracks/slice_track.ts
+++ b/ui/src/components/tracks/slice_track.ts
@@ -1218,7 +1218,8 @@
       this.trace.timeline.highlightedSliceId = this.hoveredSlice?.id;
       if (this.hoveredSlice === undefined) {
         if (this.attrs.onSliceOut) {
-          this.attrs.onSliceOut({slice: assertExists(prevHoveredSlice)});
+          assertExists(prevHoveredSlice);
+          this.attrs.onSliceOut({slice: prevHoveredSlice});
         }
       } else {
         if (this.attrs.onSliceOver) {
diff --git a/ui/src/components/widgets/charts/echart_view.ts b/ui/src/components/widgets/charts/echart_view.ts
index 48aae91..71b1eb7 100644
--- a/ui/src/components/widgets/charts/echart_view.ts
+++ b/ui/src/components/widgets/charts/echart_view.ts
@@ -49,7 +49,7 @@
 } from 'echarts/components';
 import {CanvasRenderer} from 'echarts/renderers';
 import type {EChartsType} from 'echarts/core';
-import {assertExists, assertIsInstance} from '../../../base/assert';
+import {assertInstanceOf} from '../../../base/assert';
 import {classNames} from '../../../base/classnames';
 import {SimpleResizeObserver} from '../../../base/resize_observer';
 import {Spinner} from '../../../widgets/spinner';
@@ -283,10 +283,8 @@
   private initChart(attrs: EChartViewAttrs, dom: Element): void {
     if (attrs.option === undefined) return;
 
-    const container = assertIsInstance(
-      assertExists(dom.querySelector('.pf-echart-view__canvas')),
-      HTMLElement,
-    );
+    const container = dom.querySelector('.pf-echart-view__canvas');
+    assertInstanceOf(container, HTMLElement);
 
     // Read theme colors and register/update the ECharts theme
     const colors = getChartThemeColors(container);
diff --git a/ui/src/components/widgets/sql/pivot_table/pivot_table_state.ts b/ui/src/components/widgets/sql/pivot_table/pivot_table_state.ts
index fa104a9..adc7298 100644
--- a/ui/src/components/widgets/sql/pivot_table/pivot_table_state.ts
+++ b/ui/src/components/widgets/sql/pivot_table/pivot_table_state.ts
@@ -341,7 +341,9 @@
     for (const it = res.iter({}); it.valid(); it.next()) {
       const row: Row = {};
       for (const column of res.columns()) {
-        row[assertExists(aliasToIds.get(column))] = it.get(column);
+        const index = aliasToIds.get(column);
+        assertExists(index);
+        row[index] = it.get(column);
       }
       rows.push(row);
     }
diff --git a/ui/src/components/widgets/sql/pivot_table/pivot_tree_node.ts b/ui/src/components/widgets/sql/pivot_table/pivot_tree_node.ts
index 1d1715e..15c1735 100644
--- a/ui/src/components/widgets/sql/pivot_table/pivot_tree_node.ts
+++ b/ui/src/components/widgets/sql/pivot_table/pivot_tree_node.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {assertExists, assertDefined, assertTrue} from '../../../../base/assert';
+import {assertTrue, assertExists, assertDefined} from '../../../../base/assert';
 import {Row, SqlValue} from '../../../../trace_processor/query_result';
 import {Filter, StandardFilters} from '../table/filters';
 import {TableColumn} from '../table/table_column';
@@ -127,7 +127,8 @@
   getPivotValue(index: number): SqlValue | undefined {
     if (index > this.getPivotIndex()) return undefined;
     if (index === this.getPivotIndex()) return this.pivotValue;
-    return assertExists(this.parent).getPivotValue(index);
+    assertExists(this.parent);
+    return this.parent.getPivotValue(index);
   }
 
   /**
@@ -168,7 +169,9 @@
     let valueNode: PivotTreeNode = this;
     let autoExpanded = true;
     for (let i = pivotIndex; i < this.getPivotIndex(); i++) {
-      valueNode = assertExists(valueNode.parent);
+      const parent = valueNode.parent;
+      assertExists(parent);
+      valueNode = parent;
       autoExpanded = autoExpanded && valueNode.children.size === 1;
     }
     return autoExpanded ? 'auto_expanded' : 'pivoted_value';
@@ -207,7 +210,8 @@
     );
     this.children.clear();
     for (const child of sorted) {
-      this.children.set(assertDefined(child.pivotValue), child);
+      assertDefined(child.pivotValue);
+      this.children.set(child.pivotValue, child);
     }
   }
 
@@ -239,9 +243,10 @@
   }
 
   private getFilter(): Filter {
+    assertDefined(this.pivotValue);
     return StandardFilters.valueEquals(
       this.config.pivots[this.getPivotIndex()].column,
-      assertDefined(this.pivotValue),
+      this.pivotValue,
     );
   }
 
@@ -263,7 +268,9 @@
         }),
       );
     }
-    return assertExists(this.children.get(value));
+    const child = this.children.get(value);
+    assertExists(child);
+    return child;
   }
 
   private update() {
@@ -325,10 +332,9 @@
         assertTrue(index !== -1);
         // For pivot sorting, we only compare the pivot values at the given depth.
         if (index + 1 === lhs.depth) {
-          const cmp = compareSqlValues(
-            assertDefined(lhs.pivotValue),
-            assertDefined(rhs.pivotValue),
-          );
+          assertDefined(lhs.pivotValue);
+          assertDefined(rhs.pivotValue);
+          const cmp = compareSqlValues(lhs.pivotValue, rhs.pivotValue);
           if (cmp !== 0) return direction === 'ASC' ? cmp : -cmp;
         }
       }
diff --git a/ui/src/core/app_impl.ts b/ui/src/core/app_impl.ts
index d29f546..1567b7f 100644
--- a/ui/src/core/app_impl.ts
+++ b/ui/src/core/app_impl.ts
@@ -14,7 +14,7 @@
 
 import {AsyncLimiter} from '../base/async_limiter';
 import {defer} from '../base/deferred';
-import {assertExists, assertTrue} from '../base/assert';
+import {assertTrue, checkExists} from '../base/assert';
 import {ServiceWorkerController} from '../frontend/service_worker_controller';
 import {App} from '../public/app';
 import {SqlPackage} from '../public/extra_sql_packages';
@@ -122,7 +122,7 @@
   // Singleton.
   private static _instance: AppImpl;
   static get instance(): AppImpl {
-    return assertExists(AppImpl._instance);
+    return checkExists(AppImpl._instance);
   }
 
   readonly timestampFormat: Setting<TimestampFormat>;
diff --git a/ui/src/core/load_trace.ts b/ui/src/core/load_trace.ts
index 0c9258a..12fb061 100644
--- a/ui/src/core/load_trace.ts
+++ b/ui/src/core/load_trace.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {assertExists, assertTrue} from '../base/assert';
+import {assertTrue, checkExists} from '../base/assert';
 import {time, Time, TimeSpan} from '../base/time';
 import {cacheTrace} from './cache_manager';
 import {
@@ -169,7 +169,7 @@
   engine.onResponseReceived = () => raf.scheduleFullRedraw();
 
   if (isMetatracingEnabled()) {
-    engine.enableMetatrace(assertExists(getEnabledMetatracingCategories()));
+    engine.enableMetatrace(checkExists(getEnabledMetatracingCategories()));
   }
   return engine;
 }
@@ -497,7 +497,7 @@
   // The max() is so the query returns NULL if the tz info doesn't exist.
   const queryTz = `select max(int_value) as tzOffMin from metadata
         where name = 'timezone_off_mins'`;
-  const resTz = await assertExists(engine).query(queryTz);
+  const resTz = await engine.query(queryTz);
   const tzOffMin = resTz.firstRow({tzOffMin: NUM_NULL}).tzOffMin ?? 0;
 
   // This is the offset between the unix epoch and ts in the ts domain.
diff --git a/ui/src/core/page_manager.ts b/ui/src/core/page_manager.ts
index 3b2ab95..6a510b8 100644
--- a/ui/src/core/page_manager.ts
+++ b/ui/src/core/page_manager.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import m from 'mithril';
-import {assertExists, assertTrue} from '../base/assert';
+import {assertTrue, assertExists} from '../base/assert';
 import {Registry} from '../base/registry';
 import {PageHandler, PageManager} from '../public/page';
 import {Analytics} from '../public/analytics';
@@ -67,7 +67,8 @@
         // If either the route doesn't exist or requires a trace but the trace
         // is not loaded, fall back on the default route.
         const renderedPage =
-          maybeRenderedPage ?? assertExists(this.renderPageForRoute('/', ''));
+          maybeRenderedPage ?? this.renderPageForRoute('/', '');
+        assertExists(renderedPage);
         return [key, renderedPage];
       })
       .map(([key, page]) => {
diff --git a/ui/src/core/plugin_manager.ts b/ui/src/core/plugin_manager.ts
index 0c957a0..2963631 100644
--- a/ui/src/core/plugin_manager.ts
+++ b/ui/src/core/plugin_manager.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {assertExists} from '../base/assert';
+import {checkExists} from '../base/assert';
 import {Registry} from '../base/registry';
 import {PerfettoPlugin, PerfettoPluginStatic} from '../public/plugin';
 import {Trace} from '../public/trace';
@@ -157,7 +157,7 @@
     pluginDescriptor: PerfettoPluginStatic<T>,
   ): T {
     const plugin = this.registry.get(pluginDescriptor.id);
-    return assertExists(plugin.traceContext).instance as T;
+    return checkExists(plugin.traceContext).instance as T;
   }
 
   isCorePlugin(pluginId: string): boolean {
diff --git a/ui/src/core/trace_stream.ts b/ui/src/core/trace_stream.ts
index 274322a..a94028a 100644
--- a/ui/src/core/trace_stream.ts
+++ b/ui/src/core/trace_stream.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {defer, Deferred} from '../base/deferred';
-import {assertExists, assertTrue} from '../base/assert';
+import {checkExists, assertTrue} from '../base/assert';
 import {exists} from '../base/utils';
 import {TraceChunk, TraceStream} from '../public/stream';
 
@@ -44,13 +44,13 @@
   }
 
   private onLoad() {
-    const pendingRead = assertExists(this.pendingRead);
+    const pendingRead = checkExists(this.pendingRead);
     this.pendingRead = undefined;
     if (this.reader.error) {
       pendingRead.reject(this.reader.error);
       return;
     }
-    const res = assertExists(this.reader.result) as ArrayBuffer;
+    const res = checkExists(this.reader.result) as ArrayBuffer;
     this.bytesRead += res.byteLength;
     pendingRead.resolve({
       data: new Uint8Array(res),
diff --git a/ui/src/core/track_manager_unittest.ts b/ui/src/core/track_manager_unittest.ts
index fc8106a..c854f29 100644
--- a/ui/src/core/track_manager_unittest.ts
+++ b/ui/src/core/track_manager_unittest.ts
@@ -99,7 +99,8 @@
 
 describe('TrackManager', () => {
   it('calls render on the track', () => {
-    const entry = assertExists(trackManager.getWrappedTrack(td.uri));
+    const entry = trackManager.getWrappedTrack(td.uri);
+    assertExists(entry);
 
     entry.render(dummyCtx);
     expect(mockTrack.render).toHaveBeenCalledTimes(1);
@@ -107,10 +108,12 @@
   });
 
   it('reuses tracks across render cycles', () => {
-    const first = assertExists(trackManager.getWrappedTrack(td.uri));
+    const first = trackManager.getWrappedTrack(td.uri);
+    assertExists(first);
     first.render(dummyCtx);
 
-    const second = assertExists(trackManager.getWrappedTrack(td.uri));
+    const second = trackManager.getWrappedTrack(td.uri);
+    assertExists(second);
     second.render(dummyCtx);
 
     expect(first).toBe(second);
@@ -118,7 +121,8 @@
   });
 
   it('contains crash inside render()', () => {
-    const entry = assertExists(trackManager.getWrappedTrack(td.uri));
+    const entry = trackManager.getWrappedTrack(td.uri);
+    assertExists(entry);
     const e = new Error('test error');
 
     // Mock crash inside render
@@ -133,7 +137,8 @@
   });
 
   it('does not call render after crash', () => {
-    const entry = assertExists(trackManager.getWrappedTrack(td.uri));
+    const entry = trackManager.getWrappedTrack(td.uri);
+    assertExists(entry);
     const e = new Error('test error');
 
     // Mock crash inside render
@@ -150,12 +155,14 @@
   });
 
   it('exposes the track renderer', () => {
-    const entry = assertExists(trackManager.getWrappedTrack(td.uri));
+    const entry = trackManager.getWrappedTrack(td.uri);
+    assertExists(entry);
     expect(entry.track).toBe(mockTrack);
   });
 
   it('exposes the track descriptor', () => {
-    const entry = assertExists(trackManager.getWrappedTrack(td.uri));
+    const entry = trackManager.getWrappedTrack(td.uri);
+    assertExists(entry);
     expect(entry.desc).toBe(td);
   });
 });
diff --git a/ui/src/core_plugins/dev.perfetto.CoreCommands/index.ts b/ui/src/core_plugins/dev.perfetto.CoreCommands/index.ts
index 33b01c6..00acaf7 100644
--- a/ui/src/core_plugins/dev.perfetto.CoreCommands/index.ts
+++ b/ui/src/core_plugins/dev.perfetto.CoreCommands/index.ts
@@ -43,7 +43,7 @@
 import {getTimeSpanOfSelectionOrVisibleWindow} from '../../public/utils';
 import {Workspace} from '../../public/workspace';
 import {showModal} from '../../widgets/modal';
-import {assertExists} from '../../base/assert';
+import {checkExists} from '../../base/assert';
 import {Setting} from '../../public/settings';
 import {toggleHelp} from '../../frontend/help_modal';
 
@@ -288,7 +288,7 @@
     const app = AppImpl.instance;
 
     // Rgister macros from settings first.
-    const settingMacros = assertExists(CoreCommands.macrosSetting).get();
+    const settingMacros = checkExists(CoreCommands.macrosSetting).get();
     for (const macro of settingMacros) {
       ctx.commands.registerMacro(macro);
     }
diff --git a/ui/src/core_plugins/dev.perfetto.MultiTraceOpen/multi_trace_controller_unittest.ts b/ui/src/core_plugins/dev.perfetto.MultiTraceOpen/multi_trace_controller_unittest.ts
index ccae870..af7eb97 100644
--- a/ui/src/core_plugins/dev.perfetto.MultiTraceOpen/multi_trace_controller_unittest.ts
+++ b/ui/src/core_plugins/dev.perfetto.MultiTraceOpen/multi_trace_controller_unittest.ts
@@ -79,7 +79,9 @@
     onProgress(1.0);
 
     if (this.errors.has(file.name)) {
-      throw new Error(assertExists(this.errors.get(file.name)?.message));
+      const message = this.errors.get(file.name)?.message;
+      assertExists(message);
+      throw new Error(message);
     }
     const result = this.results.get(file.name);
     if (result) {
diff --git a/ui/src/engine/wasm_bridge.ts b/ui/src/engine/wasm_bridge.ts
index ca1e4a7..e1e41a3 100644
--- a/ui/src/engine/wasm_bridge.ts
+++ b/ui/src/engine/wasm_bridge.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {defer} from '../base/deferred';
-import {assertExists, assertTrue} from '../base/assert';
+import {assertTrue, assertExists} from '../base/assert';
 
 // The 64-bit variant of TraceProcessor wasm is always built in all build
 // configurations and we can depend on it from typescript.
@@ -123,9 +123,11 @@
   // This function is bound and passed to Initialize and is called by the C++
   // code while in the ccall(trace_processor_on_rpc_request).
   private onReply(heapPtrArg: bigint | number, size: number) {
+    assertExists(this.messagePort);
+
     const heapPtr = this.wasmPtrCast(heapPtrArg);
     const data = this.connection.HEAPU8.slice(heapPtr, heapPtr + size);
-    assertExists(this.messagePort).postMessage(data, [data.buffer]);
+    this.messagePort.postMessage(data, [data.buffer]);
   }
 
   private appendAndLogErr(line: string) {
diff --git a/ui/src/frontend/help_modal.ts b/ui/src/frontend/help_modal.ts
index 8cf7a49..3a2637d 100644
--- a/ui/src/frontend/help_modal.ts
+++ b/ui/src/frontend/help_modal.ts
@@ -13,7 +13,6 @@
 // limitations under the License.
 
 import m from 'mithril';
-import {assertExists} from '../base/assert';
 import {AppImpl} from '../core/app_impl';
 import {HotkeyGlyphs, Keycap} from '../widgets/hotkey_glyphs';
 import {showModal} from '../widgets/modal';
@@ -24,6 +23,8 @@
   NotSupportedError,
 } from '../base/keyboard_layout_map';
 import {KeyMapping} from './timeline_page/wasd_navigation_handler';
+import {exists} from '../base/utils';
+import {Command} from '../public/command';
 
 export function toggleHelp() {
   AppImpl.instance.analytics.logEvent('User Actions', 'Show help');
@@ -158,7 +159,9 @@
       m(
         'table',
         AppImpl.instance.commands.commands
-          .filter(({defaultHotkey}) => defaultHotkey)
+          .filter((command): command is Required<Command> =>
+            exists(command.defaultHotkey),
+          )
           .sort((a, b) => a.name.localeCompare(b.name))
           .map(({defaultHotkey, name}) => {
             return m(
@@ -167,7 +170,7 @@
                 'td',
                 m(HotkeyGlyphs, {
                   spacing: 'large',
-                  hotkey: assertExists(defaultHotkey),
+                  hotkey: defaultHotkey,
                 }),
               ),
               m('td', name),
diff --git a/ui/src/frontend/omnibox.ts b/ui/src/frontend/omnibox.ts
index 2a70d88..50650ce 100644
--- a/ui/src/frontend/omnibox.ts
+++ b/ui/src/frontend/omnibox.ts
@@ -16,7 +16,7 @@
 import {classNames} from '../base/classnames';
 import {findRef} from '../base/dom_utils';
 import {FuzzyFinder, FuzzySegment} from '../base/fuzzy';
-import {assertExists, assertUnreachable} from '../base/assert';
+import {assertExists, assertUnreachable, checkExists} from '../base/assert';
 import {isString} from '../base/object_utils';
 import {exists} from '../base/utils';
 import {AppImpl} from '../core/app_impl';
@@ -74,7 +74,8 @@
 
   private renderPromptOmnibox(): m.Children {
     const omnibox = AppImpl.instance.omnibox;
-    const prompt = assertExists(omnibox.pendingPrompt);
+    const prompt = omnibox.pendingPrompt;
+    assertExists(prompt);
 
     let options: OmniboxOption[] | undefined = undefined;
 
@@ -192,7 +193,7 @@
 
   private renderRegisteredMode(): m.Children {
     const omnibox = AppImpl.instance.omnibox;
-    const desc = assertExists(omnibox.activeRegisteredMode);
+    const desc = checkExists(omnibox.activeRegisteredMode);
     return m(OmniboxWidget, {
       value: omnibox.text,
       placeholder: desc.placeholder,
diff --git a/ui/src/frontend/rpc_http_dialog.ts b/ui/src/frontend/rpc_http_dialog.ts
index 1235d45..573dc2c 100644
--- a/ui/src/frontend/rpc_http_dialog.ts
+++ b/ui/src/frontend/rpc_http_dialog.ts
@@ -14,7 +14,7 @@
 
 import m from 'mithril';
 import protos from '../protos';
-import {assertExists} from '../base/assert';
+import {checkExists, assertUnreachable} from '../base/assert';
 import {VERSION} from '../gen/perfetto_version';
 import {HttpRpcEngine} from '../trace_processor/http_rpc_engine';
 import {showModal} from '../widgets/modal';
@@ -155,7 +155,8 @@
     // No RPC = exit immediately to the WASM UI.
     return;
   }
-  const tpStatus = assertExists(state.status);
+
+  const tpStatus = checkExists(state.status);
 
   function forceWasm() {
     AppImpl.instance.httpRpc.newEngineMode = 'FORCE_BUILTIN_WASM';
@@ -179,8 +180,7 @@
           forceWasm();
           return;
         default:
-          const x: never = result;
-          throw new Error(`Unsupported result ${x}`);
+          assertUnreachable(result);
       }
     }
   }
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index d1c73a6..5785f9f 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -44,7 +44,7 @@
 import {classNames} from '../base/classnames';
 import {formatHotkey} from '../base/hotkeys';
 import {assetSrc} from '../base/assets';
-import {assertExists} from '../base/assert';
+import {checkExists} from '../base/assert';
 import {Icon} from '../widgets/icon';
 import {Button} from '../widgets/button';
 import {Router} from '../core/router';
@@ -156,7 +156,7 @@
           action: () => {
             enableMetatracing();
             engine.enableMetatrace(
-              assertExists(getEnabledMetatracingCategories()),
+              checkExists(getEnabledMetatracingCategories()),
             );
           },
         },
@@ -167,7 +167,7 @@
     });
   } else {
     enableMetatracing();
-    engine.enableMetatrace(assertExists(getEnabledMetatracingCategories()));
+    engine.enableMetatrace(checkExists(getEnabledMetatracingCategories()));
   }
 }
 
diff --git a/ui/src/frontend/timeline_page/minimap.ts b/ui/src/frontend/timeline_page/minimap.ts
index 41b0168..639bf24 100644
--- a/ui/src/frontend/timeline_page/minimap.ts
+++ b/ui/src/frontend/timeline_page/minimap.ts
@@ -15,7 +15,7 @@
 import m from 'mithril';
 import {Size2D} from '../../base/geom';
 import {HighPrecisionTimeSpan} from '../../base/high_precision_time_span';
-import {assertExists, assertUnreachable} from '../../base/assert';
+import {assertUnreachable, assertExists} from '../../base/assert';
 import {Time, time, TimeSpan} from '../../base/time';
 import {TimeScale} from '../../base/time_scale';
 import {colorForCpu} from '../../components/colorizer';
@@ -59,7 +59,8 @@
   }
 
   private renderBrushes(trace: TraceImpl, element: Element) {
-    const timeline = assertExists(findRef(element, MINIMAP_REF));
+    const timeline = findRef(element, MINIMAP_REF);
+    assertExists(timeline);
     const timelineWidth = timeline.getBoundingClientRect().width;
     const traceTime = trace.traceInfo;
     const pxBounds = {left: 0, right: timelineWidth};
diff --git a/ui/src/frontend/timeline_page/timeline_header.ts b/ui/src/frontend/timeline_page/timeline_header.ts
index a98fb29..e98b61b 100644
--- a/ui/src/frontend/timeline_page/timeline_header.ts
+++ b/ui/src/frontend/timeline_page/timeline_header.ts
@@ -15,9 +15,8 @@
 import m from 'mithril';
 import {canvasSave} from '../../base/canvas_utils';
 import {DisposableStack} from '../../base/disposable_stack';
-import {toHTMLElement} from '../../base/dom_utils';
 import {Rect2D, Size2D} from '../../base/geom';
-import {assertExists} from '../../base/assert';
+import {assertExists, assertInstanceOf} from '../../base/assert';
 import {TimeScale} from '../../base/time_scale';
 import {ZonedInteractionHandler} from '../../base/zoned_interaction_handler';
 import {TraceImpl} from '../../core/trace_impl';
@@ -108,8 +107,8 @@
   }
 
   oncreate({dom}: m.VnodeDOM<TimelineHeaderAttrs>) {
-    const timelineHeaderElement = toHTMLElement(dom);
-    this.interactions = new ZonedInteractionHandler(timelineHeaderElement);
+    assertInstanceOf(dom, HTMLElement);
+    this.interactions = new ZonedInteractionHandler(dom);
     this.trash.use(this.interactions);
   }
 
@@ -141,7 +140,8 @@
     const visibleWindow = this.trace.timeline.visibleWindow;
     const timescale = new TimeScale(visibleWindow, timelineRect);
 
-    assertExists(this.interactions).update([
+    assertExists(this.interactions);
+    this.interactions.update([
       shiftDragPanInteraction(this.trace, timelineRect, timescale),
       wheelNavigationInteraction(this.trace, timelineRect, timescale),
       {
diff --git a/ui/src/frontend/timeline_page/timeline_page.ts b/ui/src/frontend/timeline_page/timeline_page.ts
index 185e71e..9f0dc38 100644
--- a/ui/src/frontend/timeline_page/timeline_page.ts
+++ b/ui/src/frontend/timeline_page/timeline_page.ts
@@ -14,7 +14,7 @@
 
 import m from 'mithril';
 import {DisposableStack} from '../../base/disposable_stack';
-import {toHTMLElement} from '../../base/dom_utils';
+import {assertInstanceOf} from '../../base/assert';
 import {Rect2D} from '../../base/geom';
 import {TimeScale} from '../../base/time_scale';
 import {AppImpl} from '../../core/app_impl';
@@ -149,9 +149,11 @@
       m(ResizeHandle, {
         onResize: (deltaPx: number) => {
           if (this.pinnedTracksHeight === 'auto') {
-            this.pinnedTracksHeight = toHTMLElement(
-              document.querySelector('.pf-timeline-page__pinned-track-tree')!,
-            ).getBoundingClientRect().height;
+            const treeEl = document.querySelector(
+              '.pf-timeline-page__pinned-track-tree',
+            );
+            assertInstanceOf(treeEl, HTMLElement);
+            this.pinnedTracksHeight = treeEl.getBoundingClientRect().height;
           }
           this.pinnedTracksHeight = this.pinnedTracksHeight + deltaPx;
           m.redraw();
@@ -176,10 +178,11 @@
 
   oncreate(vnode: m.VnodeDOM<TimelinePageAttrs>) {
     const {attrs, dom} = vnode;
+    assertInstanceOf(dom, HTMLElement);
 
     // Handles WASD keybindings to pan & zoom
     const panZoomHandler = new KeyboardNavigationHandler({
-      element: toHTMLElement(dom),
+      element: dom,
       onPanned: (pannedPx: number) => {
         if (!this.timelineBounds) return;
         const timeline = attrs.trace.timeline;
diff --git a/ui/src/frontend/timeline_page/track_tree_view.ts b/ui/src/frontend/timeline_page/track_tree_view.ts
index 788efbe..e86d1e2 100644
--- a/ui/src/frontend/timeline_page/track_tree_view.ts
+++ b/ui/src/frontend/timeline_page/track_tree_view.ts
@@ -26,7 +26,7 @@
 import m from 'mithril';
 import {classNames} from '../../base/classnames';
 import {DisposableStack} from '../../base/disposable_stack';
-import {findRef, toHTMLElement} from '../../base/dom_utils';
+import {findRef} from '../../base/dom_utils';
 import {
   HorizontalBounds,
   Rect2D,
@@ -36,7 +36,7 @@
 } from '../../base/geom';
 import {HighPrecisionTime} from '../../base/high_precision_time';
 import {HighPrecisionTimeSpan} from '../../base/high_precision_time_span';
-import {assertExists} from '../../base/assert';
+import {assertExists, assertInstanceOf} from '../../base/assert';
 import {Time} from '../../base/time';
 import {TimeScale} from '../../base/time_scale';
 import {
@@ -398,15 +398,14 @@
     // or change every update cycle. This chunk of code hooks the
     // ZonedInteractionHandler back up again if the DOM element is present,
     // otherwise it just removes it.
-    const interactionTarget = findRef(dom, TRACK_CONTAINER_REF) ?? undefined;
+    const interactionTarget = findRef(dom, TRACK_CONTAINER_REF);
     if (interactionTarget !== this.interactions?.target) {
       this.interactions?.[Symbol.dispose]();
       if (!interactionTarget) {
         this.interactions = undefined;
       } else {
-        this.interactions = new ZonedInteractionHandler(
-          toHTMLElement(interactionTarget),
-        );
+        assertInstanceOf(interactionTarget, HTMLElement);
+        this.interactions = new ZonedInteractionHandler(interactionTarget);
       }
     }
   }
@@ -589,7 +588,8 @@
     const areaSelection =
       trace.selection.selection.kind === 'area' && trace.selection.selection;
 
-    assertExists(this.interactions).update([
+    assertExists(this.interactions);
+    this.interactions.update([
       shiftDragPanInteraction(trace, timelineRect, timescale),
       areaSelection !== false && {
         id: 'start-edit',
diff --git a/ui/src/plugins/com.android.DayExplorer/index.ts b/ui/src/plugins/com.android.DayExplorer/index.ts
index 3e79241..e97fcb1 100644
--- a/ui/src/plugins/com.android.DayExplorer/index.ts
+++ b/ui/src/plugins/com.android.DayExplorer/index.ts
@@ -30,7 +30,7 @@
 import SupportPlugin from '../com.android.AndroidLongBatterySupport';
 import {Store} from '../../base/store';
 import {z} from 'zod';
-import {assertExists} from '../../base/assert';
+import {assertExists, checkExists} from '../../base/assert';
 
 const DAY_EXPLORER_TRACK_KIND = 'day_explorer_counter_track';
 
@@ -153,6 +153,8 @@
       id: 'day_explorer_flamegraph_selection',
       name: 'Day Explorer Flamegraph',
       render: (selection: AreaSelection) => {
+        const store = checkExists(this.store);
+
         const selectionChanged =
           previousSelection === undefined ||
           !areaSelectionsEqual(previousSelection, selection);
@@ -166,7 +168,6 @@
         if (flameagraphWithMetrics === undefined) {
           return undefined;
         }
-        const store = assertExists(this.store);
         const {flamegraph, metrics} = flameagraphWithMetrics;
         return {
           isLoading: false,
@@ -174,6 +175,7 @@
             metrics,
             state: store.state.areaSelectionFlamegraphState,
             onStateChange: (state) => {
+              assertExists(this.store);
               store.edit((draft) => {
                 draft.areaSelectionFlamegraphState = state;
               });
@@ -188,6 +190,8 @@
     trace: Trace,
     currentSelection: AreaSelection,
   ): QueryFlamegraphWithMetrics | undefined {
+    assertExists(this.store);
+
     // The flame graph will be shown when any day explorer track is in the area
     // selection. The selection is used to filter by time, but not by track. All
     // day explorer tracks are considered for the graph.
@@ -239,8 +243,8 @@
       ],
       nameColumnLabel: 'Component',
     });
-    const store = assertExists(this.store);
-    store.edit((draft) => {
+
+    this.store.edit((draft) => {
       draft.areaSelectionFlamegraphState = Flamegraph.updateState(
         draft.areaSelectionFlamegraphState,
         metrics,
diff --git a/ui/src/plugins/dev.perfetto.AggregateProfiles/index.ts b/ui/src/plugins/dev.perfetto.AggregateProfiles/index.ts
index d244d07..bd51613 100644
--- a/ui/src/plugins/dev.perfetto.AggregateProfiles/index.ts
+++ b/ui/src/plugins/dev.perfetto.AggregateProfiles/index.ts
@@ -24,7 +24,7 @@
   AGGREGATE_PROFILES_PAGE_STATE_SCHEMA,
 } from './types';
 import {Store} from '../../base/store';
-import {assertExists} from '../../base/assert';
+import {checkExists} from '../../base/assert';
 
 export default class implements PerfettoPlugin {
   static readonly id = 'dev.perfetto.AggregateProfiles';
@@ -43,7 +43,7 @@
     if (profiles.length === 0) {
       return;
     }
-    const store = assertExists(this.store);
+    const store = checkExists(this.store);
     trace.pages.registerPage({
       route: '/aggregateprofiles',
       render: () =>
diff --git a/ui/src/plugins/dev.perfetto.AutoPinAndExpandTracks/index.ts b/ui/src/plugins/dev.perfetto.AutoPinAndExpandTracks/index.ts
index f66a662..a309950 100644
--- a/ui/src/plugins/dev.perfetto.AutoPinAndExpandTracks/index.ts
+++ b/ui/src/plugins/dev.perfetto.AutoPinAndExpandTracks/index.ts
@@ -18,7 +18,7 @@
 import {PerfettoPlugin} from '../../public/plugin';
 import {Track} from '../../public/track';
 import {z} from 'zod';
-import {assertIsInstance} from '../../base/assert';
+import {assertInstanceOf} from '../../base/assert';
 import {RouteArg, RouteArgs} from '../../public/route_schema';
 import {arrayEquals} from '../../base/array_utils';
 
@@ -182,7 +182,8 @@
       name: 'Import by name: Pinned tracks',
       callback: async () => {
         const files = document.querySelector('.pinned_tracks_import_selector');
-        assertIsInstance<HTMLInputElement>(files, HTMLInputElement).click();
+        assertInstanceOf(files, HTMLInputElement);
+        files.click();
       },
     });
 
diff --git a/ui/src/plugins/dev.perfetto.CpuProfile/index.ts b/ui/src/plugins/dev.perfetto.CpuProfile/index.ts
index 3d35887..badf75f 100644
--- a/ui/src/plugins/dev.perfetto.CpuProfile/index.ts
+++ b/ui/src/plugins/dev.perfetto.CpuProfile/index.ts
@@ -27,7 +27,7 @@
   QueryFlamegraphWithMetrics,
 } from '../../components/query_flamegraph';
 import {Flamegraph, FLAMEGRAPH_STATE_SCHEMA} from '../../widgets/flamegraph';
-import {assertExists} from '../../base/assert';
+import {assertExists, checkExists} from '../../base/assert';
 import {Store} from '../../base/store';
 import {z} from 'zod';
 
@@ -72,7 +72,6 @@
       where not is_idle
     `);
 
-    const store = assertExists(this.store);
     const it = result.iter({
       utid: NUM,
       upid: NUM_NULL,
@@ -94,9 +93,10 @@
           ctx,
           uri,
           utid,
-          store.state.detailsPanelFlamegraphState,
+          this.store.state.detailsPanelFlamegraphState,
           (state) => {
-            store.edit((draft) => {
+            assertExists(this.store);
+            this.store.edit((draft) => {
               draft.detailsPanelFlamegraphState = state;
             });
           },
@@ -142,7 +142,7 @@
           return undefined;
         }
         const {flamegraph, metrics} = flamegraphWithMetrics;
-        const store = assertExists(this.store);
+        const store = checkExists(this.store);
         return {
           isLoading: false,
           content: flamegraph.render({
@@ -211,7 +211,7 @@
       ],
       nameColumnLabel: 'Symbol',
     });
-    const store = assertExists(this.store);
+    const store = checkExists(this.store);
     store.edit((draft) => {
       draft.areaSelectionFlamegraphState = Flamegraph.updateState(
         draft.areaSelectionFlamegraphState,
@@ -223,7 +223,7 @@
 }
 
 async function selectCpuProfileCallsite(trace: Trace) {
-  const profile = await assertExists(trace.engine).query(`
+  const profile = await checkExists(trace.engine).query(`
     select utid, upid
     from cpu_profile_stack_sample
     join thread using(utid)
diff --git a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/widgets.ts b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/widgets.ts
index d678cf8..408221b 100644
--- a/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/widgets.ts
+++ b/ui/src/plugins/dev.perfetto.DataExplorer/query_builder/widgets.ts
@@ -35,8 +35,8 @@
 import {showModal} from '../../../widgets/modal';
 import {Editor} from '../../../widgets/editor';
 import {ResizeHandle} from '../../../widgets/resize_handle';
-import {findRef, toHTMLElement} from '../../../base/dom_utils';
-import {assertExists} from '../../../base/assert';
+import {findRef} from '../../../base/dom_utils';
+import {assertInstanceOf} from '../../../base/assert';
 
 /**
  * Round action button with consistent styling for Data Explorer.
@@ -1067,6 +1067,8 @@
   autofocus?: boolean;
 }
 
+const EDITOR_REF = 'editor';
+
 export class ResizableSqlEditor
   implements m.ClassComponent<ResizableSqlEditorAttrs>
 {
@@ -1074,14 +1076,15 @@
   private editorElement?: HTMLElement;
 
   oncreate({dom}: m.VnodeDOM<ResizableSqlEditorAttrs>) {
-    this.editorElement = toHTMLElement(assertExists(findRef(dom, 'editor')));
-    this.editorElement.style.height = '400px';
+    const element = findRef(dom, EDITOR_REF);
+    assertInstanceOf(element, HTMLElement);
+    element.style.height = '400px';
   }
 
   view({attrs}: m.CVnode<ResizableSqlEditorAttrs>) {
     return [
       m(Editor, {
-        ref: 'editor',
+        ref: EDITOR_REF,
         text: attrs.sql,
         onUpdate: attrs.onUpdate,
         onExecute: attrs.onExecute,
diff --git a/ui/src/plugins/dev.perfetto.HeapProfile/index.ts b/ui/src/plugins/dev.perfetto.HeapProfile/index.ts
index 51c1bc6..054282e 100644
--- a/ui/src/plugins/dev.perfetto.HeapProfile/index.ts
+++ b/ui/src/plugins/dev.perfetto.HeapProfile/index.ts
@@ -26,7 +26,6 @@
 import {FLAMEGRAPH_STATE_SCHEMA} from '../../widgets/flamegraph';
 import {Store} from '../../base/store';
 import {z} from 'zod';
-import {assertExists} from '../../base/assert';
 import {
   isProfileDescriptor,
   ProfileDescriptor,
@@ -39,6 +38,7 @@
   AreaSelectionTab,
 } from '../../public/selection';
 import {HeapProfileFlamegraphDetailsPanel} from './heap_profile_details_panel';
+import {checkExists} from '../../base/assert';
 
 const EVENT_TABLE_NAME = 'heap_profile_events';
 
@@ -194,7 +194,7 @@
         const group = trackGroupsPlugin.getGroupForProcess(upid);
         if (!group) continue;
 
-        const store = assertExists(this.store);
+        const store = checkExists(this.store);
         const uri = trackUri(upid, heapType);
         const descriptor = profileDescriptor(heapType);
         const track: Track = {
@@ -279,7 +279,7 @@
       id: `heap_profiler_flamegraph_selection_${descriptor.heapName}`,
       name: `${descriptor.label} flamegraph`,
       render: (selection: AreaSelection) => {
-        const store = assertExists(this.store);
+        const store = checkExists(this.store);
         const selectionChanged =
           previousSelection === undefined ||
           !areaSelectionsEqual(previousSelection, selection);
diff --git a/ui/src/plugins/dev.perfetto.InstrumentsSamplesProfile/index.ts b/ui/src/plugins/dev.perfetto.InstrumentsSamplesProfile/index.ts
index 4dee48e..9d9c829 100644
--- a/ui/src/plugins/dev.perfetto.InstrumentsSamplesProfile/index.ts
+++ b/ui/src/plugins/dev.perfetto.InstrumentsSamplesProfile/index.ts
@@ -21,7 +21,7 @@
   NUM_NULL,
   STR_NULL,
 } from '../../trace_processor/query_result';
-import {assertExists} from '../../base/assert';
+import {checkExists} from '../../base/assert';
 import {getThreadUriPrefix} from '../../public/utils';
 import {TrackNode} from '../../public/workspace';
 import ProcessThreadGroupsPlugin from '../dev.perfetto.ProcessThreadGroups';
@@ -85,7 +85,7 @@
       join thread using (utid)
       where callsite_id is not null and upid is not null
     `);
-    const store = assertExists(this.store);
+    const store = checkExists(this.store);
     for (const it = pResult.iter({upid: NUM}); it.valid(); it.next()) {
       const upid = it.upid;
       const uri = makeUriForProc(upid);
@@ -197,7 +197,7 @@
           return undefined;
         }
         const {flamegraph, metrics} = flamegraphWithMetrics;
-        const store = assertExists(this.store);
+        const store = checkExists(this.store);
         return {
           isLoading: false,
           content: flamegraph.render({
@@ -250,7 +250,7 @@
       dependencySql: 'include perfetto module appleos.instruments.samples',
       nameColumnLabel: 'Symbol',
     });
-    const store = assertExists(this.store);
+    const store = checkExists(this.store);
     store.edit((draft) => {
       draft.areaSelectionFlamegraphState = Flamegraph.updateState(
         draft.areaSelectionFlamegraphState,
@@ -262,7 +262,7 @@
 }
 
 async function selectInstrumentsSample(ctx: Trace) {
-  const profile = await assertExists(ctx.engine).query(`
+  const profile = await checkExists(ctx.engine).query(`
     select upid
     from instruments_sample
     join thread using (utid)
@@ -293,7 +293,7 @@
       ) &&
       trackInfo.tags?.utid === undefined
     ) {
-      upids.push(assertExists(trackInfo.tags?.upid));
+      upids.push(checkExists(trackInfo.tags?.upid));
     }
   }
   return upids;
diff --git a/ui/src/plugins/dev.perfetto.KernelTrackEvent/index.ts b/ui/src/plugins/dev.perfetto.KernelTrackEvent/index.ts
index 4d72d02..169d02e 100644
--- a/ui/src/plugins/dev.perfetto.KernelTrackEvent/index.ts
+++ b/ui/src/plugins/dev.perfetto.KernelTrackEvent/index.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {assertExists} from '../../base/assert';
+import {checkExists} from '../../base/assert';
 import {PerfettoPlugin} from '../../public/plugin';
 import {Trace} from '../../public/trace';
 import {COUNTER_TRACK_KIND, SLICE_TRACK_KIND} from '../../public/track_kinds';
@@ -109,7 +109,7 @@
       const {trackId, name, utid, upid, cpu, isCounter, scope, tid, pid} = it;
 
       const displayTrackName = this.getTrackName(
-        assertExists(name),
+        checkExists(name),
         pid ?? undefined,
         tid ?? undefined,
         cpu ?? undefined,
@@ -204,21 +204,21 @@
     cpu: number | undefined,
   ): TrackNode {
     if (upid !== undefined) {
-      return assertExists(
+      return checkExists(
         ctx.plugins
           .getPlugin(ProcessThreadGroupsPlugin)
-          .getGroupForProcess(assertExists(upid)),
+          .getGroupForProcess(checkExists(upid)),
       );
     }
     if (utid !== undefined) {
-      return assertExists(
+      return checkExists(
         ctx.plugins
           .getPlugin(ProcessThreadGroupsPlugin)
-          .getGroupForThread(assertExists(utid)),
+          .getGroupForThread(checkExists(utid)),
       );
     }
     if (cpu !== undefined) {
-      return assertExists(
+      return checkExists(
         ctx.plugins
           .getPlugin(StandardGroupsPlugin)
           .getOrCreateStandardGroup(ctx.defaultWorkspace, 'CPU'),
@@ -226,7 +226,7 @@
     }
     // custom-scoped event: "Kernel -> Kernel track events".
     if (this.kernelTrackEventsNode === undefined) {
-      const kernelGroup = assertExists(
+      const kernelGroup = checkExists(
         ctx.plugins
           .getPlugin(StandardGroupsPlugin)
           .getOrCreateStandardGroup(ctx.defaultWorkspace, 'KERNEL'),
diff --git a/ui/src/plugins/dev.perfetto.LinuxPerf/index.ts b/ui/src/plugins/dev.perfetto.LinuxPerf/index.ts
index 93fd5e9..ec96e02 100644
--- a/ui/src/plugins/dev.perfetto.LinuxPerf/index.ts
+++ b/ui/src/plugins/dev.perfetto.LinuxPerf/index.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {assertExists} from '../../base/assert';
+import {checkExists} from '../../base/assert';
 import {
   QueryFlamegraph,
   QueryFlamegraphMetric,
@@ -79,7 +79,7 @@
     this.store = trace.mountStore(LinuxPerfPlugin.id, (init) =>
       this.migrateLinuxPerfPluginState(init),
     );
-    const store = assertExists(this.store);
+    const store = checkExists(this.store);
     await this.cacheCounterTypesPerSession(trace);
     await this.addProcessPerfSamplesTracks(trace, store);
     await this.addThreadPerfSamplesTracks(trace, store);
@@ -447,7 +447,7 @@
           return undefined;
         }
         const {flamegraph, metrics} = flamegraphWithMetrics;
-        const store = assertExists(this.store);
+        const store = checkExists(this.store);
         return {
           isLoading: false,
           content: flamegraph.render({
@@ -571,7 +571,7 @@
       aggregatableProperties,
     });
 
-    const store = assertExists(this.store);
+    const store = checkExists(this.store);
     store.edit((draft) => {
       draft.areaSelectionFlamegraphState = Flamegraph.updateState(
         draft.areaSelectionFlamegraphState,
@@ -606,7 +606,7 @@
 }
 
 async function selectPerfTracksIfSingleProcess(trace: Trace) {
-  const profile = await assertExists(trace.engine).query(`
+  const profile = await checkExists(trace.engine).query(`
     select distinct upid
     from perf_sample
     join thread using (utid)
@@ -629,7 +629,7 @@
       trackInfo.tags?.utid === undefined
     ) {
       ret.push([
-        assertExists(trackInfo.tags?.upid),
+        checkExists(trackInfo.tags?.upid),
         Number(trackInfo.tags.perfSessionId),
       ]);
     }
diff --git a/ui/src/plugins/dev.perfetto.MetricsPage/metrics_page.ts b/ui/src/plugins/dev.perfetto.MetricsPage/metrics_page.ts
index 89e04ba..376b09e 100644
--- a/ui/src/plugins/dev.perfetto.MetricsPage/metrics_page.ts
+++ b/ui/src/plugins/dev.perfetto.MetricsPage/metrics_page.ts
@@ -18,7 +18,7 @@
 import {STR} from '../../trace_processor/query_result';
 import {Select} from '../../widgets/select';
 import {Spinner} from '../../widgets/spinner';
-import {assertExists, assertUnreachable} from '../../base/assert';
+import {checkExists, assertUnreachable} from '../../base/assert';
 import {Trace} from '../../public/trace';
 import {SegmentedButtons} from '../../widgets/segmented_buttons';
 import {Editor} from '../../widgets/editor';
@@ -669,7 +669,7 @@
   }
 
   view({attrs}: m.Vnode<MetricsPageAttrs>) {
-    const v1Controller = assertExists(this.v1Controller);
+    const v1Controller = checkExists(this.v1Controller);
     return m(
       '.pf-metrics-page',
       m(
diff --git a/ui/src/plugins/dev.perfetto.ProcessSummary/group_summary_track.ts b/ui/src/plugins/dev.perfetto.ProcessSummary/group_summary_track.ts
index b32f269..c44e0ae 100644
--- a/ui/src/plugins/dev.perfetto.ProcessSummary/group_summary_track.ts
+++ b/ui/src/plugins/dev.perfetto.ProcessSummary/group_summary_track.ts
@@ -20,7 +20,7 @@
 import {ColorScheme} from '../../base/color_scheme';
 import {AsyncDisposableStack} from '../../base/disposable_stack';
 import {Point2D} from '../../base/geom';
-import {assertExists, assertTrue} from '../../base/assert';
+import {assertTrue, assertExists} from '../../base/assert';
 import {Monitor} from '../../base/monitor';
 import {
   CancellationSignal,
diff --git a/ui/src/plugins/dev.perfetto.RecordTraceV2/adb/web_device_proxy/wdp_websocket_stream .ts b/ui/src/plugins/dev.perfetto.RecordTraceV2/adb/web_device_proxy/wdp_websocket_stream .ts
index be8bc36..d0f38f8 100644
--- a/ui/src/plugins/dev.perfetto.RecordTraceV2/adb/web_device_proxy/wdp_websocket_stream .ts
+++ b/ui/src/plugins/dev.perfetto.RecordTraceV2/adb/web_device_proxy/wdp_websocket_stream .ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import z from 'zod';
-import {assertExists, assertTrue} from '../../../../base/assert';
+import {checkExists, assertTrue} from '../../../../base/assert';
 import {ByteStream} from '../../interfaces/byte_stream';
 import {base64Decode} from '../../../../base/string_utils';
 
@@ -39,7 +39,7 @@
         // Unmarshall this transparently to caller.
         const parsed = this.schema.safeParse(JSON.parse(e.data));
         this.onData(
-          new Uint8Array(base64Decode(assertExists(parsed.data).response)),
+          new Uint8Array(base64Decode(checkExists(parsed.data).response)),
         );
       } else {
         assertTrue(e.data instanceof ArrayBuffer);
diff --git a/ui/src/plugins/dev.perfetto.RecordTraceV2/adb/webusb/adb_key.ts b/ui/src/plugins/dev.perfetto.RecordTraceV2/adb/webusb/adb_key.ts
index 4b636bd..f3e2c1f 100644
--- a/ui/src/plugins/dev.perfetto.RecordTraceV2/adb/webusb/adb_key.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTraceV2/adb/webusb/adb_key.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {BigInteger, RSAKey} from 'jsbn-rsa';
-import {assertExists, assertTrue} from '../../../../base/assert';
+import {checkExists, assertTrue} from '../../../../base/assert';
 import {
   base64Decode,
   base64Encode,
@@ -135,8 +135,8 @@
   getPublicKey(): string {
     const rsaKey = new RSAKey();
     rsaKey.setPublic(
-      hexEncode(base64Decode(assertExists(this.jwkPrivate.n))),
-      hexEncode(base64Decode(assertExists(this.jwkPrivate.e))),
+      hexEncode(base64Decode(checkExists(this.jwkPrivate.n))),
+      hexEncode(base64Decode(checkExists(this.jwkPrivate.e))),
     );
 
     const n0inv = R32.subtract(rsaKey.n.modInverse(R32)).intValue();
diff --git a/ui/src/plugins/dev.perfetto.RecordTraceV2/config/config_manager.ts b/ui/src/plugins/dev.perfetto.RecordTraceV2/config/config_manager.ts
index 5f9be71..ee7c79b 100644
--- a/ui/src/plugins/dev.perfetto.RecordTraceV2/config/config_manager.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTraceV2/config/config_manager.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import protos from '../../../protos';
-import {assertExists, assertFalse, assertTrue} from '../../../base/assert';
+import {checkExists, assertFalse, assertTrue} from '../../../base/assert';
 import {getOrCreate} from '../../../base/utils';
 import {ProbesSchema} from '../serialization_schema';
 import {TargetPlatformId} from '../interfaces/target_platform';
@@ -59,7 +59,7 @@
   }
 
   setProbeEnabled(probeId: string, enabled: boolean) {
-    const probe = assertExists(this.probesById.get(probeId));
+    const probe = checkExists(this.probesById.get(probeId));
     this.enabledProbes.set(probeId, enabled);
     for (const depProbeId of probe.dependencies ?? []) {
       assertTrue(this.probesById.has(depProbeId));
@@ -102,7 +102,7 @@
    */
   getProbeEnableDependants(probeId: string): string[] {
     return Array.from(this.indirectlyEnabledProbes.get(probeId) ?? []).map(
-      (id) => assertExists(this.probesById.get(id)).title,
+      (id) => checkExists(this.probesById.get(id)).title,
     );
   }
 
@@ -179,7 +179,7 @@
     const seenIds = new Set<string>();
     const queueProbe = (probeId: string) => {
       if (enabledOnly && !this.isProbeEnabled(probeId)) return;
-      const probe = assertExists(this.probesById.get(probeId));
+      const probe = checkExists(this.probesById.get(probeId));
       if (orderedProbes.includes(probe)) return; // Already added.
       if (seenIds.has(probeId)) {
         throw new Error('Cycle detected in probe ' + probeId);
diff --git a/ui/src/plugins/dev.perfetto.RecordTraceV2/config/config_sharing.ts b/ui/src/plugins/dev.perfetto.RecordTraceV2/config/config_sharing.ts
index 3e728eb..c3f9c2f 100644
--- a/ui/src/plugins/dev.perfetto.RecordTraceV2/config/config_sharing.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTraceV2/config/config_sharing.ts
@@ -14,7 +14,7 @@
 
 import m from 'mithril';
 import {GcsUploader} from '../../../base/gcs_uploader';
-import {assertExists} from '../../../base/assert';
+import {checkExists} from '../../../base/assert';
 import {CopyableLink} from '../../../widgets/copyable_link';
 import {showModal} from '../../../widgets/modal';
 import {RecordSessionSchema} from '../serialization_schema';
@@ -31,7 +31,7 @@
   const uploader = new GcsUploader(json, {mimeType: 'application/json'});
   await uploader.waitForCompletion();
   const url = uploader.uploadedUrl;
-  const hash = assertExists(url.split('/').pop());
+  const hash = checkExists(url.split('/').pop());
   showModal({
     title: 'Permalink',
     content: m(CopyableLink, {
diff --git a/ui/src/plugins/dev.perfetto.RecordTraceV2/config/trace_config_builder.ts b/ui/src/plugins/dev.perfetto.RecordTraceV2/config/trace_config_builder.ts
index 881301a..8e749dd 100644
--- a/ui/src/plugins/dev.perfetto.RecordTraceV2/config/trace_config_builder.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTraceV2/config/trace_config_builder.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {assertExists, assertFalse} from '../../../base/assert';
+import {checkExists, assertFalse} from '../../../base/assert';
 import {getOrCreate} from '../../../base/utils';
 import protos from '../../../protos';
 
@@ -39,7 +39,7 @@
   }
 
   get defaultBuffer(): BufferConfig {
-    return assertExists(this.buffers.get(DEFAULT_BUFFER_ID));
+    return checkExists(this.buffers.get(DEFAULT_BUFFER_ID));
   }
 
   // It has get-or-create semantics.
diff --git a/ui/src/plugins/dev.perfetto.RecordTraceV2/pages/memory.ts b/ui/src/plugins/dev.perfetto.RecordTraceV2/pages/memory.ts
index cebeda9..56e8ade 100644
--- a/ui/src/plugins/dev.perfetto.RecordTraceV2/pages/memory.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTraceV2/pages/memory.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {assertExists} from '../../../base/assert';
+import {checkExists} from '../../../base/assert';
 import {splitLinesNonEmpty} from '../../../base/string_utils';
 import protos from '../../../protos';
 import {
@@ -341,7 +341,7 @@
       const ds = tc.addDataSource(PROC_STATS_DS_NAME, ADV_PROC_ASSOC_BUF_ID);
       // Because of the dependency on ADV_PROC_ASSOC_PROBE_ID, we expect
       // procThreadAssociation() to create the config first.
-      const cfg = assertExists(ds.processStatsConfig);
+      const cfg = checkExists(ds.processStatsConfig);
       cfg.procStatsPollMs = settings.pollMs.value || undefined;
       cfg.recordProcessAge = settings.procAge.enabled || undefined;
       cfg.recordProcessRuntime = settings.procRuntime.enabled || undefined;
diff --git a/ui/src/plugins/dev.perfetto.RecordTraceV2/tracing_protocol/tracing_protocol.ts b/ui/src/plugins/dev.perfetto.RecordTraceV2/tracing_protocol/tracing_protocol.ts
index 2822c7e..cd10b40 100644
--- a/ui/src/plugins/dev.perfetto.RecordTraceV2/tracing_protocol/tracing_protocol.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTraceV2/tracing_protocol/tracing_protocol.ts
@@ -19,7 +19,7 @@
 import {ProtoRingBuffer} from '../../../trace_processor/proto_ring_buffer';
 import {defer} from '../../../base/deferred';
 import {exists} from '../../../base/utils';
-import {assertExists, assertFalse, assertTrue} from '../../../base/assert';
+import {checkExists, assertFalse, assertTrue} from '../../../base/assert';
 import {PacketAssembler} from './packet_assembler';
 import {ResizableArrayBuffer} from '../../../base/resizable_array_buffer';
 
@@ -70,12 +70,12 @@
     const frameData = await repsponsePromise;
     const rxFrame = protos.IPCFrame.decode(frameData);
     assertTrue(rxFrame.msg === 'msgBindServiceReply');
-    const replyMsg = assertExists(rxFrame.msgBindServiceReply);
+    const replyMsg = checkExists(rxFrame.msgBindServiceReply);
     const boundMethods = new Map<string, number>();
     assertTrue(replyMsg.success === true);
-    const serviceId = assertExists(replyMsg.serviceId);
-    for (const m of assertExists(replyMsg.methods)) {
-      boundMethods.set(assertExists(m.name), assertExists(m.id));
+    const serviceId = checkExists(replyMsg.serviceId);
+    for (const m of checkExists(replyMsg.methods)) {
+      boundMethods.set(checkExists(m.name), checkExists(m.id));
     }
     // Now that the details of the RPC methods are known, build and return the
     // TracingProtocol object, so the caller can finally make calls.
@@ -191,8 +191,8 @@
     // See 170256902#comment21
     const frame = protos.IPCFrame.decode(frameData.slice());
     if (frame.msg === 'msgInvokeMethodReply') {
-      const reply = assertExists(frame.msgInvokeMethodReply);
-      const pendInvoke = assertExists(this.pendingInvokes.get(frame.requestId));
+      const reply = checkExists(frame.msgInvokeMethodReply);
+      const pendInvoke = checkExists(this.pendingInvokes.get(frame.requestId));
       // We process messages without a `replyProto` field (for instance
       // `FreeBuffers` does not have `replyProto`). However, we ignore messages
       // without a valid 'success' field.
diff --git a/ui/src/plugins/dev.perfetto.RecordTraceV2/websocket/async_websocket.ts b/ui/src/plugins/dev.perfetto.RecordTraceV2/websocket/async_websocket.ts
index b7a96ab..01b9ff0 100644
--- a/ui/src/plugins/dev.perfetto.RecordTraceV2/websocket/async_websocket.ts
+++ b/ui/src/plugins/dev.perfetto.RecordTraceV2/websocket/async_websocket.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {defer, Deferred} from '../../../base/deferred';
-import {assertExists, assertTrue} from '../../../base/assert';
+import {checkExists, assertTrue} from '../../../base/assert';
 import {ResizableArrayBuffer} from '../../../base/resizable_array_buffer';
 import {utf8Decode, utf8Encode} from '../../../base/string_utils';
 
@@ -72,7 +72,7 @@
 
   /** Turns this back into a standard WebSocket. */
   release(): WebSocket {
-    const sock = assertExists(this.sock);
+    const sock = checkExists(this.sock);
     this.sock = undefined;
     sock.onmessage = null;
     sock.onopen = null;
@@ -82,7 +82,7 @@
   }
 
   send(data: string | ArrayBufferLike) {
-    assertExists(this.sock).send(data);
+    checkExists(this.sock).send(data);
   }
 
   waitForData(numBytes: number = ANY_SIZE): Promise<Uint8Array> {
diff --git a/ui/src/plugins/dev.perfetto.Thread/index.ts b/ui/src/plugins/dev.perfetto.Thread/index.ts
index 00d31d4..cd054bf 100644
--- a/ui/src/plugins/dev.perfetto.Thread/index.ts
+++ b/ui/src/plugins/dev.perfetto.Thread/index.ts
@@ -26,7 +26,7 @@
   STR,
   STR_NULL,
 } from '../../trace_processor/query_result';
-import {assertExists} from '../../base/assert';
+import {checkExists} from '../../base/assert';
 
 async function listThreads(trace: Trace) {
   const query = `
@@ -147,6 +147,6 @@
   }
 
   getThreadMap() {
-    return assertExists(this.threads);
+    return checkExists(this.threads);
   }
 }
diff --git a/ui/src/plugins/dev.perfetto.TimelineSync/index.ts b/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
index 9a56120..f31b953 100644
--- a/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
+++ b/ui/src/plugins/dev.perfetto.TimelineSync/index.ts
@@ -17,7 +17,7 @@
 import {PerfettoPlugin} from '../../public/plugin';
 import {Time, TimeSpan} from '../../base/time';
 import {redrawModal, showModal} from '../../widgets/modal';
-import {assertExists} from '../../base/assert';
+import {checkExists} from '../../base/assert';
 import {Button, ButtonBar, ButtonVariant} from '../../widgets/button';
 import {Intent} from '../../widgets/common';
 import {HighPrecisionTimeSpan} from '../../base/high_precision_time_span';
@@ -174,7 +174,7 @@
       // Disable any prior session.
       this.disableTimelineSync(this._sessionId);
       const selectedClients = new Array<ClientId>();
-      const sel = assertExists(clientsSelect).selectedOptions;
+      const sel = checkExists(clientsSelect).selectedOptions;
       for (let i = 0; i < sel.length; i++) {
         const clientId = parseInt(sel[i].value);
         if (!isNaN(clientId)) selectedClients.push(clientId);
diff --git a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/index.ts b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/index.ts
index 6d6d71b..c3eb83d 100644
--- a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/index.ts
+++ b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/index.ts
@@ -14,7 +14,7 @@
 
 import {removeFalsyValues} from '../../base/array_utils';
 import {AsyncLimiter} from '../../base/async_limiter';
-import {assertExists} from '../../base/assert';
+import {checkExists} from '../../base/assert';
 import {Time} from '../../base/time';
 import {
   createAggregationTab,
@@ -439,19 +439,19 @@
   ) {
     switch (topLevelGroup) {
       case 'PROCESS': {
-        const process = assertExists(
+        const process = checkExists(
           ctx.plugins
             .getPlugin(ProcessThreadGroupsPlugin)
-            .getGroupForProcess(assertExists(upid)),
+            .getGroupForProcess(checkExists(upid)),
         );
         this.getGroupByName(process, group, upid).addChildInOrder(track);
         break;
       }
       case 'THREAD': {
-        const thread = assertExists(
+        const thread = checkExists(
           ctx.plugins
             .getPlugin(ProcessThreadGroupsPlugin)
-            .getGroupForThread(assertExists(utid)),
+            .getGroupForThread(checkExists(utid)),
         );
         this.getGroupByName(thread, group, utid).addChildInOrder(track);
         break;
@@ -555,7 +555,7 @@
         if (flamegraphWithMetrics === undefined && !isLoading) {
           return undefined;
         }
-        const store = assertExists(this.store);
+        const store = checkExists(this.store);
         return {
           isLoading: isLoading,
           content: flamegraphWithMetrics?.flamegraph?.render({
@@ -658,7 +658,7 @@
       ],
       nameColumnLabel: 'Slice Name',
     });
-    const store = assertExists(this.store);
+    const store = checkExists(this.store);
     store.edit((draft) => {
       draft.areaSelectionFlamegraphState = Flamegraph.updateState(
         draft.areaSelectionFlamegraphState,
@@ -737,8 +737,8 @@
         // Only process upids that have valid track groups
         const rows: MinimapRow[] = [];
         const sortedUpids = Array.from(upidOrderMap.keys()).sort((a, b) => {
-          const orderA = assertExists(upidOrderMap.get(a));
-          const orderB = assertExists(upidOrderMap.get(b));
+          const orderA = checkExists(upidOrderMap.get(a));
+          const orderB = checkExists(upidOrderMap.get(b));
           return orderA - orderB;
         });
 
diff --git a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/pivot_table_tab.ts b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/pivot_table_tab.ts
index 5c11f92..c7a1e36 100644
--- a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/pivot_table_tab.ts
+++ b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/pivot_table_tab.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import m from 'mithril';
-import {assertExists} from '../../base/assert';
+import {checkExists} from '../../base/assert';
 import {Icons} from '../../base/semantic_icons';
 import {PivotTable} from '../../components/widgets/sql/pivot_table/pivot_table';
 import {PivotTableState} from '../../components/widgets/sql/pivot_table/pivot_table_state';
@@ -88,12 +88,10 @@
   private getOrCreateState(): PivotTableState {
     if (this.state !== undefined) return this.state;
     const sliceTable = resolveTableDefinition(this.trace, SLICE_TABLE);
-    const name = assertExists(
+    const name = checkExists(
       sliceTable.columns.find((c) => c.column === 'name'),
     );
-    const dur = assertExists(
-      sliceTable.columns.find((c) => c.column === 'dur'),
-    );
+    const dur = checkExists(sliceTable.columns.find((c) => c.column === 'dur'));
     this.state = new PivotTableState({
       trace: this.trace,
       table: sliceTable,
diff --git a/ui/src/plugins/dev.perfetto.TrackEvent/index.ts b/ui/src/plugins/dev.perfetto.TrackEvent/index.ts
index a375dd4..ca07326 100644
--- a/ui/src/plugins/dev.perfetto.TrackEvent/index.ts
+++ b/ui/src/plugins/dev.perfetto.TrackEvent/index.ts
@@ -24,7 +24,7 @@
   STR_NULL,
 } from '../../trace_processor/query_result';
 import {TrackNode} from '../../public/workspace';
-import {assertExists, assertTrue} from '../../base/assert';
+import {checkExists, assertTrue} from '../../base/assert';
 import {COUNTER_TRACK_KIND, SLICE_TRACK_KIND} from '../../public/track_kinds';
 import {createTraceProcessorSliceTrack} from '../dev.perfetto.TraceProcessorTrack/trace_processor_slice_track';
 import {TraceProcessorCounterTrack} from '../dev.perfetto.TraceProcessorTrack/trace_processor_counter_track';
@@ -339,7 +339,7 @@
           return undefined;
         }
         const {flamegraph, metrics} = flamegraphWithMetrics;
-        const store = assertExists(this.store);
+        const store = checkExists(this.store);
         return {
           isLoading: false,
           content: flamegraph.render({
@@ -438,7 +438,7 @@
       ],
       nameColumnLabel: 'Symbol',
     });
-    const store = assertExists(this.store);
+    const store = checkExists(this.store);
     store.edit((draft) => {
       draft.areaSelectionFlamegraphState = Flamegraph.updateState(
         draft.areaSelectionFlamegraphState,
@@ -458,13 +458,13 @@
     hasChildren: number,
   ): TrackNode {
     if (parentId !== undefined) {
-      return assertExists(trackIdToTrackNode.get(parentId));
+      return checkExists(trackIdToTrackNode.get(parentId));
     }
     if (utid !== undefined) {
-      return assertExists(processGroupsPlugin.getGroupForThread(utid));
+      return checkExists(processGroupsPlugin.getGroupForThread(utid));
     }
     if (upid !== undefined) {
-      return assertExists(processGroupsPlugin.getGroupForProcess(upid));
+      return checkExists(processGroupsPlugin.getGroupForProcess(upid));
     }
     if (hasChildren) {
       return ctx.defaultWorkspace.tracks;
diff --git a/ui/src/plugins/org.chromium.ChromeScrollJank/event_latency_details_panel.ts b/ui/src/plugins/org.chromium.ChromeScrollJank/event_latency_details_panel.ts
index 4d71024..de6b48b 100644
--- a/ui/src/plugins/org.chromium.ChromeScrollJank/event_latency_details_panel.ts
+++ b/ui/src/plugins/org.chromium.ChromeScrollJank/event_latency_details_panel.ts
@@ -52,7 +52,7 @@
 import {renderSliceArguments} from '../../components/details/slice_args';
 import {TrackEventRef} from '../../components/widgets/track_event_ref';
 import {SLICE_TABLE} from '../../components/widgets/sql/table_definitions';
-import {assertExists, assertTrue} from '../../base/assert';
+import {checkExists, assertTrue} from '../../base/assert';
 import {
   EVENT_LATENCY_TRACK,
   SCROLL_TIMELINE_TRACK,
@@ -523,7 +523,7 @@
   }
 
   private renderRelatedTrackReferences(): m.Child {
-    const references = assertExists(this.references);
+    const references = checkExists(this.references);
     const children: m.Children = [];
     if (references.scrollUpdatePluginSliceId !== undefined) {
       children.push(
@@ -556,7 +556,7 @@
   }
 
   private renderStdlibReferences(): m.Child {
-    const references = assertExists(this.references);
+    const references = checkExists(this.references);
     return m(
       TreeNode,
       {
@@ -585,7 +585,7 @@
   }
 
   private getStageReferences(): m.Child {
-    const parent = assertExists(this.references!.parent);
+    const parent = checkExists(this.references!.parent);
     return trackEventRefTreeNode({
       trace: this.trace,
       table: EVENT_LATENCY_TRACK.tableName,
diff --git a/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_timeline_details_panel.ts b/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_timeline_details_panel.ts
index be02be5..050df43 100644
--- a/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_timeline_details_panel.ts
+++ b/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_timeline_details_panel.ts
@@ -19,7 +19,7 @@
 import {DetailsShell} from '../../widgets/details_shell';
 import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
 import {Duration, duration, Time, time} from '../../base/time';
-import {assertExists, assertTrue} from '../../base/assert';
+import {checkExists, assertTrue} from '../../base/assert';
 import {Section} from '../../widgets/section';
 import {Tree, TreeNode} from '../../widgets/tree';
 import {Timestamp} from '../../components/widgets/timestamp';
@@ -288,7 +288,7 @@
   }
 
   private renderRelatedTrackReferences(): m.Child {
-    const scrollUpdateData = assertExists(this.scrollUpdateData);
+    const scrollUpdateData = checkExists(this.scrollUpdateData);
     const children: m.Children = [];
     if (scrollUpdateData.eventLatencyPluginSliceId !== undefined) {
       children.push(
@@ -321,7 +321,7 @@
   }
 
   private renderStdlibReferences(): m.Child {
-    const scrollUpdateData = assertExists(this.scrollUpdateData);
+    const scrollUpdateData = checkExists(this.scrollUpdateData);
     return m(
       TreeNode,
       {
diff --git a/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_timeline_v4_details_panel.ts b/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_timeline_v4_details_panel.ts
index f89d75b..f63f237 100644
--- a/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_timeline_v4_details_panel.ts
+++ b/ui/src/plugins/org.chromium.ChromeScrollJank/scroll_timeline_v4_details_panel.ts
@@ -18,7 +18,7 @@
 import {NUM, NUM_NULL, STR} from '../../trace_processor/query_result';
 import {DetailsShell} from '../../widgets/details_shell';
 import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
-import {assertExists, assertTrue} from '../../base/assert';
+import {checkExists, assertTrue, assertExists} from '../../base/assert';
 import {Section} from '../../widgets/section';
 import {Tree, TreeNode} from '../../widgets/tree';
 import {
@@ -212,7 +212,7 @@
   }
 
   private renderRelatedTrackReferences(): m.Child {
-    const frameData = assertExists(this.frameData);
+    const frameData = checkExists(this.frameData);
     const children: m.Children = [];
     if (frameData.firstEventLatencyPluginSliceId !== undefined) {
       children.push(
@@ -245,7 +245,7 @@
   }
 
   private renderStdlibReferences(): m.Child {
-    const frameData = assertExists(this.frameData);
+    const frameData = checkExists(this.frameData);
     return m(
       TreeNode,
       {
diff --git a/ui/src/test/aggregation.test.ts b/ui/src/test/aggregation.test.ts
index a428045..ff02655 100644
--- a/ui/src/test/aggregation.test.ts
+++ b/ui/src/test/aggregation.test.ts
@@ -14,8 +14,8 @@
 
 import {test, Page} from '@playwright/test';
 import {PerfettoTestHelper} from './perfetto_ui_test_helper';
-import {assertExists} from '../base/assert';
 import {Locator} from '@playwright/test';
+import {assertExists, checkExists} from '../base/assert';
 
 test.describe.configure({mode: 'serial'});
 
@@ -87,7 +87,8 @@
     'GPU/GPU Frequency/Gpu 0 Frequency',
     gpuFreqGroup,
   );
-  const coords = assertExists(await gpuTrack.boundingBox());
+  const coords = await gpuTrack.boundingBox();
+  assertExists(coords);
   await page.mouse.move(600, coords.y + 10);
   await page.mouse.down();
   await page.mouse.move(800, coords.y + 60);
@@ -106,7 +107,7 @@
     'com.android.systemui 25348/Actual Timeline',
     sysui,
   );
-  const coords = assertExists(await actualTimeline.boundingBox());
+  const coords = checkExists(await actualTimeline.boundingBox());
   await page.mouse.move(600, coords.y + 10);
   await page.mouse.down();
   await page.mouse.move(1000, coords.y + 20);
@@ -126,7 +127,7 @@
     .nth(1);
   await animThread.scrollIntoViewIfNeeded();
   await pth.waitForPerfettoIdle();
-  const coords = assertExists(await animThread.boundingBox());
+  const coords = checkExists(await animThread.boundingBox());
   await page.mouse.move(600, coords.y + 10);
   await page.mouse.down();
   await page.mouse.move(1000, coords.y + 20);
diff --git a/ui/src/test/perfetto_ui_test_helper.ts b/ui/src/test/perfetto_ui_test_helper.ts
index bcbbee4..02b4689 100644
--- a/ui/src/test/perfetto_ui_test_helper.ts
+++ b/ui/src/test/perfetto_ui_test_helper.ts
@@ -21,7 +21,7 @@
 import fs from 'fs';
 import path from 'path';
 import {IdleDetectorWindow} from '../frontend/idle_detector_interface';
-import {assertExists} from '../base/assert';
+import {checkExists} from '../base/assert';
 import {Size2D} from '../base/geom';
 import {AppImpl} from '../core/app_impl';
 
@@ -44,7 +44,7 @@
   async sidebarSize(): Promise<Size2D> {
     if (this.cachedSidebarSize === undefined) {
       const size = await this.page.locator('main > .pf-sidebar').boundingBox();
-      this.cachedSidebarSize = assertExists(size);
+      this.cachedSidebarSize = checkExists(size);
     }
     return this.cachedSidebarSize;
   }
@@ -69,7 +69,7 @@
       localStorage.setItem('dismissedPanningHint', 'true'),
     );
     const tracePath = this.getTestTracePath(traceName);
-    await assertExists(file).setInputFiles(tracePath);
+    await checkExists(file).setInputFiles(tracePath);
     await this.waitForPerfettoIdle();
     await this.applyTestingStyles();
     await this.page.mouse.move(0, 0);
diff --git a/ui/src/test/wattson.test.ts b/ui/src/test/wattson.test.ts
index 882a6e4..5f75d07 100644
--- a/ui/src/test/wattson.test.ts
+++ b/ui/src/test/wattson.test.ts
@@ -14,7 +14,7 @@
 
 import {test, Page} from '@playwright/test';
 import {PerfettoTestHelper} from './perfetto_ui_test_helper';
-import {assertExists} from '../base/assert';
+import {checkExists} from '../base/assert';
 
 test.describe.configure({mode: 'serial'});
 
@@ -44,7 +44,7 @@
   await wattsonGrp.scrollIntoViewIfNeeded();
   await pth.toggleTrackGroup(wattsonGrp);
   const cpuEstimate = pth.locateTrack('Wattson/Cpu0 estimate', wattsonGrp);
-  const coords = assertExists(await cpuEstimate.boundingBox());
+  const coords = checkExists(await cpuEstimate.boundingBox());
   await page.keyboard.press('Escape');
   await page.mouse.move(600, coords.y + 10);
   await page.mouse.down();
diff --git a/ui/src/trace_processor/engine.ts b/ui/src/trace_processor/engine.ts
index f7c7e92..a0efed4 100644
--- a/ui/src/trace_processor/engine.ts
+++ b/ui/src/trace_processor/engine.ts
@@ -14,7 +14,7 @@
 
 import protos from '../protos';
 import {defer, Deferred} from '../base/deferred';
-import {assertExists, assertTrue, assertUnreachable} from '../base/assert';
+import {checkExists, assertTrue, assertUnreachable} from '../base/assert';
 import {ProtoRingBuffer} from './proto_ring_buffer';
 import {
   createQueryResult,
@@ -254,8 +254,8 @@
 
     switch (rpc.response) {
       case TPM.TPM_APPEND_TRACE_DATA: {
-        const appendResult = assertExists(rpc.appendResult);
-        const pendingPromise = assertExists(this.pendingParses.shift());
+        const appendResult = checkExists(rpc.appendResult);
+        const pendingPromise = checkExists(this.pendingParses.shift());
         if (exists(appendResult.error) && appendResult.error.length > 0) {
           pendingPromise.reject(appendResult.error);
         } else {
@@ -264,8 +264,8 @@
         break;
       }
       case TPM.TPM_FINALIZE_TRACE_DATA: {
-        const finalizeResult = assertExists(rpc.finalizeDataResult);
-        const pendingPromise = assertExists(this.pendingEOFs.shift());
+        const finalizeResult = checkExists(rpc.finalizeDataResult);
+        const pendingPromise = checkExists(this.pendingEOFs.shift());
         if (exists(finalizeResult.error) && finalizeResult.error.length > 0) {
           pendingPromise.reject(finalizeResult.error);
         } else {
@@ -274,14 +274,14 @@
         break;
       }
       case TPM.TPM_RESET_TRACE_PROCESSOR:
-        assertExists(this.pendingResetTraceProcessors.shift()).resolve();
+        checkExists(this.pendingResetTraceProcessors.shift()).resolve();
         break;
       case TPM.TPM_RESTORE_INITIAL_TABLES:
-        assertExists(this.pendingRestoreTables.shift()).resolve();
+        checkExists(this.pendingRestoreTables.shift()).resolve();
         break;
       case TPM.TPM_QUERY_STREAMING:
-        const qRes = assertExists(rpc.queryResult) as {} as QueryResultBypass;
-        const pendingQuery = assertExists(this.pendingQueries[0]);
+        const qRes = checkExists(rpc.queryResult) as {} as QueryResultBypass;
+        const pendingQuery = checkExists(this.pendingQueries[0]);
         pendingQuery.appendResultBatch(qRes.rawQueryResult);
         if (pendingQuery.isComplete()) {
           this.pendingQueries.shift();
@@ -290,10 +290,10 @@
         }
         break;
       case TPM.TPM_COMPUTE_METRIC:
-        const metricRes = assertExists(
+        const metricRes = checkExists(
           rpc.metricResult,
         ) as protos.ComputeMetricResult;
-        const pendingComputeMetric = assertExists(
+        const pendingComputeMetric = checkExists(
           this.pendingComputeMetrics.shift(),
         );
         if (exists(metricRes.error) && metricRes.error.length > 0) {
@@ -314,15 +314,15 @@
         }
         break;
       case TPM.TPM_DISABLE_AND_READ_METATRACE:
-        const metatraceRes = assertExists(
+        const metatraceRes = checkExists(
           rpc.metatrace,
         ) as protos.DisableAndReadMetatraceResult;
-        assertExists(this.pendingReadMetatrace).resolve(metatraceRes);
+        checkExists(this.pendingReadMetatrace).resolve(metatraceRes);
         this.pendingReadMetatrace = undefined;
         break;
       case TPM.TPM_REGISTER_SQL_PACKAGE:
-        const registerResult = assertExists(rpc.registerSqlPackageResult);
-        const res = assertExists(this.pendingRegisterSqlPackage);
+        const registerResult = checkExists(rpc.registerSqlPackageResult);
+        const res = checkExists(this.pendingRegisterSqlPackage);
         if (exists(registerResult.error) && registerResult.error.length > 0) {
           res.reject(registerResult.error);
         } else {
@@ -331,40 +331,40 @@
         this.pendingRegisterSqlPackage = undefined;
         break;
       case TPM.TPM_SUMMARIZE_TRACE:
-        const summaryRes = assertExists(
+        const summaryRes = checkExists(
           rpc.traceSummaryResult,
         ) as protos.TraceSummaryResult;
-        assertExists(this.pendingTraceSummary).resolve(summaryRes);
+        checkExists(this.pendingTraceSummary).resolve(summaryRes);
         this.pendingTraceSummary = undefined;
         break;
       case TPM.TPM_CREATE_SUMMARIZER:
-        const createSummarizerRes = assertExists(
+        const createSummarizerRes = checkExists(
           rpc.createSummarizerResult,
         ) as protos.CreateSummarizerResult;
-        assertExists(this.pendingCreateSummarizer).resolve(createSummarizerRes);
+        checkExists(this.pendingCreateSummarizer).resolve(createSummarizerRes);
         this.pendingCreateSummarizer = undefined;
         break;
       case TPM.TPM_UPDATE_SUMMARIZER_SPEC:
-        const updateSummarizerSpecRes = assertExists(
+        const updateSummarizerSpecRes = checkExists(
           rpc.updateSummarizerSpecResult,
         ) as protos.UpdateSummarizerSpecResult;
-        assertExists(this.pendingUpdateSummarizerSpec).resolve(
+        checkExists(this.pendingUpdateSummarizerSpec).resolve(
           updateSummarizerSpecRes,
         );
         this.pendingUpdateSummarizerSpec = undefined;
         break;
       case TPM.TPM_QUERY_SUMMARIZER:
-        const querySummarizerRes = assertExists(
+        const querySummarizerRes = checkExists(
           rpc.querySummarizerResult,
         ) as protos.QuerySummarizerResult;
-        assertExists(this.pendingQuerySummarizer).resolve(querySummarizerRes);
+        checkExists(this.pendingQuerySummarizer).resolve(querySummarizerRes);
         this.pendingQuerySummarizer = undefined;
         break;
       case TPM.TPM_DESTROY_SUMMARIZER:
-        const destroySummarizerRes = assertExists(
+        const destroySummarizerRes = checkExists(
           rpc.destroySummarizerResult,
         ) as protos.DestroySummarizerResult;
-        assertExists(this.pendingDestroySummarizer).resolve(
+        checkExists(this.pendingDestroySummarizer).resolve(
           destroySummarizerRes,
         );
         this.pendingDestroySummarizer = undefined;
diff --git a/ui/src/trace_processor/http_rpc_engine.ts b/ui/src/trace_processor/http_rpc_engine.ts
index e3129eb..c7c2b2b 100644
--- a/ui/src/trace_processor/http_rpc_engine.ts
+++ b/ui/src/trace_processor/http_rpc_engine.ts
@@ -15,7 +15,7 @@
 import protos from '../protos';
 import {fetchWithTimeout} from '../base/http_utils';
 import {reportError} from '../base/logging';
-import {assertExists} from '../base/assert';
+import {checkExists} from '../base/assert';
 import {EngineBase} from '../trace_processor/engine';
 
 const RPC_CONNECT_TIMEOUT_MS = 2000;
@@ -69,7 +69,7 @@
     for (;;) {
       const queuedMsg = this.requestQueue.shift();
       if (queuedMsg === undefined) break;
-      assertExists(this.websocket).send(queuedMsg);
+      checkExists(this.websocket).send(queuedMsg);
     }
     this.connected = true;
   }
@@ -89,7 +89,7 @@
   }
 
   private onWebsocketMessage(e: MessageEvent) {
-    const blob = assertExists(e.data as Blob);
+    const blob = checkExists(e.data as Blob);
     this.queue.push(blob);
     this.processQueue();
   }
@@ -99,7 +99,7 @@
     this.isProcessingQueue = true;
     while (this.queue.length > 0) {
       try {
-        const blob = assertExists(this.queue.shift());
+        const blob = checkExists(this.queue.shift());
         const buf = await blob.arrayBuffer();
         super.onRpcResponseBytes(new Uint8Array(buf));
       } catch (e) {
diff --git a/ui/src/trace_processor/query_result.ts b/ui/src/trace_processor/query_result.ts
index 711e4b2..2858d7c 100644
--- a/ui/src/trace_processor/query_result.ts
+++ b/ui/src/trace_processor/query_result.ts
@@ -51,7 +51,7 @@
 import '../base/static_initializers';
 import protobuf from 'protobufjs/minimal';
 import {defer, Deferred} from '../base/deferred';
-import {assertExists, assertFalse, assertTrue} from '../base/assert';
+import {checkExists, assertFalse, assertTrue} from '../base/assert';
 import {utf8Decode} from '../base/string_utils';
 import {Duration, duration, Time, time} from '../base/time';
 
@@ -624,7 +624,7 @@
     if (this.allRowsPromise === undefined) {
       this.waitAllRows(); // Will populate |this.allRowsPromise|.
     }
-    return assertExists(this.allRowsPromise);
+    return checkExists(this.allRowsPromise);
   }
 
   get errorInfo(): QueryErrorInfo {
@@ -916,7 +916,7 @@
     this.numColumns = this.columnNames.length;
 
     this.batchIdx = nextBatchIdx;
-    const batch = assertExists(this.resultObj.batches[nextBatchIdx]);
+    const batch = checkExists(this.resultObj.batches[nextBatchIdx]);
     this.batchBytes = batch.batchBytes;
     this.nextCellTypeOff = batch.cellTypesOff;
     this.cellTypesEnd = batch.cellTypesOff + batch.cellTypesLen;
diff --git a/ui/src/traceconv/index.ts b/ui/src/traceconv/index.ts
index bfb8799..c4921ad 100644
--- a/ui/src/traceconv/index.ts
+++ b/ui/src/traceconv/index.ts
@@ -66,8 +66,8 @@
 }
 
 function fsNodeToBuffer(fsNode: traceconv.FileSystemNode): Uint8Array {
-  const fileSize = assertExists(fsNode.usedBytes);
-  return new Uint8Array(fsNode.contents.buffer, 0, fileSize);
+  assertExists(fsNode.usedBytes);
+  return new Uint8Array(fsNode.contents.buffer, 0, fsNode.usedBytes);
 }
 
 async function runTraceconv(trace: Blob, args: string[]) {
@@ -79,10 +79,11 @@
     printErr: updateStatus,
     onRuntimeInitialized: () => deferredRuntimeInitialized.resolve(),
   });
+  assertExists(module.FS.filesystems.WORKERFS);
   await deferredRuntimeInitialized;
   module.FS.mkdir('/fs');
   module.FS.mount(
-    assertExists(module.FS.filesystems.WORKERFS),
+    module.FS.filesystems.WORKERFS,
     {blobs: [{name: 'trace.proto', data: trace}]},
     '/fs',
   );
diff --git a/ui/src/widgets/flamegraph.ts b/ui/src/widgets/flamegraph.ts
index 2fdea3a..988402c 100644
--- a/ui/src/widgets/flamegraph.ts
+++ b/ui/src/widgets/flamegraph.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import m from 'mithril';
-import {assertExists, assertTrue} from '../base/assert';
+import {checkExists, assertTrue, assertExists} from '../base/assert';
 import {Monitor} from '../base/monitor';
 import {Button, ButtonBar} from './button';
 import {Chip} from './chip';
@@ -642,7 +642,7 @@
 
     const {allRootsCumulativeValue, unfilteredCumulativeValue, nodes} =
       this.attrs.data;
-    const unit = assertExists(this.selectedMetric).unit;
+    assertExists(this.selectedMetric);
 
     ctx.font = LABEL_FONT_STYLE;
     ctx.textBaseline = 'middle';
@@ -665,7 +665,10 @@
       let name: string;
       let colorScheme;
       if (source.kind === 'ROOT') {
-        const val = displaySize(allRootsCumulativeValue, unit);
+        const val = displaySize(
+          allRootsCumulativeValue,
+          this.selectedMetric.unit,
+        );
         const percent = displayPercentage(
           allRootsCumulativeValue,
           unfilteredCumulativeValue,
@@ -888,8 +891,8 @@
       unfilteredCumulativeValue,
       nodeActions,
       rootActions,
-    } = assertExists(this.attrs.data);
-    const {unit, nameColumnLabel} = assertExists(this.selectedMetric);
+    } = checkExists(this.attrs.data);
+    const {unit, nameColumnLabel} = checkExists(this.selectedMetric);
     if (source.kind === 'ROOT') {
       const val = displaySize(allRootsCumulativeValue, unit);
       const percent = displayPercentage(
@@ -1142,15 +1145,15 @@
   }
 
   private buildStackString(node: FlamegraphNode, withDetails: boolean): string {
-    const {nodes, unfilteredCumulativeValue} = assertExists(this.attrs.data);
-    const metric = assertExists(this.selectedMetric);
+    const {nodes, unfilteredCumulativeValue} = checkExists(this.attrs.data);
+    const metric = checkExists(this.selectedMetric);
     const view = this.attrs.state.view;
 
     // Walk via parentId for all modes. Reverse for TOP_DOWN and PIVOT below.
     const stack: FlamegraphNode[] = [];
     let currentId = node.id;
     while (currentId !== -1) {
-      const current = assertExists(nodes.find((n) => n.id === currentId));
+      const current = checkExists(nodes.find((n) => n.id === currentId));
       stack.push(current);
       currentId = current.parentId;
     }
diff --git a/ui/src/widgets/hotkey_context.ts b/ui/src/widgets/hotkey_context.ts
index 8e6b946..e329308 100644
--- a/ui/src/widgets/hotkey_context.ts
+++ b/ui/src/widgets/hotkey_context.ts
@@ -14,8 +14,8 @@
 
 import m from 'mithril';
 import {checkHotkey, Hotkey} from '../base/hotkeys';
-import {toHTMLElement} from '../base/dom_utils';
 import {classNames} from '../base/classnames';
+import {assertInstanceOf} from '../base/assert';
 
 export interface HotkeyConfig {
   readonly hotkey: Hotkey;
@@ -72,15 +72,17 @@
     );
   }
 
-  oncreate(vnode: m.VnodeDOM<HotkeyContextAttrs>) {
-    const focusable = vnode.attrs.focusable ?? true;
+  oncreate({attrs, dom}: m.VnodeDOM<HotkeyContextAttrs>) {
+    assertInstanceOf(dom, HTMLElement);
+
+    const focusable = attrs.focusable ?? true;
     // If focusable is false, bind to document for global hotkeys.
     // Otherwise, bind to the element itself.
-    this.eventTarget = focusable ? vnode.dom : document;
+    this.eventTarget = focusable ? dom : document;
     this.eventTarget.addEventListener('keydown', this.onKeyDown);
-    this.hotkeys = vnode.attrs.hotkeys;
-    if (vnode.attrs.autoFocus && focusable) {
-      toHTMLElement(vnode.dom).focus();
+    this.hotkeys = attrs.hotkeys;
+    if (attrs.autoFocus && focusable) {
+      dom.focus();
     }
   }
 
diff --git a/ui/src/widgets/nodegraph.ts b/ui/src/widgets/nodegraph.ts
index f4b1e4a..77d756f 100644
--- a/ui/src/widgets/nodegraph.ts
+++ b/ui/src/widgets/nodegraph.ts
@@ -830,8 +830,8 @@
     // Query ports within this NodeGraph instance only (not globally).
     // Using document.querySelectorAll would pick up ports from other
     // NodeGraph instances (e.g. hidden tabs), causing incorrect positions.
-    const container = assertExists(canvasElement);
-    const allPorts = container.querySelectorAll('.pf-port[data-port]');
+    assertExists(canvasElement);
+    const allPorts = canvasElement.querySelectorAll('.pf-port[data-port]');
     allPorts.forEach((portElement) => {
       const portId = portElement.getAttribute('data-port');
       if (!portId) return;
@@ -1071,8 +1071,8 @@
 
     // Scope to this NodeGraph instance to avoid matching elements from other
     // instances (e.g. hidden tabs with the same node IDs).
-    const scope = assertExists(canvasElement);
-    const portElement = scope.querySelector(selector);
+    assertExists(canvasElement);
+    const portElement = canvasElement.querySelector(selector);
 
     if (portElement) {
       const nodeElement = portElement.closest('.pf-node') as HTMLElement | null;
@@ -1188,8 +1188,8 @@
   }
 
   function getNodeDimensions(nodeId: string): {width: number; height: number} {
-    const scope = assertExists(canvasElement);
-    const nodeElement = scope.querySelector(`[data-node="${nodeId}"]`);
+    assertExists(canvasElement);
+    const nodeElement = canvasElement.querySelector(`[data-node="${nodeId}"]`);
     if (nodeElement) {
       const rect = nodeElement.getBoundingClientRect();
       // Divide by zoom to get canvas content space dimensions
diff --git a/ui/src/widgets/popup.ts b/ui/src/widgets/popup.ts
index ecae28a..c3dc5e2 100644
--- a/ui/src/widgets/popup.ts
+++ b/ui/src/widgets/popup.ts
@@ -17,8 +17,8 @@
 import m from 'mithril';
 import {MountOptions, Portal, PortalAttrs} from './portal';
 import {classNames} from '../base/classnames';
-import {findRef, isOrContains, toHTMLElement} from '../base/dom_utils';
-import {assertExists} from '../base/assert';
+import {findRef, isOrContains} from '../base/dom_utils';
+import {assertExists, assertInstanceOf} from '../base/assert';
 import {ExtendedModifiers} from './popper_utils';
 
 // Note: We could just use the Placement type from popper.js instead, which is a
@@ -255,9 +255,8 @@
         return {container: undefined};
       },
       onContentMount: (dom: HTMLElement) => {
-        const popupElement = toHTMLElement(
-          assertExists(findRef(dom, Popup.POPUP_REF)),
-        );
+        const popupElement = findRef(dom, Popup.POPUP_REF);
+        assertInstanceOf(popupElement, HTMLElement);
         this.popupElement = popupElement;
         this.createOrUpdatePopper(attrs);
         document.addEventListener('mousedown', this.handleDocMouseDown);
@@ -304,7 +303,9 @@
   }
 
   oncreate({dom}: m.VnodeDOM<PopupAttrs, this>) {
-    this.triggerElement = assertExists(findRef(dom, Popup.TRIGGER_REF));
+    const triggerElement = findRef(dom, Popup.TRIGGER_REF);
+    assertExists(triggerElement);
+    this.triggerElement = triggerElement;
   }
 
   onupdate({attrs}: m.VnodeDOM<PopupAttrs, this>) {
@@ -475,9 +476,12 @@
   }
 
   private eventInPopupOrTrigger(e: Event): boolean {
-    const target = e.target as HTMLElement;
-    const onTrigger = isOrContains(assertExists(this.triggerElement), target);
-    const onPopup = isOrContains(assertExists(this.popupElement), target);
+    assertExists(this.triggerElement);
+    assertExists(this.popupElement);
+    assertInstanceOf(e.target, Element);
+
+    const onTrigger = isOrContains(this.triggerElement, e.target);
+    const onPopup = isOrContains(this.popupElement, e.target);
     return onTrigger || onPopup;
   }
 
diff --git a/ui/src/widgets/settings_shell.ts b/ui/src/widgets/settings_shell.ts
index f8e9c63..d6ab2b5 100644
--- a/ui/src/widgets/settings_shell.ts
+++ b/ui/src/widgets/settings_shell.ts
@@ -52,17 +52,16 @@
     );
   }
 
-  oncreate(vnode: m.VnodeDOM<SettingsShellAttrs, this>) {
-    const canary = assertExists(
-      vnode.dom.querySelector('.pf-settings-shell__title'),
-    );
-    const header = assertExists(
-      vnode.dom.querySelector('.pf-settings-shell__header'),
-    );
+  oncreate({dom}: m.VnodeDOM<SettingsShellAttrs, this>) {
+    const canaryElement = dom.querySelector('.pf-settings-shell__title');
+    const headerElement = dom.querySelector('.pf-settings-shell__header');
+
+    assertExists(canaryElement);
+    assertExists(headerElement);
 
     this.observer = new IntersectionObserver(
       ([entry]) => {
-        header.classList.toggle(
+        headerElement.classList.toggle(
           'pf-settings-shell__header--stuck',
           !entry.isIntersecting,
         );
@@ -70,7 +69,7 @@
       {threshold: [0]},
     );
 
-    this.observer.observe(canary);
+    this.observer.observe(canaryElement);
   }
 
   onremove() {
diff --git a/ui/src/widgets/tooltip.ts b/ui/src/widgets/tooltip.ts
index dd3da34..f55e0ba 100644
--- a/ui/src/widgets/tooltip.ts
+++ b/ui/src/widgets/tooltip.ts
@@ -16,8 +16,8 @@
 import m from 'mithril';
 import {MountOptions, Portal, PortalAttrs} from './portal';
 import {classNames} from '../base/classnames';
-import {findRef, toHTMLElement} from '../base/dom_utils';
-import {assertExists} from '../base/assert';
+import {findRef} from '../base/dom_utils';
+import {assertExists, assertInstanceOf} from '../base/assert';
 import {PopupPosition} from './popup';
 import {ExtendedModifiers} from './popper_utils';
 
@@ -126,9 +126,9 @@
         return {container: undefined};
       },
       onContentMount: (dom: HTMLElement) => {
-        const popupElement = toHTMLElement(
-          assertExists(findRef(dom, Tooltip.TOOLTIP_REF)),
-        );
+        const popupElement = findRef(dom, Tooltip.TOOLTIP_REF);
+        assertInstanceOf(popupElement, HTMLElement);
+
         this.tooltipElement = popupElement;
         this.createOrUpdatePopper(attrs);
         onTooltipMount(popupElement);
@@ -162,7 +162,9 @@
   }
 
   oncreate({dom}: m.VnodeDOM<TooltipAttrs, this>) {
-    this.triggerElement = assertExists(findRef(dom, Tooltip.TRIGGER_REF));
+    const triggerElement = findRef(dom, Tooltip.TRIGGER_REF);
+    assertExists(triggerElement);
+    this.triggerElement = triggerElement;
   }
 
   onupdate({attrs}: m.VnodeDOM<TooltipAttrs, this>) {
diff --git a/ui/src/widgets/virtual_overlay_canvas.ts b/ui/src/widgets/virtual_overlay_canvas.ts
index 468afa6..8048b48 100644
--- a/ui/src/widgets/virtual_overlay_canvas.ts
+++ b/ui/src/widgets/virtual_overlay_canvas.ts
@@ -31,9 +31,9 @@
 
 import m from 'mithril';
 import {DisposableStack} from '../base/disposable_stack';
-import {findRef, toHTMLElement} from '../base/dom_utils';
+import {findRef} from '../base/dom_utils';
 import {Rect2D, Size2D} from '../base/geom';
-import {assertExists} from '../base/assert';
+import {assertDefined, assertInstanceOf, checkExists} from '../base/assert';
 import {VirtualCanvas} from '../base/virtual_canvas';
 import {WebGLRenderer} from '../base/gl/webgl_renderer';
 import {Canvas2DRenderer} from '../base/canvas2d_renderer';
@@ -167,9 +167,8 @@
 
   oncreate({attrs, dom}: m.CVnodeDOM<VirtualOverlayCanvasAttrs>) {
     this.dom = dom;
-    const canvasContainerElement = toHTMLElement(
-      assertExists(findRef(dom, CANVAS_CONTAINER_REF)),
-    );
+    const canvasContainerElement = findRef(dom, CANVAS_CONTAINER_REF);
+    assertInstanceOf(canvasContainerElement, HTMLElement);
     const {overflowX = 'visible', overflowY = 'visible'} = attrs;
 
     // Create the virtual canvas inside the canvas container element. We assume
@@ -184,7 +183,7 @@
     this.virtualCanvas = virtualCanvas;
 
     // Create the canvas rendering context
-    this.ctx = assertExists(virtualCanvas.canvasElement.getContext('2d'));
+    this.ctx = checkExists(virtualCanvas.canvasElement.getContext('2d'));
 
     // Create WebGL canvas if enabled
     if (attrs.enableWebGL) {
@@ -274,10 +273,12 @@
   }
 
   private redrawCanvas() {
-    const ctx = assertExists(this.ctx);
-    const virtualCanvas = assertExists(this.virtualCanvas);
-    const attrs = assertExists(this.attrs);
-    const containerElement = assertExists(this.dom);
+    const {ctx, virtualCanvas, attrs, dom} = this;
+
+    assertDefined(ctx);
+    assertDefined(virtualCanvas);
+    assertDefined(attrs);
+    assertDefined(dom);
 
     // Create the appropriate renderer: WebGLRenderer if available, otherwise
     // Canvas2DRenderer as fallback.
@@ -300,7 +301,7 @@
 
     // Call the user-provided draw callback to render into the canvas
     attrs.onCanvasRedraw?.({
-      dom: containerElement,
+      dom,
       ctx,
       virtualCanvasSize: virtualCanvas.size,
       canvasRect: virtualCanvas.canvasRect,