[ui] Make [Command|Track] registry APIs more consistent

Change-Id: I1a86ef65fe049785007ac0e7b003eadbc629351c
diff --git a/ui/src/common/commands.ts b/ui/src/common/commands.ts
index 2f19198..7b44775 100644
--- a/ui/src/common/commands.ts
+++ b/ui/src/common/commands.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {Disposable} from '../base/disposable';
 import {FuzzyFinder, FuzzySegment} from '../base/fuzzy';
 import {Command} from '../public';
 import {Registry} from './registry';
@@ -21,12 +22,16 @@
 }
 
 export class CommandManager {
-  readonly registry = new Registry<Command>((cmd) => cmd.id);
+  private readonly registry = new Registry<Command>((cmd) => cmd.id);
 
   get commands(): Command[] {
     return Array.from(this.registry.values());
   }
 
+  registerCommand(cmd: Command): Disposable {
+    return this.registry.register(cmd);
+  }
+
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   runCommand(id: string, ...args: any[]): any {
     const cmd = this.registry.get(id);
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index b9abe0d..86c5079 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -71,7 +71,7 @@
     // Silently ignore if context is dead.
     if (!this.alive) return;
 
-    const disposable = globals.commandManager.registry.register(cmd);
+    const disposable = globals.commandManager.registerCommand(cmd);
     this.trash.add(disposable);
   };
 
@@ -104,22 +104,24 @@
     // Silently ignore if context is dead.
     if (!this.alive) return;
 
-    const disposable = globals.commandManager.registry.register(cmd);
-    this.trash.add(disposable);
+    const dispose = globals.commandManager.registerCommand(cmd);
+    this.trash.add(dispose);
   }
 
   registerTrack(trackDesc: TrackDescriptor): void {
     // Silently ignore if context is dead.
     if (!this.alive) return;
-    globals.trackManager.registerTrack(trackDesc);
-    this.trash.addCallback(
-      () => globals.trackManager.unregisterTrack(trackDesc.uri));
+
+    const dispose = globals.trackManager.registerTrack(trackDesc);
+    this.trash.add(dispose);
   }
 
   addDefaultTrack(track: TrackRef): void {
-    globals.trackManager.addDefaultTrack(track);
-    this.trash.addCallback(
-      () => globals.trackManager.removeDefaultTrack(track));
+    // Silently ignore if context is dead.
+    if (!this.alive) return;
+
+    const dispose = globals.trackManager.addPotentialTrack(track);
+    this.trash.add(dispose);
   }
 
   registerStaticTrack(track: TrackDescriptor&TrackRef): void {
diff --git a/ui/src/common/track_cache.ts b/ui/src/common/track_cache.ts
index 43b1e70..90c004e 100644
--- a/ui/src/common/track_cache.ts
+++ b/ui/src/common/track_cache.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {Disposable, DisposableCallback} from '../base/disposable';
 import {PanelSize} from '../frontend/panel';
 import {Store} from '../frontend/store';
 import {
@@ -21,6 +22,7 @@
   TrackDescriptor,
   TrackRef,
 } from '../public';
+import {Registry} from './registry';
 
 import {State} from './state';
 
@@ -55,44 +57,39 @@
 // Third cycle
 //   flushTracks() <-- 'foo' is destroyed.
 export class TrackManager {
+  private readonly registry = new Registry<TrackDescriptor>(({uri}) => uri);
+  private readonly potentialTracks = new Set<TrackRef>();
   private safeCache = new Map<string, TrackCacheEntry>();
   private recycleBin = new Map<string, TrackCacheEntry>();
-  private trackRegistry = new Map<string, TrackDescriptor>();
-  private defaultTracks = new Set<TrackRef>();
   private store: Store<State>;
 
   constructor(store: Store<State>) {
     this.store = store;
   }
 
-  registerTrack(trackDesc: TrackDescriptor): void {
-    this.trackRegistry.set(trackDesc.uri, trackDesc);
+  registerTrack(trackDesc: TrackDescriptor): Disposable {
+    return this.registry.register(trackDesc);
   }
 
-  unregisterTrack(uri: string): void {
-    this.trackRegistry.delete(uri);
-  }
-
-  addDefaultTrack(track: TrackRef): void {
-    this.defaultTracks.add(track);
-  }
-
-  removeDefaultTrack(track: TrackRef): void {
-    this.defaultTracks.delete(track);
+  addPotentialTrack(track: TrackRef): Disposable {
+    this.potentialTracks.add(track);
+    return new DisposableCallback(() => {
+      this.potentialTracks.delete(track);
+    });
   }
 
   findPotentialTracks(): TrackRef[] {
-    return Array.from(this.defaultTracks);
+    return Array.from(this.potentialTracks);
   }
 
   getAllTracks(): TrackDescriptor[] {
-    return Array.from(this.trackRegistry.values());
+    return Array.from(this.registry.values());
   }
 
   // Look up track into for a given track's URI.
   // Returns |undefined| if no track can be found.
   resolveTrackInfo(uri: string): TrackDescriptor|undefined {
-    return this.trackRegistry.get(uri);
+    return this.registry.get(uri);
   }
 
   // Creates a new track using |uri| and |params| or retrieves a cached track if
diff --git a/ui/src/frontend/aggregation_tab.ts b/ui/src/frontend/aggregation_tab.ts
index 0db524e..f152fb3 100644
--- a/ui/src/frontend/aggregation_tab.ts
+++ b/ui/src/frontend/aggregation_tab.ts
@@ -157,7 +157,7 @@
       });
       this.trash.add(unregister);
 
-      const unregisterCmd = globals.commandManager.registry.register({
+      const unregisterCmd = globals.commandManager.registerCommand({
         id: uri,
         name: `Show ${title} Aggregation Tab`,
         callback: () => {
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index 0819338..d48eab6 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -839,7 +839,7 @@
 
     // Register each command with the command manager
     this.cmds.forEach((cmd) => {
-      const dispose = globals.commandManager.registry.register(cmd);
+      const dispose = globals.commandManager.registerCommand(cmd);
       this.trash.add(dispose);
     });
   }