Use a babel plugin to put expression in assert statements
diff --git a/ui/config/babel-plugin-assert-messages.js b/ui/config/babel-plugin-assert-messages.js
new file mode 100644
index 0000000..b11ba64
--- /dev/null
+++ b/ui/config/babel-plugin-assert-messages.js
@@ -0,0 +1,92 @@
+// 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.
+
+/**
+ * Babel plugin that automatically adds descriptive messages to assertion
+ * functions. Transforms calls like `assertExists(foo.bar)` into
+ * `assertExists(foo.bar, 'foo.bar')` so that error messages show what
+ * expression failed.
+ */
+
+const ASSERTION_FUNCTIONS = new Set([
+  'assertExists',
+  'assertTrue',
+  'assertFalse',
+  'assertDefined',
+  'assertUnreachable',
+  'assertIsInstanceOf',
+]);
+
+module.exports = function assertMessagesPlugin({types: t}) {
+  function getFunctionName(callee) {
+    // Simple identifier: assertExists(...)
+    if (t.isIdentifier(callee)) {
+      return callee.name;
+    }
+    // Member expression: logging.assertExists(...) or foo.bar.assertExists(...)
+    if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
+      return callee.property.name;
+    }
+    // Indirect call pattern: (0, logging.assertExists)(...)
+    // This is common after tsc/commonjs transforms
+    if (t.isSequenceExpression(callee)) {
+      const expressions = callee.expressions;
+      if (expressions.length === 2) {
+        const lastExpr = expressions[1];
+        if (t.isMemberExpression(lastExpr) && t.isIdentifier(lastExpr.property)) {
+          return lastExpr.property.name;
+        }
+      }
+    }
+    return null;
+  }
+
+  return {
+    name: 'assert-messages',
+    visitor: {
+      CallExpression(path) {
+        const callee = path.node.callee;
+        const funcName = getFunctionName(callee);
+
+        // Check if it's one of our assertion functions
+        if (!funcName || !ASSERTION_FUNCTIONS.has(funcName)) {
+          return;
+        }
+
+        const args = path.node.arguments;
+
+        // If no arguments, skip
+        if (args.length === 0) {
+          return;
+        }
+
+        // If last argument is already a string literal, assume it's a message
+        const lastArg = args[args.length - 1];
+        if (t.isStringLiteral(lastArg)) {
+          return;
+        }
+
+        // Extract source text directly from the original code
+        const firstArg = args[0];
+        const code = path.hub.file.code;
+        if (!code || firstArg.start == null || firstArg.end == null) {
+          return; // Can't get source text, leave the call unchanged
+        }
+
+        const sourceText = code.slice(firstArg.start, firstArg.end);
+        args.push(t.stringLiteral(sourceText));
+      },
+    },
+  };
+};
diff --git a/ui/config/rollup.config.js b/ui/config/rollup.config.js
index a781e68..420e2f8 100644
--- a/ui/config/rollup.config.js
+++ b/ui/config/rollup.config.js
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 const {uglify} = require('rollup-plugin-uglify');
+const babel = require('@rollup/plugin-babel');
 const commonjs = require('@rollup/plugin-commonjs');
 const nodeResolve = require('@rollup/plugin-node-resolve');
 const path = require('path');
@@ -154,6 +155,17 @@
         ],
       }),
 
+      // Add descriptive messages to assertion functions.
+      babel.babel({
+        babelHelpers: 'bundled',
+        babelrc: false,
+        configFile: false,
+        plugins: [path.join(__dirname, 'babel-plugin-assert-messages.js')],
+        extensions: ['.js'],
+        include: ['**/*.js'],
+        exclude: ['node_modules/**'],
+      }),
+
       // Translate source maps to point back to the .ts sources.
       sourcemaps(),
       
diff --git a/ui/package.json b/ui/package.json
index 74447ac..48c4ba7 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -53,9 +53,11 @@
     "zod": "^4.3.5"
   },
   "devDependencies": {
+    "@babel/core": "^7.28.6",
     "@eslint/eslintrc": "^3.1.0",
     "@eslint/js": "^9.6.0",
     "@playwright/test": "^1.47.0",
+    "@rollup/plugin-babel": "^6.1.0",
     "@rollup/plugin-commonjs": "^26.0.1",
     "@rollup/plugin-node-resolve": "^15.2.3",
     "@types/jest": "^29.5.12",
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index 9abd878..5e6ad1a 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -139,6 +139,9 @@
     version: 4.3.5
 
 devDependencies:
+  '@babel/core':
+    specifier: ^7.28.6
+    version: 7.28.6
   '@eslint/eslintrc':
     specifier: ^3.1.0
     version: 3.1.0
@@ -148,6 +151,9 @@
   '@playwright/test':
     specifier: ^1.47.0
     version: 1.47.0
+  '@rollup/plugin-babel':
+    specifier: ^6.1.0
+    version: 6.1.0(@babel/core@7.28.6)(rollup@2.79.1)
   '@rollup/plugin-commonjs':
     specifier: ^26.0.1
     version: 26.0.1(rollup@2.79.1)
@@ -229,14 +235,6 @@
 
 packages:
 
-  /@ampproject/remapping@2.2.1:
-    resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
-    engines: {node: '>=6.0.0'}
-    dependencies:
-      '@jridgewell/gen-mapping': 0.3.3
-      '@jridgewell/trace-mapping': 0.3.18
-    dev: true
-
   /@babel/code-frame@7.22.5:
     resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==}
     engines: {node: '>=6.9.0'}
@@ -248,58 +246,39 @@
     resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/helper-validator-identifier': 7.27.1
+      '@babel/helper-validator-identifier': 7.28.5
       js-tokens: 4.0.0
       picocolors: 1.1.1
     dev: true
 
-  /@babel/compat-data@7.22.5:
-    resolution: {integrity: sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==}
-    engines: {node: '>=6.9.0'}
-    dev: true
-
-  /@babel/compat-data@7.24.7:
-    resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==}
-    engines: {node: '>=6.9.0'}
-    dev: true
-
-  /@babel/core@7.22.5:
-    resolution: {integrity: sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==}
+  /@babel/code-frame@7.28.6:
+    resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@ampproject/remapping': 2.2.1
-      '@babel/code-frame': 7.27.1
-      '@babel/generator': 7.22.5
-      '@babel/helper-compilation-targets': 7.22.5(@babel/core@7.22.5)
-      '@babel/helper-module-transforms': 7.22.5
-      '@babel/helpers': 7.22.5
-      '@babel/parser': 7.22.5
-      '@babel/template': 7.22.5
-      '@babel/traverse': 7.22.5
-      '@babel/types': 7.22.5
-      convert-source-map: 1.9.0
-      debug: 4.4.3
-      gensync: 1.0.0-beta.2
-      json5: 2.2.3
-      semver: 6.3.1
-    transitivePeerDependencies:
-      - supports-color
+      '@babel/helper-validator-identifier': 7.28.5
+      js-tokens: 4.0.0
+      picocolors: 1.1.1
     dev: true
 
-  /@babel/core@7.24.7:
-    resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==}
+  /@babel/compat-data@7.28.6:
+    resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==}
+    engines: {node: '>=6.9.0'}
+    dev: true
+
+  /@babel/core@7.28.6:
+    resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@ampproject/remapping': 2.2.1
-      '@babel/code-frame': 7.27.1
-      '@babel/generator': 7.24.7
-      '@babel/helper-compilation-targets': 7.24.7
-      '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7)
-      '@babel/helpers': 7.24.7
-      '@babel/parser': 7.24.7
-      '@babel/template': 7.24.7
-      '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/code-frame': 7.28.6
+      '@babel/generator': 7.28.6
+      '@babel/helper-compilation-targets': 7.28.6
+      '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6)
+      '@babel/helpers': 7.28.6
+      '@babel/parser': 7.28.6
+      '@babel/template': 7.28.6
+      '@babel/traverse': 7.28.6
+      '@babel/types': 7.28.6
+      '@jridgewell/remapping': 2.3.5
       convert-source-map: 2.0.0
       debug: 4.4.3
       gensync: 1.0.0-beta.2
@@ -309,98 +288,63 @@
       - supports-color
     dev: true
 
-  /@babel/generator@7.22.5:
-    resolution: {integrity: sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.22.5
-      '@jridgewell/gen-mapping': 0.3.3
-      '@jridgewell/trace-mapping': 0.3.18
-      jsesc: 2.5.2
-    dev: true
-
   /@babel/generator@7.24.7:
     resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.24.7
-      '@jridgewell/gen-mapping': 0.3.5
-      '@jridgewell/trace-mapping': 0.3.25
+      '@babel/types': 7.28.6
+      '@jridgewell/gen-mapping': 0.3.13
+      '@jridgewell/trace-mapping': 0.3.31
       jsesc: 2.5.2
     dev: true
 
-  /@babel/helper-compilation-targets@7.22.5(@babel/core@7.22.5):
-    resolution: {integrity: sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-    dependencies:
-      '@babel/compat-data': 7.22.5
-      '@babel/core': 7.22.5
-      '@babel/helper-validator-option': 7.22.5
-      browserslist: 4.23.1
-      lru-cache: 5.1.1
-      semver: 6.3.1
-    dev: true
-
-  /@babel/helper-compilation-targets@7.24.7:
-    resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==}
+  /@babel/generator@7.28.6:
+    resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/compat-data': 7.24.7
-      '@babel/helper-validator-option': 7.24.7
-      browserslist: 4.23.1
-      lru-cache: 5.1.1
-      semver: 6.3.1
+      '@babel/parser': 7.28.6
+      '@babel/types': 7.28.6
+      '@jridgewell/gen-mapping': 0.3.13
+      '@jridgewell/trace-mapping': 0.3.31
+      jsesc: 3.1.0
     dev: true
 
-  /@babel/helper-environment-visitor@7.22.5:
-    resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==}
+  /@babel/helper-compilation-targets@7.28.6:
+    resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
     engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/compat-data': 7.28.6
+      '@babel/helper-validator-option': 7.27.1
+      browserslist: 4.28.1
+      lru-cache: 5.1.1
+      semver: 6.3.1
     dev: true
 
   /@babel/helper-environment-visitor@7.24.7:
     resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.24.7
-    dev: true
-
-  /@babel/helper-function-name@7.22.5:
-    resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/template': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/types': 7.28.6
     dev: true
 
   /@babel/helper-function-name@7.24.7:
     resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/template': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/template': 7.28.6
+      '@babel/types': 7.28.6
     dev: true
 
-  /@babel/helper-hoist-variables@7.22.5:
-    resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
+  /@babel/helper-globals@7.28.0:
+    resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
     engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.24.7
     dev: true
 
   /@babel/helper-hoist-variables@7.24.7:
     resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.24.7
-    dev: true
-
-  /@babel/helper-module-imports@7.22.5:
-    resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.28.6
     dev: true
 
   /@babel/helper-module-imports@7.24.7:
@@ -413,34 +357,26 @@
       - supports-color
     dev: true
 
-  /@babel/helper-module-transforms@7.22.5:
-    resolution: {integrity: sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==}
+  /@babel/helper-module-imports@7.28.6:
+    resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/helper-environment-visitor': 7.22.5
-      '@babel/helper-module-imports': 7.22.5
-      '@babel/helper-simple-access': 7.22.5
-      '@babel/helper-split-export-declaration': 7.22.5
-      '@babel/helper-validator-identifier': 7.27.1
-      '@babel/template': 7.22.5
-      '@babel/traverse': 7.22.5
-      '@babel/types': 7.22.5
+      '@babel/traverse': 7.28.6
+      '@babel/types': 7.28.6
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7):
-    resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==}
+  /@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6):
+    resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-environment-visitor': 7.24.7
-      '@babel/helper-module-imports': 7.24.7
-      '@babel/helper-simple-access': 7.24.7
-      '@babel/helper-split-export-declaration': 7.24.7
-      '@babel/helper-validator-identifier': 7.27.1
+      '@babel/core': 7.28.6
+      '@babel/helper-module-imports': 7.28.6
+      '@babel/helper-validator-identifier': 7.28.5
+      '@babel/traverse': 7.28.6
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -455,266 +391,204 @@
     engines: {node: '>=6.9.0'}
     dev: true
 
-  /@babel/helper-simple-access@7.22.5:
-    resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.24.7
-    dev: true
-
-  /@babel/helper-simple-access@7.24.7:
-    resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
-  /@babel/helper-split-export-declaration@7.22.5:
-    resolution: {integrity: sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.24.7
-    dev: true
-
   /@babel/helper-split-export-declaration@7.24.7:
     resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.28.6
     dev: true
 
-  /@babel/helper-string-parser@7.22.5:
-    resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==}
-    engines: {node: '>=6.9.0'}
-
   /@babel/helper-string-parser@7.24.7:
     resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==}
     engines: {node: '>=6.9.0'}
     dev: true
 
+  /@babel/helper-string-parser@7.27.1:
+    resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+    engines: {node: '>=6.9.0'}
+
   /@babel/helper-validator-identifier@7.27.1:
     resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
     engines: {node: '>=6.9.0'}
+    dev: true
 
-  /@babel/helper-validator-option@7.22.5:
-    resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==}
+  /@babel/helper-validator-identifier@7.28.5:
+    resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+    engines: {node: '>=6.9.0'}
+
+  /@babel/helper-validator-option@7.27.1:
+    resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
     engines: {node: '>=6.9.0'}
     dev: true
 
-  /@babel/helper-validator-option@7.24.7:
-    resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==}
-    engines: {node: '>=6.9.0'}
-    dev: true
-
-  /@babel/helpers@7.22.5:
-    resolution: {integrity: sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==}
+  /@babel/helpers@7.28.6:
+    resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/template': 7.22.5
-      '@babel/traverse': 7.22.5
-      '@babel/types': 7.22.5
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
-  /@babel/helpers@7.24.7:
-    resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/template': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/template': 7.28.6
+      '@babel/types': 7.28.6
     dev: true
 
   /@babel/highlight@7.22.5:
     resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/helper-validator-identifier': 7.27.1
+      '@babel/helper-validator-identifier': 7.28.5
       chalk: 2.4.2
       js-tokens: 4.0.0
     dev: true
 
-  /@babel/parser@7.22.5:
-    resolution: {integrity: sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==}
-    engines: {node: '>=6.0.0'}
-    hasBin: true
-    dependencies:
-      '@babel/types': 7.22.5
-
   /@babel/parser@7.24.7:
     resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==}
     engines: {node: '>=6.0.0'}
     hasBin: true
     dependencies:
-      '@babel/types': 7.22.5
+      '@babel/types': 7.28.6
     dev: true
 
-  /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.22.5):
+  /@babel/parser@7.28.6:
+    resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+    dependencies:
+      '@babel/types': 7.28.6
+
+  /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.6):
     resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.22.5):
+  /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.6):
     resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.22.5):
+  /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.6):
     resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.22.5):
+  /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.6):
     resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.22.5):
+  /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.6):
     resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.22.5):
+  /@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.28.6):
     resolution: {integrity: sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.22.5):
+  /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.6):
     resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.22.5):
+  /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.6):
     resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.22.5):
+  /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.6):
     resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.22.5):
+  /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.6):
     resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.22.5):
+  /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.6):
     resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.22.5):
+  /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.6):
     resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.22.5):
+  /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.6):
     resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.22.5):
+  /@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.28.6):
     resolution: {integrity: sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@babel/helper-plugin-utils': 7.24.7
     dev: true
 
-  /@babel/template@7.22.5:
-    resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==}
+  /@babel/template@7.28.6:
+    resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/code-frame': 7.27.1
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.22.5
-    dev: true
-
-  /@babel/template@7.24.7:
-    resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/code-frame': 7.27.1
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.24.7
-    dev: true
-
-  /@babel/traverse@7.22.5:
-    resolution: {integrity: sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/code-frame': 7.27.1
-      '@babel/generator': 7.22.5
-      '@babel/helper-environment-visitor': 7.22.5
-      '@babel/helper-function-name': 7.22.5
-      '@babel/helper-hoist-variables': 7.22.5
-      '@babel/helper-split-export-declaration': 7.22.5
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.22.5
-      debug: 4.4.3
-      globals: 11.12.0
-    transitivePeerDependencies:
-      - supports-color
+      '@babel/code-frame': 7.28.6
+      '@babel/parser': 7.28.6
+      '@babel/types': 7.28.6
     dev: true
 
   /@babel/traverse@7.24.7:
@@ -735,13 +609,20 @@
       - supports-color
     dev: true
 
-  /@babel/types@7.22.5:
-    resolution: {integrity: sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==}
+  /@babel/traverse@7.28.6:
+    resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/helper-string-parser': 7.22.5
-      '@babel/helper-validator-identifier': 7.27.1
-      to-fast-properties: 2.0.0
+      '@babel/code-frame': 7.28.6
+      '@babel/generator': 7.28.6
+      '@babel/helper-globals': 7.28.0
+      '@babel/parser': 7.28.6
+      '@babel/template': 7.28.6
+      '@babel/types': 7.28.6
+      debug: 4.4.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
 
   /@babel/types@7.24.7:
     resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==}
@@ -752,6 +633,13 @@
       to-fast-properties: 2.0.0
     dev: true
 
+  /@babel/types@7.28.6:
+    resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/helper-string-parser': 7.27.1
+      '@babel/helper-validator-identifier': 7.28.5
+
   /@bcoe/v8-coverage@0.2.3:
     resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
     dev: true
@@ -1295,7 +1183,7 @@
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@jridgewell/trace-mapping': 0.3.18
+      '@jridgewell/trace-mapping': 0.3.31
       '@types/node': 20.14.9
       chalk: 4.1.2
       collect-v8-coverage: 1.0.1
@@ -1329,7 +1217,7 @@
     resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@jridgewell/trace-mapping': 0.3.18
+      '@jridgewell/trace-mapping': 0.3.31
       callsites: 3.1.0
       graceful-fs: 4.2.11
     dev: true
@@ -1358,9 +1246,9 @@
     resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@jest/types': 29.6.3
-      '@jridgewell/trace-mapping': 0.3.18
+      '@jridgewell/trace-mapping': 0.3.31
       babel-plugin-istanbul: 6.1.1
       chalk: 4.1.2
       convert-source-map: 2.0.0
@@ -1389,13 +1277,11 @@
       chalk: 4.1.2
     dev: true
 
-  /@jridgewell/gen-mapping@0.3.3:
-    resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
-    engines: {node: '>=6.0.0'}
+  /@jridgewell/gen-mapping@0.3.13:
+    resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
     dependencies:
-      '@jridgewell/set-array': 1.1.2
-      '@jridgewell/sourcemap-codec': 1.4.15
-      '@jridgewell/trace-mapping': 0.3.18
+      '@jridgewell/sourcemap-codec': 1.5.5
+      '@jridgewell/trace-mapping': 0.3.31
     dev: true
 
   /@jridgewell/gen-mapping@0.3.5:
@@ -1404,6 +1290,13 @@
     dependencies:
       '@jridgewell/set-array': 1.2.1
       '@jridgewell/sourcemap-codec': 1.4.15
+      '@jridgewell/trace-mapping': 0.3.31
+    dev: true
+
+  /@jridgewell/remapping@2.3.5:
+    resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+    dependencies:
+      '@jridgewell/gen-mapping': 0.3.5
       '@jridgewell/trace-mapping': 0.3.25
     dev: true
 
@@ -1412,29 +1305,17 @@
     engines: {node: '>=6.0.0'}
     dev: true
 
-  /@jridgewell/set-array@1.1.2:
-    resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
-    engines: {node: '>=6.0.0'}
-    dev: true
-
   /@jridgewell/set-array@1.2.1:
     resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
     engines: {node: '>=6.0.0'}
     dev: true
 
-  /@jridgewell/sourcemap-codec@1.4.14:
-    resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
-    dev: true
-
   /@jridgewell/sourcemap-codec@1.4.15:
     resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
     dev: true
 
-  /@jridgewell/trace-mapping@0.3.18:
-    resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
-    dependencies:
-      '@jridgewell/resolve-uri': 3.1.0
-      '@jridgewell/sourcemap-codec': 1.4.14
+  /@jridgewell/sourcemap-codec@1.5.5:
+    resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
     dev: true
 
   /@jridgewell/trace-mapping@0.3.25:
@@ -1444,6 +1325,13 @@
       '@jridgewell/sourcemap-codec': 1.4.15
     dev: true
 
+  /@jridgewell/trace-mapping@0.3.31:
+    resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+    dependencies:
+      '@jridgewell/resolve-uri': 3.1.0
+      '@jridgewell/sourcemap-codec': 1.4.15
+    dev: true
+
   /@jsdoc/salty@0.2.5:
     resolution: {integrity: sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==}
     engines: {node: '>=v12.0.0'}
@@ -1631,6 +1519,27 @@
       - supports-color
     dev: true
 
+  /@rollup/plugin-babel@6.1.0(@babel/core@7.28.6)(rollup@2.79.1):
+    resolution: {integrity: sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+      '@types/babel__core': ^7.1.9
+      rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+    peerDependenciesMeta:
+      '@types/babel__core':
+        optional: true
+      rollup:
+        optional: true
+    dependencies:
+      '@babel/core': 7.28.6
+      '@babel/helper-module-imports': 7.24.7
+      '@rollup/pluginutils': 5.2.0(rollup@2.79.1)
+      rollup: 2.79.1
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@rollup/plugin-commonjs@26.0.1(rollup@2.79.1):
     resolution: {integrity: sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==}
     engines: {node: '>=16.0.0 || 14 >= 14.17'}
@@ -1720,7 +1629,6 @@
       estree-walker: 2.0.2
       picomatch: 4.0.3
       rollup: 2.79.1
-    dev: false
 
   /@sinclair/typebox@0.27.8:
     resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
@@ -1750,8 +1658,8 @@
   /@types/babel__core@7.20.1:
     resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==}
     dependencies:
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.22.5
+      '@babel/parser': 7.28.6
+      '@babel/types': 7.28.6
       '@types/babel__generator': 7.6.4
       '@types/babel__template': 7.4.1
       '@types/babel__traverse': 7.20.1
@@ -1760,20 +1668,20 @@
   /@types/babel__generator@7.6.4:
     resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==}
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.28.6
     dev: true
 
   /@types/babel__template@7.4.1:
     resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==}
     dependencies:
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/parser': 7.28.6
+      '@babel/types': 7.28.6
     dev: true
 
   /@types/babel__traverse@7.20.1:
     resolution: {integrity: sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==}
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.28.6
     dev: true
 
   /@types/chrome@0.0.268:
@@ -2439,17 +2347,17 @@
     resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==}
     dev: true
 
-  /babel-jest@29.7.0(@babel/core@7.22.5):
+  /babel-jest@29.7.0(@babel/core@7.28.6):
     resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     peerDependencies:
       '@babel/core': ^7.8.0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@jest/transform': 29.7.0
       '@types/babel__core': 7.20.1
       babel-plugin-istanbul: 6.1.1
-      babel-preset-jest: 29.6.3(@babel/core@7.22.5)
+      babel-preset-jest: 29.6.3(@babel/core@7.28.6)
       chalk: 4.1.2
       graceful-fs: 4.2.11
       slash: 3.0.0
@@ -2474,41 +2382,41 @@
     resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@babel/template': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/template': 7.28.6
+      '@babel/types': 7.28.6
       '@types/babel__core': 7.20.1
       '@types/babel__traverse': 7.20.1
     dev: true
 
-  /babel-preset-current-node-syntax@1.0.1(@babel/core@7.22.5):
+  /babel-preset-current-node-syntax@1.0.1(@babel/core@7.28.6):
     resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/core': 7.22.5
-      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.5)
-      '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.22.5)
-      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.5)
-      '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.22.5)
-      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.5)
-      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.5)
-      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.5)
-      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.5)
-      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.5)
-      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.5)
-      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.5)
-      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.5)
+      '@babel/core': 7.28.6
+      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.6)
+      '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.6)
+      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.6)
+      '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.6)
+      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.6)
+      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.6)
+      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.6)
+      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.6)
+      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.6)
+      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.6)
+      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.6)
+      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.6)
     dev: true
 
-  /babel-preset-jest@29.6.3(@babel/core@7.22.5):
+  /babel-preset-jest@29.6.3(@babel/core@7.28.6):
     resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       babel-plugin-jest-hoist: 29.6.3
-      babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.5)
+      babel-preset-current-node-syntax: 1.0.1(@babel/core@7.28.6)
     dev: true
 
   /balanced-match@1.0.2:
@@ -2596,6 +2504,11 @@
   /base64-js@1.5.1:
     resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
 
+  /baseline-browser-mapping@2.9.15:
+    resolution: {integrity: sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==}
+    hasBin: true
+    dev: true
+
   /basic-ftp@5.0.5:
     resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==}
     engines: {node: '>=10.0.0'}
@@ -2660,6 +2573,18 @@
       update-browserslist-db: 1.0.16(browserslist@4.23.1)
     dev: true
 
+  /browserslist@4.28.1:
+    resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+    hasBin: true
+    dependencies:
+      baseline-browser-mapping: 2.9.15
+      caniuse-lite: 1.0.30001765
+      electron-to-chromium: 1.5.267
+      node-releases: 2.0.27
+      update-browserslist-db: 1.2.3(browserslist@4.28.1)
+    dev: true
+
   /bser@2.1.1:
     resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
     dependencies:
@@ -2737,6 +2662,10 @@
     resolution: {integrity: sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==}
     dev: true
 
+  /caniuse-lite@1.0.30001765:
+    resolution: {integrity: sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==}
+    dev: true
+
   /catharsis@0.9.0:
     resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==}
     engines: {node: '>= 10'}
@@ -2886,10 +2815,6 @@
     engines: {node: '>= 0.6'}
     dev: false
 
-  /convert-source-map@1.9.0:
-    resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
-    dev: true
-
   /convert-source-map@2.0.0:
     resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
     dev: true
@@ -3409,6 +3334,10 @@
     resolution: {integrity: sha512-GVulpHjFu1Y9ZvikvbArHmAhZXtm3wHlpjTMcXNGKl4IQ4jMQjlnz8yMQYYqdLHKi/jEL2+CBC2akWVCoIGUdw==}
     dev: true
 
+  /electron-to-chromium@1.5.267:
+    resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==}
+    dev: true
+
   /emittery@0.13.1:
     resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
     engines: {node: '>=12'}
@@ -4532,8 +4461,8 @@
     resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==}
     engines: {node: '>=8'}
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/parser': 7.24.7
+      '@babel/core': 7.28.6
+      '@babel/parser': 7.28.6
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.0
       semver: 6.3.1
@@ -4545,8 +4474,8 @@
     resolution: {integrity: sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==}
     engines: {node: '>=10'}
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/parser': 7.24.7
+      '@babel/core': 7.28.6
+      '@babel/parser': 7.28.6
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.0
       semver: 7.7.2
@@ -4676,11 +4605,11 @@
       ts-node:
         optional: true
     dependencies:
-      '@babel/core': 7.22.5
+      '@babel/core': 7.28.6
       '@jest/test-sequencer': 29.7.0
       '@jest/types': 29.6.3
       '@types/node': 20.14.9
-      babel-jest: 29.7.0(@babel/core@7.22.5)
+      babel-jest: 29.7.0(@babel/core@7.28.6)
       chalk: 4.1.2
       ci-info: 3.9.0
       deepmerge: 4.3.1
@@ -4818,7 +4747,7 @@
     resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@babel/code-frame': 7.27.1
+      '@babel/code-frame': 7.28.6
       '@jest/types': 29.6.3
       '@types/stack-utils': 2.0.1
       chalk: 4.1.2
@@ -4943,15 +4872,15 @@
     resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@babel/core': 7.22.5
-      '@babel/generator': 7.22.5
-      '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.22.5)
-      '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.22.5)
-      '@babel/types': 7.22.5
+      '@babel/core': 7.28.6
+      '@babel/generator': 7.28.6
+      '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.28.6)
+      '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.28.6)
+      '@babel/types': 7.28.6
       '@jest/expect-utils': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.5)
+      babel-preset-current-node-syntax: 1.0.1(@babel/core@7.28.6)
       chalk: 4.1.2
       expect: 29.7.0
       graceful-fs: 4.2.11
@@ -5091,7 +5020,7 @@
     engines: {node: '>=12.0.0'}
     hasBin: true
     dependencies:
-      '@babel/parser': 7.22.5
+      '@babel/parser': 7.28.6
       '@jsdoc/salty': 0.2.5
       '@types/markdown-it': 12.2.3
       bluebird: 3.7.2
@@ -5155,6 +5084,12 @@
     hasBin: true
     dev: true
 
+  /jsesc@3.1.0:
+    resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+    engines: {node: '>=6'}
+    hasBin: true
+    dev: true
+
   /json-bigint@1.0.0:
     resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==}
     dependencies:
@@ -5518,6 +5453,10 @@
     resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
     dev: true
 
+  /node-releases@2.0.27:
+    resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+    dev: true
+
   /noice-json-rpc@1.2.0:
     resolution: {integrity: sha512-Wm+otW+drKzdqlSPoSwj34tUEq/Xj1gX6Cr2avrykvTW4IY7d3ngLmP+PErALzS0s9nYRokXvYDM54sbFvLlDA==}
     dev: false
@@ -5682,7 +5621,7 @@
     resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
     engines: {node: '>=8'}
     dependencies:
-      '@babel/code-frame': 7.27.1
+      '@babel/code-frame': 7.28.6
       error-ex: 1.3.2
       json-parse-even-better-errors: 2.3.1
       lines-and-columns: 1.2.4
@@ -5749,7 +5688,6 @@
   /picomatch@4.0.3:
     resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
     engines: {node: '>=12'}
-    dev: false
 
   /pirates@4.0.6:
     resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
@@ -6559,6 +6497,7 @@
   /to-fast-properties@2.0.0:
     resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
     engines: {node: '>=4'}
+    dev: true
 
   /to-regex-range@5.0.1:
     resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
@@ -6700,6 +6639,17 @@
       picocolors: 1.1.1
     dev: true
 
+  /update-browserslist-db@1.2.3(browserslist@4.28.1):
+    resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+    hasBin: true
+    peerDependencies:
+      browserslist: '>= 4.21.0'
+    dependencies:
+      browserslist: 4.28.1
+      escalade: 3.2.0
+      picocolors: 1.1.1
+    dev: true
+
   /uri-js@4.4.1:
     resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
     dependencies:
@@ -6741,7 +6691,7 @@
     resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
     engines: {node: '>=10.12.0'}
     dependencies:
-      '@jridgewell/trace-mapping': 0.3.18
+      '@jridgewell/trace-mapping': 0.3.31
       '@types/istanbul-lib-coverage': 2.0.4
       convert-source-map: 2.0.0
     dev: true
diff --git a/ui/src/base/logging.ts b/ui/src/base/logging.ts
index 5856774..f4192b6 100644
--- a/ui/src/base/logging.ts
+++ b/ui/src/base/logging.ts
@@ -29,42 +29,50 @@
 export type ErrorHandler = (err: ErrorDetails) => void;
 const errorHandlers: ErrorHandler[] = [];
 
-export function assertExists<A>(
-  value: A | null | undefined,
-  optMsg?: string,
-): A {
+export function assertExists<A>(value: A | null | undefined, expr?: string): A {
   if (value === null || value === undefined) {
-    throw new Error(optMsg ?? "Value doesn't exist");
+    throw new Error(`\`${expr ?? '<expression>'}\` doesn't exist`);
   }
   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): T {
-  if (value === undefined) throw new Error('Value is undefined');
+export function assertDefined<T>(value: T | undefined, expr?: string): T {
+  if (value === undefined) {
+    throw new Error(`\`${expr ?? '<expression>'}\` is undefined`);
+  }
   return value;
 }
 
-export function assertIsInstance<T>(
+export function assertIsInstanceOf<T>(
   value: unknown,
   clazz: Function,
-  optMsg?: string,
+  expr?: string,
 ): T {
-  assertTrue(
-    value instanceof clazz,
-    optMsg ?? `Value is not an instance of ${clazz.name}`,
-  );
+  if (!(value instanceof clazz)) {
+    throw new Error(
+      `\`${expr ?? '<expression>'}\` is not an instance of ${clazz.name}`,
+    );
+  }
   return value as T;
 }
 
-export function assertTrue(value: boolean, optMsg?: string) {
+export function assertTrue(value: boolean, expr?: string) {
   if (!value) {
-    throw new Error(optMsg ?? 'Failed assertion');
+    throw new Error(`\`${expr ?? '<expression>'}\` is falsy`);
   }
 }
 
-export function assertFalse(value: boolean, optMsg?: string) {
-  assertTrue(!value, optMsg);
+export function assertFalse(value: boolean, expr?: string) {
+  if (value) {
+    throw new Error(`\`${expr ?? '<expression>'}\` is truthy`);
+  }
+}
+
+// Unconditionally throws an error. Use for code paths that shouldn't be reached
+// but where TypeScript can't prove unreachability.
+export function fail(message: string): never {
+  throw new Error(message);
 }
 
 // This function serves two purposes.
@@ -74,9 +82,24 @@
 // 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 {
+export function assertUnreachable(value: never, expr?: string): never {
+  let valueStr: string;
+  try {
+    valueStr =
+      JSON.stringify(value, (_, v) => {
+        if (typeof v === 'bigint') return `${v}n`;
+        if (typeof v === 'symbol') return v.toString();
+        if (typeof v === 'function') {
+          return `[function ${(v.name as string) || 'anonymous'}]`;
+        }
+        return v;
+      }) ?? String(value);
+  } catch {
+    // Circular reference or other issue
+    valueStr = String(value);
+  }
   throw new Error(
-    optMsg ?? `This code should not be reachable ${value as unknown}`,
+    `Unreachable code reached when \`${expr ?? '<expression>'}\` = ${valueStr}`,
   );
 }
 
diff --git a/ui/src/base/logging_unittest.ts b/ui/src/base/logging_unittest.ts
new file mode 100644
index 0000000..959816b
--- /dev/null
+++ b/ui/src/base/logging_unittest.ts
@@ -0,0 +1,253 @@
+// 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,
+  assertExists,
+  assertFalse,
+  assertIsInstanceOf,
+  assertTrue,
+  assertUnreachable,
+  fail,
+} from './logging';
+
+describe('assertExists', () => {
+  test('returns value when not null or undefined', () => {
+    expect(assertExists(42)).toBe(42);
+    expect(assertExists('hello')).toBe('hello');
+    expect(assertExists(0)).toBe(0);
+    expect(assertExists('')).toBe('');
+    expect(assertExists(false)).toBe(false);
+  });
+
+  test('throws on null', () => {
+    expect(() => assertExists(null)).toThrow("`<expression>` doesn't exist");
+  });
+
+  test('throws on undefined', () => {
+    expect(() => assertExists(undefined)).toThrow(
+      "`<expression>` doesn't exist",
+    );
+  });
+
+  test('includes description in error message', () => {
+    expect(() => assertExists(null, 'foo.bar')).toThrow(
+      "`foo.bar` doesn't exist",
+    );
+  });
+});
+
+describe('assertDefined', () => {
+  test('returns value when defined', () => {
+    expect(assertDefined(42)).toBe(42);
+    expect(assertDefined(null)).toBe(null); // null is defined
+    expect(assertDefined(0)).toBe(0);
+    expect(assertDefined('')).toBe('');
+  });
+
+  test('throws on undefined', () => {
+    expect(() => assertDefined(undefined)).toThrow(
+      '`<expression>` is undefined',
+    );
+  });
+
+  test('includes description in error message', () => {
+    expect(() => assertDefined(undefined, 'config.option')).toThrow(
+      '`config.option` is undefined',
+    );
+  });
+});
+
+describe('assertTrue', () => {
+  test('does not throw when value is true', () => {
+    expect(() => assertTrue(true)).not.toThrow();
+  });
+
+  test('throws when value is false', () => {
+    expect(() => assertTrue(false)).toThrow('`<expression>` is falsy');
+  });
+
+  test('includes description in error message', () => {
+    expect(() => assertTrue(false, 'x > 0')).toThrow('`x > 0` is falsy');
+  });
+});
+
+describe('assertFalse', () => {
+  test('does not throw when value is false', () => {
+    expect(() => assertFalse(false)).not.toThrow();
+  });
+
+  test('throws when value is true', () => {
+    expect(() => assertFalse(true)).toThrow('`<expression>` is truthy');
+  });
+
+  test('includes description in error message', () => {
+    expect(() => assertFalse(true, 'isDisabled')).toThrow(
+      '`isDisabled` is truthy',
+    );
+  });
+});
+
+describe('assertIsInstanceOf', () => {
+  test('returns value when instance matches', () => {
+    const arr = [1, 2, 3];
+    expect(assertIsInstanceOf(arr, Array)).toBe(arr);
+
+    const date = new Date();
+    expect(assertIsInstanceOf(date, Date)).toBe(date);
+  });
+
+  test('throws when instance does not match', () => {
+    expect(() => assertIsInstanceOf({}, Array)).toThrow(
+      '`<expression>` is not an instance of Array',
+    );
+  });
+
+  test('includes description in error message', () => {
+    expect(() => assertIsInstanceOf('hello', Array, 'myVar')).toThrow(
+      '`myVar` is not an instance of Array',
+    );
+  });
+});
+
+describe('fail', () => {
+  test('always throws with the given message', () => {
+    expect(() => fail('something went wrong')).toThrow('something went wrong');
+  });
+});
+
+describe('assertUnreachable', () => {
+  test('throws with value in error message', () => {
+    // We need to cast to never to test this
+    const value = 'unexpected' as never;
+    expect(() => assertUnreachable(value)).toThrow(
+      'Unreachable code reached when `<expression>` = "unexpected"',
+    );
+  });
+
+  test('includes description in error message', () => {
+    const value = 'oops' as never;
+    expect(() => assertUnreachable(value, 'mode')).toThrow(
+      'Unreachable code reached when `mode` = "oops"',
+    );
+  });
+
+  test('handles numeric values', () => {
+    const value = 42 as never;
+    expect(() => assertUnreachable(value, 'count')).toThrow(
+      'Unreachable code reached when `count` = 42',
+    );
+  });
+
+  test('handles object values', () => {
+    const value = {foo: 'bar'} as never;
+    expect(() => assertUnreachable(value, 'obj')).toThrow(
+      'Unreachable code reached when `obj` = {"foo":"bar"}',
+    );
+  });
+
+  test('handles bigint values', () => {
+    const value = BigInt(123) as never;
+    expect(() => assertUnreachable(value, 'big')).toThrow(
+      'Unreachable code reached when `big` = "123n"',
+    );
+  });
+
+  test('handles symbol values', () => {
+    const value = Symbol('test') as never;
+    expect(() => assertUnreachable(value, 'sym')).toThrow(
+      'Unreachable code reached when `sym` = "Symbol(test)"',
+    );
+  });
+
+  test('handles objects with bigint properties', () => {
+    const value = {id: BigInt(456)} as never;
+    expect(() => assertUnreachable(value, 'data')).toThrow(
+      'Unreachable code reached when `data` = {"id":"456n"}',
+    );
+  });
+
+  test('handles complex expression descriptions from babel plugin', () => {
+    const value = 'unknown_state' as never;
+    expect(() => assertUnreachable(value, 'config.settings.mode')).toThrow(
+      'Unreachable code reached when `config.settings.mode` = "unknown_state"',
+    );
+  });
+
+  test('handles array access expression descriptions', () => {
+    const value = 99 as never;
+    expect(() => assertUnreachable(value, 'items[0].type')).toThrow(
+      'Unreachable code reached when `items[0].type` = 99',
+    );
+  });
+
+  test('handles call expression descriptions', () => {
+    const value = {invalid: true} as never;
+    expect(() => assertUnreachable(value, 'getStatus(...)')).toThrow(
+      'Unreachable code reached when `getStatus(...)` = {"invalid":true}',
+    );
+  });
+
+  test('handles function call result as value', () => {
+    const getValue = () => ({computed: true, status: 'error'});
+    expect(() => assertUnreachable(getValue() as never, 'result')).toThrow(
+      'Unreachable code reached when `result` = {"computed":true,"status":"error"}',
+    );
+  });
+
+  test('handles deeply nested object as value', () => {
+    const value = {
+      level1: {
+        level2: {
+          level3: {
+            data: 'deep',
+          },
+        },
+      },
+    } as never;
+    expect(() => assertUnreachable(value, 'nested')).toThrow(
+      'Unreachable code reached when `nested` = {"level1":{"level2":{"level3":{"data":"deep"}}}}',
+    );
+  });
+
+  test('handles array as value', () => {
+    const value = [1, 2, 3, 'mixed', {obj: true}] as never;
+    expect(() => assertUnreachable(value, 'arr')).toThrow(
+      'Unreachable code reached when `arr` = [1,2,3,"mixed",{"obj":true}]',
+    );
+  });
+
+  test('handles inline literal object', () => {
+    expect(() =>
+      assertUnreachable({type: 'UNKNOWN', payload: null} as never, 'action'),
+    ).toThrow(
+      'Unreachable code reached when `action` = {"type":"UNKNOWN","payload":null}',
+    );
+  });
+
+  test('handles function value', () => {
+    const value = function myFunc() {} as never;
+    expect(() => assertUnreachable(value, 'fn')).toThrow(
+      'Unreachable code reached when `fn` = "[function myFunc]"',
+    );
+  });
+
+  test('handles anonymous function value', () => {
+    // Use IIFE to create a truly anonymous function (variable assignment names it)
+    const value = (() => () => {})() as never;
+    expect(() => assertUnreachable(value, 'fn')).toThrow(
+      'Unreachable code reached when `fn` = "[function anonymous]"',
+    );
+  });
+});
diff --git a/ui/src/components/details/thread_slice_details_tab.ts b/ui/src/components/details/thread_slice_details_tab.ts
index 9d493a2..9efc87d 100644
--- a/ui/src/components/details/thread_slice_details_tab.ts
+++ b/ui/src/components/details/thread_slice_details_tab.ts
@@ -35,7 +35,7 @@
 import {DurationWidget} from '../widgets/duration';
 import {SliceRef} from '../widgets/slice';
 import {Grid, GridCell, GridHeaderCell} from '../../widgets/grid';
-import {assertIsInstance} from '../../base/logging';
+import {assertIsInstanceOf} from '../../base/logging';
 import {Trace} from '../../public/trace';
 import {TrackEventDetailsPanel} from '../../public/details_panel';
 import {TrackEventSelection} from '../../public/selection';
@@ -224,10 +224,10 @@
   private readonly attrs: ThreadSliceDetailsPanelAttrs;
 
   constructor(trace: Trace, attrs?: ThreadSliceDetailsPanelAttrs) {
-    // Rationale for the assertIsInstance: ThreadSliceDetailsPanel requires a
+    // Rationale for the assertIsInstanceOf: 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);
+    this.trace = assertIsInstanceOf(trace, TraceImpl);
     this.attrs = attrs ?? {};
   }
 
diff --git a/ui/src/frontend/timeline_page/gridline_helper.ts b/ui/src/frontend/timeline_page/gridline_helper.ts
index 92ceb4b..33f03ea 100644
--- a/ui/src/frontend/timeline_page/gridline_helper.ts
+++ b/ui/src/frontend/timeline_page/gridline_helper.ts
@@ -133,8 +133,8 @@
   maxMajorTicks: number,
   offset: time = Time.ZERO,
 ): Generator<Tick> {
-  assertTrue(timeSpan.duration > 0n, 'timeSpan.duration cannot be lte 0');
-  assertTrue(maxMajorTicks > 0, 'maxMajorTicks cannot be lte 0');
+  assertTrue(timeSpan.duration > 0n);
+  assertTrue(maxMajorTicks > 0);
 
   timeSpan = timeSpan.translate(-offset);
   const minStepSize = BigInt(
diff --git a/ui/src/plugins/dev.perfetto.AutoPinAndExpandTracks/index.ts b/ui/src/plugins/dev.perfetto.AutoPinAndExpandTracks/index.ts
index d6dafff..5982e49 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/logging';
+import {assertIsInstanceOf} from '../../base/logging';
 import {RouteArg, RouteArgs} from '../../public/route_schema';
 import {arrayEquals} from '../../base/array_utils';
 
@@ -182,7 +182,7 @@
       name: 'Import by name: Pinned tracks',
       callback: async () => {
         const files = document.querySelector('.pinned_tracks_import_selector');
-        assertIsInstance<HTMLInputElement>(files, HTMLInputElement).click();
+        assertIsInstanceOf<HTMLInputElement>(files, HTMLInputElement).click();
       },
     });
 
diff --git a/ui/src/plugins/dev.perfetto.Chaos/index.ts b/ui/src/plugins/dev.perfetto.Chaos/index.ts
index 1b09f5c..f435d15 100644
--- a/ui/src/plugins/dev.perfetto.Chaos/index.ts
+++ b/ui/src/plugins/dev.perfetto.Chaos/index.ts
@@ -16,11 +16,27 @@
 import {App} from '../../public/app';
 import {addDebugSliceTrack} from '../../components/tracks/debug_tracks';
 import {PerfettoPlugin} from '../../public/plugin';
+import {
+  assertDefined,
+  assertExists,
+  assertFalse,
+  assertIsInstanceOf,
+  assertTrue,
+  assertUnreachable,
+  fail,
+} from '../../base/logging';
 
 export default class implements PerfettoPlugin {
   static readonly id = 'dev.perfetto.Chaos';
 
   static onActivate(ctx: App): void {
+    const testObject = {
+      a: 123,
+      b: null,
+      c: undefined,
+      d: 'invalid' as 'a' | 'b',
+    };
+
     ctx.commands.registerCommand({
       id: 'dev.perfetto.CrashNow',
       name: 'Chaos: crash now',
@@ -28,6 +44,69 @@
         throw new Error('Manual crash from dev.perfetto.Chaos#CrashNow');
       },
     });
+
+    ctx.commands.registerCommand({
+      id: 'dev.perfetto.AssertTrue',
+      name: 'Chaos: assertTrue failure',
+      callback: () => {
+        assertTrue(testObject.a > 200);
+      },
+    });
+
+    ctx.commands.registerCommand({
+      id: 'dev.perfetto.AssertFalse',
+      name: 'Chaos: assertFalse failure',
+      callback: () => {
+        assertFalse(testObject.a < 200);
+      },
+    });
+
+    ctx.commands.registerCommand({
+      id: 'dev.perfetto.AssertExists',
+      name: 'Chaos: assertExists failure',
+      callback: () => {
+        assertExists(testObject.b);
+      },
+    });
+
+    ctx.commands.registerCommand({
+      id: 'dev.perfetto.AssertDefined',
+      name: 'Chaos: assertDefined failure',
+      callback: () => {
+        assertDefined(testObject.c);
+      },
+    });
+
+    ctx.commands.registerCommand({
+      id: 'dev.perfetto.AssertUnreachable',
+      name: 'Chaos: assertUnreachable failure',
+      callback: () => {
+        switch (testObject.d) {
+          case 'a':
+            break;
+          case 'b':
+            break;
+          default:
+            assertUnreachable(testObject.d);
+        }
+      },
+    });
+
+    ctx.commands.registerCommand({
+      id: 'dev.perfetto.assertIsInstanceOf',
+      name: 'Chaos: assertIsInstanceOf failure',
+      callback: () => {
+        assertIsInstanceOf(testObject, Array);
+      },
+    });
+
+    ctx.commands.registerCommand({
+      id: 'dev.perfetto.Fail',
+      name: 'Chaos: fail()',
+      callback: () => {
+        fail('Intentional failure from Chaos plugin');
+      },
+    });
   }
 
   async onTraceLoad(ctx: Trace): Promise<void> {
diff --git a/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts b/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts
index ff01e91..8664e2a 100644
--- a/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts
+++ b/ui/src/plugins/dev.perfetto.HeapProfile/heap_profile_details_panel.ts
@@ -14,7 +14,7 @@
 
 import m from 'mithril';
 
-import {assertFalse} from '../../base/logging';
+import {fail} from '../../base/logging';
 import {createPerfettoTable} from '../../trace_processor/sql_utils';
 import {extensions} from '../../components/extensions';
 import {time} from '../../base/time';
@@ -508,11 +508,9 @@
     case ProfileType.NATIVE_HEAP_PROFILE:
       return 'Native heap profile';
     case ProfileType.PERF_SAMPLE:
-      assertFalse(false, 'Perf sample not supported');
-      return 'Impossible';
+      fail('Perf sample not supported');
     case ProfileType.INSTRUMENTS_SAMPLE:
-      assertFalse(false, 'Instruments sample not supported');
-      return 'Impossible';
+      fail('Instruments sample not supported');
   }
 }