Add docs a lot of public interfaces

Change-Id: I875fc21c5a7a5e1e3b359cbb2b50a98e789cc7a8
diff --git a/python/tools/check_ratchet.py b/python/tools/check_ratchet.py
index 8ea5a0d..3395f27 100755
--- a/python/tools/check_ratchet.py
+++ b/python/tools/check_ratchet.py
@@ -36,7 +36,7 @@
 
 from dataclasses import dataclass
 
-EXPECTED_ANY_COUNT = 34
+EXPECTED_ANY_COUNT = 32
 EXPECTED_RUN_METRIC_COUNT = 4
 
 ROOT_DIR = os.path.dirname(
diff --git a/ui/src/plugins/com.android.AndroidNetwork/index.ts b/ui/src/plugins/com.android.AndroidNetwork/index.ts
index 10e5c43..9dcc7dd 100644
--- a/ui/src/plugins/com.android.AndroidNetwork/index.ts
+++ b/ui/src/plugins/com.android.AndroidNetwork/index.ts
@@ -43,19 +43,21 @@
     ctx.commands.registerCommand({
       id: 'com.android.AddBatteryEventsTrack',
       name: 'Add track: battery events',
-      callback: async (track) => {
-        if (track === undefined) {
-          track = await ctx.omnibox.prompt('Battery Track');
-          if (track === undefined) return;
-        }
+      callback: async (rawTrackName) => {
+        const trackName = await (async function () {
+          if (typeof rawTrackName === 'string') return rawTrackName;
+          return await ctx.omnibox.prompt('Battery Track');
+        })();
+
+        if (!trackName) return;
 
         await ctx.engine.query(`SELECT IMPORT('android.battery_stats');`);
         await this.addSimpleTrack(
           ctx,
-          track,
+          trackName,
           `(SELECT *
             FROM android_battery_stats_event_slices
-            WHERE track_name = "${track}")`,
+            WHERE track_name = "${trackName}")`,
           ['ts', 'dur', 'str_value', 'int_value'],
         );
       },
@@ -64,16 +66,23 @@
     ctx.commands.registerCommand({
       id: 'com.android.AddNetworkActivityTrack',
       name: 'Add track: network activity',
-      callback: async (groupby, filter, trackName) => {
-        if (groupby === undefined) {
-          groupby = await ctx.omnibox.prompt('Group by', 'package_name');
-          if (groupby === undefined) return;
-        }
+      callback: async (rawGroupby, rawFilter, rawTrackName) => {
+        const groupby = await (async function () {
+          if (typeof rawGroupby === 'string') return rawGroupby;
+          return await ctx.omnibox.prompt('Group by', 'package_name');
+        })();
 
-        if (filter === undefined) {
-          filter = await ctx.omnibox.prompt('Filter', 'TRUE');
-          if (filter === undefined) return;
-        }
+        const filter = await (async function () {
+          if (typeof rawFilter === 'string') return rawFilter;
+          return await ctx.omnibox.prompt('Group by', 'package_name');
+        })();
+
+        if (!groupby || !filter) return;
+
+        const trackName = await (async function () {
+          if (typeof rawTrackName === 'string') return rawTrackName;
+          return 'Network Activity';
+        })();
 
         const suffix = new Date().getTime();
         await ctx.engine.query(`
@@ -91,7 +100,7 @@
         const groupCols = groupby.replaceAll(' ', '').split(',');
         await this.addSimpleTrack(
           ctx,
-          trackName ?? 'Network Activity',
+          trackName,
           `android_network_activity_${suffix}`,
           ['ts', 'dur', ...groupCols, 'packet_length', 'packet_count'],
         );
diff --git a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/index.ts b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/index.ts
index 37132e9..a21f812 100644
--- a/ui/src/plugins/dev.perfetto.TraceProcessorTrack/index.ts
+++ b/ui/src/plugins/dev.perfetto.TraceProcessorTrack/index.ts
@@ -24,7 +24,7 @@
   metricsFromTableOrSubquery,
   QueryFlamegraph,
 } from '../../components/query_flamegraph';
-import {MinimapRow} from '../../public/minimap';
+import {MinimapCell, MinimapRow} from '../../public/minimap';
 import {PerfettoPlugin} from '../../public/plugin';
 import {AreaSelection, areaSelectionsEqual} from '../../public/selection';
 import {Trace} from '../../public/trace';
@@ -460,7 +460,7 @@
                 upid;
             `);
 
-        const slicesData = new Map<number, MinimapRow>();
+        const slicesData = new Map<number, MinimapCell[]>();
         const it = sliceResult.iter({bucket: LONG, upid: NUM, load: NUM});
         for (; it.valid(); it.next()) {
           const bucket = it.bucket;
diff --git a/ui/src/public/analytics.ts b/ui/src/public/analytics.ts
index 653b1ea..066903f 100644
--- a/ui/src/public/analytics.ts
+++ b/ui/src/public/analytics.ts
@@ -16,8 +16,33 @@
 
 export type TraceCategories = 'Trace Actions' | 'Record Trace' | 'User Actions';
 
+/**
+ * Logs analytics events and errors.
+ *
+ * Use this to track user actions, trace operations, and error conditions.
+ * Events are categorized (e.g., 'Trace Actions', 'User Actions') and can
+ * be used to understand how users interact with your plugin.
+ */
 export interface Analytics {
+  /**
+   * Logs a generic analytics event.
+   *
+   * @param category The category of the event (e.g., 'Trace Actions').
+   * @param event The name of the event (e.g., 'Save trace').
+   */
   logEvent(category: TraceCategories | null, event: string): void;
+
+  /**
+   * Logs an error event.
+   *
+   * @param err The error details to log.
+   */
   logError(err: ErrorDetails): void;
+
+  /**
+   * Checks if analytics is enabled.
+   *
+   * @returns `true` if analytics is enabled, `false` otherwise.
+   */
   isEnabled(): boolean;
 }
diff --git a/ui/src/public/command.ts b/ui/src/public/command.ts
index abf884f..c1aa105 100644
--- a/ui/src/public/command.ts
+++ b/ui/src/public/command.ts
@@ -14,29 +14,87 @@
 
 import {Hotkey} from '../base/hotkeys';
 
+/**
+ * Manages the registration and execution of commands.
+ *
+ * Commands are user-invokable actions that can be triggered via hotkeys,
+ * the command palette, or programmatically. Use this to register plugin
+ * commands that users can execute.
+ */
 export interface CommandManager {
+  /**
+   * Registers a new command.
+   *
+   * The command is uniquely identified by its `id`. If a command with the same
+   * `id` is already registered, this method will throw an error.
+   *
+   * @param command The command to register.
+   */
   registerCommand(command: Command): void;
 
+  /**
+   * Checks if a command with the given `id` is registered.
+   *
+   * @param commandId The unique identifier of the command.
+   * @returns `true` if the command is registered, `false` otherwise.
+   */
   hasCommand(commandId: string): boolean;
 
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  runCommand(id: string, ...args: any[]): any;
+  /**
+   * Executes a command by its `id`.
+   *
+   * Any additional arguments are passed to the command's `callback` function.
+   *
+   * @param id The unique identifier of the command to run.
+   * @param args A list of arguments to pass to the command's callback.
+   * @returns The result of the command's callback, if any.
+   */
+  runCommand(id: string, ...args: unknown[]): unknown;
 }
 
+/**
+ * Represents a command that can be executed within the application.
+ *
+ * A command is a self-contained unit of work that can be invoked by the user
+ * or programmatically. It includes a unique identifier, a human-readable name,
+ * a callback function to execute, and an optional default hotkey.
+ */
 export interface Command {
-  // A unique id for this command.
-  id: string;
-  // A human-friendly name for this command.
-  name: string;
-  // Callback is called when the command is invoked.
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  callback: (...args: any[]) => any;
-  // Default hotkey for this command.
-  // Note: this is just the default and may be changed by the user.
-  // Examples:
-  // - 'P'
-  // - 'Shift+P'
-  // - '!Mod+Shift+P'
-  // See hotkeys.ts for guidance on hotkey syntax.
-  defaultHotkey?: Hotkey;
+  /**
+   * A unique identifier for this command.
+   *
+   * This `id` is used to register, unregister, and execute the command. It is
+   * recommended to use a namespace to avoid collisions (e.g.,
+   * `myPlugin.myCommand`).
+   */
+  readonly id: string;
+
+  /**
+   * A human-friendly name for this command.
+   *
+   * This name is displayed to the user in the command palette and other UI
+   * elements. It should be concise and descriptive.
+   */
+  readonly name: string;
+
+  /**
+   * The function to call when the command is invoked.
+   *
+   * This function receives any arguments passed to `runCommand` and can return
+   * a value.
+   */
+  callback(...args: unknown[]): unknown;
+
+  /**
+   * The default hotkey for this command.
+   *
+   * This is just the default and may be changed by the user in the settings.
+   * See `hotkeys.ts` for guidance on hotkey syntax.
+   *
+   * @example
+   * - 'P'
+   * - 'Shift+P'
+   * - '!Mod+Shift+P'
+   */
+  readonly defaultHotkey?: Hotkey;
 }
diff --git a/ui/src/public/feature_flag.ts b/ui/src/public/feature_flag.ts
index c82d38a..f076f3f 100644
--- a/ui/src/public/feature_flag.ts
+++ b/ui/src/public/feature_flag.ts
@@ -12,53 +12,141 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+/**
+ * Manages feature flags for experimental or togglable features.
+ *
+ * Feature flags allow plugins to expose experimental functionality that
+ * users can enable/disable. Flags are persisted across sessions and can be
+ * configured on the flags page.
+ */
 export interface FeatureFlagManager {
+  /**
+   * Registers a new feature flag.
+   *
+   * @param settings The settings for the new feature flag.
+   * @returns The registered feature flag.
+   */
   register(settings: FlagSettings): Flag;
 }
 
+/**
+ * Settings for defining a new feature flag.
+ */
 export interface FlagSettings {
-  id: string;
-  defaultValue: boolean;
-  description: string;
-  name?: string;
-  devOnly?: boolean;
-}
-
-export interface Flag {
-  // A unique identifier for this flag ("magicSorting")
+  /**
+   * A unique identifier for this flag (e.g., "magicSorting").
+   */
   readonly id: string;
 
-  // The name of the flag the user sees ("New track sorting algorithm")
-  readonly name: string;
-
-  // A longer description which is displayed to the user.
-  // "Sort tracks using an embedded tfLite model based on your expression
-  // while waiting for the trace to load."
-  readonly description: string;
-
-  // Whether the flag defaults to true or false.
-  // If !flag.isOverridden() then flag.get() === flag.defaultValue
+  /**
+   * The default value of the flag (true or false).
+   *
+   * If `flag.isOverridden()` is false, then `flag.get()` will return
+   * `flag.defaultValue`.
+   */
   readonly defaultValue: boolean;
 
-  // Get the current value of the flag.
+  /**
+   * A longer description which is displayed to the user.
+   *
+   * Example: "Sort tracks using an embedded tfLite model based on your
+   * expression while waiting for the trace to load."
+   */
+  readonly description: string;
+
+  /**
+   * The name of the flag the user sees (e.g., "New track sorting algorithm").
+   * If omitted, the `id` will be used as the name.
+   */
+  readonly name?: string;
+
+  /**
+   * If true, this flag will only be visible and configurable in development
+   * builds of the Perfetto UI.
+   */
+  readonly devOnly?: boolean;
+}
+
+/**
+ * Represents a feature flag that can be enabled or disabled.
+ */
+export interface Flag {
+  /**
+   * A unique identifier for this flag (e.g., "magicSorting").
+   */
+  readonly id: string;
+
+  /**
+   * The name of the flag the user sees (e.g., "New track sorting algorithm").
+   */
+  readonly name: string;
+
+  /**
+   * A longer description which is displayed to the user.
+   *
+   * Example: "Sort tracks using an embedded tfLite model based on your
+   * expression while waiting for the trace to load."
+   */
+  readonly description: string;
+
+  /**
+   * Whether the flag defaults to true or false.
+   *
+   * If `!flag.isOverridden()`, then `flag.get()` will return
+   * `flag.defaultValue`.
+   */
+  readonly defaultValue: boolean;
+
+  /**
+   * Get the current value of the flag.
+   *
+   * @returns The current boolean value of the flag.
+   */
   get(): boolean;
 
-  // Override the flag and persist the new value.
+  /**
+   * Override the flag and persist the new value.
+   *
+   * This will change the flag's value for the current and future sessions.
+   *
+   * @param value The new boolean value for the flag.
+   */
   set(value: boolean): void;
 
-  // If the flag has been overridden.
-  // Note: A flag can be overridden to its default value.
+  /**
+   * Checks if the flag has been explicitly overridden by the user.
+   *
+   * Note: A flag can be overridden to its default value.
+   *
+   * @returns `true` if the flag's value has been explicitly set, `false`
+   *   otherwise.
+   */
   isOverridden(): boolean;
 
-  // Reset the flag to its default setting.
+  /**
+   * Reset the flag to its default setting.
+   *
+   * This will remove any user override and revert the flag to its
+   * `defaultValue`.
+   */
   reset(): void;
 
-  // Get the current state of the flag.
+  /**
+   * Get the current state of the flag's override status.
+   *
+   * @returns The {@link OverrideState} of the flag.
+   */
   overriddenState(): OverrideState;
 }
 
+/**
+ * Represents the override state of a feature flag.
+ */
 export enum OverrideState {
+  /** The flag is currently using its default value. */
   DEFAULT = 'DEFAULT',
+  /** The flag has been overridden to `true`. */
   TRUE = 'OVERRIDE_TRUE',
+  /** The flag has been overridden to `false`. */
   FALSE = 'OVERRIDE_FALSE',
 }
diff --git a/ui/src/public/minimap.ts b/ui/src/public/minimap.ts
index f78c3de..d4bcf54 100644
--- a/ui/src/public/minimap.ts
+++ b/ui/src/public/minimap.ts
@@ -15,22 +15,61 @@
 import {HighPrecisionTimeSpan} from '../base/high_precision_time_span';
 import {duration, time} from '../base/time';
 
+/**
+ * Represents a single cell in the minimap, containing data for a specific time
+ * range.
+ */
 export interface MinimapCell {
+  /**
+   * The start timestamp of the cell.
+   */
   readonly ts: time;
+
+  /**
+   * The duration of the cell.
+   */
   readonly dur: duration;
+
+  /**
+   * The load value for the cell, typically a normalized value between 0 and 1.
+   */
   readonly load: number;
 }
 
-export type MinimapRow = MinimapCell[];
+/**
+ * Represents a row of data in the minimap.
+ */
+export type MinimapRow = readonly MinimapCell[];
 
+/**
+ * Provides content for the minimap.
+ */
 export interface MinimapContentProvider {
+  /**
+   * The priority of this content provider. Higher priority providers are
+   * preferred.
+   */
   readonly priority: number;
+
+  /**
+   * Gets the data for the minimap for a given time span and resolution.
+   * @param timeSpan The time span for which to provide data.
+   * @param resolution The resolution at which to provide the data.
+   * @returns A promise that resolves to an array of minimap rows.
+   */
   getData(
     timeSpan: HighPrecisionTimeSpan,
     resolution: duration,
   ): Promise<MinimapRow[]>;
 }
 
+/**
+ * Manages content providers for the minimap.
+ */
 export interface MinimapManager {
-  registerContentProvider(x: MinimapContentProvider): void;
+  /**
+   * Registers a new content provider for the minimap.
+   * @param provider The content provider to register.
+   */
+  registerContentProvider(provider: MinimapContentProvider): void;
 }
diff --git a/ui/src/public/note.ts b/ui/src/public/note.ts
index 20b462a..08276ce 100644
--- a/ui/src/public/note.ts
+++ b/ui/src/public/note.ts
@@ -14,45 +14,127 @@
 
 import {time} from '../base/time';
 
+/**
+ * Manages notes and span notes on the timeline.
+ *
+ * Notes are flags on the timeline marker, while span notes represent flagged
+ * ranges.
+ */
 export interface NoteManager {
+  /**
+   * Retrieves a note or span note by its ID.
+   * @param id The unique identifier of the note.
+   * @returns The note or span note if found, or `undefined`.
+   */
   getNote(id: string): Note | SpanNote | undefined;
 
-  // Adds a note (a flag on the timeline marker). Returns the id.
+  /**
+   * Adds a new note (a flag on the timeline marker).
+   * @param args The arguments for adding the note.
+   * @returns The unique ID of the newly added note.
+   */
   addNote(args: AddNoteArgs): string;
 
-  // Adds a span note (a flagged range). Returns the id.
+  /**
+   * Adds a new span note (a flagged range).
+   * @param args The arguments for adding the span note.
+   * @returns The unique ID of the newly added span note.
+   */
   addSpanNote(args: AddSpanNoteArgs): string;
 }
 
+/**
+ * Arguments for adding a new note.
+ */
 export interface AddNoteArgs {
+  /**
+   * The timestamp of the note.
+   */
   readonly timestamp: time;
-  readonly color?: string; // Default: randomColor().
-  readonly text?: string; // Default: ''.
-  // The id is optional. If present, allows overriding a previosly created note.
-  // If not present it will be auto-assigned with a montonic counter.
+  /**
+   * The color of the note. If not provided, a random color will be assigned.
+   */
+  readonly color?: string;
+  /**
+   * The text content of the note. If not provided, an empty string will be used.
+   */
+  readonly text?: string;
+  /**
+   * The unique ID of the note. If provided, it allows overriding a previously
+   * created note. If not present, an ID will be auto-assigned with a monotonic
+   * counter.
+   */
   readonly id?: string;
 }
 
+/**
+ * Represents a note (a flag on the timeline marker).
+ */
 export interface Note extends AddNoteArgs {
+  /**
+   * The type of the note, always 'DEFAULT' for a regular note.
+   */
   readonly noteType: 'DEFAULT';
+  /**
+   * The unique ID of the note.
+   */
   readonly id: string;
+  /**
+   * The color of the note.
+   */
   readonly color: string;
+  /**
+   * The text content of the note.
+   */
   readonly text: string;
 }
 
+/**
+ * Arguments for adding a new span note.
+ */
 export interface AddSpanNoteArgs {
+  /**
+   * The start timestamp of the span note.
+   */
   readonly start: time;
+  /**
+   * The end timestamp of the span note.
+   */
   readonly end: time;
-  readonly color?: string; // Default: randomColor().
-  readonly text?: string; // Default: ''.
-  // The id is optional. If present, allows overriding a previosly created note.
-  // If not present it will be auto-assigned with a montonic counter.
+  /**
+   * The color of the span note. If not provided, a random color will be assigned.
+   */
+  readonly color?: string;
+  /**
+   * The text content of the span note. If not provided, an empty string will be used.
+   */
+  readonly text?: string;
+  /**
+   * The unique ID of the span note. If provided, it allows overriding a previously
+   * created span note. If not present, an ID will be auto-assigned with a monotonic
+   * counter.
+   */
   readonly id?: string;
 }
 
+/**
+ * Represents a span note (a flagged range).
+ */
 export interface SpanNote extends AddSpanNoteArgs {
+  /**
+   * The type of the note, always 'SPAN' for a span note.
+   */
   readonly noteType: 'SPAN';
+  /**
+   * The unique ID of the span note.
+   */
   readonly id: string;
+  /**
+   * The color of the span note.
+   */
   readonly color: string;
+  /**
+   * The text content of the span note.
+   */
   readonly text: string;
 }
diff --git a/ui/src/public/omnibox.ts b/ui/src/public/omnibox.ts
index daa2a83..cfd2733 100644
--- a/ui/src/public/omnibox.ts
+++ b/ui/src/public/omnibox.ts
@@ -12,6 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+/**
+ * Provides non-modal user prompts integrated into the UI.
+ *
+ * The omnibox can prompt users for free-form text input or selection from a
+ * list of choices. Think of it as window.prompt() but non-blocking and
+ * better integrated with the UI. Returns a promise that resolves to the
+ * user's input or undefined if dismissed.
+ */
 export interface OmniboxManager {
   /**
    * Turns the omnibox into an interactive prompt for the user. Think of
@@ -70,7 +78,23 @@
   prompt<T>(text: string, choices: PromptChoices<T>): Promise<T | undefined>;
 }
 
+/**
+ * Represents a set of choices for the omnibox prompt.
+ *
+ * This interface allows for providing complex objects as choices, with a
+ * custom function to extract the display name for each choice.
+ * @template T The type of the values in the choices list.
+ */
 export interface PromptChoices<T> {
-  values: ReadonlyArray<T>;
-  getName: (x: T) => string;
+  /**
+   * An array of values that the user can choose from.
+   */
+  readonly values: ReadonlyArray<T>;
+
+  /**
+   * A function that returns the display name for a given choice value.
+   * @param x The choice value.
+   * @returns The string representation of the choice.
+   */
+  getName(x: T): string;
 }
diff --git a/ui/src/public/page.ts b/ui/src/public/page.ts
index 82a5bb9..30b5c6e 100644
--- a/ui/src/public/page.ts
+++ b/ui/src/public/page.ts
@@ -15,24 +15,46 @@
 import m from 'mithril';
 
 /**
- * Allows to register custom page endpoints that response to given routes, e.g.
- * /viewer, /record etc.
+ * Manages custom page registration and routing.
+ *
+ * Use this to register pages that respond to specific routes (e.g.,
+ * '/settings', '/query'). Pages are automatically unregistered when the
+ * trace is closed or the plugin is unloaded.
  */
 export interface PageManager {
   /**
-   * Example usage:
-   *   registerPage({route: '/foo', page: FooPage})
-   *   class FooPage implements m.ClassComponent<PageWithTrace> {
-   *     view({attrs}: m.CVnode<PageWithTrace>) {
-   *        return m('div', ...
-   *            onclick: () => attrs.trace.timeline.zoom(...);
-   *        )
-   *     }
+   * Registers a new custom page handler.
+   *
+   * The page handler defines the route it responds to and the content to
+   * render. Returns a `Disposable` that can be used to unregister the page.
+   *
+   * @param pageHandler The page handler to register.
+   * @returns A `Disposable` to unregister the page.
+   *
+   * @example
+   * ```ts
+   * // Example usage:
+   * registerPage({route: '/foo', render: (subpage) => m(FooPage, {subpage})})
+   *
+   * class FooPage implements m.ClassComponent<{subpage?: string}> {
+   *   view({attrs}: m.CVnode<{subpage?: string}>) {
+   *      return m('div',
+   *          m('h1', `Foo Page ${attrs.subpage ? `(${attrs.subpage})` : ''}`),
+   *          m('button', {onclick: () => console.log('Button clicked')}, 'Click me')
+   *      );
    *   }
+   * }
+   * ```
    */
   registerPage(pageHandler: PageHandler): Disposable;
 }
 
+/**
+ * Defines a handler for a custom page.
+ *
+ * A page handler specifies the route it responds to and provides a render
+ * function to display its content.
+ */
 export interface PageHandler {
   /**
    * The route path this page handler responds to (e.g., '/', '/viewer').
@@ -50,7 +72,8 @@
    * Renders the page content.
    * Called during each Mithril render cycle.
    *
-   * @param subpage Optional subpage path segment after the main route
+   * @param subpage Optional subpage path segment after the main route.
+   * @returns The Mithril children to render for the page.
    */
-  readonly render: (subpage: string | undefined) => m.Children;
+  render(subpage: string | undefined): m.Children;
 }
diff --git a/ui/src/public/plugin.ts b/ui/src/public/plugin.ts
index 0891433..a693431 100644
--- a/ui/src/public/plugin.ts
+++ b/ui/src/public/plugin.ts
@@ -24,13 +24,65 @@
  *
  * On trace load, the core will create a new class instance by calling new on
  * this constructor and then call its onTraceLoad() function.
+ * @template T The type of the plugin instance.
  */
 export interface PerfettoPluginStatic<T extends PerfettoPlugin> {
+  /**
+   * A unique identifier for the plugin.
+   *
+   * This ID is used to identify the plugin within the Perfetto UI. It is
+   * recommended to use a reverse domain name style (e.g.,
+   * `dev.perfetto.MyPlugin`).
+   */
   readonly id: string;
+
+  /**
+   * An optional human-readable description of the plugin.
+   *
+   * This description may be displayed in the UI to provide more information
+   * about the plugin's functionality.
+   */
   readonly description?: string;
+
+  /**
+   * An optional list of other plugins that this plugin depends on.
+   *
+   * The Perfetto UI ensures that all dependencies are loaded before activating
+   * this plugin.
+   */
   readonly dependencies?: ReadonlyArray<PerfettoPluginStatic<PerfettoPlugin>>;
+
+  /**
+   * Called when the plugin is activated, before a trace has been loaded.
+   *
+   * This method is suitable for registering commands, sidebar items, or pages
+   * that are not dependent on a loaded trace.
+   *
+   * @param app The {@link App} instance, providing access to app-wide
+   *   functionality.
+   * @param args The initial route arguments when the app was loaded.
+   */
   onActivate?(app: App, args: RouteArgs): void;
+
+  /**
+   * Returns a list of metric visualisations provided by this plugin.
+   *
+   * Metric visualisations are used to display data from trace metrics in a
+   * graphical format.
+   *
+   * @returns An array of {@link MetricVisualisation} objects.
+   */
   metricVisualisations?(): MetricVisualisation[];
+
+  /**
+   * The constructor for the plugin's trace-scoped instance.
+   *
+   * This constructor is called when a trace is loaded, creating a new instance
+   * of the plugin that is scoped to the loaded trace.
+   *
+   * @param trace The {@link Trace} instance, providing access to trace-scoped
+   *   functionality.
+   */
   new (trace: Trace): T;
 }
 
@@ -39,43 +91,98 @@
  * is created from the class constructor above at trace load time.
  */
 export interface PerfettoPlugin {
+  /**
+   * Called when a trace is loaded.
+   *
+   * This method is suitable for performing trace-specific initialization, such
+   * as querying trace data or registering trace-scoped UI elements.
+   *
+   * @param ctx The {@link Trace} instance, providing access to trace-scoped
+   *   functionality.
+   * @param args Optional arguments passed to the plugin during trace loading.
+   */
   onTraceLoad?(ctx: Trace, args?: {[key: string]: unknown}): Promise<void>;
 }
 
+/**
+ * Represents a metric visualisation provided by a plugin.
+ *
+ * Metric visualisations are used to display data from trace metrics in a
+ * graphical format, typically using Vega or Vega-Lite specifications.
+ */
 export interface MetricVisualisation {
-  // The name of the metric e.g. 'android_camera'
+  /**
+   * The name of the metric (e.g., 'android_camera').
+   *
+   * This name corresponds to a metric generated by the trace processor.
+   */
   metric: string;
 
-  // A vega or vega-lite visualisation spec.
-  // The data from the metric under path will be exposed as a
-  // datasource named "metric" in Vega(-Lite)
+  /**
+   * A Vega or Vega-Lite visualisation specification.
+   *
+   * The data from the metric, extracted by the `path` property, will be
+   * exposed as a datasource named "metric" within the Vega(-Lite) spec.
+   */
   spec: string;
 
-  // A path index into the metric.
-  // For example if the metric returns the folowing protobuf:
-  // {
-  //   foo {
-  //     bar {
-  //       baz: { name: "a" }
-  //       baz: { name: "b" }
-  //       baz: { name: "c" }
-  //     }
-  //   }
-  // }
-  // That becomes the following json:
-  // { "foo": { "bar": { "baz": [
-  //  {"name": "a"},
-  //  {"name": "b"},
-  //  {"name": "c"},
-  // ]}}}
-  // And given path = ["foo", "bar", "baz"]
-  // We extract:
-  // [ {"name": "a"}, {"name": "b"}, {"name": "c"} ]
-  // And pass that to the vega(-lite) visualisation.
+  /**
+   * A path index into the metric data.
+   *
+   * This path specifies how to extract the relevant data from the metric's
+   * protobuf output. For example, if the metric returns:
+   * ```protobuf
+   * {
+   *   foo {
+   *     bar {
+   *       baz: { name: "a" }
+   *       baz: { name: "b" }
+   *       baz: { name: "c" }
+   *     }
+   *   }
+   * }
+   * ```
+   * This becomes the following JSON:
+   * ```json
+   * { "foo": { "bar": { "baz": [
+   *  {"name": "a"},
+   *  {"name": "b"},
+   *  {"name": "c"},
+   * ]}}}
+   * ```
+   * And given `path = ["foo", "bar", "baz"]`, the extracted data
+   * `[ {"name": "a"}, {"name": "b"}, {"name": "c"} ]` is passed to the
+   * Vega(-Lite) visualisation.
+   */
   path: string[];
 }
 
+/**
+ * Manages registered plugins and their instances.
+ *
+ * Use this to get instances of other plugins (for inter-plugin
+ * communication) or access metric visualizations. Note that plugin instances
+ * are only available after a trace has been loaded.
+ */
 export interface PluginManager {
+  /**
+   * Retrieves an instance of a registered plugin.
+   *
+   * This method can only be called after a trace has been loaded, as plugin
+   * instances are trace-scoped.
+   *
+   * @param plugin The static plugin class (constructor) of the plugin to
+   *   retrieve.
+   * @returns The trace-scoped instance of the requested plugin.
+   * @template T The type of the plugin instance.
+   */
   getPlugin<T extends PerfettoPlugin>(plugin: PerfettoPluginStatic<T>): T;
+
+  /**
+   * Returns a list of all metric visualisations provided by all registered
+   * plugins.
+   *
+   * @returns An array of {@link MetricVisualisation} objects.
+   */
   metricVisualisations(): MetricVisualisation[];
 }
diff --git a/ui/src/public/search.ts b/ui/src/public/search.ts
index f0db9a6..839572c 100644
--- a/ui/src/public/search.ts
+++ b/ui/src/public/search.ts
@@ -15,17 +15,41 @@
 import {time} from '../base/time';
 import {Track} from './track';
 
+/**
+ * Defines the possible sources for a search result.
+ */
 export type SearchSource = 'cpu' | 'log' | 'slice' | 'track' | 'event';
 
+/**
+ * Represents a single search result.
+ */
 export interface SearchResult {
-  eventId: number;
-  ts: time;
-  trackUri: string;
-  source: SearchSource;
+  /**
+   * The ID of the event found.
+   */
+  readonly eventId: number;
+  /**
+   * The timestamp of the event.
+   */
+  readonly ts: time;
+  /**
+   * The URI of the track where the event is located.
+   */
+  readonly trackUri: string;
+  /**
+   * The source of the search result.
+   */
+  readonly source: SearchSource;
 }
 
+/**
+ * A callback function type for handling search result steps.
+ */
 export type ResultStepEventHandler = (r: SearchResult) => void;
 
+/**
+ * Represents a filter expression used for searching.
+ */
 export interface FilterExpression {
   /**
    * A SQL WHERE clause that filters the events in the selected tracks. The
@@ -40,6 +64,9 @@
   readonly join?: string;
 }
 
+/**
+ * Defines a provider for search functionality.
+ */
 export interface SearchProvider {
   /**
    * A human-readable name for this search provider. This is not currently used
@@ -49,7 +76,7 @@
 
   /**
    * Returns a set of tracks that this provider is interested in.
-   * @param tracks - A list of all tracks we want to search inside.
+   * @param tracks A list of all tracks we want to search inside.
    * @returns A subset of tracks that this provider is interested in.
    */
   selectTracks(tracks: ReadonlyArray<Track>): ReadonlyArray<Track>;
@@ -61,13 +88,20 @@
    * This function is async because it may need to query some data using the
    * search term before it can return a filter expression.
    *
-   * @param searchTerm - The raw search term entered by the user.
+   * @param searchTerm The raw search term entered by the user.
    * @returns A promise that resolves to a FilterExpression that is compiled into
-   * the resulting SQL query. If undefined, this provider will not be used.
+   *   the resulting SQL query. If `undefined`, this provider will not be used.
    */
   getSearchFilter(searchTerm: string): Promise<FilterExpression | undefined>;
 }
 
+/**
+ * Manages the registration of search providers.
+ */
 export interface SearchManager {
+  /**
+   * Registers a new search provider.
+   * @param provider The search provider to register.
+   */
   registerSearchProvider(provider: SearchProvider): void;
 }
diff --git a/ui/src/public/selection.ts b/ui/src/public/selection.ts
index cd0bdef..f468894 100644
--- a/ui/src/public/selection.ts
+++ b/ui/src/public/selection.ts
@@ -17,51 +17,223 @@
 import {duration, time, TimeSpan} from '../base/time';
 import {Track} from './track';
 
+/**
+ * Represents content that may be in a loading state.
+ */
 export interface ContentWithLoadingFlag {
+  /**
+   * Indicates whether the content is currently loading.
+   */
   readonly isLoading: boolean;
+  /**
+   * The actual content to be displayed.
+   */
   readonly content: m.Children;
 }
 
+/**
+ * Defines a tab within the area selection details panel.
+ */
 export interface AreaSelectionTab {
-  // Unique id for this tab.
+  /**
+   * Unique ID for this tab.
+   */
   readonly id: string;
 
-  // A name for this tab.
+  /**
+   * A human-readable name for this tab.
+   */
   readonly name: string;
 
-  // Defines the sort order of this tab - higher values appear first.
+  /**
+   * Defines the sort order of this tab - higher values appear first.
+   */
   readonly priority?: number;
 
   /**
    * Called every Mithril render cycle to render the content of the tab. The
    * returned content will be displayed inside the current selection tab.
    *
-   * If undefined is returned then the tab handle will be hidden, which gives
+   * If `undefined` is returned then the tab handle will be hidden, which gives
    * the tab the option to dynamically remove itself from the list of tabs if it
    * has nothing relevant to show.
    *
-   * The |isLoading| flag is used to avoid flickering. If set to true, we keep
-   * hold of the the previous vnodes, rendering them instead, for up to 50ms
+   * The `isLoading` flag is used to avoid flickering. If set to `true`, we keep
+   * hold of the previous vnodes, rendering them instead, for up to 50ms
    * before switching to the new content. This avoids very fast load times
    * from causing flickering loading screens, which can be somewhat jarring.
+   * @param selection The current area selection.
+   * @returns The content to render, or `undefined` if the tab should be hidden.
    */
   render(selection: AreaSelection): ContentWithLoadingFlag | undefined;
 }
 
 /**
- * Compare two area selections for equality. Returns true if the selections are
- * equivalent, false otherwise.
+ * Represents the different types of selections that can be made in the UI.
  */
-export function areaSelectionsEqual(a: AreaSelection, b: AreaSelection) {
-  if (a.start !== b.start) return false;
-  if (a.end !== b.end) return false;
-  if (!arrayEquals(a.trackUris, b.trackUris)) {
-    return false;
-  }
-  return true;
+export type Selection =
+  | TrackEventSelection
+  | TrackSelection
+  | AreaSelection
+  | NoteSelection
+  | EmptySelection;
+
+/**
+ * Defines how changes to selection affect the rest of the UI state.
+ */
+export interface SelectionOpts {
+  /**
+   * If `true`, clears the search input. Defaults to `true`.
+   */
+  readonly clearSearch?: boolean;
+  /**
+   * If `true`, switches to the tab relevant to the current selection. Defaults
+   * to `true`.
+   */
+  readonly switchToCurrentSelectionTab?: boolean;
+  /**
+   * If `true`, scrolls the timeline to reveal the selection. Defaults to `false`.
+   */
+  readonly scrollToSelection?: boolean;
 }
 
+/**
+ * Represents a selection of a specific track event.
+ */
+export interface TrackEventSelection extends TrackEventDetails {
+  /**
+   * The kind of selection, always 'track_event'.
+   */
+  readonly kind: 'track_event';
+  /**
+   * The URI of the track where the event is located.
+   */
+  readonly trackUri: string;
+  /**
+   * The ID of the selected event.
+   */
+  readonly eventId: number;
+}
+
+/**
+ * Represents a selection of an entire track.
+ */
+export interface TrackSelection {
+  /**
+   * The kind of selection, always 'track'.
+   */
+  readonly kind: 'track';
+  /**
+   * The URI of the selected track.
+   */
+  readonly trackUri: string;
+}
+
+/**
+ * Details about a track event.
+ */
+export interface TrackEventDetails {
+  /**
+   * The timestamp of the event. Required by the core.
+   */
+  readonly ts: time;
+
+  /**
+   * The duration of the event. Can be 0 for instant events or -1 for DNF
+   * slices. Will be `undefined` if this selection has no duration (e.g.,
+   * profile/counter samples).
+   */
+  readonly dur?: duration;
+}
+
+/**
+ * Defines an area on the timeline.
+ */
+export interface Area {
+  /**
+   * The start timestamp of the area.
+   */
+  readonly start: time;
+  /**
+   * The end timestamp of the area.
+   */
+  readonly end: time;
+  /**
+   * An array of URIs of the tracks included in the area.
+   */
+  readonly trackUris: ReadonlyArray<string>;
+}
+
+/**
+ * Represents a selection of an area on the timeline.
+ */
+export interface AreaSelection extends Area {
+  /**
+   * The kind of selection, always 'area'.
+   */
+  readonly kind: 'area';
+
+  /**
+   * This array contains the resolved Tracks from `Area.trackUris`. The
+   * resolution is done by `SelectionManager` whenever a `kind='area'` selection
+   * is performed.
+   */
+  readonly tracks: ReadonlyArray<Track>;
+}
+
+/**
+ * Represents a selection of a note.
+ */
+export interface NoteSelection {
+  /**
+   * The kind of selection, always 'note'.
+   */
+  readonly kind: 'note';
+  /**
+   * The ID of the selected note.
+   */
+  readonly id: string;
+}
+
+/**
+ * Represents an empty selection.
+ */
+export interface EmptySelection {
+  /**
+   * The kind of selection, always 'empty'.
+   */
+  readonly kind: 'empty';
+}
+
+/**
+ * Resolves SQL events to track events.
+ */
+export interface SqlSelectionResolver {
+  /**
+   * The name of the SQL table to resolve.
+   */
+  readonly sqlTableName: string;
+  /**
+   * A callback function that resolves an event ID from a SQL table to a track
+   * URI and event ID.
+   * @param id The ID of the event in the SQL table.
+   * @param sqlTable The name of the SQL table.
+   * @returns A promise that resolves to an object containing `trackUri` and
+   *   `eventId`, or `undefined` if not found.
+   */
+  callback(
+    id: number,
+    sqlTable: string,
+  ): Promise<{readonly trackUri: string; readonly eventId: number} | undefined>;
+}
+
+/**
+ * Manages the current selection state in the UI.
+ */
 export interface SelectionManager {
+  /**
+   * The current selection.
+   */
   readonly selection: Selection;
 
   /**
@@ -70,16 +242,16 @@
   readonly areaSelectionTabs: ReadonlyArray<AreaSelectionTab>;
 
   /**
-   * Clears the current selection, selects nothing.
+   * Clears the current selection, selecting nothing.
    */
   clearSelection(): void;
 
   /**
-   * Select a track event.
+   * Selects a track event.
    *
-   * @param trackUri - The URI of the track to select.
-   * @param eventId - The value of the events ID column.
-   * @param opts - Additional options.
+   * @param trackUri The URI of the track to select.
+   * @param eventId The value of the event's ID column.
+   * @param opts Additional options for the selection.
    */
   selectTrackEvent(
     trackUri: string,
@@ -88,43 +260,47 @@
   ): void;
 
   /**
-   * Select a track.
+   * Selects a track.
    *
-   * @param trackUri - The URI for the track to select.
-   * @param opts - Additional options.
+   * @param trackUri The URI for the track to select.
+   * @param opts Additional options for the selection.
    */
   selectTrack(trackUri: string, opts?: SelectionOpts): void;
 
   /**
-   * Resolves events via a sql table name + ids.
+   * Resolves events via a SQL table name and IDs.
    *
-   * @param sqlTableName - The name of the SQL table to resolve.
-   * @param ids - The IDs of the events in that table.
+   * @param sqlTableName The name of the SQL table to resolve.
+   * @param ids The IDs of the events in that table.
+   * @returns A promise that resolves to an array of objects containing eventId
+   *   and trackUri.
    */
   resolveSqlEvents(
     sqlTableName: string,
     ids: ReadonlyArray<number>,
-  ): Promise<ReadonlyArray<{eventId: number; trackUri: string}>>;
+  ): Promise<
+    ReadonlyArray<{readonly eventId: number; readonly trackUri: string}>
+  >;
 
   /**
-   * Select a track event via a sql table name + id.
+   * Selects a track event via a SQL table name and ID.
    *
-   * @param sqlTableName - The name of the SQL table to resolve.
-   * @param id - The ID of the event in that table.
-   * @param opts - Additional options.
+   * @param sqlTableName The name of the SQL table to resolve.
+   * @param id The ID of the event in that table.
+   * @param opts Additional options for the selection.
    */
   selectSqlEvent(sqlTableName: string, id: number, opts?: SelectionOpts): void;
 
   /**
-   * Create an area selection for the purposes of aggregation.
+   * Creates an area selection for the purposes of aggregation.
    *
-   * @param args - The area to select.
-   * @param opts - Additional options.
+   * @param args The area to select.
+   * @param opts Additional options for the selection.
    */
   selectArea(args: Area, opts?: SelectionOpts): void;
 
   /**
-   * Scroll the timeline horizontally and vertically to reveal the currently
+   * Scrolls the timeline horizontally and vertically to reveal the currently
    * selected entity.
    */
   scrollToSelection(): void;
@@ -133,78 +309,32 @@
    * Returns the smallest time span that contains the currently selected entity.
    *
    * @returns The time span, if a timeline entity is selected, otherwise
-   * undefined.
+   *   `undefined`.
    */
   getTimeSpanOfSelection(): TimeSpan | undefined;
 
   /**
-   * Register a new tab under the area selection details panel.
+   * Registers a new tab under the area selection details panel.
+   * @param tab The area selection tab to register.
    */
   registerAreaSelectionTab(tab: AreaSelectionTab): void;
 }
 
-export type Selection =
-  | TrackEventSelection
-  | TrackSelection
-  | AreaSelection
-  | NoteSelection
-  | EmptySelection;
-
-/** Defines how changes to selection affect the rest of the UI state */
-export interface SelectionOpts {
-  clearSearch?: boolean; // Default: true.
-  switchToCurrentSelectionTab?: boolean; // Default: true.
-  scrollToSelection?: boolean; // Default: false.
-}
-
-export interface TrackEventSelection extends TrackEventDetails {
-  readonly kind: 'track_event';
-  readonly trackUri: string;
-  readonly eventId: number;
-}
-
-export interface TrackSelection {
-  readonly kind: 'track';
-  readonly trackUri: string;
-}
-
-export interface TrackEventDetails {
-  // ts and dur are required by the core, and must be provided.
-  readonly ts: time;
-
-  // Note: dur can be 0 for instant events or -1 for DNF slices. Will be
-  // undefined if this selection has no duration, i.e. profile / counter
-  // samples.
-  readonly dur?: duration;
-}
-
-export interface Area {
-  readonly start: time;
-  readonly end: time;
-  readonly trackUris: ReadonlyArray<string>;
-}
-
-export interface AreaSelection extends Area {
-  readonly kind: 'area';
-
-  // This array contains the resolved Tracks from Area.trackUris. The resolution
-  // is done by SelectionManager whenever a kind='area' selection is performed.
-  readonly tracks: ReadonlyArray<Track>;
-}
-
-export interface NoteSelection {
-  readonly kind: 'note';
-  readonly id: string;
-}
-
-export interface EmptySelection {
-  readonly kind: 'empty';
-}
-
-export interface SqlSelectionResolver {
-  readonly sqlTableName: string;
-  readonly callback: (
-    id: number,
-    sqlTable: string,
-  ) => Promise<{trackUri: string; eventId: number} | undefined>;
+/**
+ * Compare two area selections for equality. Returns true if the selections are
+ * equivalent, false otherwise.
+ * @param a The first area selection.
+ * @param b The second area selection.
+ * @returns `true` if the selections are equal, `false` otherwise.
+ */
+export function areaSelectionsEqual(
+  a: AreaSelection,
+  b: AreaSelection,
+): boolean {
+  if (a.start !== b.start) return false;
+  if (a.end !== b.end) return false;
+  if (!arrayEquals(a.trackUris, b.trackUris)) {
+    return false;
+  }
+  return true;
 }
diff --git a/ui/src/public/settings.ts b/ui/src/public/settings.ts
index 592e0a9..5ceb8bb 100644
--- a/ui/src/public/settings.ts
+++ b/ui/src/public/settings.ts
@@ -21,6 +21,12 @@
 import {z} from 'zod';
 import m from 'mithril';
 
+/**
+ * A function type for rendering a custom UI for a setting.
+ * @template T The type of the setting's value.
+ * @param setting The setting instance to render.
+ * @returns Mithril children to be rendered.
+ */
 export type SettingRenderer<T> = (setting: Setting<T>) => m.Children;
 
 /**
@@ -29,24 +35,44 @@
  * @template T The type of the setting's value.
  */
 export interface SettingDescriptor<T> {
-  // A unique identifier for the setting. Used as the storage key.
+  /**
+   * A unique identifier for the setting. Used as the storage key.
+   */
   readonly id: string;
-  // A human-readable name for the setting, used on the settings page.
+
+  /**
+   * A human-readable name for the setting, used on the settings page.
+   */
   readonly name: string;
-  // A detailed description of what the setting does, used on the settings page.
+
+  /**
+   * A detailed description of what the setting does, used on the settings page.
+   */
   readonly description: string;
-  // The Zod schema used for validating the setting's value, and defining the
-  // structure and type of this setting.
+
+  /**
+   * The Zod schema used for validating the setting's value, and defining the
+   * structure and type of this setting.
+   */
   readonly schema: z.ZodType<T>;
-  // The default value of the setting if the setting is absent from the
-  // underlying storage.
+
+  /**
+   * The default value of the setting if the setting is absent from the
+   * underlying storage.
+   */
   readonly defaultValue: T;
-  // If true, the user will be prompted to reload the the page when this setting
-  // is changed.
+
+  /**
+   * If true, the user will be prompted to reload the the page when this setting
+   * is changed.
+   */
   readonly requiresReload?: boolean;
-  // An optional render function for customizing the UI of this setting in the
-  // settings page. Required for settings that are move complex than a primitive
-  // type, such as objects or arrays.
+
+  /**
+   * An optional render function for customizing the UI of this setting in the
+   * settings page. Required for settings that are move complex than a primitive
+   * type, such as objects or arrays.
+   */
   readonly render?: SettingRenderer<T>;
 }
 
@@ -57,22 +83,41 @@
  * @template T The type of the setting's value.
  */
 export interface Setting<T> extends SettingDescriptor<T>, Disposable {
-  // Returns true if this settings is currently set to the default value.
+  /**
+   * Returns true if this settings is currently set to the default value.
+   */
   readonly isDefault: boolean;
-  // Get the current value of the setting.
+
+  /**
+   * Get the current value of the setting.
+   * @returns The current value of the setting.
+   */
   get(): T;
-  // Set the value of the setting. This will also update the underlying storage.
+
+  /**
+   * Set the value of the setting. This will also update the underlying storage.
+   * @param value The new value for the setting.
+   */
   set(value: T): void;
-  // Resets back to default.
+
+  /**
+   * Resets back to default.
+   */
   reset(): void;
 }
 
 /**
  * Manages the registration and retrieval of application settings.
+ *
+ * Settings are stored in local storage and can be configured on the settings
+ * page. They support validation via Zod schemas, custom rendering, and can
+ * optionally require an app reload when changed.
  */
 export interface SettingsManager {
   /**
    * Registers a new setting.
+   * @template T The type of the setting's value.
+   * @param setting The descriptor for the setting to register.
    * @returns A handle used to interact with the setting.
    */
   register<T>(setting: SettingDescriptor<T>): Setting<T>;
@@ -82,6 +127,7 @@
   resetAll(): void;
   /**
    * Retrieves a list of all currently registered settings.
+   * @returns A read-only array of all registered settings.
    */
   getAllSettings(): ReadonlyArray<Setting<unknown>>;
   /**
@@ -93,7 +139,9 @@
 
   /**
    * Get the a setting by its ID.
+   * @template T The expected type of the setting's value.
    * @param id The unique identifier of the setting.
+   * @returns The setting instance if found, or `undefined` otherwise.
    */
   get<T>(id: string): Setting<T> | undefined;
 }
diff --git a/ui/src/public/sidebar.ts b/ui/src/public/sidebar.ts
index 980766f..85a5795 100644
--- a/ui/src/public/sidebar.ts
+++ b/ui/src/public/sidebar.ts
@@ -4,7 +4,7 @@
 // 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
+//      http://www.apache.org/licenses/LICENSE-20.0
 //
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -42,43 +42,107 @@
 
 export type SidebarSections = keyof typeof SIDEBAR_SECTIONS;
 
+/**
+ * Manages the sidebar menu items and visibility.
+ *
+ * The sidebar is organized into sections (navigation, settings, support,
+ * etc.). Use this to add menu entries that can navigate to pages, execute
+ * actions, or trigger commands. Menu items are automatically removed when
+ * the trace is closed or the plugin is unloaded.
+ */
 export interface SidebarManager {
+  /**
+   * Whether the sidebar is currently enabled.
+   *
+   * When the sidebar is disabled, menu items cannot be added, and its
+   * visibility cannot be toggled.
+   */
   readonly enabled: boolean;
 
   /**
    * Adds a new menu item to the sidebar.
-   * All entries must map to a command. This will allow the shortcut and
+   *
+   * All entries must map to a command, which allows the shortcut and
    * optional shortcut to be displayed on the UI.
+   *
+   * @param menuItem The menu item to add.
    */
   addMenuItem(menuItem: SidebarMenuItem): void;
 
   /**
    * Gets the current visibility of the sidebar.
+   *
+   * @returns `true` if the sidebar is visible, `false` otherwise.
    */
   get visible(): boolean;
 
   /**
-   * Toggles the visibility of the sidebar. Can only be called when
-   * `sidebarEnabled` returns `ENABLED`.
+   * Toggles the visibility of the sidebar.
+   *
+   * This method can only be called when `enabled` is `true`.
    */
   toggleVisibility(): void;
 }
 
+/**
+ * Represents a single menu item in the sidebar.
+ *
+ * A sidebar menu item can either navigate to a URL, execute an action, or
+ * trigger a command.
+ */
 export type SidebarMenuItem = {
+  /**
+   * The section of the sidebar where this menu item will be placed.
+   *
+   * Must be one of the predefined `SIDEBAR_SECTIONS`.
+   */
   readonly section: SidebarSections;
+
+  /**
+   * An optional number to control the sort order of menu items within a
+   * section.
+   *
+   * Lower numbers appear before higher numbers. If omitted, items are sorted
+   * by their `text` property.
+   */
   readonly sortOrder?: number;
 
-  // The properties below can be mutated by passing a callback rather than a
-  // direct value. The callback is invoked on every render frame, keep it cheap.
-  // readonly text: string | (() => string);
+  /**
+   * An optional icon to display next to the menu item.
+   *
+   * Can be a string (e.g., 'settings') or a function that returns a string.
+   * The function is invoked on every render frame, so keep it cheap.
+   */
   readonly icon?: string | (() => string);
+
+  /**
+   * An optional tooltip to display when hovering over the menu item.
+   *
+   * Can be a string or a function that returns a string. The function is
+   * invoked on every render frame, so keep it cheap.
+   */
   readonly tooltip?: string | (() => string);
+
+  /**
+   * An optional CSS class to apply to the menu item.
+   *
+   * Can be a string (without trailing '.') or a function that returns a string.
+   * The function is invoked on every render frame, so keep it cheap.
+   */
   readonly cssClass?: string | (() => string); // Without trailing '.'.
 
-  // If false or omitted the item works normally.
-  // If true the item is striken through and the action/href will be a no-op.
-  // If a string, the item acts as disabled and clicking on it shows a popup
-  // that shows the returned text (the string has "disabled reason" semantic);
+  /**
+   * Controls the disabled state of the menu item.
+   *
+   * - If `false` or omitted, the item works normally.
+   * - If `true`, the item is struck through, and its action/href will be a
+   *   no-op.
+   * - If a `string`, the item acts as disabled, and clicking on it shows a
+   *   popup with the returned text (which has "disabled reason" semantics).
+   *
+   * Can be a string, boolean, or a function that returns either. The function
+   * is invoked on every render frame, so keep it cheap.
+   */
   readonly disabled?: string | boolean | (() => string | boolean);
 
   // One of the three following arguments must be specified.
@@ -104,7 +168,7 @@
        * a promise, a spinner will be drawn next to the sidebar entry until the
        * promise resolves.
        */
-      readonly action: () => unknown | Promise<unknown>;
+      action(): unknown | Promise<unknown>;
 
       /** Optional. If omitted href = '#'. */
       readonly href?: string;
diff --git a/ui/src/public/statusbar.ts b/ui/src/public/statusbar.ts
index 6854be2..4ad6399 100644
--- a/ui/src/public/statusbar.ts
+++ b/ui/src/public/statusbar.ts
@@ -15,17 +15,42 @@
 import m from 'mithril';
 import {Intent} from '../widgets/common';
 
+/**
+ * Represents an item to be displayed in the status bar.
+ */
 export interface StatusbarItem {
-  readonly renderItem: () => {
+  /**
+   * A function that returns the properties for rendering the status bar item.
+   * @returns An object with label, optional icon, optional intent, and an
+   *   optional click handler.
+   */
+  renderItem(): {
     readonly label: string;
     readonly icon?: string;
     readonly intent?: Intent;
-    readonly onclick?: (event: MouseEvent) => void;
+    onclick?(event: MouseEvent): void;
   };
-  readonly popupContent?: () => m.Children;
+
+  /**
+   * An optional function that returns the content to be displayed in a popup
+   * when the status bar item is clicked.
+   * @returns The Mithril children to render in the popup.
+   */
+  popupContent?(): m.Children;
 }
 
+/**
+ * Manages items in the status bar.
+ */
 export interface StatusbarManager {
+  /**
+   * A read-only array of all currently registered status bar items.
+   */
   readonly statusBarItems: ReadonlyArray<StatusbarItem>;
+
+  /**
+   * Registers a new item to be displayed in the status bar.
+   * @param item The status bar item to register.
+   */
   registerItem(item: StatusbarItem): void;
 }
diff --git a/ui/src/public/tab.ts b/ui/src/public/tab.ts
index 97d7065..6032404 100644
--- a/ui/src/public/tab.ts
+++ b/ui/src/public/tab.ts
@@ -14,22 +14,86 @@
 
 import m from 'mithril';
 
+/**
+ * Manages the registration, display, and hiding of tabs within the UI.
+ *
+ * Tabs provide a way to organize different views or functionalities within
+ * the application, allowing users to switch between them easily.
+ */
 export interface TabManager {
+  /**
+   * Registers a new tab with the TabManager.
+   *
+   * @param tab The descriptor for the tab to register.
+   */
   registerTab(tab: TabDescriptor): void;
+
+  /**
+   * Displays the tab associated with the given URI.
+   *
+   * If the tab is not currently visible, it will be brought to the foreground.
+   * @param uri The unique URI of the tab to show.
+   */
   showTab(uri: string): void;
+
+  /**
+   * Hides the tab associated with the given URI.
+   *
+   * @param uri The unique URI of the tab to hide.
+   */
   hideTab(uri: string): void;
+
+  /**
+   * Adds a tab to the list of default tabs.
+   *
+   * Default tabs are automatically opened when the application starts or
+   * when a new trace is loaded.
+   * @param uri The unique URI of the tab to add as a default.
+   */
   addDefaultTab(uri: string): void;
 }
 
+/**
+ * Represents the content and title of a tab.
+ */
 export interface Tab {
+  /**
+   * Renders the content of the tab.
+   * @returns The Mithril children to render for the tab's content.
+   */
   render(): m.Children;
+
+  /**
+   * Gets the title of the tab.
+   * @returns The human-readable title of the tab.
+   */
   getTitle(): string;
 }
 
+/**
+ * Describes a tab to be registered with the TabManager.
+ */
 export interface TabDescriptor {
-  uri: string; // TODO(stevegolton): Maybe optional for ephemeral tabs.
-  content: Tab;
-  isEphemeral?: boolean; // Defaults false
+  /**
+   * The unique URI for this tab.
+   * TODO(stevegolton): Maybe optional for ephemeral tabs.
+   */
+  readonly uri: string;
+  /**
+   * The content of the tab, including its render function and title.
+   */
+  readonly content: Tab;
+  /**
+   * If true, this tab is ephemeral and may be closed automatically under
+   * certain conditions (e.g., when a trace is closed). Defaults to `false`.
+   */
+  readonly isEphemeral?: boolean;
+  /**
+   * An optional callback function that is invoked when the tab is hidden.
+   */
   onHide?(): void;
+  /**
+   * An optional callback function that is invoked when the tab is shown.
+   */
   onShow?(): void;
 }
diff --git a/ui/src/public/timeline.ts b/ui/src/public/timeline.ts
index 58507f9..dc45eb7 100644
--- a/ui/src/public/timeline.ts
+++ b/ui/src/public/timeline.ts
@@ -16,56 +16,130 @@
 import {time} from '../base/time';
 import {Setting} from './settings';
 
+/**
+ * Defines the various formats for displaying timestamps in the UI.
+ */
 export enum TimestampFormat {
+  /** Displays time as a timecode (e.g., HH:MM:SS.mmm). */
   Timecode = 'timecode',
+  /** Displays raw trace nanoseconds. */
   TraceNs = 'traceNs',
+  /** Displays raw trace nanoseconds, formatted according to locale. */
   TraceNsLocale = 'traceNsLocale',
+  /** Displays time in seconds. */
   Seconds = 'seconds',
+  /** Displays time in milliseconds. */
   Milliseconds = 'milliseconds',
+  /** Displays time in microseconds. */
   Microseconds = 'microseconds',
+  /** Displays time in UTC format. */
   UTC = 'utc',
+  /** Displays time in a custom timezone. */
   CustomTimezone = 'customTimezone',
+  /** Displays time in the trace's timezone. */
   TraceTz = 'traceTz',
 }
 
+/**
+ * Defines the precision for displaying durations in the UI.
+ */
 export enum DurationPrecision {
+  /** Displays full precision for durations. */
   Full = 'full',
+  /** Displays human-readable durations (e.g., 1h 2m 3s). */
   HumanReadable = 'human_readable',
 }
 
+/**
+ * Manages the interactive timeline and viewport.
+ *
+ * The timeline allows users to navigate through the trace, zoom in and out,
+ * and interact with various time-based elements.
+ */
 export interface Timeline {
-  // Bring a timestamp into view.
+  /**
+   * Brings a specific timestamp into the current viewport.
+   * @param ts The timestamp to pan to.
+   */
   panToTimestamp(ts: time): void;
 
-  // Move the viewport.
+  /**
+   * Sets the start and end times of the current viewport.
+   * @param start The start timestamp of the viewport.
+   * @param end The end timestamp of the viewport.
+   */
   setViewportTime(start: time, end: time): void;
 
-  // A span representing the current viewport location.
+  /**
+   * A span representing the current visible time range in the viewport.
+   */
   readonly visibleWindow: HighPrecisionTimeSpan;
 
-  // Render a vertical line on the timeline at this timestamp.
+  /**
+   * The timestamp where the hover cursor is currently located on the timeline.
+   * Setting this value will render a vertical line at the specified timestamp.
+   */
   hoverCursorTimestamp: time | undefined;
 
+  /**
+   * The timestamp of a hovered note on the timeline.
+   * Setting this value will highlight the corresponding note.
+   */
   hoveredNoteTimestamp: time | undefined;
+
+  /**
+   * The ID of the currently highlighted slice.
+   * Setting this value will highlight the corresponding slice in the UI.
+   */
   highlightedSliceId: number | undefined;
 
+  /**
+   * The UTID (Unique Thread ID) of the currently hovered thread.
+   */
   hoveredUtid: number | undefined;
+
+  /**
+   * The PID (Process ID) of the currently hovered process.
+   */
   hoveredPid: bigint | undefined;
 
-  // This value defines the time of the origin of the time axis in trace time.
-  // Depending on the timestamp format setting, this value can change:
-  // E.g.
-  // - Raw - origin = 0
-  // - Seconds - origin = trace.start.
-  // - Realtime - origin = midnight before trace.start.
+  /**
+   * Gets the time of the origin of the time axis in trace time.
+   *
+   * Depending on the timestamp format setting, this value can change:
+   * - Raw: origin = 0
+   * - Seconds: origin = trace.start
+   * - Realtime: origin = midnight before trace.start
+   *
+   * @returns The origin timestamp of the time axis.
+   */
   getTimeAxisOrigin(): time;
 
-  // Get a time in the current domain as specified by timestampOffset.
+  /**
+   * Converts a timestamp to a time in the current domain, as specified by
+   * the timestamp offset.
+   * @param ts The timestamp to convert.
+   * @returns The timestamp in the current domain.
+   */
   toDomainTime(ts: time): time;
 
-  // These control how timestamps and durations are formatted throughout the UI
+  /**
+   * Controls how timestamps are formatted throughout the UI.
+   */
   timestampFormat: TimestampFormat;
+
+  /**
+   * Controls how durations are formatted throughout the UI.
+   */
   durationPrecision: DurationPrecision;
+
+  /**
+   * The custom timezone offset in minutes.
+   */
   customTimezoneOffset: number;
+
+  /**
+   * The setting for overriding the default timezone.
+   */
   timezoneOverride: Setting<string>;
 }
diff --git a/ui/src/public/trace.ts b/ui/src/public/trace.ts
index 80865f2..58a5039 100644
--- a/ui/src/public/trace.ts
+++ b/ui/src/public/trace.ts
@@ -32,7 +32,7 @@
 // Lists all the possible event listeners using the key as the event name and
 // the type as the type of the callback.
 export interface EventListeners {
-  traceready: () => Promise<void> | void;
+  traceready(): Promise<void> | void;
 }
 
 /**
diff --git a/ui/src/public/trace_info.ts b/ui/src/public/trace_info.ts
index 4e20a90..71b7c38 100644
--- a/ui/src/public/trace_info.ts
+++ b/ui/src/public/trace_info.ts
@@ -4,7 +4,7 @@
 // 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
+//      http://www.apache.org/licenses/LICENSE-20.0
 //
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,46 +14,80 @@
 
 import {time} from '../base/time';
 
+/**
+ * Provides information about the currently loaded trace.
+ */
 export interface TraceInfo {
-  readonly traceTitle: string; // File name and size of the current trace.
-  readonly traceUrl: string; // URL of the Trace.
+  /**
+   * The title of the trace, typically the file name and size.
+   */
+  readonly traceTitle: string;
+  /**
+   * The URL from which the trace was loaded, if applicable.
+   */
+  readonly traceUrl: string;
 
+  /**
+   * The start timestamp of the trace.
+   */
   readonly start: time;
+  /**
+   * The end timestamp of the trace.
+   */
   readonly end: time;
 
-  // This is the ts value at the time of the Unix epoch.
-  // Normally some large negative value, because the unix epoch is normally in
-  // the past compared to ts=0.
+  /**
+   * The timestamp value at the time of the Unix epoch.
+   *
+   * Normally some large negative value, because the Unix epoch is typically in
+   * the past compared to `ts=0`.
+   */
   readonly unixOffset: time;
 
-  // Represents the reported timezone in minutes from UTC.
+  /**
+   * The reported timezone offset in minutes from UTC.
+   */
   readonly tzOffMin: number;
 
-  // The number of import/analysis errors present in the `stats` table.
+  /**
+   * The number of import/analysis errors present in the `stats` table.
+   */
   readonly importErrors: number;
 
-  // The trace type inferred by TraceProcessor (e.g. 'proto', 'json, ...).
-  // See TraceTypeToString() in src/trace_processor/util/trace_type.cc for
-  // all the available types.
+  /**
+   * The trace type inferred by TraceProcessor (e.g., 'proto', 'json', ...).
+   *
+   * See `TraceTypeToString()` in `src/trace_processor/util/trace_type.cc` for
+   * all available types.
+   */
   readonly traceType?: string;
 
-  // True if the trace contains any ftrace data (sched or other ftrace events).
+  /**
+   * `true` if the trace contains any ftrace data (sched or other ftrace events).
+   */
   readonly hasFtrace: boolean;
 
-  // The UUID of the trace. This is generated by TraceProcessor by either
-  // looking at the TraceUuid packet emitted by traced or, as a fallback, by
-  // hashing the first KB of the trace. This can be an empty string in rare
-  // cases (e.g., opening an empty trace).
+  /**
+   * The UUID of the trace. This is generated by TraceProcessor by either
+   * looking at the `TraceUuid` packet emitted by `traced` or, as a fallback, by
+   * hashing the first KB of the trace. This can be an empty string in rare
+   * cases (e.g., opening an empty trace).
+   */
   readonly uuid: string;
 
-  // Wheteher the current trace has been successfully stored into cache storage.
+  /**
+   * Whether the current trace has been successfully stored into cache storage.
+   */
   readonly cached: boolean;
 
-  // Returns true if the current trace can be downloaded via getTraceFile().
-  // The trace isn't downloadable in the following cases:
-  // - It comes from a source (e.g. HTTP+RPC) that doesn't support re-download
-  //   due to technical limitations.
-  // - Download is disabled because the trace was pushed via postMessage and
-  //   the caller has asked to disable downloads.
+  /**
+   * Returns `true` if the current trace can be downloaded via `getTraceFile()`.
+   *
+   * The trace isn't downloadable in the following cases:
+   * - It comes from a source (e.g., HTTP+RPC) that doesn't support re-download
+   *   due to technical limitations.
+   * - Download is disabled because the trace was pushed via `postMessage` and
+   *   the caller has asked to disable downloads.
+   */
   readonly downloadable: boolean;
 }
diff --git a/ui/src/public/track.ts b/ui/src/public/track.ts
index 58ac999..c15416f 100644
--- a/ui/src/public/track.ts
+++ b/ui/src/public/track.ts
@@ -25,52 +25,99 @@
 import {CanvasColors} from './canvas_colors';
 import {z} from 'zod';
 
+/**
+ * Defines criteria for filtering tracks.
+ */
 export interface TrackFilterCriteria {
+  /**
+   * The human-readable name of the filter criteria.
+   */
   readonly name: string;
 
-  // Run on each node to work out whether it satisfies the selected filter
-  // option.
+  /**
+   * A predicate function that determines whether a track satisfies the selected
+   * filter option.
+   * @param track The track node to evaluate.
+   * @param filterOption The selected filter option string.
+   * @returns `true` if the track satisfies the filter, `false` otherwise.
+   */
   readonly predicate: (track: TrackNode, filterOption: string) => boolean;
 
-  // The list of possible filter options.
-  readonly options: ReadonlyArray<{key: string; label: string}>;
+  /**
+   * The list of possible filter options for this criteria.
+   */
+  readonly options: ReadonlyArray<{
+    readonly key: string;
+    readonly label: string;
+  }>;
 }
 
+/**
+ * Manages the registration and discovery of tracks.
+ */
 export interface TrackManager {
   /**
-   * Register a new track against a unique key known as a URI. The track is not
-   * shown by default and callers need to either manually add it to a
-   * Workspace or use registerTrackAndShowOnTraceLoad() below.
+   * Registers a new track with a unique URI.
+   *
+   * The track is not shown by default; callers need to either manually add it
+   * to a Workspace or use `registerTrackAndShowOnTraceLoad()` (if available)
+   * to display it.
+   * @param track The track to register.
    */
   registerTrack(track: Track): void;
 
+  /**
+   * Finds a track that satisfies the given predicate.
+   * @param predicate A function that returns `true` for the desired track.
+   * @returns The first track that satisfies the predicate, or `undefined` if
+   *   none is found.
+   */
   findTrack(
     predicate: (track: Track) => boolean | undefined,
   ): Track | undefined;
 
+  /**
+   * Retrieves all currently registered tracks.
+   * @returns An array of all registered tracks.
+   */
   getAllTracks(): Track[];
 
+  /**
+   * Retrieves a track by its unique URI.
+   * @param uri The unique URI of the track.
+   * @returns The track if found, or `undefined`.
+   */
   getTrack(uri: string): Track | undefined;
 
   /**
-   * Register a track filter criteria, which can be used by end users to control
-   * the list of tracks they see in workspaces. These criteria can provide more
-   * power to the user compared to e.g. purely filtering by name.
+   * Registers a track filter criteria, which can be used by end users to
+   * control the list of tracks they see in workspaces.
+   *
+   * These criteria can provide more power to the user compared to purely
+   * filtering by name.
+   * @param filter The track filter criteria to register.
    */
   registerTrackFilterCriteria(filter: TrackFilterCriteria): void;
 
   /**
-   * Register a timeline overlay renderer.
+   * Registers a timeline overlay renderer.
    *
    * Overlays are rendered on top of all tracks in the timeline view and can be
    * used to draw annotations that span multiple tracks, such as flow arrows or
    * vertical lines marking specific events.
+   * @param overlay The overlay to register.
    */
   registerOverlay(overlay: Overlay): void;
 }
 
+/**
+ * Contextual information about the track passed to track lifecycle hooks &
+ * render hooks.
+ */
 export interface TrackContext {
-  // This track's URI, used for making selections et al.
+  /**
+   * This track's URI, used for making selections and other operations.
+   */
   readonly trackUri: string;
 }
 
@@ -116,30 +163,46 @@
   readonly colors: CanvasColors;
 }
 
-// A definition of a track, including a renderer implementation and metadata.
+/**
+ * A definition of a track, including a renderer implementation and metadata.
+ */
 export interface Track {
-  // A unique identifier for this track.
+  /**
+   * A unique identifier for this track.
+   */
   readonly uri: string;
 
-  // Describes how to render the track.
+  /**
+   * Describes how to render the track.
+   */
   readonly renderer: TrackRenderer;
 
-  // Optional: A human readable description of the track. This can be a simple
-  // string or a render function that returns Mithril vnodes.
+  /**
+   * Optional: A human readable description of the track. This can be a simple
+   * string or a render function that returns Mithril vnodes.
+   */
   readonly description?: string | (() => m.Children);
 
-  // Optional: Human readable subtitle. Sometimes displayed if there is room.
+  /**
+   * Optional: Human readable subtitle. Sometimes displayed if there is room.
+   */
   readonly subtitle?: string;
 
-  // Optional: A list of tags which provide additional metadata about the track.
-  // Used mainly for legacy purposes that predate dataset.
+  /**
+   * Optional: A list of tags which provide additional metadata about the track.
+   * Used mainly for legacy purposes that predate dataset.
+   */
   readonly tags?: TrackTags;
 
-  // Optional: A list of strings which are displayed as "chips" in the track
-  // shell.
+  /**
+   * Optional: A list of strings which are displayed as "chips" in the track
+   * shell.
+   */
   readonly chips?: ReadonlyArray<string>;
 
-  // Filled in by the core.
+  /**
+   * Filled in by the core.
+   */
   readonly pluginId?: string;
 }
 
@@ -148,12 +211,12 @@
  */
 export interface TrackMouseEvent {
   /**
-   * X coordinate of the mouse event w.r.t. the top-left of the track.
+   * X coordinate of the mouse event with respect to the top-left of the track.
    */
   readonly x: number;
 
   /**
-   * Y coordinate of the mouse event w.r.t the top-left of the track.
+   * Y coordinate of the mouse event with respect to the top-left of the track.
    */
   readonly y: number;
 
@@ -172,35 +235,51 @@
  *
  * A lot of the fields in this interface are currently unused, but they will be
  * used in the future when track serialization is implemented.
+ * @template T The type of the setting's value.
  */
 export interface TrackSettingDescriptor<T> {
-  // A unique identifier for this setting. Will be used to store the serialized
-  // value for this setting. Currently unused.
+  /**
+   * A unique identifier for this setting. Will be used to store the serialized
+   * value for this setting. Currently unused.
+   */
   readonly id: string;
 
-  // A human readable name for this setting. This is displayed in the settings
-  // menu unless overridden.
+  /**
+   * A human readable name for this setting. This is displayed in the settings
+   * menu unless overridden.
+   */
   readonly name: string;
 
-  // A human readable description for this setting. Currently unused, but good
-  // practice to require this in order to document what a setting does and is
-  // used for.
+  /**
+   * A human readable description for this setting. Currently unused, but good
+   * practice to require this in order to document what a setting does and is
+   * used for.
+   */
   readonly description: string;
 
-  // A Zod schema describing the setting's value type which is used to infer the
-  // automatic settings menu type and options, and will be used for
-  // serialization and deserialization.
+  /**
+   * A Zod schema describing the setting's value type which is used to infer the
+   * automatic settings menu type and options, and will be used for
+   * serialization and deserialization.
+   */
   readonly schema: z.ZodType<T>;
 
-  // The default value for this setting. This will be used to render a 'reset'
-  // button in the render menu, and possibly as a fallback if parsing fails when
-  // we add serialization. Currently unused.
+  /**
+   * The default value for this setting. This will be used to render a 'reset'
+   * button in the render menu, and possibly as a fallback if parsing fails when
+   * we add serialization. Currently unused.
+   */
   readonly defaultValue: T;
 
-  // An optional function used to render a control for this setting. This
-  // describes what the control looks like in the settings menu on the track and
-  // also the bulk settings menu when multiple tracks are selected. If omitted,
-  // a control will be automatically generated based on the schema and name.
+  /**
+   * An optional function used to render a control for this setting. This
+   * describes what the control looks like in the settings menu on the track and
+   * also the bulk settings menu when multiple tracks are selected. If omitted,
+   * a control will be automatically generated based on the schema and name.
+   * @param setter A function to set the value of the setting.
+   * @param values An array of current values for the setting (for bulk editing).
+   * @returns Mithril children to render the control.
+   */
   render?(setter: (value: T) => void, values: ReadonlyArray<T>): m.Children;
 }
 
@@ -208,13 +287,28 @@
  * A setting that can be changed by the user that affects how the track is
  * rendered or behaves. References a TrackSettingDescriptor which describes the
  * setting's metadata and how to render a control for it.
+ * @template T The type of the setting's value.
  */
 export interface TrackSetting<T> {
+  /**
+   * The descriptor for this track setting.
+   */
   readonly descriptor: TrackSettingDescriptor<T>;
-  getValue: () => T;
+  /**
+   * Gets the current value of the setting.
+   * @returns The current value of the setting.
+   */
+  getValue(): T;
+  /**
+   * Sets the value of the setting.
+   * @param newValue The new value for the setting.
+   */
   setValue(newValue: T): void;
 }
 
+/**
+ * Defines the rendering logic and lifecycle hooks for a track.
+ */
 export interface TrackRenderer {
   /**
    * Describes which root table the events on this track come from. This is
@@ -251,6 +345,7 @@
    *
    * Note: On the first render cycle, both onCreate and onUpdate are called one
    * after another.
+   * @param ctx The track context.
    */
   onCreate?(ctx: TrackContext): Promise<void>;
 
@@ -260,6 +355,7 @@
    * The track should inspect things like the visible window, track size, and
    * resolution to work out whether any data needs to be reloaded based on these
    * properties and perform a reload.
+   * @param ctx The track render context.
    */
   onUpdate?(ctx: TrackRenderContext): Promise<void>;
 
@@ -272,48 +368,93 @@
   /**
    * Required method used to render the track's content to the canvas, called
    * synchronously on every render cycle.
+   * @param ctx The track render context.
    */
   render(ctx: TrackRenderContext): void;
+
+  /**
+   * Optional: Called when a full redraw of the track is required.
+   */
   onFullRedraw?(): void;
 
   /**
    * Return the vertical bounds (top & bottom) of a slice were it to be rendered
    * at a specific depth, given the slice height and padding/spacing that this
    * track uses.
+   * @param depth The depth of the slice.
+   * @returns The vertical bounds of the slice, or `undefined`.
    */
   getSliceVerticalBounds?(depth: number): VerticalBounds | undefined;
+
+  /**
+   * Optional: Returns the height of the track.
+   * @returns The height of the track in pixels.
+   */
   getHeight?(): number;
+
+  /**
+   * Optional: Returns Mithril children for buttons to be displayed in the track shell.
+   * @returns Mithril children representing the track shell buttons.
+   */
   getTrackShellButtons?(): m.Children;
+
+  /**
+   * Optional: Called when the mouse moves over the track.
+   * @param event The track mouse event.
+   */
   onMouseMove?(event: TrackMouseEvent): void;
+
+  /**
+   * Optional: Called when the mouse is clicked on the track.
+   * @param event The track mouse event.
+   * @returns `true` if the click was handled, `false` otherwise.
+   */
   onMouseClick?(event: TrackMouseEvent): boolean;
+
+  /**
+   * Optional: Called when the mouse leaves the track area.
+   */
   onMouseOut?(): void;
 
   /**
    * Optional: Returns a dataset that represents the events displayed on this
    * track.
+   * @returns The source dataset for the track, or `undefined`.
    */
   getDataset?(): SourceDataset | undefined;
 
   /**
    * Optional: Get details of a track event given by eventId on this track.
+   * @param eventId The ID of the track event.
+   * @returns A promise that resolves to the track event details, or `undefined`.
    */
   getSelectionDetails?(eventId: number): Promise<TrackEventDetails | undefined>;
 
-  // Optional: A factory that returns a details panel object for a given track
-  // event selection. This is called each time the selection is changed (and the
-  // selection is relevant to this track).
+  /**
+   * Optional: A factory that returns a details panel object for a given track
+   * event selection. This is called each time the selection is changed (and the
+   * selection is relevant to this track).
+   * @param sel The track event selection.
+   * @returns The track event details panel, or `undefined`.
+   */
   detailsPanel?(sel: TrackEventSelection): TrackEventDetailsPanel | undefined;
 
-  // Optional: Returns tooltip content if available. If the return value is
-  // falsy, no tooltip is rendered.
+  /**
+   * Optional: Returns tooltip content if available. If the return value is
+   * falsy, no tooltip is rendered.
+   * @returns Mithril children for the tooltip content, or `undefined`.
+   */
   renderTooltip?(): m.Children;
 }
 
-// An set of key/value pairs describing a given track. These are used for
-// selecting tracks to pin/unpin, diplsaying "chips" in the track shell, and
-// (in future) the sorting and grouping of tracks.
-// We define a handful of well known fields, and the rest are arbitrary key-
-// value pairs.
+/**
+ * An set of key/value pairs describing a given track. These are used for
+ * selecting tracks to pin/unpin, displaying "chips" in the track shell, and
+ * (in future) the sorting and grouping of tracks.
+ *
+ * We define a handful of well known fields, and the rest are arbitrary key-
+ * value pairs.
+ */
 export type TrackTags = Partial<WellKnownTrackTags> & {
   // There may be arbitrary other key/value pairs.
   [key: string]:
@@ -325,72 +466,147 @@
     | ReadonlyArray<number>;
 };
 
+/**
+ * Well-known track tags used for various subsystems.
+ */
 interface WellKnownTrackTags {
-  // The track "kinds", are by various subsystems e.g. aggregation controllers
-  // in order to select tracks to operate on. A good analogy is how CSS
-  // selectors can match elements using their class list.
-  kinds: ReadonlyArray<string>;
+  /**
+   * The track "kinds", are by various subsystems e.g. aggregation controllers
+   * in order to select tracks to operate on. A good analogy is how CSS
+   * selectors can match elements using their class list.
+   */
+  readonly kinds: ReadonlyArray<string>;
 
-  // Optional: list of track IDs represented by this trace.
-  // This list is used for participation in track indexing by track ID.
-  // This index is used by various subsystems to find links between tracks based
-  // on the track IDs used by trace processor.
-  trackIds: ReadonlyArray<number>;
+  /**
+   * Optional: list of track IDs represented by this trace.
+   * This list is used for participation in track indexing by track ID.
+   * This index is used by various subsystems to find links between tracks based
+   * on the track IDs used by trace processor.
+   */
+  readonly trackIds: ReadonlyArray<number>;
 
-  // Optional: The CPU number associated with this track.
-  cpu: number;
+  /**
+   * Optional: The CPU number associated with this track.
+   */
+  readonly cpu: number;
 
-  // Optional: The UTID associated with this track.
-  utid: number;
+  /**
+   * Optional: The UTID associated with this track.
+   */
+  readonly utid: number;
 
-  // Optional: The UPID associated with this track.
-  upid: number;
+  /**
+   * Optional: The UPID associated with this track.
+   */
+  readonly upid: number;
 
-  // Track type, used for filtering
-  type: string;
+  /**
+   * Track type, used for filtering.
+   */
+  readonly type: string;
 }
 
+/**
+ * Represents a single slice on a track.
+ */
 export interface Slice {
-  // These properties are updated only once per query result when the Slice
-  // object is created and don't change afterwards.
+  /**
+   * The unique ID of the slice.
+   * These properties are updated only once per query result when the Slice
+   * object is created and don't change afterwards.
+   */
   readonly id: number;
+  /**
+   * The start timestamp of the slice in nanoseconds.
+   */
   readonly startNs: time;
+  /**
+   * The end timestamp of the slice in nanoseconds.
+   */
   readonly endNs: time;
+  /**
+   * The duration of the slice in nanoseconds.
+   */
   readonly durNs: duration;
+  /**
+   * The timestamp of the slice.
+   */
   readonly ts: time;
+  /**
+   * The count associated with the slice.
+   */
   readonly count: number;
+  /**
+   * The duration of the slice.
+   */
   readonly dur: duration;
+  /**
+   * The depth of the slice in its track.
+   */
   readonly depth: number;
+  /**
+   * Flags associated with the slice.
+   */
   readonly flags: number;
 
-  // Each slice can represent some extra numerical information by rendering a
-  // portion of the slice with a lighter tint.
-  // |fillRatio\ describes the ratio of the normal area to the tinted area
-  // width of the slice, normalized between 0.0 -> 1.0.
-  // 0.0 means the whole slice is tinted.
-  // 1.0 means none of the slice is tinted.
-  // E.g. If |fillRatio| = 0.65 the slice will be rendered like this:
-  // [############|*******]
-  // ^------------^-------^
-  //     Normal     Light
+  /**
+   * Each slice can represent some extra numerical information by rendering a
+   * portion of the slice with a lighter tint.
+   * `fillRatio` describes the ratio of the normal area to the tinted area
+   * width of the slice, normalized between 0.0 -> 1.0.
+   * 0.0 means the whole slice is tinted.
+   * 1.0 means none of the slice is tinted.
+   * E.g. If `fillRatio` = 0.65 the slice will be rendered like this:
+   * [############|*******]
+   * ^------------^-------^
+   *     Normal     Light
+   */
   readonly fillRatio: number;
 
-  // These can be changed by the Impl.
+  /**
+   * The title of the slice. These can be changed by the Impl.
+   */
   title?: string;
+  /**
+   * The subtitle of the slice.
+   */
   subTitle: string;
+  /**
+   * The color scheme used for the slice.
+   */
   colorScheme: ColorScheme;
+  /**
+   * Whether the slice is currently highlighted.
+   */
   isHighlighted: boolean;
 }
 
 /**
- * Contains a track and it's top and bottom coordinates in the timeline.
+ * Contains a track and its top and bottom coordinates in the timeline.
  */
 export interface TrackBounds {
+  /**
+   * The track node.
+   */
   readonly node: TrackNode;
+  /**
+   * The vertical bounds of the track.
+   */
   readonly verticalBounds: VerticalBounds;
 }
 
+/**
+ * Defines a timeline overlay renderer.
+ */
 export interface Overlay {
+  /**
+   * Renders the overlay on top of the tracks.
+   * @param ctx The 2D rendering context of the canvas.
+   * @param timescale The time scale used for translating between pixels and time.
+   * @param size The dimensions of the canvas in pixels.
+   * @param tracks A read-only array of track bounds.
+   * @param theme The canvas colors for the current theme.
+   */
   render(
     ctx: CanvasRenderingContext2D,
     timescale: TimeScale,
diff --git a/ui/src/public/workspace.ts b/ui/src/public/workspace.ts
index a8987c7..ed506cb 100644
--- a/ui/src/public/workspace.ts
+++ b/ui/src/public/workspace.ts
@@ -15,11 +15,29 @@
 import {assertTrue} from '../base/logging';
 import {errResult, okResult, Result} from '../base/result';
 
+/**
+ * Manages workspaces, allowing for creation, switching, and access to the
+ * current workspace.
+ */
 export interface WorkspaceManager {
-  // This is the same of ctx.workspace, exposed for consistency also here.
+  /**
+   * The currently active workspace. This is the same as `ctx.workspace`.
+   */
   readonly currentWorkspace: Workspace;
+  /**
+   * A read-only array of all available workspaces.
+   */
   readonly all: ReadonlyArray<Workspace>;
+  /**
+   * Creates a new, empty workspace.
+   * @param displayName The human-readable name for the new workspace.
+   * @returns The newly created workspace.
+   */
   createEmptyWorkspace(displayName: string): Workspace;
+  /**
+   * Switches the currently active workspace.
+   * @param workspace The workspace to switch to.
+   */
   switchWorkspace(workspace: Workspace): void;
 }
 
@@ -37,80 +55,100 @@
  *
  * TODO(stevegolton): We could possibly move this into its own module and use it
  * everywhere where session-unique ids are required.
+ * @returns A session-unique ID.
  */
 function createSessionUniqueId(): string {
   return (sessionUniqueIdCounter++).toString();
 }
 
 /**
- * Describes generic parent track node functionality - i.e. any entity that can
- * contain child TrackNodes, providing methods to add, remove, and access child
- * nodes.
- *
- * This class is abstract because, while it can technically be instantiated on
- * its own (no abstract methods/properties), it can't and shouldn't be
- * instantiated anywhere in practice - all APIs require either a TrackNode or a
- * Workspace.
- *
- * Thus, it serves two purposes:
- * 1. Avoiding duplication between Workspace and TrackNode, which is an internal
- *    implementation detail of this module.
- * 2. Providing a typescript interface for a generic TrackNode container class,
- *    which otherwise you might have to achieve using `Workspace | TrackNode`
- *    which is uglier.
- *
- * If you find yourself using this as a Javascript class in external code, e.g.
- * `instance of TrackNodeContainer`, you're probably doing something wrong.
+ * Arguments for creating a new `TrackNode`.
  */
-
 export interface TrackNodeArgs {
-  name: string;
-  uri: string;
-  headless: boolean;
-  sortOrder: number;
-  collapsed: boolean;
-  isSummary: boolean;
-  removable: boolean;
+  /**
+   * The human-readable name for this track, displayed in the track shell.
+   */
+  readonly name: string;
+  /**
+   * The URI of the track content to display.
+   */
+  readonly uri: string;
+  /**
+   * If `true`, the header for this track will not be shown, and its children
+   * will be displayed un-nested.
+   */
+  readonly headless: boolean;
+  /**
+   * An optional sort order, which workspaces may use for sorting. Lower numbers
+   * appear first.
+   */
+  readonly sortOrder: number;
+  /**
+   * If `true`, the track will be initially collapsed.
+   */
+  readonly collapsed: boolean;
+  /**
+   * If `true`, this track will be used as a summary for its children.
+   */
+  readonly isSummary: boolean;
+  /**
+   * If `true`, this node will be removable by the user.
+   */
+  readonly removable: boolean;
 }
 
 /**
  * A base class for any node with children (i.e. a group or a workspace).
  */
 export class TrackNode {
-  // Immutable unique (within the workspace) ID of this track node. Used for
-  // efficiently retrieving this node object from a workspace. Note: This is
-  // different to |uri| which is used to reference a track to render on the
-  // track. If this means nothing to you, don't bother using it.
+  /**
+   * Immutable unique (within the workspace) ID of this track node. Used for
+   * efficiently retrieving this node object from a workspace. Note: This is
+   * different to `uri` which is used to reference a track to render on the
+   * track. If this means nothing to you, don't bother using it.
+   */
   public readonly id: string;
 
-  // A human readable string for this track - displayed in the track shell.
-  // TODO(stevegolton): Make this optional, so that if we implement a string for
-  // this track then we can implement it here as well.
+  /**
+   * A human readable string for this track - displayed in the track shell.
+   * TODO(stevegolton): Make this optional, so that if we implement a string for
+   * this track then we can implement it here as well.
+   */
   public name: string;
 
-  // The URI of the track content to display here.
+  /**
+   * The URI of the track content to display here.
+   */
   public uri?: string;
 
-  // Optional sort order, which workspaces may or may not take advantage of for
-  // sorting when displaying the workspace. Lower numbers appear first.
+  /**
+   * Optional sort order, which workspaces may or may not take advantage of for
+   * sorting when displaying the workspace. Lower numbers appear first.
+   */
   public sortOrder?: number;
 
-  // Don't show the header at all for this track, just show its un-nested
-  // children. This is helpful to group together tracks that logically belong to
-  // the same group (e.g. all ftrace cpu tracks) and ease the job of
-  // sorting/grouping plugins.
+  /**
+   * Don't show the header at all for this track, just show its un-nested
+   * children. This is helpful to group together tracks that logically belong to
+   * the same group (e.g. all ftrace cpu tracks) and ease the job of
+   * sorting/grouping plugins.
+   */
   public headless: boolean;
 
-  // If true, this track is to be used as a summary for its children. When the
-  // group is expanded the track will become sticky to the top of the viewport
-  // to provide context for the tracks within, and the content of this track
-  // shall be omitted. It will also be squashed down to a smaller height to save
-  // vertical space.
+  /**
+   * If true, this track is to be used as a summary for its children. When the
+   * group is expanded the track will become sticky to the top of the viewport
+   * to provide context for the tracks within, and the content of this track
+   * shall be omitted. It will also be squashed down to a smaller height to save
+   * vertical space.
+   */
   public isSummary: boolean;
 
-  // If true, this node will be removable by the user. It will show a little
-  // close button in the track shell which the user can press to remove the
-  // track from the workspace.
+  /**
+   * If true, this node will be removable by the user. It will show a little
+   * close button in the track shell which the user can press to remove the
+   * track from the workspace.
+   */
   public removable: boolean;
 
   protected _collapsed = true;
@@ -120,6 +158,9 @@
   private _parent?: TrackNode;
   public _workspace?: Workspace;
 
+  /**
+   * The parent of this track node, or `undefined` if it is a root node.
+   */
   get parent(): TrackNode | undefined {
     return this._parent;
   }
@@ -146,7 +187,8 @@
   }
 
   /**
-   * Remove this track from it's parent & unpin from the workspace if pinned.
+   * Removes this track from its parent and unpins it from the workspace if
+   * pinned.
    */
   remove(): void {
     this.workspace?.unpinTrack(this);
@@ -154,7 +196,7 @@
   }
 
   /**
-   * Add this track to the list of pinned tracks in its parent workspace.
+   * Adds this track to the list of pinned tracks in its parent workspace.
    *
    * Has no effect if this track is not added to a workspace.
    */
@@ -163,7 +205,7 @@
   }
 
   /**
-   * Remove this track from the list of pinned tracks in its parent workspace.
+   * Removes this track from the list of pinned tracks in its parent workspace.
    *
    * Has no effect if this track is not added to a workspace.
    */
@@ -172,23 +214,24 @@
   }
 
   /**
-   * Returns true if this node is added to a workspace as is in the pinned track
-   * list of that workspace.
+   * Returns `true` if this node is added to a workspace and is in the pinned
+   * track list of that workspace.
    */
   get isPinned(): boolean {
     return Boolean(this.workspace?.hasPinnedTrack(this));
   }
 
   /**
-   * Find the closest visible ancestor TrackNode.
+   * Finds the closest visible ancestor TrackNode.
    *
-   * Given the path from the root workspace to this node, find the fist one,
+   * Given the path from the root workspace to this node, find the first one,
    * starting from the root, which is collapsed. This will be, from the user's
    * point of view, the closest ancestor of this node.
    *
-   * Returns undefined if this node is actually visible.
+   * Returns `undefined` if this node is actually visible.
    *
    * TODO(stevegolton): Should it return itself in this case?
+   * @returns The closest visible ancestor, or `this` if the node is visible.
    */
   findClosestVisibleAncestor(): TrackNode {
     // Build a path from the root workspace to this node
@@ -206,7 +249,7 @@
   }
 
   /**
-   * Expand all ancestor nodes.
+   * Expands all ancestor nodes to make this node visible.
    */
   reveal(): void {
     let parent = this.parent;
@@ -217,8 +260,9 @@
   }
 
   /**
-   * Get all ancestors of this node from root to immediate parent.
-   * Returns an empty array if this node has no parent.
+   * Gets all ancestors of this node from root to immediate parent.
+   * @returns An array of ancestor nodes, or an empty array if this node has no
+   *   parent.
    */
   getAncestors(): TrackNode[] {
     const ancestors: TrackNode[] = [];
@@ -231,7 +275,7 @@
   }
 
   /**
-   * Find this node's root node - this may be a workspace or another node.
+   * Finds this node's root node - this may be a workspace or another node.
    */
   get rootNode(): TrackNode {
     let node: TrackNode = this;
@@ -242,21 +286,21 @@
   }
 
   /**
-   * Find this node's workspace if it is attached to one.
+   * Finds this node's workspace if it is attached to one.
    */
   get workspace(): Workspace | undefined {
     return this.rootNode._workspace;
   }
 
   /**
-   * Mark this node as un-collapsed, indicating its children should be rendered.
+   * Marks this node as un-collapsed, indicating its children should be rendered.
    */
   expand(): void {
     this._collapsed = false;
   }
 
   /**
-   * Mark this node as collapsed, indicating its children should not be
+   * Marks this node as collapsed, indicating its children should not be
    * rendered.
    */
   collapse(): void {
@@ -264,14 +308,15 @@
   }
 
   /**
-   * Toggle the collapsed state.
+   * Toggles the collapsed state.
    */
   toggleCollapsed(): void {
     this._collapsed = !this._collapsed;
   }
 
   /**
-   * Whether this node is collapsed, indicating its children should be rendered.
+   * Whether this node is collapsed, indicating its children should not be
+   * rendered.
    */
   get collapsed(): boolean {
     return this._collapsed;
@@ -287,7 +332,7 @@
 
   /**
    * Returns the list of titles representing the full path from the root node to
-   * the current node. This path consists only of node titles, workspaces are
+   * the current node. This path consists only of node titles; workspaces are
    * omitted.
    */
   get fullPath(): ReadonlyArray<string> {
@@ -304,7 +349,7 @@
   }
 
   /**
-   * True if this node has children, false otherwise.
+   * `true` if this node has children, `false` otherwise.
    */
   get hasChildren(): boolean {
     return this._children.length > 0;
@@ -318,14 +363,15 @@
   }
 
   /**
-   * Inserts a new child node considering it's sortOrder.
+   * Inserts a new child node considering its `sortOrder`.
    *
-   * The child will be added before the first child whose |sortOrder| is greater
+   * The child will be added before the first child whose `sortOrder` is greater
    * than the child node's sort order, or at the end if one does not exist. If
-   * |sortOrder| is omitted on either node in the comparison it is assumed to be
+   * `sortOrder` is omitted on either node in the comparison it is assumed to be
    * 0.
    *
-   * @param child - The child node to add.
+   * @param child The child node to add.
+   * @returns A `Result` indicating success or failure.
    */
   addChildInOrder(child: TrackNode): Result {
     const insertPoint = this._children.find(
@@ -339,9 +385,10 @@
   }
 
   /**
-   * Add a new child node at the start of the list of children.
+   * Adds a new child node at the end of the list of children.
    *
    * @param child The new child node to add.
+   * @returns A `Result` indicating success or failure.
    */
   addChildLast(child: TrackNode): Result {
     const result = this.adopt(child);
@@ -351,9 +398,10 @@
   }
 
   /**
-   * Add a new child node at the end of the list of children.
+   * Adds a new child node at the start of the list of children.
    *
    * @param child The child node to add.
+   * @returns A `Result` indicating success or failure.
    */
   addChildFirst(child: TrackNode): Result {
     const result = this.adopt(child);
@@ -363,11 +411,12 @@
   }
 
   /**
-   * Add a new child node before an existing child node.
+   * Adds a new child node before an existing child node.
    *
    * @param child The child node to add.
    * @param referenceNode An existing child node. The new node will be added
-   * before this node.
+   *   before this node.
+   * @returns A `Result` indicating success or failure.
    */
   addChildBefore(child: TrackNode, referenceNode: TrackNode): Result {
     // Nodes are the same, nothing to do.
@@ -385,11 +434,12 @@
   }
 
   /**
-   * Add a new child node after an existing child node.
+   * Adds a new child node after an existing child node.
    *
    * @param child The child node to add.
    * @param referenceNode An existing child node. The new node will be added
-   * after this node.
+   *   after this node.
+   * @returns A `Result` indicating success or failure.
    */
   addChildAfter(child: TrackNode, referenceNode: TrackNode): Result {
     // Nodes are the same, nothing to do.
@@ -407,7 +457,7 @@
   }
 
   /**
-   * Remove a child node from this node.
+   * Removes a child node from this node.
    *
    * @param child The child node to remove.
    */
@@ -419,9 +469,9 @@
   }
 
   /**
-   * The flattened list of all descendent nodes in depth first order.
+   * The flattened list of all descendent nodes in depth-first order.
    *
-   * Use flatTracksUnordered if you don't care about track order, as it's more
+   * Use `flatTracks` if you don't care about track order, as it's more
    * efficient.
    */
   get flatTracksOrdered(): ReadonlyArray<TrackNode> {
@@ -445,7 +495,7 @@
   }
 
   /**
-   * Remove all children from this node.
+   * Removes all children from this node.
    */
   clear(): void {
     this._children = [];
@@ -453,24 +503,24 @@
   }
 
   /**
-   * Get a track node by its id.
+   * Gets a track node by its ID.
    *
-   * Node: This is an O(1) operation.
+   * Note: This is an O(1) operation.
    *
-   * @param id The id of the node we want to find.
-   * @returns The node or undefined if no such node exists.
+   * @param id The ID of the node we want to find.
+   * @returns The node or `undefined` if no such node exists.
    */
   getTrackById(id: string): TrackNode | undefined {
     return this.tracksById.get(id);
   }
 
   /**
-   * Get a track node via its URI.
+   * Gets a track node via its URI.
    *
-   * Node: This is an O(1) operation.
+   * Note: This is an O(1) operation.
    *
-   * @param uri The uri of the track to find.
-   * @returns The node or undefined if no such node exists with this URI.
+   * @param uri The URI of the track to find.
+   * @returns The node or `undefined` if no such node exists with this URI.
    */
   getTrackByUri(uri: string): TrackNode | undefined {
     return this.tracksByUri.get(uri);
@@ -479,8 +529,8 @@
   /**
    * Creates a copy of this node with a new ID.
    *
-   * @param deep - If true, children are copied too.
-   * @returns - A copy of this node.
+   * @param deep If `true`, children are copied too.
+   * @returns A copy of this node.
    */
   clone(deep = false): TrackNode {
     const cloned = new TrackNode({...this, id: undefined});
@@ -552,14 +602,31 @@
  * Defines a workspace containing a track tree and a pinned area.
  */
 export class Workspace {
+  /**
+   * The human-readable title of the workspace.
+   */
   public title = '<untitled-workspace>';
+  /**
+   * The unique ID of the workspace.
+   */
   public readonly id: string;
+  /**
+   * Whether the workspace is user-editable.
+   */
   public userEditable: boolean = true;
 
-  // Dummy node to contain the pinned tracks
+  /**
+   * A dummy node to contain the pinned tracks.
+   */
   public readonly pinnedTracksNode = new TrackNode();
+  /**
+   * The root node for the main track tree.
+   */
   public readonly tracks = new TrackNode();
 
+  /**
+   * A read-only array of pinned tracks in this workspace.
+   */
   get pinnedTracks(): ReadonlyArray<TrackNode> {
     return this.pinnedTracksNode.children;
   }
@@ -575,7 +642,7 @@
   }
 
   /**
-   * Reset the entire workspace including the pinned tracks.
+   * Resets the entire workspace including the pinned tracks.
    */
   clear(): void {
     this.pinnedTracksNode.clear();
@@ -584,6 +651,7 @@
 
   /**
    * Adds a track node to this workspace's pinned area.
+   * @param track The track to pin.
    */
   pinTrack(track: TrackNode): void {
     // Make a lightweight clone of this track - just the uri and the title.
@@ -597,6 +665,7 @@
 
   /**
    * Removes a track node from this workspace's pinned area.
+   * @param track The track to unpin.
    */
   unpinTrack(track: TrackNode): void {
     const foundNode = this.pinnedTracksNode.children.find(
@@ -608,31 +677,33 @@
   }
 
   /**
-   * Check if this workspace has a pinned track with the same URI as |track|.
+   * Checks if this workspace has a pinned track with the same URI as `track`.
+   * @param track The track to check.
+   * @returns `true` if a matching pinned track is found, `false` otherwise.
    */
   hasPinnedTrack(track: TrackNode): boolean {
     return this.pinnedTracksNode.flatTracks.some((p) => p.uri === track.uri);
   }
 
   /**
-   * Get a track node via its URI.
+   * Gets a track node via its URI.
    *
-   * Node: This is an O(1) operation.
+   * Note: This is an O(1) operation.
    *
-   * @param uri The uri of the track to find.
-   * @returns The node or undefined if no such node exists with this URI.
+   * @param uri The URI of the track to find.
+   * @returns The node or `undefined` if no such node exists with this URI.
    */
   getTrackByUri(uri: string): TrackNode | undefined {
     return this.tracks.flatTracks.find((t) => t.uri === uri);
   }
 
   /**
-   * Get a track node by its id.
+   * Gets a track node by its ID.
    *
-   * Node: This is an O(1) operation.
+   * Note: This is an O(1) operation.
    *
-   * @param id The id of the node we want to find.
-   * @returns The node or undefined if no such node exists.
+   * @param id The ID of the node we want to find.
+   * @returns The node or `undefined` if no such node exists.
    */
   getTrackById(id: string): TrackNode | undefined {
     return (
@@ -648,61 +719,66 @@
   }
 
   /**
-   * Inserts a new child node considering it's sortOrder.
+   * Inserts a new child node considering its `sortOrder`.
    *
-   * The child will be added before the first child whose |sortOrder| is greater
+   * The child will be added before the first child whose `sortOrder` is greater
    * than the child node's sort order, or at the end if one does not exist. If
-   * |sortOrder| is omitted on either node in the comparison it is assumed to be
+   * `sortOrder` is omitted on either node in the comparison it is assumed to be
    * 0.
    *
-   * @param child - The child node to add.
+   * @param child The child node to add.
+   * @returns A `Result` indicating success or failure.
    */
   addChildInOrder(child: TrackNode): Result {
     return this.tracks.addChildInOrder(child);
   }
 
   /**
-   * Add a new child node at the start of the list of children.
+   * Adds a new child node at the end of the list of children.
    *
    * @param child The new child node to add.
+   * @returns A `Result` indicating success or failure.
    */
   addChildLast(child: TrackNode): Result {
     return this.tracks.addChildLast(child);
   }
 
   /**
-   * Add a new child node at the end of the list of children.
+   * Adds a new child node at the start of the list of children.
    *
    * @param child The child node to add.
+   * @returns A `Result` indicating success or failure.
    */
   addChildFirst(child: TrackNode): Result {
     return this.tracks.addChildFirst(child);
   }
 
   /**
-   * Add a new child node before an existing child node.
+   * Adds a new child node before an existing child node.
    *
    * @param child The child node to add.
    * @param referenceNode An existing child node. The new node will be added
-   * before this node.
+   *   before this node.
+   * @returns A `Result` indicating success or failure.
    */
   addChildBefore(child: TrackNode, referenceNode: TrackNode): Result {
     return this.tracks.addChildBefore(child, referenceNode);
   }
 
   /**
-   * Add a new child node after an existing child node.
+   * Adds a new child node after an existing child node.
    *
    * @param child The child node to add.
    * @param referenceNode An existing child node. The new node will be added
-   * after this node.
+   *   after this node.
+   * @returns A `Result` indicating success or failure.
    */
   addChildAfter(child: TrackNode, referenceNode: TrackNode): Result {
     return this.tracks.addChildAfter(child, referenceNode);
   }
 
   /**
-   * Remove a child node from this node.
+   * Removes a child node from this node.
    *
    * @param child The child node to remove.
    */
@@ -711,19 +787,19 @@
   }
 
   /**
-   * The flattened list of all descendent nodes in depth first order.
+   * The flattened list of all descendent nodes in depth-first order.
    *
-   * Use flatTracksUnordered if you don't care about track order, as it's more
+   * Use `flatTracks` if you don't care about track order, as it's more
    * efficient.
    */
-  get flatTracksOrdered() {
+  get flatTracksOrdered(): ReadonlyArray<TrackNode> {
     return this.tracks.flatTracksOrdered;
   }
 
   /**
    * The flattened list of all descendent nodes in no particular order.
    */
-  get flatTracks() {
+  get flatTracks(): ReadonlyArray<TrackNode> {
     return this.tracks.flatTracks;
   }
 }