Modify transactions presenter.

Change trace data type to HierarchyTreeNode.
Populate filters by query.

Bug: 411363817
Test: npm run test:unit:ci

Change-Id: If1767bd48db7753e743e9cc334bd304059a83291
diff --git a/tools/winscope/src/parsers/perfetto/utils.ts b/tools/winscope/src/parsers/perfetto/utils.ts
index 026caa4..5e86916 100644
--- a/tools/winscope/src/parsers/perfetto/utils.ts
+++ b/tools/winscope/src/parsers/perfetto/utils.ts
@@ -172,3 +172,30 @@
         ORDER BY tbl.id;
     `;
 }
+
+export async function getDistinctValues(
+  traceProcessor: TraceProcessor,
+  tableName: string,
+  columns: string[],
+): Promise<string[]> {
+  const uniqueValueCol = 'unique_value';
+  const sql =
+    columns
+      .map((col) => {
+        return `SELECT DISTINCT ${col} AS ${uniqueValueCol} FROM ${tableName}`;
+      })
+      .join(' UNION ') + ` ORDER BY ${uniqueValueCol}`;
+
+  const rows = await traceProcessor.query(sql);
+  if (rows.numRows() === 0) {
+    return [];
+  }
+
+  const options: string[] = [];
+  for (const it = rows.iter({}); it.valid(); it.next()) {
+    const val = it.get(uniqueValueCol);
+    const option = val !== null && val !== undefined ? val.toString() : 'N/A';
+    options.push(option);
+  }
+  return options;
+}
diff --git a/tools/winscope/src/parsers/transactions/perfetto/parser_transactions.ts b/tools/winscope/src/parsers/transactions/perfetto/parser_transactions.ts
index dd49da0..10f24a4 100644
--- a/tools/winscope/src/parsers/transactions/perfetto/parser_transactions.ts
+++ b/tools/winscope/src/parsers/transactions/perfetto/parser_transactions.ts
@@ -21,7 +21,11 @@
 import {SetFormatters} from 'parsers/operations/set_formatters';
 import {AbstractParser} from 'parsers/perfetto/abstract_parser';
 import {FakeProtoTransformer} from 'parsers/perfetto/fake_proto_transformer';
-import {queryArgs, queryVsyncId} from 'parsers/perfetto/utils';
+import {
+  getDistinctValues,
+  queryArgs,
+  queryVsyncId,
+} from 'parsers/perfetto/utils';
 import {PropertyTreeBuilderFromProto} from 'parsers/property_tree_builder_from_proto';
 import {PropertyTreeBuilderFromQueryRow} from 'parsers/property_tree_builder_from_query_row';
 import {
@@ -31,6 +35,7 @@
 import {TransactionType} from 'parsers/transactions/transaction_type';
 import {perfetto} from 'protos/perfetto/trace/static';
 import {
+  CustomQueryParamTypeMap,
   CustomQueryParserResultTypeMap,
   CustomQueryType,
   VisitableParserCustomQuery,
@@ -38,6 +43,7 @@
 import {EntriesRange} from 'trace/index_types';
 import {TraceFile} from 'trace/trace_file';
 import {TraceType} from 'trace/trace_type';
+import {TransactionColumnType} from 'trace/transaction_column_type';
 import {
   EnumFormatter,
   FixedStringFormatter,
@@ -105,6 +111,7 @@
   override async customQuery<Q extends CustomQueryType>(
     type: Q,
     entriesRange: EntriesRange,
+    param?: CustomQueryParamTypeMap[Q],
   ): Promise<CustomQueryParserResultTypeMap[Q]> {
     return new VisitableParserCustomQuery(type)
       .visit(CustomQueryType.VSYNCID, async () => {
@@ -116,6 +123,44 @@
           ParserTransactions.createVsyncIdQuery,
         );
       })
+      .visit(CustomQueryType.LOG_TABLE_FILTER_VALUES, async () => {
+        let tableName: string;
+        let columns: string[];
+        switch (param) {
+          case TransactionColumnType.TRANSACTION_ID:
+            tableName = '__intrinsic_surfaceflinger_transaction';
+            columns = ['transaction_id'];
+            break;
+          case TransactionColumnType.VSYNC_ID:
+            tableName = 'surfaceflinger_transactions';
+            columns = ['vsync_id'];
+            break;
+          case TransactionColumnType.PID:
+            tableName = '__intrinsic_surfaceflinger_transaction';
+            columns = ['pid'];
+            break;
+          case TransactionColumnType.UID:
+            tableName = '__intrinsic_surfaceflinger_transaction';
+            columns = ['uid'];
+            break;
+          case TransactionColumnType.TRANSACTION_TYPE:
+            tableName = '__intrinsic_surfaceflinger_transaction';
+            columns = ['transaction_type'];
+            break;
+          case TransactionColumnType.LAYER_OR_DISPLAY_ID:
+            tableName = '__intrinsic_surfaceflinger_transaction';
+            columns = ['layer_id', 'display_id'];
+            break;
+          case TransactionColumnType.FLAGS:
+            tableName = '__intrinsic_surfaceflinger_transaction_flag';
+            columns = ['flag'];
+            break;
+
+          default:
+            throw new Error('unexpected transaction column type requested');
+        }
+        return getDistinctValues(this.traceProcessor, tableName, columns);
+      })
       .getResult();
   }
 
@@ -166,7 +211,9 @@
     const argSetId = row.get('arg_set_id') ?? undefined;
 
     let field: TamperedProtoField | undefined;
-    const transactionType = assertDefined(row.get('transaction_type')) as string;
+    const transactionType = assertDefined(
+      row.get('transaction_type'),
+    ) as string;
     const entryProtoType = assertDefined(
       ParserTransactions.TransactionsTraceEntryField.tamperedMessageType,
     );
diff --git a/tools/winscope/src/test/unit/mock_log_viewer_presenter.ts b/tools/winscope/src/test/unit/mock_log_viewer_presenter.ts
index 4cba678..142974d 100644
--- a/tools/winscope/src/test/unit/mock_log_viewer_presenter.ts
+++ b/tools/winscope/src/test/unit/mock_log_viewer_presenter.ts
@@ -135,10 +135,7 @@
     return headers;
   }
 
-  protected override updateFiltersInHeaders(
-    headers: LogHeader[],
-    allEntries: LogEntry[],
-  ) {
+  protected override async updateFiltersInHeaders(headers: LogHeader[]) {
     for (const header of headers) {
       if (header.spec === this.stringColumn) {
         (assertDefined(header.filter) as LogSelectFilter).options = [
diff --git a/tools/winscope/src/trace/custom_query.ts b/tools/winscope/src/trace/custom_query.ts
index bd6eeb7..8d95da1 100644
--- a/tools/winscope/src/trace/custom_query.ts
+++ b/tools/winscope/src/trace/custom_query.ts
@@ -21,6 +21,7 @@
   VIEW_CAPTURE_METADATA,
   VSYNCID,
   WM_WINDOWS_TOKEN_AND_TITLE,
+  LOG_TABLE_FILTER_VALUES,
 }
 
 export class ProcessParserResult {
@@ -53,6 +54,12 @@
   ): CustomQueryResultTypeMap<T>[CustomQueryType.WM_WINDOWS_TOKEN_AND_TITLE] {
     return parserResult;
   }
+
+  static [CustomQueryType.LOG_TABLE_FILTER_VALUES]<T>(
+    parserResult: CustomQueryParserResultTypeMap[CustomQueryType.LOG_TABLE_FILTER_VALUES],
+  ): CustomQueryResultTypeMap<T>[CustomQueryType.LOG_TABLE_FILTER_VALUES] {
+    return parserResult;
+  }
 }
 
 export interface CustomQueryParamTypeMap {
@@ -60,6 +67,7 @@
   [CustomQueryType.VIEW_CAPTURE_METADATA]: never;
   [CustomQueryType.VSYNCID]: never;
   [CustomQueryType.WM_WINDOWS_TOKEN_AND_TITLE]: never;
+  [CustomQueryType.LOG_TABLE_FILTER_VALUES]: number;
 }
 
 export interface CustomQueryParserResultTypeMap {
@@ -73,6 +81,7 @@
     token: string;
     title: string;
   }>;
+  [CustomQueryType.LOG_TABLE_FILTER_VALUES]: string[];
 }
 
 export interface CustomQueryResultTypeMap<T> {
@@ -86,6 +95,7 @@
     token: string;
     title: string;
   }>;
+  [CustomQueryType.LOG_TABLE_FILTER_VALUES]: string[];
 }
 
 export class VisitableParserCustomQuery<Q extends CustomQueryType> {
diff --git a/tools/winscope/src/trace/transaction_column_type.ts b/tools/winscope/src/trace/transaction_column_type.ts
new file mode 100644
index 0000000..9288cd9
--- /dev/null
+++ b/tools/winscope/src/trace/transaction_column_type.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export enum TransactionColumnType {
+  TRANSACTION_ID,
+  VSYNC_ID,
+  PID,
+  UID,
+  TRANSACTION_TYPE,
+  LAYER_OR_DISPLAY_ID,
+  FLAGS,
+}
diff --git a/tools/winscope/src/viewers/common/abstract_log_viewer_presenter.ts b/tools/winscope/src/viewers/common/abstract_log_viewer_presenter.ts
index 8f8d1cf..fed60d0 100644
--- a/tools/winscope/src/viewers/common/abstract_log_viewer_presenter.ts
+++ b/tools/winscope/src/viewers/common/abstract_log_viewer_presenter.ts
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+import {assertDefined} from 'common/assert_utils';
 import {isElementVisible, KeyboardEventKey} from 'common/dom_utils';
 import {FunctionUtils} from 'common/function_utils';
 import {Timestamp} from 'common/time/time';
@@ -24,6 +25,7 @@
   WinscopeEventType,
 } from 'messaging/winscope_event';
 import {EmitEvent} from 'messaging/winscope_event_emitter';
+import {CustomQueryType} from 'trace/custom_query';
 import {Trace, TraceEntry} from 'trace/trace';
 import {TraceEntryFinder} from 'trace/trace_entry_finder';
 import {TRACE_INFO} from 'trace/trace_info';
@@ -32,6 +34,7 @@
 import {PropertiesPresenter} from 'viewers/common/properties_presenter';
 import {TextFilter} from 'viewers/common/text_filter';
 import {UserOptions} from 'viewers/common/user_options';
+import {LogSelectFilter} from './log_filters';
 import {LogPresenter} from './log_presenter';
 import {LogEntry, LogHeader, UiDataLog} from './ui_data_log';
 import {
@@ -324,7 +327,7 @@
       const traceName = TRACE_INFO[this.trace.type].name;
       const propertiesStartTime = Date.now();
 
-      const tree = this.getPropertiesTree();
+      const tree = await this.getPropertiesTree();
       this.propertiesPresenter.setPropertiesTree(tree);
       if (updateDefaultAllowlist && this.updateDefaultAllowlist) {
         this.updateDefaultAllowlist(tree);
@@ -362,19 +365,36 @@
     this.uiData.scrollToIndex = this.logPresenter.getScrollToIndex();
   }
 
-  private getPropertiesTree(): PropertyTreeNode | undefined {
+  private async getPropertiesTree(): Promise<PropertyTreeNode | undefined> {
     const entries = this.logPresenter.getFilteredEntries();
     const selectedIndex = this.logPresenter.getSelectedIndex();
     const currentIndex = this.logPresenter.getCurrentIndex();
     if (selectedIndex !== undefined) {
-      return entries.at(selectedIndex)?.propertiesTree;
+      const entry = entries.at(selectedIndex);
+      return (
+        entry?.propertiesTree ??
+        (entry?.getPropertiesTree ? await entry.getPropertiesTree() : undefined)
+      );
     }
     if (currentIndex !== undefined) {
-      return entries.at(currentIndex)?.propertiesTree;
+      const entry = entries.at(currentIndex);
+      return (
+        entry?.propertiesTree ??
+        (entry?.getPropertiesTree ? await entry.getPropertiesTree() : undefined)
+      );
     }
     return undefined;
   }
 
+  protected async updateFilterByCustomQuery(header: LogHeader) {
+    const filterValues = await this.trace.customQuery(
+      CustomQueryType.LOG_TABLE_FILTER_VALUES,
+      assertDefined(header.spec.columnType),
+    );
+    (header.filter as LogSelectFilter).options = filterValues;
+    return;
+  }
+
   protected notifyViewChanged() {
     this.notifyViewCallback(this.uiData);
   }
@@ -384,9 +404,9 @@
     headers: LogHeader[],
   ): Promise<LogEntry[]>;
   protected initializeTraceSpecificData?(): Promise<void>;
-  protected updateFiltersInHeaders?(
+  protected async updateFiltersInHeaders?(
     headers: LogHeader[],
     allEntries: LogEntry[],
-  ): void;
+  ): Promise<void>;
   protected updateDefaultAllowlist?(tree: PropertyTreeNode | undefined): void;
 }
diff --git a/tools/winscope/src/viewers/common/abstract_log_viewer_presenter_test.ts b/tools/winscope/src/viewers/common/abstract_log_viewer_presenter_test.ts
index aa80b96..230d90f 100644
--- a/tools/winscope/src/viewers/common/abstract_log_viewer_presenter_test.ts
+++ b/tools/winscope/src/viewers/common/abstract_log_viewer_presenter_test.ts
@@ -18,6 +18,8 @@
 import {TimestampConverterUtils} from 'common/time/test_utils';
 import {TimeUtils} from 'common/time/time_utils';
 import {TracePositionUpdate} from 'messaging/winscope_event';
+import {setNumRowsSpyQueryResult} from 'trace_processor/test_utils';
+import {TraceProcessor} from 'trace_processor/trace_processor';
 import {
   AbstractLogViewerPresenter,
   NotifyLogViewCallbackType,
@@ -50,6 +52,9 @@
         const presenter = await this.createPresenterWithEmptyTrace(
           (newData: UiData) => (uiData = newData),
         );
+        spyOn(TraceProcessor.prototype, 'queryAllRows').and.returnValue(
+          Promise.resolve(setNumRowsSpyQueryResult(0)),
+        );
         await presenter.onAppEvent(
           TracePositionUpdate.fromTimestamp(
             TimestampConverterUtils.makeRealTimestamp(0n),
diff --git a/tools/winscope/src/viewers/common/log_viewer_presenter_test.ts b/tools/winscope/src/viewers/common/log_viewer_presenter_test.ts
index e123abb..7765a5b 100644
--- a/tools/winscope/src/viewers/common/log_viewer_presenter_test.ts
+++ b/tools/winscope/src/viewers/common/log_viewer_presenter_test.ts
@@ -538,7 +538,7 @@
   });
 
   it('is robust to empty trace', async () => {
-    const trace = makeEmptyTrace(TraceType.TRANSACTIONS);
+    const trace = makeEmptyTrace(TraceType.TRANSITION);
     const presenter = new MockPresenter(
       trace,
       new InMemoryStorage(),
diff --git a/tools/winscope/src/viewers/common/properties_presenter.ts b/tools/winscope/src/viewers/common/properties_presenter.ts
index 6df2f62..baa41cd 100644
--- a/tools/winscope/src/viewers/common/properties_presenter.ts
+++ b/tools/winscope/src/viewers/common/properties_presenter.ts
@@ -135,7 +135,7 @@
     }
 
     const predicatesKeepingChildren = [this.propertiesFilter];
-    const predicatesDiscardingChildren = [];
+    const predicatesDiscardingChildren = [UiTreeUtils.isNotFromTP];
 
     if (this.propertiesDenylist) {
       predicatesDiscardingChildren.push(
diff --git a/tools/winscope/src/viewers/common/ui_data_log.ts b/tools/winscope/src/viewers/common/ui_data_log.ts
index d22a8bb..33e4d58 100644
--- a/tools/winscope/src/viewers/common/ui_data_log.ts
+++ b/tools/winscope/src/viewers/common/ui_data_log.ts
@@ -16,6 +16,7 @@
 
 import {Timestamp} from 'common/time/time';
 import {TraceEntry} from 'trace/trace';
+import {LazyPropertiesStrategyType} from 'trace/tree_node/properties_provider';
 import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
 import {TextFilter} from 'viewers/common/text_filter';
 import {UserOptions} from 'viewers/common/user_options';
@@ -39,6 +40,7 @@
 export interface ColumnSpec {
   name: string;
   cssClass: string;
+  columnType?: number;
   canCopy?: boolean;
 }
 
@@ -50,6 +52,7 @@
   traceEntry: TraceEntry<object>;
   fields: LogField[];
   propertiesTree?: undefined | PropertyTreeNode;
+  getPropertiesTree?: LazyPropertiesStrategyType | undefined;
 }
 
 export interface LogField {
diff --git a/tools/winscope/src/viewers/common/ui_tree_utils.ts b/tools/winscope/src/viewers/common/ui_tree_utils.ts
index 46208ad..7662fdd 100644
--- a/tools/winscope/src/viewers/common/ui_tree_utils.ts
+++ b/tools/winscope/src/viewers/common/ui_tree_utils.ts
@@ -55,6 +55,12 @@
     );
   };
 
+  static isNotFromTP: TreeNodeFilter = (node: TreeNode) => {
+    return (
+      node instanceof UiPropertyTreeNode && node.source !== PropertySource.TP
+    );
+  };
+
   static makeNodeFilter(predicate: StringFilterPredicate): TreeNodeFilter {
     return (node: TreeNode) => {
       return (
diff --git a/tools/winscope/src/viewers/viewer_input/presenter.ts b/tools/winscope/src/viewers/viewer_input/presenter.ts
index e7ffb3e..897629e 100644
--- a/tools/winscope/src/viewers/viewer_input/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_input/presenter.ts
@@ -238,7 +238,7 @@
     return uniqueFieldValues;
   }
 
-  protected override updateFiltersInHeaders(
+  protected override async updateFiltersInHeaders(
     headers: LogHeader[],
     entries: LogEntry[],
   ) {
diff --git a/tools/winscope/src/viewers/viewer_protolog/presenter.ts b/tools/winscope/src/viewers/viewer_protolog/presenter.ts
index ba4d957..b4d190e 100644
--- a/tools/winscope/src/viewers/viewer_protolog/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_protolog/presenter.ts
@@ -121,7 +121,7 @@
     return messages;
   }
 
-  protected override updateFiltersInHeaders(
+  protected override async updateFiltersInHeaders(
     headers: LogHeader[],
     allEntries: ProtologEntry[],
   ) {
diff --git a/tools/winscope/src/viewers/viewer_transactions/operations/set_root_display_name.ts b/tools/winscope/src/viewers/viewer_transactions/operations/set_root_display_name.ts
deleted file mode 100644
index e7d0ab9..0000000
--- a/tools/winscope/src/viewers/viewer_transactions/operations/set_root_display_name.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {Operation} from 'trace/tree_node/operations/operation';
-import {UiPropertyTreeNode} from 'viewers/common/ui_property_tree_node';
-
-export class SetRootDisplayNames implements Operation<UiPropertyTreeNode> {
-  apply(node: UiPropertyTreeNode): void {
-    if (node.id.includes('layerChanges')) {
-      node.setDisplayName('LayerState');
-      return;
-    }
-
-    if (
-      node.id.includes('displayChanges') ||
-      node.id.includes('addedDisplays')
-    ) {
-      node.setDisplayName('DisplayState');
-      return;
-    }
-
-    if (node.id.includes('addedLayers')) {
-      node.setDisplayName('LayerCreationArgs');
-      return;
-    }
-
-    if (node.id.includes('destroyedLayers')) {
-      node.setDisplayName('destroyedLayerId');
-      return;
-    }
-
-    if (node.id.includes('removedDisplays')) {
-      node.setDisplayName('removedDisplayId');
-      return;
-    }
-
-    if (node.id.includes('destroyedLayerHandles')) {
-      node.setDisplayName('destroyedLayerHandleId');
-      return;
-    }
-  }
-}
diff --git a/tools/winscope/src/viewers/viewer_transactions/operations/set_root_display_name_test.ts b/tools/winscope/src/viewers/viewer_transactions/operations/set_root_display_name_test.ts
deleted file mode 100644
index 4b20f9a..0000000
--- a/tools/winscope/src/viewers/viewer_transactions/operations/set_root_display_name_test.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {PropertyTreeBuilder} from 'test/unit/property_tree_builder';
-import {UiPropertyTreeNode} from 'viewers/common/ui_property_tree_node';
-import {SetRootDisplayNames} from './set_root_display_name';
-
-describe('SetRootDisplayNames', () => {
-  let operation: SetRootDisplayNames;
-
-  beforeEach(() => {
-    operation = new SetRootDisplayNames();
-  });
-
-  it('sets display name LayerState', () => {
-    const propertyRoot = UiPropertyTreeNode.from(
-      new PropertyTreeBuilder().setRootId('layerChanges').setName('0').build(),
-    );
-
-    operation.apply(propertyRoot);
-    expect(propertyRoot.getDisplayName()).toEqual('LayerState');
-  });
-
-  it('sets display name DisplayState for change in display', () => {
-    const propertyRoot = UiPropertyTreeNode.from(
-      new PropertyTreeBuilder()
-        .setRootId('displayChanges')
-        .setName('0')
-        .build(),
-    );
-
-    operation.apply(propertyRoot);
-    expect(propertyRoot.getDisplayName()).toEqual('DisplayState');
-  });
-
-  it('sets display name DisplayState for added display', () => {
-    const propertyRoot = UiPropertyTreeNode.from(
-      new PropertyTreeBuilder().setRootId('addedDisplays').setName('0').build(),
-    );
-
-    operation.apply(propertyRoot);
-    expect(propertyRoot.getDisplayName()).toEqual('DisplayState');
-  });
-
-  it('sets display name LayerCreationArgs', () => {
-    const propertyRoot = UiPropertyTreeNode.from(
-      new PropertyTreeBuilder().setRootId('addedLayers').setName('0').build(),
-    );
-
-    operation.apply(propertyRoot);
-    expect(propertyRoot.getDisplayName()).toEqual('LayerCreationArgs');
-  });
-
-  it('sets display name destroyedLayerId', () => {
-    const propertyRoot = UiPropertyTreeNode.from(
-      new PropertyTreeBuilder()
-        .setRootId('destroyedLayers')
-        .setName('0')
-        .build(),
-    );
-
-    operation.apply(propertyRoot);
-    expect(propertyRoot.getDisplayName()).toEqual('destroyedLayerId');
-  });
-
-  it('sets display name removedDisplayId', () => {
-    const propertyRoot = UiPropertyTreeNode.from(
-      new PropertyTreeBuilder()
-        .setRootId('removedDisplays')
-        .setName('0')
-        .build(),
-    );
-
-    operation.apply(propertyRoot);
-    expect(propertyRoot.getDisplayName()).toEqual('removedDisplayId');
-  });
-
-  it('sets display name destroyedLayerHandleId', () => {
-    const propertyRoot = UiPropertyTreeNode.from(
-      new PropertyTreeBuilder()
-        .setRootId('destroyedLayerHandles')
-        .setName('0')
-        .build(),
-    );
-
-    operation.apply(propertyRoot);
-    expect(propertyRoot.getDisplayName()).toEqual('destroyedLayerHandleId');
-  });
-});
diff --git a/tools/winscope/src/viewers/viewer_transactions/presenter.ts b/tools/winscope/src/viewers/viewer_transactions/presenter.ts
index 3a9dba01..057ec6e 100644
--- a/tools/winscope/src/viewers/viewer_transactions/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_transactions/presenter.ts
@@ -17,7 +17,11 @@
 import {assertDefined} from 'common/assert_utils';
 import {PersistentStoreProxy} from 'common/store/persistent_store_proxy';
 import {Store} from 'common/store/store';
+import {TransactionType} from 'parsers/transactions/transaction_type';
 import {Trace} from 'trace/trace';
+import {TransactionColumnType} from 'trace/transaction_column_type';
+import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
+import {LazyPropertiesStrategyType} from 'trace/tree_node/properties_provider';
 import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
 import {
   AbstractLogViewerPresenter,
@@ -29,24 +33,48 @@
 import {TextFilter} from 'viewers/common/text_filter';
 import {LogField, LogHeader} from 'viewers/common/ui_data_log';
 import {UserOptions} from 'viewers/common/user_options';
-import {SetRootDisplayNames} from './operations/set_root_display_name';
-import {TransactionsEntry, TransactionsEntryType, UiData} from './ui_data';
+import {TransactionsEntry, UiData} from './ui_data';
 
 export class Presenter extends AbstractLogViewerPresenter<
   UiData,
-  PropertyTreeNode
+  HierarchyTreeNode
 > {
   private static readonly COLUMNS = {
-    id: {name: 'TX ID', cssClass: 'transaction-id right-align'},
-    vsyncId: {name: 'VSYNC ID', cssClass: 'vsyncid right-align'},
-    pid: {name: 'PID', cssClass: 'pid right-align'},
-    uid: {name: 'UID', cssClass: 'uid right-align'},
-    type: {name: 'TYPE', cssClass: 'transaction-type'},
+    id: {
+      name: 'TX ID',
+      cssClass: 'transaction-id right-align',
+      columnType: TransactionColumnType.TRANSACTION_ID,
+    },
+    vsyncId: {
+      name: 'VSYNC ID',
+      cssClass: 'vsyncid right-align',
+      columnType: TransactionColumnType.VSYNC_ID,
+    },
+    pid: {
+      name: 'PID',
+      cssClass: 'pid right-align',
+      columnType: TransactionColumnType.PID,
+    },
+    uid: {
+      name: 'UID',
+      cssClass: 'uid right-align',
+      columnType: TransactionColumnType.UID,
+    },
+    type: {
+      name: 'TYPE',
+      cssClass: 'transaction-type',
+      columnType: TransactionColumnType.TRANSACTION_TYPE,
+    },
     layerOrDisplayId: {
       name: 'LAYER/DISP ID',
       cssClass: 'layer-or-display-id right-align',
+      columnType: TransactionColumnType.LAYER_OR_DISPLAY_ID,
     },
-    flags: {name: 'Flags', cssClass: 'flags'},
+    flags: {
+      name: 'Flags',
+      cssClass: 'flags',
+      columnType: TransactionColumnType.FLAGS,
+    },
   };
 
   protected override keepCalculated = true;
@@ -69,11 +97,10 @@
     ),
     new TextFilter(),
     [],
-    [new SetRootDisplayNames()],
   );
 
   constructor(
-    trace: Trace<PropertyTreeNode>,
+    trace: Trace<HierarchyTreeNode>,
     readonly storage: Store,
     notifyViewCallback: NotifyLogViewCallbackType<UiData>,
   ) {
@@ -148,288 +175,84 @@
     ) {
       const entry = this.trace.getEntry(traceIndex);
       const entryNode = entryProtos[traceIndex];
-      const vsyncId = Number(
-        assertDefined(entryNode.getChildByName('vsyncId')).getValue(),
-      );
+      const vsyncId = entryNode.getEagerPropertyByName('vsyncId')?.getValue();
 
-      for (const transactionState of assertDefined(
-        entryNode.getChildByName('transactions'),
-      ).getAllChildren()) {
-        const transactionId = assertDefined(
-          transactionState.getChildByName('transactionId'),
+      for (const transactionNode of entryNode.getAllChildren()) {
+        const transactionType = assertDefined(
+          transactionNode.getEagerPropertyByName('transactionType'),
         ).formattedValue();
-        const pid = assertDefined(
-          transactionState.getChildByName('pid'),
-        ).formattedValue();
-        const uid = assertDefined(
-          transactionState.getChildByName('uid'),
-        ).formattedValue();
-        const layerChanges = assertDefined(
-          transactionState.getChildByName('layerChanges'),
-        ).getAllChildren();
+        const transactionId = transactionNode
+          .getEagerPropertyByName('transactionId')
+          ?.formattedValue();
+        const pid = transactionNode
+          .getEagerPropertyByName('pid')
+          ?.formattedValue();
+        const uid = transactionNode
+          .getEagerPropertyByName('uid')
+          ?.formattedValue();
+        const layerId = transactionNode
+          .getEagerPropertyByName('layerId')
+          ?.formattedValue();
+        const displayId = transactionNode
+          .getEagerPropertyByName('displayId')
+          ?.formattedValue();
+        const flags = transactionNode
+          .getEagerPropertyByName('flagsId')
+          ?.formattedValue();
 
-        for (const layerState of layerChanges) {
-          const fields: LogField[] = [
-            {spec: Presenter.COLUMNS.id, value: transactionId},
-            {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
-            {spec: Presenter.COLUMNS.pid, value: pid},
-            {spec: Presenter.COLUMNS.uid, value: uid},
-            {
-              spec: Presenter.COLUMNS.type,
-              value: TransactionsEntryType.LAYER_CHANGED,
-            },
-            {
-              spec: Presenter.COLUMNS.layerOrDisplayId,
-              value: assertDefined(
-                layerState.getChildByName('layerId'),
-              ).formattedValue(),
-            },
-            {
-              spec: Presenter.COLUMNS.flags,
-              value: assertDefined(
-                layerState.getChildByName('what'),
-              ).formattedValue(),
-            },
-          ];
-          entries.push(new TransactionsEntry(entry, fields, layerState));
+        let getPropertiesTree: LazyPropertiesStrategyType | undefined;
+        switch (transactionType) {
+          case TransactionType.LAYER_CHANGED:
+          case TransactionType.DISPLAY_CHANGED:
+          case TransactionType.LAYER_ADDED:
+          case TransactionType.DISPLAY_ADDED:
+            getPropertiesTree = async () => {
+              return await transactionNode.getAllProperties();
+            };
+            break;
+
+          default:
+            // do nothing
+            break;
         }
 
-        const displayChanges = assertDefined(
-          transactionState.getChildByName('displayChanges'),
-        ).getAllChildren();
-        for (const displayState of displayChanges) {
-          const fields: LogField[] = [
-            {spec: Presenter.COLUMNS.id, value: transactionId},
-            {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
-            {spec: Presenter.COLUMNS.pid, value: pid},
-            {spec: Presenter.COLUMNS.uid, value: uid},
-            {
-              spec: Presenter.COLUMNS.type,
-              value: TransactionsEntryType.DISPLAY_CHANGED,
-            },
-            {
-              spec: Presenter.COLUMNS.layerOrDisplayId,
-              value: assertDefined(
-                displayState.getChildByName('id'),
-              ).formattedValue(),
-            },
-            {
-              spec: Presenter.COLUMNS.flags,
-              value: assertDefined(
-                displayState.getChildByName('what'),
-              ).formattedValue(),
-            },
-          ];
-          entries.push(new TransactionsEntry(entry, fields, displayState));
-        }
+        const layerOrDisplayId =
+          (layerId?.length ?? 0) > 0
+            ? assertDefined(layerId)
+            : displayId ?? Presenter.VALUE_NA;
 
-        if (layerChanges.length === 0 && displayChanges.length === 0) {
-          const fields: LogField[] = [
-            {spec: Presenter.COLUMNS.id, value: transactionId},
-            {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
-            {spec: Presenter.COLUMNS.pid, value: pid},
-            {spec: Presenter.COLUMNS.uid, value: uid},
-            {
-              spec: Presenter.COLUMNS.type,
-              value: TransactionsEntryType.NO_OP,
-            },
-            {spec: Presenter.COLUMNS.layerOrDisplayId, value: ''},
-            {spec: Presenter.COLUMNS.flags, value: ''},
-          ];
-          entries.push(new TransactionsEntry(entry, fields, undefined));
-        }
-      }
-
-      for (const layerCreationArgs of assertDefined(
-        entryNode.getChildByName('addedLayers'),
-      ).getAllChildren()) {
         const fields: LogField[] = [
-          {spec: Presenter.COLUMNS.id, value: ''},
-          {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
-          {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA},
-          {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA},
+          {
+            spec: Presenter.COLUMNS.id,
+            value: transactionId ?? Presenter.VALUE_NA,
+          },
+          {spec: Presenter.COLUMNS.vsyncId, value: assertDefined(vsyncId)},
+          {spec: Presenter.COLUMNS.pid, value: pid ?? Presenter.VALUE_NA},
+          {spec: Presenter.COLUMNS.uid, value: uid ?? Presenter.VALUE_NA},
           {
             spec: Presenter.COLUMNS.type,
-            value: TransactionsEntryType.LAYER_ADDED,
+            value: transactionType,
           },
           {
             spec: Presenter.COLUMNS.layerOrDisplayId,
-            value: assertDefined(
-              layerCreationArgs.getChildByName('layerId'),
-            ).formattedValue(),
-          },
-          {spec: Presenter.COLUMNS.flags, value: ''},
-        ];
-        entries.push(new TransactionsEntry(entry, fields, layerCreationArgs));
-      }
-
-      for (const destroyedLayerId of assertDefined(
-        entryNode.getChildByName('destroyedLayers'),
-      ).getAllChildren()) {
-        const fields: LogField[] = [
-          {spec: Presenter.COLUMNS.id, value: ''},
-          {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
-          {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA},
-          {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA},
-          {
-            spec: Presenter.COLUMNS.type,
-            value: TransactionsEntryType.LAYER_DESTROYED,
-          },
-          {
-            spec: Presenter.COLUMNS.layerOrDisplayId,
-            value: destroyedLayerId.formattedValue(),
-          },
-          {spec: Presenter.COLUMNS.flags, value: ''},
-        ];
-        entries.push(new TransactionsEntry(entry, fields, destroyedLayerId));
-      }
-
-      for (const displayState of assertDefined(
-        entryNode.getChildByName('addedDisplays'),
-      ).getAllChildren()) {
-        const fields: LogField[] = [
-          {spec: Presenter.COLUMNS.id, value: ''},
-          {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
-          {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA},
-          {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA},
-          {
-            spec: Presenter.COLUMNS.type,
-            value: TransactionsEntryType.DISPLAY_ADDED,
-          },
-          {
-            spec: Presenter.COLUMNS.layerOrDisplayId,
-            value: assertDefined(
-              displayState.getChildByName('id'),
-            ).formattedValue(),
+            value: layerOrDisplayId,
           },
           {
             spec: Presenter.COLUMNS.flags,
-            value: assertDefined(
-              displayState.getChildByName('what'),
-            ).formattedValue(),
+            value: flags ?? Presenter.VALUE_NA,
           },
         ];
-        entries.push(new TransactionsEntry(entry, fields, displayState));
-      }
-
-      for (const removedDisplayId of assertDefined(
-        entryNode.getChildByName('removedDisplays'),
-      ).getAllChildren()) {
-        const fields: LogField[] = [
-          {spec: Presenter.COLUMNS.id, value: ''},
-          {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
-          {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA},
-          {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA},
-          {
-            spec: Presenter.COLUMNS.type,
-            value: TransactionsEntryType.DISPLAY_REMOVED,
-          },
-          {
-            spec: Presenter.COLUMNS.layerOrDisplayId,
-            value: removedDisplayId.formattedValue(),
-          },
-          {spec: Presenter.COLUMNS.flags, value: ''},
-        ];
-        entries.push(new TransactionsEntry(entry, fields, removedDisplayId));
-      }
-
-      for (const destroyedLayerHandleId of assertDefined(
-        entryNode.getChildByName('destroyedLayerHandles'),
-      ).getAllChildren()) {
-        const fields: LogField[] = [
-          {spec: Presenter.COLUMNS.id, value: ''},
-          {spec: Presenter.COLUMNS.vsyncId, value: vsyncId},
-          {spec: Presenter.COLUMNS.pid, value: Presenter.VALUE_NA},
-          {spec: Presenter.COLUMNS.uid, value: Presenter.VALUE_NA},
-          {
-            spec: Presenter.COLUMNS.type,
-            value: TransactionsEntryType.LAYER_HANDLE_DESTROYED,
-          },
-          {
-            spec: Presenter.COLUMNS.layerOrDisplayId,
-            value: destroyedLayerHandleId.formattedValue(),
-          },
-          {spec: Presenter.COLUMNS.flags, value: ''},
-        ];
-        entries.push(
-          new TransactionsEntry(entry, fields, destroyedLayerHandleId),
-        );
+        entries.push(new TransactionsEntry(entry, fields, getPropertiesTree));
       }
     }
-
     return entries;
   }
 
-  protected override updateFiltersInHeaders(
-    headers: LogHeader[],
-    allEntries: TransactionsEntry[],
-  ) {
+  protected override async updateFiltersInHeaders(headers: LogHeader[]) {
     for (const header of headers) {
-      if (header.spec === Presenter.COLUMNS.flags) {
-        (assertDefined(header.filter) as LogSelectFilter).options =
-          this.getUniqueUiDataEntryValues(
-            allEntries,
-            (entry: TransactionsEntry) =>
-              assertDefined(
-                entry.fields.find((f) => f.spec === header.spec)
-                  ?.value as string,
-              )
-                .split('|')
-                .map((flag) => flag.trim()),
-          );
-      } else {
-        (assertDefined(header.filter) as LogSelectFilter).options =
-          this.getUniqueUiDataEntryValues(
-            allEntries,
-            (entry: TransactionsEntry) =>
-              assertDefined(
-                entry.fields.find((f) => f.spec === header.spec),
-              ).value.toString(),
-          );
-      }
+      this.updateFilterByCustomQuery(header);
     }
   }
-
-  private getUniqueUiDataEntryValues<T>(
-    entries: TransactionsEntry[],
-    getValue: (entry: TransactionsEntry) => T | T[],
-  ): T[] {
-    const uniqueValues = new Set<T>();
-    entries.forEach((entry: TransactionsEntry) => {
-      const value = getValue(entry);
-      if (Array.isArray(value)) {
-        value.forEach((val) => uniqueValues.add(val));
-      } else {
-        uniqueValues.add(value);
-      }
-    });
-
-    const result = [...uniqueValues];
-
-    result.sort((a, b) => {
-      const aIsNumber = !isNaN(Number(a));
-      const bIsNumber = !isNaN(Number(b));
-
-      if (aIsNumber && bIsNumber) {
-        return Number(a) - Number(b);
-      } else if (aIsNumber) {
-        return 1; // place number after strings in the result
-      } else if (bIsNumber) {
-        return -1; // place number after strings in the result
-      }
-
-      // a and b are both strings
-      if (a < b) {
-        return -1;
-      } else if (a > b) {
-        return 1;
-      } else {
-        return 0;
-      }
-    });
-
-    return result;
-  }
 }
 
 const layerChangeFlagToPropertiesMap = new Map([
diff --git a/tools/winscope/src/viewers/viewer_transactions/presenter_test.ts b/tools/winscope/src/viewers/viewer_transactions/presenter_test.ts
index 28bc0ee..b2bdfd7 100644
--- a/tools/winscope/src/viewers/viewer_transactions/presenter_test.ts
+++ b/tools/winscope/src/viewers/viewer_transactions/presenter_test.ts
@@ -23,7 +23,8 @@
 import {makeEmptyTrace} from 'test/unit/trace_utils';
 import {Trace} from 'trace/trace';
 import {TraceType} from 'trace/trace_type';
-import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+import {TransactionColumnType} from 'trace/transaction_column_type';
+import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
 import {NotifyLogViewCallbackType} from 'viewers/common/abstract_log_viewer_presenter';
 import {AbstractLogViewerPresenterTest} from 'viewers/common/abstract_log_viewer_presenter_test';
 import {LogSelectFilter} from 'viewers/common/log_filters';
@@ -35,33 +36,53 @@
   override readonly expectedHeaders = [
     {
       header: new LogHeader(
-        {name: 'TX ID', cssClass: 'transaction-id right-align'},
+        {
+          name: 'TX ID',
+          cssClass: 'transaction-id right-align',
+          columnType: TransactionColumnType.TRANSACTION_ID,
+        },
         new LogSelectFilter(Array.from({length: 1295}, () => '')),
       ),
     },
     {
       header: new LogHeader(
-        {name: 'VSYNC ID', cssClass: 'vsyncid right-align'},
-        new LogSelectFilter(Array.from({length: 710}, () => '')),
+        {
+          name: 'VSYNC ID',
+          cssClass: 'vsyncid right-align',
+          columnType: TransactionColumnType.VSYNC_ID,
+        },
+        new LogSelectFilter(Array.from({length: 712}, () => '')),
       ),
     },
     {
       header: new LogHeader(
-        {name: 'PID', cssClass: 'pid right-align'},
+        {
+          name: 'PID',
+          cssClass: 'pid right-align',
+          columnType: TransactionColumnType.PID,
+        },
         new LogSelectFilter(Array.from({length: 8}, () => '')),
       ),
       options: ['N/A', '0', '515', '1593', '2022', '2322', '2463', '3300'],
     },
     {
       header: new LogHeader(
-        {name: 'UID', cssClass: 'uid right-align'},
+        {
+          name: 'UID',
+          cssClass: 'uid right-align',
+          columnType: TransactionColumnType.UID,
+        },
         new LogSelectFilter(Array.from({length: 6}, () => '')),
       ),
       options: ['N/A', '1000', '1003', '10169', '10235', '10239'],
     },
     {
       header: new LogHeader(
-        {name: 'TYPE', cssClass: 'transaction-type'},
+        {
+          name: 'TYPE',
+          cssClass: 'transaction-type',
+          columnType: TransactionColumnType.TRANSACTION_TYPE,
+        },
         new LogSelectFilter(Array.from({length: 6}, () => '')),
       ),
       options: [
@@ -70,28 +91,72 @@
         'LAYER_CHANGED',
         'LAYER_DESTROYED',
         'LAYER_HANDLE_DESTROYED',
-        'NO_OP',
+        'NOOP',
       ],
     },
     {
       header: new LogHeader(
-        {name: 'LAYER/DISP ID', cssClass: 'layer-or-display-id right-align'},
-        new LogSelectFilter(Array.from({length: 117}, () => '')),
+        {
+          name: 'LAYER/DISP ID',
+          cssClass: 'layer-or-display-id right-align',
+          columnType: TransactionColumnType.LAYER_OR_DISPLAY_ID,
+        },
+        new LogSelectFilter(Array.from({length: 116}, () => '')),
       ),
+      options: [
+        'N/A',
+        ...Array.from({length: 114}, (_, i) => `${i + 1}`),
+        '4294967295',
+      ],
     },
     {
       header: new LogHeader(
-        {name: 'Flags', cssClass: 'flags'},
+        {
+          name: 'Flags',
+          cssClass: 'flags',
+          columnType: TransactionColumnType.FLAGS,
+        },
         new LogSelectFilter(
-          Array.from({length: 30}, () => ''),
+          Array.from({length: 29}, () => ''),
           true,
           '250',
           '100%',
         ),
       ),
+      options: [
+        'eAcquireFenceChanged',
+        'eAlphaChanged',
+        'eAutoRefreshChanged',
+        'eBackgroundBlurRadiusChanged',
+        'eBufferChanged',
+        'eBufferCropChanged',
+        'eBufferTransformChanged',
+        'eColorChanged',
+        'eColorSpaceAgnosticChanged',
+        'eCornerRadiusChanged',
+        'eCropChanged',
+        'eDataspaceChanged',
+        'eDestinationFrameChanged',
+        'eDisplayProjectionChanged',
+        'eFlagsChanged',
+        'eFrameRateSelectionPriority',
+        'eHasListenerCallbacksChanged',
+        'eHdrMetadataChanged',
+        'eInputInfoChanged',
+        'eLayerChanged',
+        'eLayerStackChanged',
+        'eMatrixChanged',
+        'eMetadataChanged',
+        'ePositionChanged',
+        'eProducerDisconnect',
+        'eRelativeLayerChanged',
+        'eReparent',
+        'eSurfaceDamageRegionChanged',
+        'eTransformToDisplayInverseChanged',
+      ],
     },
   ];
-  private trace: Trace<PropertyTreeNode> | undefined;
+  private trace: Trace<HierarchyTreeNode> | undefined;
   private positionUpdate: TracePositionUpdate | undefined;
 
   override executeSpecializedTests() {
@@ -162,8 +227,8 @@
     const parser = await new LegacyParserProvider()
       .addFilename('traces/elapsed_and_real_timestamp/Transactions.pb')
       .setConvertToPerfetto(true)
-      .getParser<PropertyTreeNode>();
-    this.trace = new TraceBuilder<PropertyTreeNode>()
+      .getParser<HierarchyTreeNode>();
+    this.trace = new TraceBuilder<HierarchyTreeNode>()
       .setType(TraceType.TRANSACTIONS)
       .setParser(parser)
       .build();
diff --git a/tools/winscope/src/viewers/viewer_transactions/ui_data.ts b/tools/winscope/src/viewers/viewer_transactions/ui_data.ts
index f5bfe1a..2646cde 100644
--- a/tools/winscope/src/viewers/viewer_transactions/ui_data.ts
+++ b/tools/winscope/src/viewers/viewer_transactions/ui_data.ts
@@ -15,7 +15,8 @@
  */
 
 import {TraceEntry} from 'trace/trace';
-import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
+import {LazyPropertiesStrategyType} from 'trace/tree_node/properties_provider';
 import {
   LogEntry,
   LogField,
@@ -45,19 +46,8 @@
 
 export class TransactionsEntry implements LogEntry {
   constructor(
-    public traceEntry: TraceEntry<PropertyTreeNode>,
+    public traceEntry: TraceEntry<HierarchyTreeNode>,
     public fields: LogField[],
-    public propertiesTree: PropertyTreeNode | undefined,
+    public getPropertiesTree: LazyPropertiesStrategyType | undefined,
   ) {}
 }
-
-export enum TransactionsEntryType {
-  DISPLAY_ADDED = 'DISPLAY_ADDED',
-  DISPLAY_REMOVED = 'DISPLAY_REMOVED',
-  DISPLAY_CHANGED = 'DISPLAY_CHANGED',
-  LAYER_ADDED = 'LAYER_ADDED',
-  LAYER_DESTROYED = 'LAYER_DESTROYED',
-  LAYER_CHANGED = 'LAYER_CHANGED',
-  LAYER_HANDLE_DESTROYED = 'LAYER_HANDLE_DESTROYED',
-  NO_OP = 'NO_OP',
-}
diff --git a/tools/winscope/src/viewers/viewer_transactions/viewer_transactions.ts b/tools/winscope/src/viewers/viewer_transactions/viewer_transactions.ts
index ce1f62e..ed0244c 100644
--- a/tools/winscope/src/viewers/viewer_transactions/viewer_transactions.ts
+++ b/tools/winscope/src/viewers/viewer_transactions/viewer_transactions.ts
@@ -18,21 +18,21 @@
 import {Trace} from 'trace/trace';
 import {Traces} from 'trace/traces';
 import {TraceType} from 'trace/trace_type';
-import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
 import {AbstractViewer} from 'viewers/abstract_viewer';
 import {ViewerComponent} from 'viewers/components/viewer_component';
 import {Presenter} from './presenter';
 import {UiData} from './ui_data';
 
-export class ViewerTransactions extends AbstractViewer<PropertyTreeNode> {
+export class ViewerTransactions extends AbstractViewer<HierarchyTreeNode> {
   static readonly DEPENDENCIES: TraceType[] = [TraceType.TRANSACTIONS];
 
-  constructor(trace: Trace<PropertyTreeNode>, traces: Traces, store: Store) {
+  constructor(trace: Trace<HierarchyTreeNode>, traces: Traces, store: Store) {
     super(trace, traces, 'viewer-transactions', store);
   }
 
   protected override initializePresenter(
-    trace: Trace<PropertyTreeNode>,
+    trace: Trace<HierarchyTreeNode>,
     traces: Traces,
     store: Store,
   ): Presenter {
diff --git a/tools/winscope/src/viewers/viewer_transactions/viewer_transactions_component.ts b/tools/winscope/src/viewers/viewer_transactions/viewer_transactions_component.ts
index d1b75fb..864ed53 100644
--- a/tools/winscope/src/viewers/viewer_transactions/viewer_transactions_component.ts
+++ b/tools/winscope/src/viewers/viewer_transactions/viewer_transactions_component.ts
@@ -51,7 +51,7 @@
         [traceType]="${TraceType.TRANSACTIONS}"
         [isProtoDump]="false"
         [textFilter]="inputData?.propertiesFilter"
-        placeholderText="No current or selected transaction."
+        placeholderText="No current or selected transaction with additional properties."
         (collapseButtonClicked)="sections.onCollapseStateChange(CollapsibleSectionType.PROPERTIES, true)"
         [class.collapsed]="sections.isSectionCollapsed(CollapsibleSectionType.PROPERTIES)"></properties-view>
     </div>
diff --git a/tools/winscope/src/viewers/viewer_transactions/viewer_transactions_component_test.ts b/tools/winscope/src/viewers/viewer_transactions/viewer_transactions_component_test.ts
index 4a0a3f6..0f9c87a 100644
--- a/tools/winscope/src/viewers/viewer_transactions/viewer_transactions_component_test.ts
+++ b/tools/winscope/src/viewers/viewer_transactions/viewer_transactions_component_test.ts
@@ -17,10 +17,11 @@
 import {CdkVirtualScrollViewport} from '@angular/cdk/scrolling';
 import {TimestampConverterUtils} from 'common/time/test_utils';
 import {DOMTestHelper} from 'test/unit/dom_test_utils';
+import {HierarchyTreeBuilder} from 'test/unit/hierarchy_tree_builder';
 import {PropertyTreeBuilder} from 'test/unit/property_tree_builder';
 import {TraceBuilder} from 'test/unit/trace_builder';
 import {TraceType} from 'trace/trace_type';
-import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
 import {AbstractLogViewerComponentTest} from 'viewers/common/abstract_log_viewer_component_test';
 import {LogSelectFilter} from 'viewers/common/log_filters';
 import {LogHeader} from 'viewers/common/ui_data_log';
@@ -36,7 +37,7 @@
   protected override readonly propertiesSectionTitle =
     'PROPERTIES - PROTO DUMP';
   protected override readonly propertiesPlaceholder =
-    'No current or selected transaction.';
+    'No current or selected transaction with additional properties.';
 
   protected override checkTimestampInTable(
     dom: DOMTestHelper<ViewerTransactionsComponent>,
@@ -52,6 +53,11 @@
       ViewerTransactionsComponent,
     ]
   > {
+    const hierarchyTree = new HierarchyTreeBuilder()
+      .setId('Transactions')
+      .setName('tree')
+      .build();
+
     const propertiesTree = new PropertyTreeBuilder()
       .setRootId('Transactions')
       .setName('tree')
@@ -60,15 +66,15 @@
 
     const ts = TimestampConverterUtils.makeElapsedTimestamp(1n);
 
-    const trace = new TraceBuilder<PropertyTreeNode>()
-      .setEntries([propertiesTree, propertiesTree])
+    const trace = new TraceBuilder<HierarchyTreeNode>()
+      .setEntries([hierarchyTree, hierarchyTree])
       .setTimestamps([ts, ts])
       .build();
 
     const entry1 = new TransactionsEntry(
       trace.getEntry(0),
       Array.from({length: 7}, () => this.testField),
-      propertiesTree,
+      async () => propertiesTree,
     );
 
     const uiData = new UiData(
@@ -88,6 +94,11 @@
   protected override async setUpTestEnvironmentForScroll(): Promise<
     [DOMTestHelper<ViewerTransactionsComponent>, CdkVirtualScrollViewport]
   > {
+    const hierarchyTree = new HierarchyTreeBuilder()
+      .setId('Transactions')
+      .setName('tree')
+      .build();
+
     const propertiesTree = new PropertyTreeBuilder()
       .setRootId('Transactions')
       .setName('tree')
@@ -96,9 +107,9 @@
 
     const ts = TimestampConverterUtils.makeElapsedTimestamp(1n);
 
-    const trace = new TraceBuilder<PropertyTreeNode>()
+    const trace = new TraceBuilder<HierarchyTreeNode>()
       .setType(TraceType.TRANSACTIONS)
-      .setEntries([propertiesTree, propertiesTree])
+      .setEntries([hierarchyTree, hierarchyTree])
       .setTimestamps([ts, ts])
       .build();
 
@@ -124,7 +135,7 @@
             value: i % 2 === 0 ? shortMessage : longMessage,
           },
         ]),
-        propertiesTree,
+        async () => propertiesTree,
       );
       uiData.entries.push(entry);
     }
diff --git a/tools/winscope/src/viewers/viewer_transitions/presenter.ts b/tools/winscope/src/viewers/viewer_transitions/presenter.ts
index 210ae45..301e0dd 100644
--- a/tools/winscope/src/viewers/viewer_transitions/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_transitions/presenter.ts
@@ -141,7 +141,7 @@
     return transitions;
   }
 
-  protected override updateFiltersInHeaders(headers: LogHeader[]) {
+  protected override async updateFiltersInHeaders(headers: LogHeader[]) {
     headers.forEach((header) => {
       if (!(header.filter instanceof LogSelectFilter)) return;
       header.filter.options = this.getUniqueUiDataEntryValues(header.spec);