Transition refactor.

Final presenter/parser integration.
Cleaned up and fixed transitions in timeline (showing only play stage of transitions).

Bug: b/311643292
Test: npm run test:unit:ci

Change-Id: I1f1be39adcd8b1bf71ec4ad5829121e2e5bec636
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/expanded_timeline_component_test.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/expanded_timeline_component_test.ts
index 3bc3469..c81c89f 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/expanded_timeline_component_test.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/expanded_timeline_component_test.ts
@@ -28,7 +28,7 @@
 import {TimelineData} from 'app/timeline_data';
 import {assertDefined} from 'common/assert_utils';
 import {RealTimestamp} from 'common/time';
-import {Transition} from 'flickerlib/common';
+import {PropertyTreeBuilder} from 'test/unit/property_tree_builder';
 import {TracesBuilder} from 'test/unit/traces_builder';
 import {TracePosition} from 'trace/trace_position';
 import {TraceType} from 'trace/trace_type';
@@ -78,14 +78,38 @@
       .setEntries(TraceType.TRANSACTIONS, [{}])
       .setTimestamps(TraceType.TRANSACTIONS, [new RealTimestamp(12n)])
       .setEntries(TraceType.TRANSITION, [
-        {
-          createTime: {unixNanos: 10n},
-          finishTime: {unixNanos: 30n},
-        } as Transition,
-        {
-          createTime: {unixNanos: 60n},
-          finishTime: {unixNanos: 110n},
-        } as Transition,
+        new PropertyTreeBuilder()
+          .setIsRoot(true)
+          .setRootId('TransitionsTraceEntry')
+          .setName('transition')
+          .setChildren([
+            {
+              name: 'wmData',
+              children: [{name: 'finishTimeNs', value: 30n}],
+            },
+            {
+              name: 'shellData',
+              children: [{name: 'dispatchTimeNs', value: 10n}],
+            },
+            {name: 'aborted', value: false},
+          ])
+          .build(),
+        new PropertyTreeBuilder()
+          .setIsRoot(true)
+          .setRootId('TransitionsTraceEntry')
+          .setName('transition')
+          .setChildren([
+            {
+              name: 'wmData',
+              children: [{name: 'finishTimeNs', value: 110n}],
+            },
+            {
+              name: 'shellData',
+              children: [{name: 'dispatchTimeNs', value: 60n}],
+            },
+            {name: 'aborted', value: false},
+          ])
+          .build(),
       ])
       .setTimestamps(TraceType.TRANSITION, [new RealTimestamp(10n), new RealTimestamp(60n)])
       .setTimestamps(TraceType.PROTO_LOG, [])
@@ -116,7 +140,7 @@
 
     // initially only first entry of SF is set
     singleTimelines.forEach((timeline) => {
-      if (timeline.trace.type === TraceType.SURFACE_FLINGER) {
+      if (assertDefined(timeline.trace).type === TraceType.SURFACE_FLINGER) {
         const entry = assertDefined(timeline.selectedEntry);
         expect(entry.getFullTrace().type).toBe(TraceType.SURFACE_FLINGER);
       } else {
@@ -130,7 +154,9 @@
 
   it('passes selectedEntry of correct type into each timeline on position change', () => {
     // 3 out of the 5 traces have timestamps before or at 11n
-    component.timelineData.setPosition(TracePosition.fromTimestamp(new RealTimestamp(11n)));
+    assertDefined(component.timelineData).setPosition(
+      TracePosition.fromTimestamp(new RealTimestamp(11n))
+    );
     fixture.detectChanges();
 
     const singleTimelines = assertDefined(component.singleTimelines);
@@ -139,24 +165,28 @@
     singleTimelines.forEach((timeline) => {
       // protolog and transactions traces have no timestamps before current position
       if (
-        timeline.trace.type === TraceType.PROTO_LOG ||
-        timeline.trace.type === TraceType.TRANSACTIONS
+        assertDefined(timeline.trace).type === TraceType.PROTO_LOG ||
+        assertDefined(timeline.trace).type === TraceType.TRANSACTIONS
       ) {
         expect(timeline.selectedEntry).toBeUndefined();
       } else {
         const selectedEntry = assertDefined(timeline.selectedEntry);
-        expect(selectedEntry.getFullTrace().type).toEqual(timeline.trace.type);
+        expect(selectedEntry.getFullTrace().type).toEqual(assertDefined(timeline.trace).type);
       }
     });
 
     const transitionTimeline = assertDefined(component.transitionTimelines).first;
     const selectedEntry = assertDefined(transitionTimeline.selectedEntry);
-    expect(selectedEntry.getFullTrace().type).toEqual(transitionTimeline.trace.type);
+    expect(selectedEntry.getFullTrace().type).toEqual(assertDefined(transitionTimeline.trace).type);
   });
 
   it('getAllLoadedTraces causes timelines to render in correct order', () => {
     // traces in timelineData are in order of being set in Traces API
-    expect(component.timelineData.getTraces().mapTrace((trace) => trace.type)).toEqual([
+    expect(
+      assertDefined(component.timelineData)
+        .getTraces()
+        .mapTrace((trace) => trace.type)
+    ).toEqual([
       TraceType.SURFACE_FLINGER,
       TraceType.WINDOW_MANAGER,
       TraceType.TRANSACTIONS,
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component.ts
index 0eb262b..472baaa 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component.ts
@@ -15,13 +15,15 @@
  */
 
 import {Component, ElementRef, EventEmitter, Input, Output, ViewChild} from '@angular/core';
+import {assertDefined} from 'common/assert_utils';
 import {Point} from 'common/geometry_types';
 import {Rect} from 'common/rect';
-import {ElapsedTimestamp, RealTimestamp, TimeRange, Timestamp, TimestampType} from 'common/time';
-import {Transition} from 'flickerlib/common';
+import {TimeRange, Timestamp} from 'common/time';
 import {Trace, TraceEntry} from 'trace/trace';
 import {TracePosition} from 'trace/trace_position';
 import {TraceType} from 'trace/trace_type';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+import {TimelineUtils} from '../timeline_utils';
 import {AbstractTimelineRowComponent} from './abstract_timeline_row_component';
 
 @Component({
@@ -29,7 +31,7 @@
   template: `
     <div
       class="transition-timeline"
-      matTooltip="Some or all transitions will not be rendered in timeline due to unknown creation time"
+      matTooltip="Some or all transitions will not be rendered in timeline due to unknown dispatch time"
       [matTooltipDisabled]="shouldNotRenderEntries.length === 0"
       #wrapper>
       <canvas #canvas></canvas>
@@ -43,19 +45,18 @@
     `,
   ],
 })
-export class TransitionTimelineComponent extends AbstractTimelineRowComponent<Transition> {
+export class TransitionTimelineComponent extends AbstractTimelineRowComponent<PropertyTreeNode> {
   @Input() color = '#AF5CF7';
-  @Input() trace!: Trace<Transition>;
-  @Input() selectedEntry: TraceEntry<Transition> | undefined = undefined;
-  @Input() selectionRange!: TimeRange;
+  @Input() trace: Trace<PropertyTreeNode> | undefined;
+  @Input() selectedEntry: TraceEntry<PropertyTreeNode> | undefined;
+  @Input() selectionRange: TimeRange | undefined;
 
   @Output() readonly onTracePositionUpdate = new EventEmitter<TracePosition>();
 
-  @ViewChild('canvas', {static: false}) override canvasRef!: ElementRef;
-  @ViewChild('wrapper', {static: false}) override wrapperRef!: ElementRef;
+  @ViewChild('canvas', {static: false}) override canvasRef: ElementRef | undefined;
+  @ViewChild('wrapper', {static: false}) override wrapperRef: ElementRef | undefined;
 
-  hoveringEntry?: TraceEntry<Transition>;
-
+  hoveringEntry?: TraceEntry<PropertyTreeNode>;
   rowsToUse = new Map<number, number>();
   maxRowsRequires = 0;
   shouldNotRenderEntries: number[] = [];
@@ -70,20 +71,30 @@
   private async computeRowsForEntries(): Promise<void> {
     const rowAvailableFrom: Array<bigint | undefined> = [];
     await Promise.all(
-      (this.trace as Trace<Transition>).mapEntry(async (entry) => {
+      (this.trace as Trace<PropertyTreeNode>).mapEntry(async (entry) => {
         const transition = await entry.getValue();
         const index = entry.getIndex();
-        if (transition.createTime.isMin) {
+
+        const timeRange = TimelineUtils.getTimeRangeForTransition(
+          transition,
+          entry.getTimestamp().getType(),
+          assertDefined(this.selectionRange)
+        );
+
+        if (!timeRange) {
           this.shouldNotRenderEntries.push(index);
         }
+
         let rowToUse = 0;
         while (
-          (rowAvailableFrom[rowToUse] ?? 0n) > BigInt(transition.createTime.unixNanos.toString())
+          (rowAvailableFrom[rowToUse] ?? 0n) >
+          (timeRange?.from.getValueNs() ?? assertDefined(this.selectionRange).from.getValueNs())
         ) {
           rowToUse++;
         }
 
-        rowAvailableFrom[rowToUse] = BigInt(transition.finishTime.unixNanos.toString());
+        rowAvailableFrom[rowToUse] =
+          timeRange?.to.getValueNs() ?? assertDefined(this.selectionRange).to.getValueNs();
 
         if (rowToUse + 1 > this.maxRowsRequires) {
           this.maxRowsRequires = rowToUse + 1;
@@ -93,7 +104,7 @@
     );
   }
 
-  private getRowToUseFor(entry: TraceEntry<Transition>): number {
+  private getRowToUseFor(entry: TraceEntry<PropertyTreeNode>): number {
     const rowToUse = this.rowsToUse.get(entry.getIndex());
     if (rowToUse === undefined) {
       console.error('Failed to find', entry, 'in', this.rowsToUse);
@@ -124,33 +135,49 @@
 
     this.hoveringEntry = currentHoverEntry;
 
-    if (!this.hoveringEntry || this.shouldNotRenderEntry(this.hoveringEntry)) {
+    if (!this.hoveringEntry) {
       return;
     }
 
-    const hoveringSegment = await this.getSegmentForTransition(this.hoveringEntry);
+    const transition = await this.hoveringEntry.getValue();
+    const timeRange = TimelineUtils.getTimeRangeForTransition(
+      transition,
+      this.hoveringEntry.getTimestamp().getType(),
+      assertDefined(this.selectionRange)
+    );
+
+    if (!timeRange) {
+      return;
+    }
+
     const rowToUse = this.getRowToUseFor(this.hoveringEntry);
-    const rect = this.getSegmentRect(hoveringSegment.from, hoveringSegment.to, rowToUse);
+    const rect = this.getSegmentRect(timeRange.from, timeRange.to, rowToUse);
     this.canvasDrawer.drawRectBorder(rect);
   }
 
   protected override async getEntryAt(
     mousePoint: Point
-  ): Promise<TraceEntry<Transition> | undefined> {
-    if (this.trace.type !== TraceType.TRANSITION) {
+  ): Promise<TraceEntry<PropertyTreeNode> | undefined> {
+    if (assertDefined(this.trace).type !== TraceType.TRANSITION) {
       return undefined;
     }
 
-    const transitionEntries: Array<Promise<TraceEntry<Transition> | undefined>> = [];
-    this.trace.forEachEntry((entry) => {
+    const transitionEntries: Array<Promise<TraceEntry<PropertyTreeNode> | undefined>> = [];
+    assertDefined(this.trace).forEachEntry((entry) => {
       transitionEntries.push(
         (async () => {
-          if (this.shouldNotRenderEntry(entry)) {
+          const transition = await entry.getValue();
+          const timeRange = TimelineUtils.getTimeRangeForTransition(
+            transition,
+            entry.getTimestamp().getType(),
+            assertDefined(this.selectionRange)
+          );
+
+          if (!timeRange) {
             return undefined;
           }
-          const transitionSegment = await this.getSegmentForTransition(entry);
           const rowToUse = this.getRowToUseFor(entry);
-          const rect = this.getSegmentRect(transitionSegment.from, transitionSegment.to, rowToUse);
+          const rect = this.getSegmentRect(timeRange.from, timeRange.to, rowToUse);
           if (rect.containsPoint(mousePoint)) {
             return entry;
           }
@@ -178,16 +205,16 @@
   }
 
   private getXPosOf(entry: Timestamp): number {
-    const start = this.selectionRange.from.getValueNs();
-    const end = this.selectionRange.to.getValueNs();
+    const start = assertDefined(this.selectionRange).from.getValueNs();
+    const end = assertDefined(this.selectionRange).to.getValueNs();
 
     return Number((BigInt(this.availableWidth) * (entry.getValueNs() - start)) / (end - start));
   }
 
   private getSegmentRect(start: Timestamp, end: Timestamp, rowToUse: number): Rect {
     const xPosStart = this.getXPosOf(start);
-    const selectionStart = this.selectionRange.from.getValueNs();
-    const selectionEnd = this.selectionRange.to.getValueNs();
+    const selectionStart = assertDefined(this.selectionRange).from.getValueNs();
+    const selectionEnd = assertDefined(this.selectionRange).to.getValueNs();
 
     const width = Number(
       (BigInt(this.availableWidth) * (end.getValueNs() - start.getValueNs())) /
@@ -212,37 +239,24 @@
 
   override async drawTimeline() {
     await Promise.all(
-      (this.trace as Trace<Transition>).mapEntry(async (entry) => {
-        if (this.shouldNotRenderEntry(entry)) {
+      (this.trace as Trace<PropertyTreeNode>).mapEntry(async (entry) => {
+        const transition = await entry.getValue();
+        const timeRange = TimelineUtils.getTimeRangeForTransition(
+          transition,
+          entry.getTimestamp().getType(),
+          assertDefined(this.selectionRange)
+        );
+        if (!timeRange) {
           return;
         }
-        const transitionSegment = await this.getSegmentForTransition(entry);
         const rowToUse = this.getRowToUseFor(entry);
-        const aborted = (await entry.getValue()).aborted;
-        this.drawSegment(transitionSegment.from, transitionSegment.to, rowToUse, aborted);
+        const aborted = assertDefined(transition.getChildByName('aborted')).getValue();
+        this.drawSegment(timeRange.from, timeRange.to, rowToUse, aborted);
       })
     );
     this.drawSelectedTransitionEntry();
   }
 
-  private async getSegmentForTransition(entry: TraceEntry<Transition>): Promise<TimeRange> {
-    const transition = await entry.getValue();
-
-    let createTime: Timestamp;
-    let finishTime: Timestamp;
-    if (entry.getTimestamp().getType() === TimestampType.REAL) {
-      createTime = new RealTimestamp(BigInt(transition.createTime.unixNanos.toString()));
-      finishTime = new RealTimestamp(BigInt(transition.finishTime.unixNanos.toString()));
-    } else if (entry.getTimestamp().getType() === TimestampType.ELAPSED) {
-      createTime = new ElapsedTimestamp(BigInt(transition.createTime.elapsedNanos.toString()));
-      finishTime = new ElapsedTimestamp(BigInt(transition.finishTime.elapsedNanos.toString()));
-    } else {
-      throw new Error('Unspported timestamp type');
-    }
-
-    return {from: createTime, to: finishTime};
-  }
-
   private drawSegment(start: Timestamp, end: Timestamp, rowToUse: number, aborted: boolean) {
     const rect = this.getSegmentRect(start, end, rowToUse);
     const alpha = aborted ? 0.25 : 1.0;
@@ -250,21 +264,23 @@
   }
 
   private async drawSelectedTransitionEntry() {
-    if (this.selectedEntry === undefined || this.shouldNotRenderEntry(this.selectedEntry)) {
+    if (this.selectedEntry === undefined) {
+      return;
+    }
+    const transition = await this.selectedEntry.getValue();
+    const timeRange = TimelineUtils.getTimeRangeForTransition(
+      transition,
+      this.selectedEntry.getTimestamp().getType(),
+      assertDefined(this.selectionRange)
+    );
+    if (!timeRange) {
       return;
     }
 
-    const transitionSegment = await this.getSegmentForTransition(this.selectedEntry);
-
-    const transition = await this.selectedEntry.getValue();
     const rowIndex = this.getRowToUseFor(this.selectedEntry);
-    const rect = this.getSegmentRect(transitionSegment.from, transitionSegment.to, rowIndex);
-    const alpha = transition.aborted ? 0.25 : 1.0;
+    const rect = this.getSegmentRect(timeRange.from, timeRange.to, rowIndex);
+    const alpha = transition.getChildByName('aborted') ? 0.25 : 1.0;
     this.canvasDrawer.drawRect(rect, this.color, alpha);
     this.canvasDrawer.drawRectBorder(rect);
   }
-
-  private shouldNotRenderEntry(entry: TraceEntry<Transition>): boolean {
-    return this.shouldNotRenderEntries.includes(entry.getIndex());
-  }
 }
diff --git a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts
index 2c2b149..bacad72 100644
--- a/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts
+++ b/tools/winscope/src/app/components/timeline/expanded-timeline/transition_timeline_component_test.ts
@@ -27,10 +27,11 @@
 import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
 import {Rect} from 'common/rect';
 import {RealTimestamp} from 'common/time';
-import {Transition} from 'flickerlib/common';
+import {PropertyTreeBuilder} from 'test/unit/property_tree_builder';
 import {TraceBuilder} from 'test/unit/trace_builder';
 import {waitToBeCalled} from 'test/utils';
 import {TraceType} from 'trace/trace_type';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
 import {TransitionTimelineComponent} from './transition_timeline_component';
 
 describe('TransitionTimelineComponent', () => {
@@ -66,17 +67,42 @@
   });
 
   it('can draw non-overlapping transitions', async () => {
-    component.trace = new TraceBuilder()
+    component.trace = new TraceBuilder<PropertyTreeNode>()
       .setType(TraceType.TRANSITION)
       .setEntries([
-        {
-          createTime: {unixNanos: 10n},
-          finishTime: {unixNanos: 30n},
-        } as Transition,
-        {
-          createTime: {unixNanos: 60n},
-          finishTime: {unixNanos: 110n},
-        } as Transition,
+        new PropertyTreeBuilder()
+          .setIsRoot(true)
+          .setRootId('TransitionsTraceEntry')
+          .setName('transition')
+          .setChildren([
+            {
+              name: 'wmData',
+              children: [{name: 'finishTimeNs', value: 30n}],
+            },
+            {
+              name: 'shellData',
+              children: [{name: 'dispatchTimeNs', value: 10n}],
+            },
+            {name: 'aborted', value: false},
+          ])
+          .build(),
+
+        new PropertyTreeBuilder()
+          .setIsRoot(true)
+          .setRootId('TransitionsTraceEntry')
+          .setName('transition')
+          .setChildren([
+            {
+              name: 'wmData',
+              children: [{name: 'finishTimeNs', value: 110n}],
+            },
+            {
+              name: 'shellData',
+              children: [{name: 'dispatchTimeNs', value: 60n}],
+            },
+            {name: 'aborted', value: false},
+          ])
+          .build(),
       ])
       .setTimestamps([new RealTimestamp(10n), new RealTimestamp(60n)])
       .build();
@@ -107,17 +133,41 @@
   });
 
   it('can draw transitions zoomed in', async () => {
-    component.trace = new TraceBuilder()
+    component.trace = new TraceBuilder<PropertyTreeNode>()
       .setType(TraceType.TRANSITION)
       .setEntries([
-        {
-          createTime: {unixNanos: 0n},
-          finishTime: {unixNanos: 20n},
-        } as Transition,
-        {
-          createTime: {unixNanos: 60n},
-          finishTime: {unixNanos: 160n},
-        } as Transition,
+        new PropertyTreeBuilder()
+          .setIsRoot(true)
+          .setRootId('TransitionsTraceEntry')
+          .setName('transition')
+          .setChildren([
+            {
+              name: 'wmData',
+              children: [{name: 'finishTimeNs', value: 20n}],
+            },
+            {
+              name: 'shellData',
+              children: [{name: 'dispatchTimeNs', value: 0n}],
+            },
+            {name: 'aborted', value: false},
+          ])
+          .build(),
+        new PropertyTreeBuilder()
+          .setIsRoot(true)
+          .setRootId('TransitionsTraceEntry')
+          .setName('transition')
+          .setChildren([
+            {
+              name: 'wmData',
+              children: [{name: 'finishTimeNs', value: 160n}],
+            },
+            {
+              name: 'shellData',
+              children: [{name: 'dispatchTimeNs', value: 60n}],
+            },
+            {name: 'aborted', value: false},
+          ])
+          .build(),
       ])
       .setTimestamps([new RealTimestamp(10n), new RealTimestamp(60n)])
       .build();
@@ -136,7 +186,7 @@
 
     expect(drawRectSpy).toHaveBeenCalledTimes(2);
     expect(drawRectSpy).toHaveBeenCalledWith(
-      new Rect(-Math.floor(width / 10), padding, Math.floor(width / 5), oneRowHeight),
+      new Rect(0, padding, Math.floor(width / 10), oneRowHeight),
       component.color,
       1
     );
@@ -148,12 +198,23 @@
   });
 
   it('can draw selected entry', async () => {
-    const transition: Transition = {
-      createTime: {unixNanos: 35n},
-      finishTime: {unixNanos: 85n},
-      aborted: true,
-    };
-    component.trace = new TraceBuilder()
+    const transition = new PropertyTreeBuilder()
+      .setIsRoot(true)
+      .setRootId('TransitionsTraceEntry')
+      .setName('transition')
+      .setChildren([
+        {
+          name: 'wmData',
+          children: [{name: 'finishTimeNs', value: 85n}],
+        },
+        {
+          name: 'shellData',
+          children: [{name: 'dispatchTimeNs', value: 35n}],
+        },
+        {name: 'aborted', value: false},
+      ])
+      .build();
+    component.trace = new TraceBuilder<PropertyTreeNode>()
       .setType(TraceType.TRANSITION)
       .setEntries([transition])
       .setTimestamps([new RealTimestamp(35n)])
@@ -163,19 +224,16 @@
 
     const drawRectSpy = spyOn(component.canvasDrawer, 'drawRect');
     const drawRectBorderSpy = spyOn(component.canvasDrawer, 'drawRectBorder');
-
     const waitPromises = [waitToBeCalled(drawRectSpy, 1), waitToBeCalled(drawRectBorderSpy, 1)];
 
     fixture.detectChanges();
     await fixture.whenRenderingDone();
-
     await Promise.all(waitPromises);
 
     const padding = 5;
     const oneRowTotalHeight = 30;
     const oneRowHeight = oneRowTotalHeight - padding;
     const width = component.canvasDrawer.getScaledCanvasWidth();
-
     const expectedRect = new Rect(
       Math.floor((width * 1) / 4),
       padding,
@@ -183,19 +241,29 @@
       oneRowHeight
     );
     expect(drawRectSpy).toHaveBeenCalledTimes(2); // once drawn as a normal entry another time with rect border
-    expect(drawRectSpy).toHaveBeenCalledWith(expectedRect, component.color, 0.25);
-
+    expect(drawRectSpy).toHaveBeenCalledWith(expectedRect, component.color, 1);
     expect(drawRectBorderSpy).toHaveBeenCalledTimes(1);
     expect(drawRectBorderSpy).toHaveBeenCalledWith(expectedRect);
   });
 
   it('can draw hovering entry', async () => {
-    const transition: Transition = {
-      createTime: {unixNanos: 35n},
-      finishTime: {unixNanos: 85n},
-      aborted: true,
-    };
-    component.trace = new TraceBuilder()
+    const transition = new PropertyTreeBuilder()
+      .setIsRoot(true)
+      .setRootId('TransitionsTraceEntry')
+      .setName('transition')
+      .setChildren([
+        {
+          name: 'wmData',
+          children: [{name: 'finishTimeNs', value: 85n}],
+        },
+        {
+          name: 'shellData',
+          children: [{name: 'dispatchTimeNs', value: 35n}],
+        },
+        {name: 'aborted', value: false},
+      ])
+      .build();
+    component.trace = new TraceBuilder<PropertyTreeNode>()
       .setType(TraceType.TRANSITION)
       .setEntries([transition])
       .setTimestamps([new RealTimestamp(35n)])
@@ -212,17 +280,14 @@
     const oneRowTotalHeight = 30;
     const oneRowHeight = oneRowTotalHeight - padding;
     const width = component.canvasDrawer.getScaledCanvasWidth();
-
     component.handleMouseMove({
       offsetX: Math.floor(width / 2),
       offsetY: oneRowTotalHeight / 2,
       preventDefault: () => {},
       stopPropagation: () => {},
     } as MouseEvent);
-
     await waitToBeCalled(drawRectSpy, 1);
     await waitToBeCalled(drawRectBorderSpy, 1);
-
     const expectedRect = new Rect(
       Math.floor((width * 1) / 4),
       padding,
@@ -230,24 +295,47 @@
       oneRowHeight
     );
     expect(drawRectSpy).toHaveBeenCalledTimes(1);
-    expect(drawRectSpy).toHaveBeenCalledWith(expectedRect, component.color, 0.25);
-
+    expect(drawRectSpy).toHaveBeenCalledWith(expectedRect, component.color, 1);
     expect(drawRectBorderSpy).toHaveBeenCalledTimes(1);
     expect(drawRectBorderSpy).toHaveBeenCalledWith(expectedRect);
   });
 
   it('can draw overlapping transitions (default)', async () => {
-    component.trace = new TraceBuilder()
+    component.trace = new TraceBuilder<PropertyTreeNode>()
       .setType(TraceType.TRANSITION)
       .setEntries([
-        {
-          createTime: {unixNanos: 10n},
-          finishTime: {unixNanos: 85n},
-        } as Transition,
-        {
-          createTime: {unixNanos: 60n},
-          finishTime: {unixNanos: 110n},
-        } as Transition,
+        new PropertyTreeBuilder()
+          .setIsRoot(true)
+          .setRootId('TransitionsTraceEntry')
+          .setName('transition')
+          .setChildren([
+            {
+              name: 'wmData',
+              children: [{name: 'finishTimeNs', value: 85n}],
+            },
+            {
+              name: 'shellData',
+              children: [{name: 'dispatchTimeNs', value: 10n}],
+            },
+            {name: 'aborted', value: false},
+          ])
+          .build(),
+        new PropertyTreeBuilder()
+          .setIsRoot(true)
+          .setRootId('TransitionsTraceEntry')
+          .setName('transition')
+          .setChildren([
+            {
+              name: 'wmData',
+              children: [{name: 'finishTimeNs', value: 110n}],
+            },
+            {
+              name: 'shellData',
+              children: [{name: 'dispatchTimeNs', value: 60n}],
+            },
+            {name: 'aborted', value: false},
+          ])
+          .build(),
       ])
       .setTimestamps([new RealTimestamp(10n), new RealTimestamp(60n)])
       .build();
@@ -284,17 +372,41 @@
   });
 
   it('can draw overlapping transitions (contained)', async () => {
-    component.trace = new TraceBuilder()
+    component.trace = new TraceBuilder<PropertyTreeNode>()
       .setType(TraceType.TRANSITION)
       .setEntries([
-        {
-          createTime: {unixNanos: 10n},
-          finishTime: {unixNanos: 85n},
-        } as Transition,
-        {
-          createTime: {unixNanos: 35n},
-          finishTime: {unixNanos: 60n},
-        } as Transition,
+        new PropertyTreeBuilder()
+          .setIsRoot(true)
+          .setRootId('TransitionsTraceEntry')
+          .setName('transition')
+          .setChildren([
+            {
+              name: 'wmData',
+              children: [{name: 'finishTimeNs', value: 85n}],
+            },
+            {
+              name: 'shellData',
+              children: [{name: 'dispatchTimeNs', value: 10n}],
+            },
+            {name: 'aborted', value: false},
+          ])
+          .build(),
+        new PropertyTreeBuilder()
+          .setIsRoot(true)
+          .setRootId('TransitionsTraceEntry')
+          .setName('transition')
+          .setChildren([
+            {
+              name: 'wmData',
+              children: [{name: 'finishTimeNs', value: 60n}],
+            },
+            {
+              name: 'shellData',
+              children: [{name: 'dispatchTimeNs', value: 35n}],
+            },
+            {name: 'aborted', value: false},
+          ])
+          .build(),
       ])
       .setTimestamps([new RealTimestamp(10n), new RealTimestamp(35n)])
       .build();
@@ -331,14 +443,28 @@
   });
 
   it('can draw aborted transitions', async () => {
-    component.trace = new TraceBuilder()
+    component.trace = new TraceBuilder<PropertyTreeNode>()
       .setType(TraceType.TRANSITION)
       .setEntries([
-        {
-          createTime: {unixNanos: 35n},
-          finishTime: {unixNanos: 85n},
-          aborted: true,
-        } as Transition,
+        new PropertyTreeBuilder()
+          .setIsRoot(true)
+          .setRootId('TransitionsTraceEntry')
+          .setName('transition')
+          .setChildren([
+            {
+              name: 'wmData',
+              value: null,
+            },
+            {
+              name: 'shellData',
+              children: [
+                {name: 'dispatchTimeNs', value: 35n},
+                {name: 'abortTimeNs', value: 85n},
+              ],
+            },
+            {name: 'aborted', value: true},
+          ])
+          .build(),
       ])
       .setTimestamps([new RealTimestamp(35n)])
       .build();
@@ -363,18 +489,28 @@
     );
   });
 
-  it('does not render transition with min creation time', async () => {
-    component.trace = new TraceBuilder()
+  it('does not render transition with create time but no dispatch time', async () => {
+    component.trace = new TraceBuilder<PropertyTreeNode>()
       .setType(TraceType.TRANSITION)
       .setEntries([
-        {
-          createTime: {unixNanos: 10n, isMin: true},
-          finishTime: {unixNanos: 30n},
-        } as Transition,
+        new PropertyTreeBuilder()
+          .setIsRoot(true)
+          .setRootId('TransitionsTraceEntry')
+          .setName('transition')
+          .setChildren([
+            {
+              name: 'wmData',
+              children: [
+                {name: 'createTimeNs', value: 10n},
+                {name: 'finishTimeNs', value: 85n},
+              ],
+            },
+            {name: 'aborted', value: false},
+          ])
+          .build(),
       ])
       .setTimestamps([new RealTimestamp(10n)])
       .build();
-    component.shouldNotRenderEntries.push(0);
     component.selectionRange = {from: new RealTimestamp(10n), to: new RealTimestamp(110n)};
 
     const drawRectSpy = spyOn(component.canvasDrawer, 'drawRect');
diff --git a/tools/winscope/src/app/components/timeline/mini-timeline/drawer/mini_timeline_drawer_input.ts b/tools/winscope/src/app/components/timeline/mini-timeline/drawer/mini_timeline_drawer_input.ts
index 7ce94f2..49b304b 100644
--- a/tools/winscope/src/app/components/timeline/mini-timeline/drawer/mini_timeline_drawer_input.ts
+++ b/tools/winscope/src/app/components/timeline/mini-timeline/drawer/mini_timeline_drawer_input.ts
@@ -1,11 +1,29 @@
+/*
+ * Copyright (C) 2023 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 {Transformer} from 'app/components/timeline/mini-timeline/transformer';
+import {Segment} from 'app/components/timeline/segment';
+import {TimelineUtils} from 'app/components/timeline/timeline_utils';
 import {TimelineData} from 'app/timeline_data';
-import {ElapsedTimestamp, RealTimestamp, TimeRange, Timestamp, TimestampType} from 'common/time';
-import {Transition} from 'flickerlib/common';
+import {assertDefined} from 'common/assert_utils';
+import {TimeRange, Timestamp} from 'common/time';
 import {Trace, TraceEntry} from 'trace/trace';
 import {Traces} from 'trace/traces';
 import {TraceType} from 'trace/trace_type';
-import {Segment} from '../../segment';
-import {Transformer} from '../transformer';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
 import {MiniCanvasDrawerData, TimelineEntries} from './mini_canvas_drawer_data';
 
 export class MiniTimelineDrawerInput {
@@ -47,11 +65,13 @@
 
     await Promise.all(
       this.traces.mapTrace(async (trace, type) => {
-        const activeEntry = this.timelineData.findCurrentEntryFor(trace.type);
+        const activeEntry = this.timelineData.findCurrentEntryFor(
+          trace.type
+        ) as TraceEntry<PropertyTreeNode>;
 
         if (type === TraceType.TRANSITION) {
           // Transition trace is a special case, with entries with time ranges
-          const transitionTrace = this.traces.getTrace(type)!;
+          const transitionTrace = assertDefined(this.traces.getTrace(type));
           transformedTraceSegments.set(trace.type, {
             points: [],
             activePoint: undefined,
@@ -78,7 +98,7 @@
 
   private async transformTransitionTraceTimestamps(
     transformer: Transformer,
-    trace: Trace<Transition>
+    trace: Trace<PropertyTreeNode>
   ): Promise<Segment[]> {
     const promises: Array<Promise<Segment | undefined>> = [];
     trace.forEachEntry((entry) => {
@@ -90,27 +110,21 @@
 
   private async transformTransitionEntry(
     transformer: Transformer,
-    entry: TraceEntry<Transition>
+    entry: TraceEntry<PropertyTreeNode>
   ): Promise<Segment | undefined> {
-    const transition = await entry.getValue();
-    let createTime: Timestamp;
-    let finishTime: Timestamp;
+    const transition: PropertyTreeNode = await entry.getValue();
 
-    if (transition.createTime.isMin || transition.finishTime.isMax) {
+    const timeRange = TimelineUtils.getTimeRangeForTransition(
+      transition,
+      entry.getTimestamp().getType(),
+      this.selection
+    );
+
+    if (!timeRange) {
       return undefined;
     }
 
-    if (entry.getTimestamp().getType() === TimestampType.REAL) {
-      createTime = new RealTimestamp(BigInt(transition.createTime.unixNanos.toString()));
-      finishTime = new RealTimestamp(BigInt(transition.finishTime.unixNanos.toString()));
-    } else if (entry.getTimestamp().getType() === TimestampType.ELAPSED) {
-      createTime = new ElapsedTimestamp(BigInt(transition.createTime.elapsedNanos.toString()));
-      finishTime = new ElapsedTimestamp(BigInt(transition.finishTime.elapsedNanos.toString()));
-    } else {
-      throw new Error('Unspported timestamp type');
-    }
-
-    return {from: transformer.transform(createTime), to: transformer.transform(finishTime)};
+    return {from: transformer.transform(timeRange.from), to: transformer.transform(timeRange.to)};
   }
 
   private transformTraceTimestamps(transformer: Transformer, trace: Trace<{}>): number[] {
diff --git a/tools/winscope/src/parsers/perfetto/parser_transitions.ts b/tools/winscope/src/parsers/perfetto/parser_transitions.ts
index 4e8cb74..fb1876c 100644
--- a/tools/winscope/src/parsers/perfetto/parser_transitions.ts
+++ b/tools/winscope/src/parsers/perfetto/parser_transitions.ts
@@ -15,15 +15,6 @@
  */
 import {assertDefined} from 'common/assert_utils';
 import {TimestampType} from 'common/time';
-import {
-  CrossPlatform,
-  ShellTransitionData,
-  Timestamp,
-  Transition,
-  TransitionChange,
-  TransitionType,
-  WmTransitionData,
-} from 'flickerlib/common';
 import {ParserTransitionsUtils} from 'parsers/transitions/parser_transitions_utils';
 import {perfetto} from 'protos/transitions/latest/static';
 import {TraceFile} from 'trace/trace_file';
@@ -33,7 +24,7 @@
 import {AbstractParser} from './abstract_parser';
 import {FakeProtoBuilder} from './fake_proto_builder';
 
-export class ParserTransitions extends AbstractParser<Transition> {
+export class ParserTransitions extends AbstractParser<PropertyTreeNode> {
   private handlerIdToName: {[id: number]: string} | undefined = undefined;
 
   constructor(traceFile: TraceFile, traceProcessor: WasmEngineProxy) {
@@ -44,7 +35,7 @@
     return TraceType.TRANSITION;
   }
 
-  override async getEntry(index: number, timestampType: TimestampType): Promise<Transition> {
+  override async getEntry(index: number, timestampType: TimestampType): Promise<PropertyTreeNode> {
     const transitionProto = await this.queryTransition(index);
 
     if (this.handlerIdToName === undefined) {
@@ -53,35 +44,7 @@
       handlers.forEach((it) => (assertDefined(this.handlerIdToName)[it.id] = it.name));
     }
 
-    return new Transition(
-      Number(transitionProto.id),
-      new WmTransitionData(
-        this.toTimestamp(transitionProto.createTimeNs?.toString()),
-        this.toTimestamp(transitionProto.sendTimeNs?.toString()),
-        this.toTimestamp(transitionProto.wmAbortTimeNs?.toString()),
-        this.toTimestamp(transitionProto.finishTimeNs?.toString()),
-        this.toTimestamp(transitionProto.startingWindowRemoveTimeNs?.toString()),
-        transitionProto.startTransactionId.toString(),
-        transitionProto.finishTransactionId.toString(),
-        TransitionType.Companion.fromInt(Number(transitionProto.type)),
-        transitionProto.targets.map(
-          (it: perfetto.protos.ShellTransition.ITarget) =>
-            new TransitionChange(
-              TransitionType.Companion.fromInt(Number(it.mode)),
-              Number(it.layerId),
-              Number(it.windowId)
-            )
-        )
-      ),
-      new ShellTransitionData(
-        this.toTimestamp(transitionProto.dispatchTimeNs?.toString()),
-        this.toTimestamp(transitionProto.mergeRequestTimeNs?.toString()),
-        this.toTimestamp(transitionProto.mergeTimeNs?.toString()),
-        this.toTimestamp(transitionProto.shellAbortTimeNs?.toString()),
-        this.handlerIdToName[Number(transitionProto.handler)],
-        transitionProto.mergeTarget ? Number(transitionProto.mergeTarget) : null
-      )
-    );
+    return this.makePropertiesTree(timestampType, transitionProto);
   }
 
   protected override getTableName(): string {
@@ -125,17 +88,6 @@
     return ParserTransitionsUtils.makeTransitionPropertiesTree(shellEntryTree, wmEntryTree);
   }
 
-  private toTimestamp(n: string | undefined): Timestamp | null {
-    if (n === undefined) {
-      return null;
-    }
-
-    const realToElapsedTimeOffsetNs = assertDefined(this.realToElapsedTimeOffsetNs);
-    const unixNs = BigInt(n) + realToElapsedTimeOffsetNs;
-
-    return CrossPlatform.timestamp.fromString(n.toString(), null, unixNs.toString());
-  }
-
   private async queryTransition(index: number): Promise<perfetto.protos.ShellTransition> {
     const protoBuilder = new FakeProtoBuilder();
 
diff --git a/tools/winscope/src/parsers/perfetto/parser_transitions_test.ts b/tools/winscope/src/parsers/perfetto/parser_transitions_test.ts
index 84b4324..2096f03 100644
--- a/tools/winscope/src/parsers/perfetto/parser_transitions_test.ts
+++ b/tools/winscope/src/parsers/perfetto/parser_transitions_test.ts
@@ -15,14 +15,14 @@
  */
 import {assertDefined} from 'common/assert_utils';
 import {ElapsedTimestamp, RealTimestamp, TimestampType} from 'common/time';
-import {Transition, TransitionType} from 'flickerlib/common';
 import {UnitTestUtils} from 'test/unit/utils';
 import {Parser} from 'trace/parser';
 import {TraceType} from 'trace/trace_type';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
 
 describe('Perfetto ParserTransitions', () => {
   describe('valid trace', () => {
-    let parser: Parser<Transition>;
+    let parser: Parser<PropertyTreeNode>;
 
     beforeAll(async () => {
       parser = await UnitTestUtils.getPerfettoParser(
@@ -57,30 +57,51 @@
 
     it('decodes transition properties', async () => {
       const entry = await parser.getEntry(0, TimestampType.REAL);
+      const wmDataNode = assertDefined(entry.getChildByName('wmData'));
+      const shellDataNode = assertDefined(entry.getChildByName('shellData'));
 
-      expect(entry.id).toEqual(32);
-      expect(entry.createTime.elapsedNanos.toString()).toEqual('479583450794');
-      expect(entry.sendTime.elapsedNanos.toString()).toEqual('479596405791');
-      expect(entry.abortTime).toEqual(null);
-      expect(entry.finishTime.elapsedNanos.toString()).toEqual('480124777862');
-      expect(entry.startingWindowRemoveTime.elapsedNanos.toString()).toEqual('479719652658');
-      expect(entry.dispatchTime.elapsedNanos.toString()).toEqual('479602824452');
-      expect(entry.mergeRequestTime).toEqual(null);
-      expect(entry.mergeTime).toEqual(null);
-      expect(entry.shellAbortTime).toEqual(null);
-      expect(entry.startTransactionId.toString()).toEqual('5811090758076');
-      expect(entry.finishTransactionId.toString()).toEqual('5811090758077');
-      expect(entry.type).toEqual(TransitionType.OPEN);
-      expect(entry.mergeTarget).toEqual(null);
-      expect(entry.handler).toEqual('com.android.wm.shell.transition.DefaultMixedHandler');
-      expect(entry.merged).toEqual(false);
-      expect(entry.played).toEqual(true);
-      expect(entry.aborted).toEqual(false);
-      expect(entry.changes.length).toEqual(2);
-      expect(entry.changes[0].layerId).toEqual(398);
-      expect(entry.changes[1].layerId).toEqual(47);
-      expect(entry.changes[0].transitMode).toEqual(TransitionType.TO_FRONT);
-      expect(entry.changes[1].transitMode).toEqual(TransitionType.TO_BACK);
+      expect(entry.getChildByName('id')?.getValue()).toEqual(32n);
+      expect(wmDataNode.getChildByName('createTimeNs')?.formattedValue()).toEqual(
+        '2023-11-21T13:38:23.083364560'
+      );
+      expect(wmDataNode.getChildByName('sendTimeNs')?.formattedValue()).toEqual(
+        '2023-11-21T13:38:23.096319557'
+      );
+      expect(wmDataNode.getChildByName('finishTimeNs')?.formattedValue()).toEqual(
+        '2023-11-21T13:38:23.624691628'
+      );
+      expect(entry.getChildByName('merged')?.getValue()).toBeFalse();
+      expect(entry.getChildByName('played')?.getValue()).toBeTrue();
+      expect(entry.getChildByName('aborted')?.getValue()).toBeFalse();
+
+      expect(
+        assertDefined(wmDataNode.getChildByName('startingWindowRemoveTimeNs')).formattedValue()
+      ).toEqual('2023-11-21T13:38:23.219566424');
+      expect(
+        assertDefined(wmDataNode.getChildByName('startTransactionId')).formattedValue()
+      ).toEqual('5811090758076');
+      expect(
+        assertDefined(wmDataNode.getChildByName('finishTransactionId')).formattedValue()
+      ).toEqual('5811090758077');
+      expect(assertDefined(wmDataNode.getChildByName('type')).formattedValue()).toEqual('OPEN');
+
+      const targets = assertDefined(wmDataNode.getChildByName('targets')).getAllChildren();
+      expect(targets.length).toEqual(2);
+      expect(assertDefined(targets[0].getChildByName('layerId')).formattedValue()).toEqual('398');
+      expect(assertDefined(targets[1].getChildByName('layerId')).formattedValue()).toEqual('47');
+      expect(assertDefined(targets[0].getChildByName('mode')).formattedValue()).toEqual('TO_FRONT');
+      expect(assertDefined(targets[1].getChildByName('mode')).formattedValue()).toEqual('TO_BACK');
+
+      expect(
+        assertDefined(shellDataNode.getChildByName('dispatchTimeNs')).formattedValue()
+      ).toEqual('2023-11-21T13:38:23.102738218');
+      expect(shellDataNode.getChildByName('mergeRequestTime')).toBeUndefined();
+      expect(shellDataNode.getChildByName('mergeTime')).toBeUndefined();
+      expect(shellDataNode.getChildByName('abortTimeNs')).toBeUndefined();
+      expect(shellDataNode.getChildByName('mergeTarget')).toBeUndefined();
+      expect(assertDefined(shellDataNode.getChildByName('handler')).formattedValue()).toEqual(
+        'com.android.wm.shell.transition.DefaultMixedHandler'
+      );
     });
   });
 });
diff --git a/tools/winscope/src/parsers/transitions/parser_transitions_shell.ts b/tools/winscope/src/parsers/transitions/parser_transitions_shell.ts
index 4dddd63..5babf30 100644
--- a/tools/winscope/src/parsers/transitions/parser_transitions_shell.ts
+++ b/tools/winscope/src/parsers/transitions/parser_transitions_shell.ts
@@ -16,7 +16,6 @@
 
 import {assertDefined} from 'common/assert_utils';
 import {ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType} from 'common/time';
-import {CrossPlatform, ShellTransitionData, Transition, WmTransitionData} from 'flickerlib/common';
 import {AbstractParser} from 'parsers/abstract_parser';
 import root from 'protos/transitions/udc/json';
 import {com} from 'protos/transitions/udc/static';
@@ -25,13 +24,14 @@
 import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
 import {ParserTransitionsUtils} from './parser_transitions_utils';
 
-export class ParserTransitionsShell extends AbstractParser {
+export class ParserTransitionsShell extends AbstractParser<PropertyTreeNode> {
   private static readonly WmShellTransitionsTraceProto = root.lookupType(
     'com.android.wm.shell.WmShellTransitionTraceProto'
   );
 
   private realToElapsedTimeOffsetNs: undefined | bigint;
   private handlerMapping: undefined | {[key: number]: string};
+  protected override shouldAddDefaultsToProto = false;
 
   constructor(trace: TraceFile) {
     super(trace);
@@ -61,98 +61,37 @@
     index: number,
     timestampType: TimestampType,
     entryProto: com.android.wm.shell.ITransition
-  ): Transition {
-    return this.parseShellTransitionEntry(entryProto);
+  ): PropertyTreeNode {
+    return this.makePropertiesTree(timestampType, entryProto);
   }
 
-  override getTimestamp(type: TimestampType, decodedEntry: Transition): undefined | Timestamp {
-    decodedEntry = this.parseShellTransitionEntry(decodedEntry);
-
+  override getTimestamp(
+    type: TimestampType,
+    entry: com.android.wm.shell.ITransition
+  ): undefined | Timestamp {
+    // for consistency with all transitions, elapsed nanos are defined as shell dispatch time else 0n
+    const decodedEntry = this.processDecodedEntry(0, type, entry);
+    const dispatchTimeLong = decodedEntry
+      .getChildByName('shellData')
+      ?.getChildByName('dispatchTimeNs')
+      ?.getValue();
+    const timestampNs = dispatchTimeLong ? BigInt(dispatchTimeLong.toString()) : 0n;
     if (type === TimestampType.ELAPSED) {
-      return new ElapsedTimestamp(BigInt(decodedEntry.timestamp.elapsedNanos.toString()));
+      return new ElapsedTimestamp(timestampNs);
     }
-
     if (type === TimestampType.REAL) {
-      return new RealTimestamp(BigInt(decodedEntry.timestamp.unixNanos.toString()));
+      return new RealTimestamp(timestampNs + assertDefined(this.realToElapsedTimeOffsetNs));
     }
-
-    throw new Error('Timestamp type unsupported');
+    throw Error('Timestamp type unsupported');
   }
 
   protected getMagicNumber(): number[] | undefined {
     return [0x09, 0x57, 0x4d, 0x53, 0x54, 0x52, 0x41, 0x43, 0x45]; // .WMSTRACE
   }
 
-  private parseShellTransitionEntry(entry: com.android.wm.shell.ITransition): Transition {
-    this.validateShellTransitionEntry(entry);
-
-    if (this.realToElapsedTimeOffsetNs === undefined) {
-      throw new Error('missing realToElapsedTimeOffsetNs');
-    }
-
-    let dispatchTime = null;
-    if (entry.dispatchTimeNs && BigInt(entry.dispatchTimeNs.toString()) !== 0n) {
-      const unixNs = BigInt(entry.dispatchTimeNs.toString()) + this.realToElapsedTimeOffsetNs;
-      dispatchTime = CrossPlatform.timestamp.fromString(
-        entry.dispatchTimeNs.toString(),
-        null,
-        unixNs.toString()
-      );
-    }
-
-    let mergeRequestTime = null;
-    if (entry.mergeRequestTimeNs && BigInt(entry.mergeRequestTimeNs.toString()) !== 0n) {
-      const unixNs = BigInt(entry.mergeRequestTimeNs.toString()) + this.realToElapsedTimeOffsetNs;
-      mergeRequestTime = CrossPlatform.timestamp.fromString(
-        entry.mergeRequestTimeNs.toString(),
-        null,
-        unixNs.toString()
-      );
-    }
-
-    let mergeTime = null;
-    if (entry.mergeTimeNs && BigInt(entry.mergeTimeNs.toString()) !== 0n) {
-      const unixNs = BigInt(entry.mergeTimeNs.toString()) + this.realToElapsedTimeOffsetNs;
-      mergeTime = CrossPlatform.timestamp.fromString(
-        entry.mergeTimeNs.toString(),
-        null,
-        unixNs.toString()
-      );
-    }
-
-    let abortTime = null;
-    if (entry.abortTimeNs && BigInt(entry.abortTimeNs.toString()) !== 0n) {
-      const unixNs = BigInt(entry.abortTimeNs.toString()) + this.realToElapsedTimeOffsetNs;
-      abortTime = CrossPlatform.timestamp.fromString(
-        entry.abortTimeNs.toString(),
-        null,
-        unixNs.toString()
-      );
-    }
-
-    const mergeTarget = entry.mergeTarget ? entry.mergeTarget : null;
-
-    if (this.handlerMapping === undefined) {
-      throw new Error('Missing handler mapping!');
-    }
-
-    return new Transition(
-      entry.id,
-      new WmTransitionData(),
-      new ShellTransitionData(
-        dispatchTime,
-        mergeRequestTime,
-        mergeTime,
-        abortTime,
-        this.handlerMapping[assertDefined(entry.handler)],
-        mergeTarget
-      )
-    );
-  }
-
   private validateShellTransitionEntry(entry: com.android.wm.shell.ITransition) {
     if (entry.id === 0) {
-      throw new Error('Entry need a non null id');
+      throw new Error('Proto needs a non-null id');
     }
     if (
       !entry.dispatchTimeNs &&
@@ -162,6 +101,12 @@
     ) {
       throw new Error('Requires at least one non-null timestamp');
     }
+    if (this.realToElapsedTimeOffsetNs === undefined) {
+      throw new Error('missing realToElapsedTimeOffsetNs');
+    }
+    if (this.handlerMapping === undefined) {
+      throw new Error('Missing handler mapping');
+    }
   }
 
   private makePropertiesTree(
diff --git a/tools/winscope/src/parsers/transitions/parser_transitions_shell_test.ts b/tools/winscope/src/parsers/transitions/parser_transitions_shell_test.ts
index 0e85f2e..02e1140 100644
--- a/tools/winscope/src/parsers/transitions/parser_transitions_shell_test.ts
+++ b/tools/winscope/src/parsers/transitions/parser_transitions_shell_test.ts
@@ -14,18 +14,20 @@
  * limitations under the License.
  */
 
+import {assertDefined} from 'common/assert_utils';
 import {ElapsedTimestamp, RealTimestamp, TimestampType} from 'common/time';
 import {UnitTestUtils} from 'test/unit/utils';
 import {Parser} from 'trace/parser';
 import {TraceType} from 'trace/trace_type';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
 
 describe('ShellFileParserTransitions', () => {
-  let parser: Parser<object>;
+  let parser: Parser<PropertyTreeNode>;
 
   beforeAll(async () => {
-    parser = await UnitTestUtils.getParser(
+    parser = (await UnitTestUtils.getParser(
       'traces/elapsed_and_real_timestamp/shell_transition_trace.pb'
-    );
+    )) as Parser<PropertyTreeNode>;
   });
 
   it('has expected trace type', () => {
@@ -33,29 +35,28 @@
   });
 
   it('provides elapsed timestamps', () => {
-    const timestamps = parser.getTimestamps(TimestampType.ELAPSED)!;
-
-    expect(timestamps.length).toEqual(6);
-
+    const timestamps = assertDefined(parser.getTimestamps(TimestampType.ELAPSED));
     const expected = [
       new ElapsedTimestamp(57649649922341n),
-      new ElapsedTimestamp(57649829445249n),
-      new ElapsedTimestamp(57649829526223n),
+      new ElapsedTimestamp(0n),
+      new ElapsedTimestamp(0n),
+      new ElapsedTimestamp(57651299086892n),
+      new ElapsedTimestamp(0n),
+      new ElapsedTimestamp(0n),
     ];
-    expect(timestamps.slice(0, 3)).toEqual(expected);
+    expect(timestamps).toEqual(expected);
   });
 
   it('provides real timestamps', () => {
+    const timestamps = assertDefined(parser.getTimestamps(TimestampType.REAL));
     const expected = [
       new RealTimestamp(1683188477607285317n),
-      new RealTimestamp(1683188477786808225n),
-      new RealTimestamp(1683188477786889199n),
+      new RealTimestamp(1683130827957362976n),
+      new RealTimestamp(1683130827957362976n),
+      new RealTimestamp(1683188479256449868n),
+      new RealTimestamp(1683130827957362976n),
+      new RealTimestamp(1683130827957362976n),
     ];
-
-    const timestamps = parser.getTimestamps(TimestampType.REAL)!;
-
-    expect(timestamps.length).toEqual(6);
-
-    expect(timestamps.slice(0, 3)).toEqual(expected);
+    expect(timestamps).toEqual(expected);
   });
 });
diff --git a/tools/winscope/src/parsers/transitions/parser_transitions_wm.ts b/tools/winscope/src/parsers/transitions/parser_transitions_wm.ts
index 4bf330b..670b277 100644
--- a/tools/winscope/src/parsers/transitions/parser_transitions_wm.ts
+++ b/tools/winscope/src/parsers/transitions/parser_transitions_wm.ts
@@ -14,15 +14,8 @@
  * limitations under the License.
  */
 
+import {assertDefined} from 'common/assert_utils';
 import {ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType} from 'common/time';
-import {
-  CrossPlatform,
-  ShellTransitionData,
-  Transition,
-  TransitionChange,
-  TransitionType,
-  WmTransitionData,
-} from 'flickerlib/common';
 import {AbstractParser} from 'parsers/abstract_parser';
 import root from 'protos/transitions/udc/json';
 import {com} from 'protos/transitions/udc/static';
@@ -31,12 +24,13 @@
 import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
 import {ParserTransitionsUtils} from './parser_transitions_utils';
 
-export class ParserTransitionsWm extends AbstractParser {
+export class ParserTransitionsWm extends AbstractParser<PropertyTreeNode> {
   private static readonly TransitionTraceProto = root.lookupType(
     'com.android.server.wm.shell.TransitionTraceProto'
   );
 
   private realToElapsedTimeOffsetNs: undefined | bigint;
+  protected override shouldAddDefaultsToProto = false;
 
   constructor(trace: TraceFile) {
     super(trace);
@@ -50,8 +44,8 @@
     index: number,
     timestampType: TimestampType,
     entryProto: com.android.server.wm.shell.ITransition
-  ): Transition {
-    return this.parseWmTransitionEntry(entryProto);
+  ): PropertyTreeNode {
+    return this.makePropertiesTree(timestampType, entryProto);
   }
 
   override decodeTrace(buffer: Uint8Array): com.android.server.wm.shell.ITransition[] {
@@ -69,126 +63,20 @@
     return [0x09, 0x54, 0x52, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45]; // .TRNTRACE
   }
 
-  override getTimestamp(type: TimestampType, decodedEntry: Transition): undefined | Timestamp {
-    decodedEntry = this.parseWmTransitionEntry(decodedEntry);
-
+  override getTimestamp(
+    type: TimestampType,
+    entry: com.android.server.wm.shell.ITransition
+  ): undefined | Timestamp {
+    // for consistency with all transitions, elapsed nanos are defined as shell dispatch time else 0n
     if (type === TimestampType.ELAPSED) {
-      return new ElapsedTimestamp(BigInt(decodedEntry.timestamp.elapsedNanos.toString()));
+      return new ElapsedTimestamp(0n);
     }
-
     if (type === TimestampType.REAL) {
-      return new RealTimestamp(BigInt(decodedEntry.timestamp.unixNanos.toString()));
+      return new RealTimestamp(assertDefined(this.realToElapsedTimeOffsetNs));
     }
-
     throw new Error('Timestamp type unsupported');
   }
 
-  private parseWmTransitionEntry(entry: com.android.server.wm.shell.ITransition): Transition {
-    this.validateWmTransitionEntry(entry);
-    let changes: TransitionChange[] | null;
-    if (!entry.targets || entry.targets.length === 0) {
-      changes = null;
-    } else {
-      changes = entry.targets.map(
-        (target) =>
-          new TransitionChange(
-            TransitionType.Companion.fromInt(target.mode),
-            target.layerId,
-            target.windowId
-          )
-      );
-    }
-
-    if (this.realToElapsedTimeOffsetNs === undefined) {
-      throw new Error('missing realToElapsedTimeOffsetNs');
-    }
-
-    let createTime = null;
-    if (entry.createTimeNs && BigInt(entry.createTimeNs.toString()) !== 0n) {
-      const unixNs = BigInt(entry.createTimeNs.toString()) + this.realToElapsedTimeOffsetNs;
-      createTime = CrossPlatform.timestamp.fromString(
-        entry.createTimeNs.toString(),
-        null,
-        unixNs.toString()
-      );
-    }
-
-    let sendTime = null;
-    if (entry.sendTimeNs && BigInt(entry.sendTimeNs.toString()) !== 0n) {
-      const unixNs = BigInt(entry.sendTimeNs.toString()) + this.realToElapsedTimeOffsetNs;
-      sendTime = CrossPlatform.timestamp.fromString(
-        entry.sendTimeNs.toString(),
-        null,
-        unixNs.toString()
-      );
-    }
-
-    let abortTime = null;
-    if (entry.abortTimeNs && BigInt(entry.abortTimeNs.toString()) !== 0n) {
-      const unixNs = BigInt(entry.abortTimeNs.toString()) + this.realToElapsedTimeOffsetNs;
-      abortTime = CrossPlatform.timestamp.fromString(
-        entry.abortTimeNs.toString(),
-        null,
-        unixNs.toString()
-      );
-    }
-
-    let finishTime = null;
-    if (entry.finishTimeNs && BigInt(entry.finishTimeNs.toString()) !== 0n) {
-      const unixNs = BigInt(entry.finishTimeNs.toString()) + this.realToElapsedTimeOffsetNs;
-      finishTime = CrossPlatform.timestamp.fromString(
-        entry.finishTimeNs.toString(),
-        null,
-        unixNs.toString()
-      );
-    }
-
-    const startingWindowRemoveTime = null;
-    if (
-      entry.startingWindowRemoveTimeNs &&
-      BigInt(entry.startingWindowRemoveTimeNs.toString()) !== 0n
-    ) {
-      const unixNs =
-        BigInt(entry.startingWindowRemoveTimeNs.toString()) + this.realToElapsedTimeOffsetNs;
-      finishTime = CrossPlatform.timestamp.fromString(
-        entry.startingWindowRemoveTimeNs.toString(),
-        null,
-        unixNs.toString()
-      );
-    }
-
-    let startTransactionId = null;
-    if (entry.startTransactionId && BigInt(entry.startTransactionId.toString()) !== 0n) {
-      startTransactionId = BigInt(entry.startTransactionId.toString());
-    }
-
-    let finishTransactionId = null;
-    if (entry.finishTransactionId && BigInt(entry.finishTransactionId.toString()) !== 0n) {
-      finishTransactionId = BigInt(entry.finishTransactionId.toString());
-    }
-
-    let type = null;
-    if (entry.type !== 0) {
-      type = TransitionType.Companion.fromInt(entry.type);
-    }
-
-    return new Transition(
-      entry.id,
-      new WmTransitionData(
-        createTime,
-        sendTime,
-        abortTime,
-        finishTime,
-        startingWindowRemoveTime,
-        startTransactionId?.toString(),
-        finishTransactionId?.toString(),
-        type,
-        changes
-      ),
-      new ShellTransitionData()
-    );
-  }
-
   private validateWmTransitionEntry(entry: com.android.server.wm.shell.ITransition) {
     if (entry.id === 0) {
       throw new Error('Entry need a non null id');
@@ -196,6 +84,9 @@
     if (!entry.createTimeNs && !entry.sendTimeNs && !entry.abortTimeNs && !entry.finishTimeNs) {
       throw new Error('Requires at least one non-null timestamp');
     }
+    if (this.realToElapsedTimeOffsetNs === undefined) {
+      throw new Error('missing realToElapsedTimeOffsetNs');
+    }
   }
 
   private makePropertiesTree(
diff --git a/tools/winscope/src/parsers/transitions/parser_transitions_wm_test.ts b/tools/winscope/src/parsers/transitions/parser_transitions_wm_test.ts
index 06ba000..374b980 100644
--- a/tools/winscope/src/parsers/transitions/parser_transitions_wm_test.ts
+++ b/tools/winscope/src/parsers/transitions/parser_transitions_wm_test.ts
@@ -14,18 +14,20 @@
  * limitations under the License.
  */
 
+import {assertDefined} from 'common/assert_utils';
 import {ElapsedTimestamp, RealTimestamp, TimestampType} from 'common/time';
 import {UnitTestUtils} from 'test/unit/utils';
 import {Parser} from 'trace/parser';
 import {TraceType} from 'trace/trace_type';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
 
 describe('WmFileParserTransitions', () => {
-  let parser: Parser<object>;
+  let parser: Parser<PropertyTreeNode>;
 
   beforeAll(async () => {
-    parser = await UnitTestUtils.getParser(
+    parser = (await UnitTestUtils.getParser(
       'traces/elapsed_and_real_timestamp/wm_transition_trace.pb'
-    );
+    )) as Parser<PropertyTreeNode>;
   });
 
   it('has expected trace type', () => {
@@ -33,29 +35,16 @@
   });
 
   it('provides elapsed timestamps', () => {
-    const timestamps = parser.getTimestamps(TimestampType.ELAPSED)!;
-
+    const timestamps = assertDefined(parser.getTimestamps(TimestampType.ELAPSED));
     expect(timestamps.length).toEqual(8);
-
-    const expected = [
-      new ElapsedTimestamp(57649586217344n),
-      new ElapsedTimestamp(57649691956439n),
-      new ElapsedTimestamp(57650183020323n),
-    ];
-    expect(timestamps.slice(0, 3)).toEqual(expected);
+    const expected = new ElapsedTimestamp(0n);
+    timestamps.forEach((timestamp) => expect(timestamp).toEqual(expected));
   });
 
   it('provides real timestamps', () => {
-    const expected = [
-      new RealTimestamp(1683188477542869667n),
-      new RealTimestamp(1683188477648608762n),
-      new RealTimestamp(1683188478139672646n),
-    ];
-
-    const timestamps = parser.getTimestamps(TimestampType.REAL)!;
-
+    const timestamps = assertDefined(parser.getTimestamps(TimestampType.REAL));
     expect(timestamps.length).toEqual(8);
-
-    expect(timestamps.slice(0, 3)).toEqual(expected);
+    const expected = new RealTimestamp(1683130827956652323n);
+    timestamps.forEach((timestamp) => expect(timestamp).toEqual(expected));
   });
 });
diff --git a/tools/winscope/src/parsers/transitions/traces_parser_transitions.ts b/tools/winscope/src/parsers/transitions/traces_parser_transitions.ts
index fcc02d2..7d9085b 100644
--- a/tools/winscope/src/parsers/transitions/traces_parser_transitions.ts
+++ b/tools/winscope/src/parsers/transitions/traces_parser_transitions.ts
@@ -16,7 +16,6 @@
 
 import {assertDefined} from 'common/assert_utils';
 import {Timestamp, TimestampType} from 'common/time';
-import {Transition, TransitionsTrace} from 'flickerlib/common';
 import {AbstractTracesParser} from 'parsers/abstract_traces_parser';
 import {Trace} from 'trace/trace';
 import {Traces} from 'trace/traces';
@@ -24,11 +23,11 @@
 import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
 import {ParserTransitionsUtils} from './parser_transitions_utils';
 
-export class TracesParserTransitions extends AbstractTracesParser<Transition> {
-  private readonly wmTransitionTrace: Trace<object> | undefined;
-  private readonly shellTransitionTrace: Trace<object> | undefined;
+export class TracesParserTransitions extends AbstractTracesParser<PropertyTreeNode> {
+  private readonly wmTransitionTrace: Trace<PropertyTreeNode> | undefined;
+  private readonly shellTransitionTrace: Trace<PropertyTreeNode> | undefined;
   private readonly descriptors: string[];
-  private decodedEntries: Transition[] | undefined;
+  private decodedEntries: PropertyTreeNode[] | undefined;
 
   constructor(traces: Traces) {
     super();
@@ -54,19 +53,17 @@
       throw new Error('Missing Shell Transition trace');
     }
 
-    const wmTransitionEntries: Transition[] = await Promise.all(
+    const wmTransitionEntries: PropertyTreeNode[] = await Promise.all(
       this.wmTransitionTrace.mapEntry((entry) => entry.getValue())
     );
 
-    const shellTransitionEntries: Transition[] = await Promise.all(
+    const shellTransitionEntries: PropertyTreeNode[] = await Promise.all(
       this.shellTransitionTrace.mapEntry((entry) => entry.getValue())
     );
 
-    const transitionsTrace = new TransitionsTrace(
-      wmTransitionEntries.concat(shellTransitionEntries)
-    );
+    const allEntries = wmTransitionEntries.concat(shellTransitionEntries);
 
-    this.decodedEntries = transitionsTrace.asCompressed().entries as Transition[];
+    this.decodedEntries = this.compressEntries(allEntries);
 
     await this.parseTimestamps();
   }
@@ -75,7 +72,7 @@
     return assertDefined(this.decodedEntries).length;
   }
 
-  override getEntry(index: number, timestampType: TimestampType): Promise<Transition> {
+  override getEntry(index: number, timestampType: TimestampType): Promise<PropertyTreeNode> {
     const entry = assertDefined(this.decodedEntries)[index];
     return Promise.resolve(entry);
   }
@@ -88,13 +85,20 @@
     return TraceType.TRANSITION;
   }
 
-  override getTimestamp(type: TimestampType, transition: Transition): undefined | Timestamp {
-    if (type === TimestampType.ELAPSED) {
-      return new Timestamp(type, BigInt(transition.timestamp.elapsedNanos.toString()));
-    } else if (type === TimestampType.REAL) {
-      return new Timestamp(type, BigInt(transition.timestamp.unixNanos.toString()));
-    }
-    return undefined;
+  override getTimestamp(
+    type: TimestampType,
+    decodedEntry: PropertyTreeNode
+  ): undefined | Timestamp {
+    // for consistency with all transitions, elapsed nanos are defined as shell dispatch time else 0n
+    const realToElapsedTimeOffsetNs = decodedEntry
+      .getChildByName('realToElapsedTimeOffsetNs')
+      ?.getValue();
+    const dispatchTimeLong = decodedEntry
+      .getChildByName('shellData')
+      ?.getChildByName('dispatchTimeNs')
+      ?.getValue();
+    const timestampNs = dispatchTimeLong ? BigInt(dispatchTimeLong.toString()) : 0n;
+    return Timestamp.from(type, timestampNs, realToElapsedTimeOffsetNs);
   }
 
   private compressEntries(allTransitions: PropertyTreeNode[]): PropertyTreeNode[] {
@@ -122,9 +126,26 @@
   }
 
   private compareByTimestamp(a: PropertyTreeNode, b: PropertyTreeNode): number {
-    const aTimestamp = assertDefined(a.getChildByName('timestampNs')).getValue();
-    const bTimestamp = assertDefined(b.getChildByName('timestampNs')).getValue();
-    return aTimestamp < bTimestamp ? -1 : 1;
+    const aTimestamp = BigInt(
+      assertDefined(a.getChildByName('shellData'))
+        .getChildByName('dispatchTimeNs')
+        ?.getValue()
+        .toString() ?? 0n
+    );
+    const bTimestamp = BigInt(
+      assertDefined(b.getChildByName('shellData'))
+        .getChildByName('dispatchTimeNs')
+        ?.getValue()
+        .toString() ?? 0n
+    );
+    if (aTimestamp !== bTimestamp) {
+      return aTimestamp < bTimestamp ? -1 : 1;
+    }
+    // if dispatchTimeNs not present for both, fallback to id
+    return assertDefined(a.getChildByName('id')).getValue() <
+      assertDefined(b.getChildByName('id')).getValue()
+      ? -1
+      : 1;
   }
 
   private mergePartialTransitions(
diff --git a/tools/winscope/src/parsers/transitions/traces_parser_transitions_test.ts b/tools/winscope/src/parsers/transitions/traces_parser_transitions_test.ts
index 7f9dbe0..4620d9f 100644
--- a/tools/winscope/src/parsers/transitions/traces_parser_transitions_test.ts
+++ b/tools/winscope/src/parsers/transitions/traces_parser_transitions_test.ts
@@ -14,20 +14,21 @@
  * limitations under the License.
  */
 
+import {assertDefined} from 'common/assert_utils';
 import {Timestamp, TimestampType} from 'common/time';
-import {Transition} from 'flickerlib/common';
 import {UnitTestUtils} from 'test/unit/utils';
 import {Parser} from 'trace/parser';
 import {TraceType} from 'trace/trace_type';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
 
 describe('ParserTransitions', () => {
-  let parser: Parser<Transition>;
+  let parser: Parser<PropertyTreeNode>;
 
   beforeAll(async () => {
-    parser = await UnitTestUtils.getTracesParser([
+    parser = (await UnitTestUtils.getTracesParser([
       'traces/elapsed_and_real_timestamp/wm_transition_trace.pb',
       'traces/elapsed_and_real_timestamp/shell_transition_trace.pb',
-    ]);
+    ])) as Parser<PropertyTreeNode>;
   });
 
   it('has expected trace type', () => {
@@ -35,29 +36,24 @@
   });
 
   it('provides elapsed timestamps', () => {
-    const timestamps = parser.getTimestamps(TimestampType.ELAPSED)!;
-
-    expect(timestamps.length).toEqual(4);
-
+    const timestamps = assertDefined(parser.getTimestamps(TimestampType.ELAPSED));
     const expected = [
-      new Timestamp(TimestampType.ELAPSED, 57649586217344n),
-      new Timestamp(TimestampType.ELAPSED, 57649691956439n),
-      new Timestamp(TimestampType.ELAPSED, 57651263812071n),
+      new Timestamp(TimestampType.ELAPSED, 0n),
+      new Timestamp(TimestampType.ELAPSED, 0n),
+      new Timestamp(TimestampType.ELAPSED, 57649649922341n),
+      new Timestamp(TimestampType.ELAPSED, 57651299086892n),
     ];
-    expect(timestamps.slice(0, 3)).toEqual(expected);
+    expect(timestamps).toEqual(expected);
   });
 
   it('provides real timestamps', () => {
+    const timestamps = assertDefined(parser.getTimestamps(TimestampType.REAL));
     const expected = [
-      new Timestamp(TimestampType.REAL, 1683188477542869667n),
-      new Timestamp(TimestampType.REAL, 1683188477648608762n),
-      new Timestamp(TimestampType.REAL, 1683188479220464394n),
+      new Timestamp(TimestampType.REAL, 1683130827956652323n),
+      new Timestamp(TimestampType.REAL, 1683130827956652323n),
+      new Timestamp(TimestampType.REAL, 1683188477606574664n),
+      new Timestamp(TimestampType.REAL, 1683188479255739215n),
     ];
-
-    const timestamps = parser.getTimestamps(TimestampType.REAL)!;
-
-    expect(timestamps.length).toEqual(4);
-
-    expect(timestamps.slice(0, 3)).toEqual(expected);
+    expect(timestamps).toEqual(expected);
   });
 });
diff --git a/tools/winscope/src/parsers/view_capture/parser_view_capture_window.ts b/tools/winscope/src/parsers/view_capture/parser_view_capture_window.ts
index 27e5439..818dcca 100644
--- a/tools/winscope/src/parsers/view_capture/parser_view_capture_window.ts
+++ b/tools/winscope/src/parsers/view_capture/parser_view_capture_window.ts
@@ -78,7 +78,7 @@
     private readonly descriptors: string[],
     private readonly frameData: com.android.app.viewcapture.data.IFrameData[],
     private readonly traceType: TraceType,
-    private readonly realToElapsedTimeOffsetNanos: bigint,
+    private readonly realToElapsedTimeOffsetNs: bigint,
     private readonly packageName: string,
     private readonly classNames: string[]
   ) {
@@ -139,7 +139,7 @@
         const timestamp = Timestamp.from(
           type,
           BigInt(assertDefined(entry.timestamp).toString()),
-          this.realToElapsedTimeOffsetNanos
+          this.realToElapsedTimeOffsetNs
         );
         if (timestamp === undefined) {
           areTimestampsValid = false;
diff --git a/tools/winscope/src/trace/trace_type.ts b/tools/winscope/src/trace/trace_type.ts
index 2094f14..e033d38 100644
--- a/tools/winscope/src/trace/trace_type.ts
+++ b/tools/winscope/src/trace/trace_type.ts
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-import {Cuj, Event, Transition} from 'flickerlib/common';
+import {Cuj, Event} from 'flickerlib/common';
 import {LogMessage} from './protolog';
 import {ScreenRecordingTraceEntry} from './screen_recording';
 import {HierarchyTreeNode} from './tree_node/hierarchy_tree_node';
@@ -62,9 +62,9 @@
   [TraceType.INPUT_METHOD_MANAGER_SERVICE]: HierarchyTreeNode;
   [TraceType.INPUT_METHOD_SERVICE]: HierarchyTreeNode;
   [TraceType.EVENT_LOG]: Event;
-  [TraceType.WM_TRANSITION]: object;
-  [TraceType.SHELL_TRANSITION]: object;
-  [TraceType.TRANSITION]: Transition;
+  [TraceType.WM_TRANSITION]: PropertyTreeNode;
+  [TraceType.SHELL_TRANSITION]: PropertyTreeNode;
+  [TraceType.TRANSITION]: PropertyTreeNode;
   [TraceType.CUJS]: Cuj;
   [TraceType.TAG]: object;
   [TraceType.ERROR]: object;
diff --git a/tools/winscope/src/viewers/viewer_transitions/presenter.ts b/tools/winscope/src/viewers/viewer_transitions/presenter.ts
index 9c58541..9c070bf 100644
--- a/tools/winscope/src/viewers/viewer_transitions/presenter.ts
+++ b/tools/winscope/src/viewers/viewer_transitions/presenter.ts
@@ -15,21 +15,25 @@
  */
 
 import {assertDefined} from 'common/assert_utils';
-import {TimeUtils} from 'common/time_utils';
-import {Transition} from 'flickerlib/common';
 import {WinscopeEvent, WinscopeEventType} from 'messaging/winscope_event';
 import {CustomQueryType} from 'trace/custom_query';
 import {Trace} from 'trace/trace';
 import {Traces} from 'trace/traces';
 import {TraceEntryFinder} from 'trace/trace_entry_finder';
 import {TraceType} from 'trace/trace_type';
+import {Transition} from 'trace/transition';
 import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
-import {PropertiesTreeNodeLegacy} from 'viewers/common/ui_tree_utils_legacy';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+import {Filter} from 'viewers/common/operations/filter';
+import {UiPropertyTreeNode} from 'viewers/common/ui_property_tree_node';
+import {UiTreeFormatter} from 'viewers/common/ui_tree_formatter';
+import {UiTreeUtils} from 'viewers/common/ui_tree_utils';
+import {UpdateTransitionChangesNames} from './operations/update_transition_changes_names';
 import {UiData} from './ui_data';
 
 export class Presenter {
   private isInitialized = false;
-  private transitionTrace: Trace<object>;
+  private transitionTrace: Trace<PropertyTreeNode>;
   private surfaceFlingerTrace: Trace<HierarchyTreeNode> | undefined;
   private windowManagerTrace: Trace<HierarchyTreeNode> | undefined;
   private layerIdToName = new Map<number, string>();
@@ -54,20 +58,17 @@
 
       const entry = TraceEntryFinder.findCorrespondingEntry(this.transitionTrace, event.position);
 
-      this.uiData.selectedTransition = await entry?.getValue();
-      if (this.uiData.selectedTransition !== undefined) {
-        await this.onTransitionSelected(this.uiData.selectedTransition);
+      const transition = await entry?.getValue();
+      if (transition !== undefined) {
+        this.onTransitionSelected(transition);
       }
 
       this.notifyUiDataCallback(this.uiData);
     });
   }
 
-  async onTransitionSelected(transition: Transition): Promise<void> {
-    this.uiData.selectedTransition = transition;
-    this.uiData.selectedTransitionPropertiesTree = await this.makeSelectedTransitionPropertiesTree(
-      transition
-    );
+  onTransitionSelected(transition: PropertyTreeNode): void {
+    this.uiData.selectedTransition = this.makeUiPropertiesTree(transition);
     this.notifyUiDataCallback(this.uiData);
   }
 
@@ -98,141 +99,45 @@
   }
 
   private async computeUiData(): Promise<UiData> {
-    const entryPromises = this.transitionTrace.mapEntry((entry, originalIndex) => {
+    const entryPromises = this.transitionTrace.mapEntry((entry) => {
       return entry.getValue();
     });
-    const transitions = await Promise.all(entryPromises);
-
+    const entries = await Promise.all(entryPromises);
+    const transitions = this.makeTransitions(entries);
     const selectedTransition = this.uiData?.selectedTransition ?? undefined;
-    const selectedTransitionPropertiesTree =
-      this.uiData?.selectedTransitionPropertiesTree ?? undefined;
 
-    const timestampType = this.transitionTrace.getTimestampType();
-    if (timestampType === undefined) {
-      throw new Error('Missing timestamp type in trace!');
-    }
-    return new UiData(
-      transitions,
-      selectedTransition,
-      timestampType,
-      selectedTransitionPropertiesTree
-    );
+    return new UiData(transitions, selectedTransition);
   }
 
-  private async makeSelectedTransitionPropertiesTree(
-    transition: Transition
-  ): Promise<PropertiesTreeNodeLegacy> {
-    const changes: PropertiesTreeNodeLegacy[] = [];
+  private makeTransitions(entries: PropertyTreeNode[]): Transition[] {
+    return entries.map((transitionNode) => {
+      const wmDataNode = assertDefined(transitionNode.getChildByName('wmData'));
 
-    for (const change of transition.changes) {
-      const layerName = this.layerIdToName.get(change.layerId);
-      const windowTitle = this.windowTokenToTitle.get(change.windowId.toString(16));
+      const transition: Transition = {
+        id: assertDefined(transitionNode.getChildByName('id')).getValue(),
+        type: assertDefined(wmDataNode.getChildByName('type')).formattedValue(),
+        sendTime: wmDataNode.getChildByName('sendTimeNs')?.formattedValue(),
+        finishTime: wmDataNode.getChildByName('finishTimeNs')?.formattedValue(),
+        duration: transitionNode.getChildByName('duration')?.formattedValue(),
+        merged: assertDefined(transitionNode.getChildByName('merged')).getValue(),
+        aborted: assertDefined(transitionNode.getChildByName('aborted')).getValue(),
+        played: assertDefined(transitionNode.getChildByName('played')).getValue(),
+        realToElapsedTimeOffsetNs: assertDefined(
+          transitionNode.getChildByName('realToElapsedTimeOffsetNs')
+        ).getValue(),
+        propertiesTree: transitionNode,
+      };
+      return transition;
+    });
+  }
 
-      const layerIdValue = layerName ? `${change.layerId} (${layerName})` : change.layerId;
-      const windowIdValue = windowTitle
-        ? `0x${change.windowId.toString(16)} (${windowTitle})`
-        : `0x${change.windowId.toString(16)}`;
+  private makeUiPropertiesTree(transitionNode: PropertyTreeNode): UiPropertyTreeNode {
+    const tree = UiPropertyTreeNode.from(transitionNode);
 
-      changes.push({
-        propertyKey: 'change',
-        children: [
-          {propertyKey: 'transitMode', propertyValue: change.transitMode},
-          {propertyKey: 'layerId', propertyValue: layerIdValue},
-          {propertyKey: 'windowId', propertyValue: windowIdValue},
-        ],
-      });
-    }
-
-    const properties: PropertiesTreeNodeLegacy[] = [
-      {propertyKey: 'id', propertyValue: transition.id},
-      {propertyKey: 'type', propertyValue: transition.type},
-      {propertyKey: 'aborted', propertyValue: `${transition.aborted}`},
-    ];
-
-    if (transition.handler) {
-      properties.push({propertyKey: 'handler', propertyValue: transition.handler});
-    }
-
-    const timestampType = this.transitionTrace.getTimestampType();
-
-    if (!transition.createTime.isMin) {
-      properties.push({
-        propertyKey: 'createTime',
-        propertyValue: TimeUtils.formattedKotlinTimestamp(transition.createTime, timestampType),
-      });
-    }
-
-    if (!transition.sendTime.isMin) {
-      properties.push({
-        propertyKey: 'sendTime',
-        propertyValue: TimeUtils.formattedKotlinTimestamp(transition.sendTime, timestampType),
-      });
-    }
-
-    if (!transition.dispatchTime.isMin) {
-      properties.push({
-        propertyKey: 'dispatchTime',
-        propertyValue: TimeUtils.formattedKotlinTimestamp(transition.dispatchTime, timestampType),
-      });
-    }
-
-    if (!transition.finishTime.isMax) {
-      properties.push({
-        propertyKey: 'finishTime',
-        propertyValue: TimeUtils.formattedKotlinTimestamp(transition.finishTime, timestampType),
-      });
-    }
-
-    if (transition.mergeRequestTime) {
-      properties.push({
-        propertyKey: 'mergeRequestTime',
-        propertyValue: TimeUtils.formattedKotlinTimestamp(
-          transition.mergeRequestTime,
-          timestampType
-        ),
-      });
-    }
-
-    if (transition.shellAbortTime) {
-      properties.push({
-        propertyKey: 'shellAbortTime',
-        propertyValue: TimeUtils.formattedKotlinTimestamp(transition.shellAbortTime, timestampType),
-      });
-    }
-
-    if (transition.mergeTime) {
-      properties.push({
-        propertyKey: 'mergeTime',
-        propertyValue: TimeUtils.formattedKotlinTimestamp(transition.mergeTime, timestampType),
-      });
-    }
-
-    if (transition.mergeTarget) {
-      properties.push({
-        propertyKey: 'mergeTarget',
-        propertyValue: transition.mergeTarget,
-      });
-    }
-
-    if (transition.startTransactionId !== -1) {
-      properties.push({
-        propertyKey: 'startTransactionId',
-        propertyValue: transition.startTransactionId,
-      });
-    }
-    if (transition.finishTransactionId !== -1) {
-      properties.push({
-        propertyKey: 'finishTransactionId',
-        propertyValue: transition.finishTransactionId,
-      });
-    }
-    if (changes.length > 0) {
-      properties.push({propertyKey: 'changes', children: changes});
-    }
-
-    return {
-      children: properties,
-      propertyKey: 'Selected Transition',
-    };
+    return new UiTreeFormatter<UiPropertyTreeNode>()
+      .setUiTree(tree)
+      .addOperation(new Filter([UiTreeUtils.isNotCalculated], false))
+      .addOperation(new UpdateTransitionChangesNames(this.layerIdToName, this.windowTokenToTitle))
+      .format();
   }
 }
diff --git a/tools/winscope/src/viewers/viewer_transitions/ui_data.ts b/tools/winscope/src/viewers/viewer_transitions/ui_data.ts
index 131be8e..cf95247 100644
--- a/tools/winscope/src/viewers/viewer_transitions/ui_data.ts
+++ b/tools/winscope/src/viewers/viewer_transitions/ui_data.ts
@@ -14,17 +14,11 @@
  * limitations under the License.
  */
 
-import {TimestampType} from 'common/time';
-import {Transition} from 'flickerlib/common';
-import {PropertiesTreeNodeLegacy} from 'viewers/common/ui_tree_utils_legacy';
+import {Transition} from 'trace/transition';
+import {UiPropertyTreeNode} from 'viewers/common/ui_property_tree_node';
 
 export class UiData {
-  constructor(
-    public entries: Transition[],
-    public selectedTransition: Transition,
-    public timestampType: TimestampType,
-    public selectedTransitionPropertiesTree?: PropertiesTreeNodeLegacy
-  ) {}
+  constructor(public entries: Transition[], public selectedTransition?: UiPropertyTreeNode) {}
 
-  static EMPTY = new UiData([], undefined, TimestampType.REAL, undefined);
+  static EMPTY = new UiData([], undefined);
 }
diff --git a/tools/winscope/src/viewers/viewer_transitions/viewer_transitions_component.ts b/tools/winscope/src/viewers/viewer_transitions/viewer_transitions_component.ts
index a3efd1c..8e8a066 100644
--- a/tools/winscope/src/viewers/viewer_transitions/viewer_transitions_component.ts
+++ b/tools/winscope/src/viewers/viewer_transitions/viewer_transitions_component.ts
@@ -15,10 +15,8 @@
  */
 
 import {Component, ElementRef, Inject, Input} from '@angular/core';
-import {ElapsedTimestamp, TimestampType} from 'common/time';
-import {TimeUtils} from 'common/time_utils';
-import {Transition} from 'flickerlib/common';
-import {Terminal} from 'viewers/common/ui_tree_utils_legacy';
+import {Transition} from 'trace/transition';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
 import {Events} from './events';
 import {UiData} from './ui_data';
 
@@ -28,11 +26,11 @@
     <div class="card-grid container">
       <div class="entries">
         <div class="table-header table-row">
-          <div class="id">Id</div>
-          <div class="type">Type</div>
-          <div class="send-time">Send Time</div>
-          <div class="duration">Duration</div>
-          <div class="status">Status</div>
+          <div class="id mat-body-2">Id</div>
+          <div class="type mat-body-2">Type</div>
+          <div class="send-time mat-body-2">Send Time</div>
+          <div class="duration mat-body-2">Duration</div>
+          <div class="status mat-body-2">Status</div>
         </div>
         <cdk-virtual-scroll-viewport itemSize="53" class="scroll">
           <div
@@ -47,34 +45,22 @@
               <span class="mat-body-1">{{ transition.type }}</span>
             </div>
             <div class="send-time">
-              <span *ngIf="!transition.sendTime.isMin" class="mat-body-1">{{
-                formattedTime(transition.sendTime, uiData.timestampType)
-              }}</span>
-              <span *ngIf="transition.sendTime.isMin"> n/a </span>
+              <span *ngIf="transition.sendTime" class="mat-body-1">{{ transition.sendTime }}</span>
+              <span *ngIf="!transition.sendTime" class="mat-body-1"> n/a </span>
             </div>
             <div class="duration">
-              <span
-                *ngIf="!transition.sendTime.isMin && !transition.finishTime.isMax"
-                class="mat-body-1"
-                >{{
-                  formattedTimeDiff(
-                    transition.sendTime,
-                    transition.finishTime,
-                    uiData.timestampType
-                  )
-                }}</span
-              >
-              <span *ngIf="transition.sendTime.isMin || transition.finishTime.isMax">n/a</span>
+              <span *ngIf="transition.duration" class="mat-body-1">{{ transition.duration }}</span>
+              <span *ngIf="!transition.duration" class="mat-body-1"> n/a </span>
             </div>
             <div class="status">
               <div *ngIf="transition.merged">
-                <span>MERGED</span>
+                <span class="mat-body-1">MERGED</span>
                 <mat-icon aria-hidden="false" fontIcon="merge" matTooltip="merged" icon-gray>
                 </mat-icon>
               </div>
 
               <div *ngIf="transition.aborted && !transition.merged">
-                <span>ABORTED</span>
+                <span class="mat-body-1">ABORTED</span>
                 <mat-icon
                   aria-hidden="false"
                   fontIcon="close"
@@ -84,7 +70,7 @@
               </div>
 
               <div *ngIf="transition.played && !transition.aborted && !transition.merged">
-                <span>PLAYED</span>
+                <span class="mat-body-1">PLAYED</span>
                 <mat-icon
                   aria-hidden="false"
                   fontIcon="check"
@@ -101,12 +87,8 @@
 
       <div class="container-properties">
         <h3 class="properties-title mat-title">Selected Transition</h3>
-        <tree-view-legacy
-          [item]="uiData.selectedTransitionPropertiesTree"
-          [showNode]="showNode"
-          [isLeaf]="isLeaf">
-        </tree-view-legacy>
-        <div *ngIf="!uiData.selectedTransitionPropertiesTree">No selected transition.</div>
+        <tree-view [node]="uiData.selectedTransition"></tree-view>
+        <div *ngIf="!uiData.selectedTransition" class="mat-body-1">No selected transition.</div>
       </div>
     </div>
   `,
@@ -214,210 +196,39 @@
   ],
 })
 export class ViewerTransitionsComponent {
-  transitionHeight = '20px';
-  transitionDividerWidth = '3px';
-
-  constructor(@Inject(ElementRef) elementRef: ElementRef) {
-    this.elementRef = elementRef;
-  }
+  constructor(@Inject(ElementRef) private elementRef: ElementRef) {}
 
   @Input()
   set inputData(data: UiData) {
     this.uiData = data;
   }
 
-  getMinOfRanges(): bigint {
-    if (this.uiData.entries.length === 0) {
-      return 0n;
-    }
-    const minOfRange = bigIntMin(
-      ...this.uiData.entries
-        .filter((it) => !it.createTime.isMin)
-        .map((it) => BigInt(it.createTime.elapsedNanos.toString()))
-    );
-    return minOfRange;
-  }
-
-  getMaxOfRanges(): bigint {
-    if (this.uiData.entries.length === 0) {
-      return 0n;
-    }
-    const maxOfRange = bigIntMax(
-      ...this.uiData.entries
-        .filter((it) => !it.finishTime.isMax)
-        .map((it) => BigInt(it.finishTime.elapsedNanos.toString()))
-    );
-    return maxOfRange;
-  }
-
-  formattedTime(time: any, timestampType: TimestampType): string {
-    return TimeUtils.formattedKotlinTimestamp(time, timestampType);
-  }
-
-  formattedTimeDiff(time1: any, time2: any, timestampType: TimestampType): string {
-    const timeDiff = new ElapsedTimestamp(
-      BigInt(time2.elapsedNanos.toString()) - BigInt(time1.elapsedNanos.toString())
-    );
-    return TimeUtils.format(timeDiff);
-  }
-
-  widthOf(transition: Transition) {
-    const fullRange = this.getMaxOfRanges() - this.getMinOfRanges();
-
-    let finish = BigInt(transition.finishTime.elapsedNanos.toString());
-    if (transition.finishTime.elapsedNanos.isMax) {
-      finish = this.getMaxOfRanges();
-    }
-
-    let start = BigInt(transition.createTime.elapsedNanos.toString());
-    if (transition.createTime.elapsedNanos.isMin) {
-      start = this.getMinOfRanges();
-    }
-
-    const minWidthPercent = 0.5;
-    return `${Math.max(minWidthPercent, Number((finish - start) * 100n) / Number(fullRange))}%`;
-  }
-
-  startOf(transition: Transition) {
-    const fullRange = this.getMaxOfRanges() - this.getMinOfRanges();
-    return `${
-      Number(
-        (BigInt(transition.createTime.elapsedNanos.toString()) - this.getMinOfRanges()) * 100n
-      ) / Number(fullRange)
-    }%`;
-  }
-
-  sendOf(transition: Transition) {
-    const fullRange = this.getMaxOfRanges() - this.getMinOfRanges();
-    return `${
-      Number((BigInt(transition.sendTime.elapsedNanos.toString()) - this.getMinOfRanges()) * 100n) /
-      Number(fullRange)
-    }%`;
-  }
-
   onTransitionClicked(transition: Transition): void {
-    this.emitEvent(Events.TransitionSelected, transition);
-  }
-
-  transitionRectStyle(transition: Transition): string {
-    if (this.uiData.selectedTransition === transition) {
-      return 'fill:rgb(0, 0, 230)';
-    } else if (transition.aborted) {
-      return 'fill:rgb(255, 0, 0)';
-    } else {
-      return 'fill:rgb(78, 205, 230)';
-    }
-  }
-
-  transitionDividerRectStyle(transition: Transition): string {
-    return 'fill:rgb(255, 0, 0)';
-  }
-
-  showNode(item: any) {
-    return (
-      !(item instanceof Terminal) &&
-      !(item.name instanceof Terminal) &&
-      !(item.propertyKey instanceof Terminal)
-    );
-  }
-
-  isLeaf(item: any) {
-    return (
-      !item.children ||
-      item.children.length === 0 ||
-      item.children.filter((c: any) => !(c instanceof Terminal)).length === 0
-    );
+    this.emitEvent(Events.TransitionSelected, transition.propertiesTree);
   }
 
   isCurrentTransition(transition: Transition): boolean {
-    return this.uiData.selectedTransition === transition;
+    return (
+      transition.id ===
+        this.uiData.selectedTransition
+          ?.getChildByName('wmData')
+          ?.getChildByName('id')
+          ?.getValue() ||
+      transition.id ===
+        this.uiData.selectedTransition
+          ?.getChildByName('shellData')
+          ?.getChildByName('id')
+          ?.getValue()
+    );
   }
 
-  assignRowsToTransitions(): Map<Transition, number> {
-    const fullRange = this.getMaxOfRanges() - this.getMinOfRanges();
-    const assignedRows = new Map<Transition, number>();
-
-    const sortedTransitions = [...this.uiData.entries].sort((t1, t2) => {
-      const diff =
-        BigInt(t1.createTime.elapsedNanos.toString()) -
-        BigInt(t2.createTime.elapsedNanos.toString());
-      if (diff < 0) {
-        return -1;
-      }
-      if (diff > 0) {
-        return 1;
-      }
-      return 0;
-    });
-
-    const rowFirstAvailableTime = new Map<number, bigint>();
-    let rowsUsed = 1;
-    rowFirstAvailableTime.set(0, 0n);
-
-    for (const transition of sortedTransitions) {
-      const start = BigInt(transition.createTime.elapsedNanos.toString());
-      const end = BigInt(transition.finishTime.elapsedNanos.toString());
-
-      let rowIndexWithSpace = undefined;
-      for (let rowIndex = 0; rowIndex < rowsUsed; rowIndex++) {
-        if (start > rowFirstAvailableTime.get(rowIndex)!) {
-          // current row has space
-          rowIndexWithSpace = rowIndex;
-          break;
-        }
-      }
-
-      if (rowIndexWithSpace === undefined) {
-        rowIndexWithSpace = rowsUsed;
-        rowsUsed++;
-      }
-
-      assignedRows.set(transition, rowIndexWithSpace);
-
-      const minimumPaddingBetweenEntries = fullRange / 100n;
-
-      rowFirstAvailableTime.set(rowIndexWithSpace, end + minimumPaddingBetweenEntries);
-    }
-
-    return assignedRows;
-  }
-
-  timelineRows(): number[] {
-    return [...new Set(this.assignRowsToTransitions().values())];
-  }
-
-  transitionsOnRow(row: number): Transition[] {
-    const transitions = [];
-    const assignedRows = this.assignRowsToTransitions();
-
-    for (const transition of assignedRows.keys()) {
-      if (row === assignedRows.get(transition)) {
-        transitions.push(transition);
-      }
-    }
-
-    return transitions;
-  }
-
-  rowsRequiredForTransitions(): number {
-    return Math.max(...this.assignRowsToTransitions().values());
-  }
-
-  emitEvent(event: string, data: any) {
+  emitEvent(event: string, propertiesTree: PropertyTreeNode) {
     const customEvent = new CustomEvent(event, {
       bubbles: true,
-      detail: data,
+      detail: propertiesTree,
     });
     this.elementRef.nativeElement.dispatchEvent(customEvent);
   }
 
   uiData: UiData = UiData.EMPTY;
-  private elementRef: ElementRef;
-}
-
-function bigIntMax(...args: Array<bigint>) {
-  return args.reduce((m, e) => (e > m ? e : m));
-}
-function bigIntMin(...args: Array<bigint>) {
-  return args.reduce((m, e) => (e < m ? e : m));
 }
diff --git a/tools/winscope/src/viewers/viewer_transitions/viewer_transitions_component_test.ts b/tools/winscope/src/viewers/viewer_transitions/viewer_transitions_component_test.ts
index 143a0bf..3c4f110 100644
--- a/tools/winscope/src/viewers/viewer_transitions/viewer_transitions_component_test.ts
+++ b/tools/winscope/src/viewers/viewer_transitions/viewer_transitions_component_test.ts
@@ -20,24 +20,19 @@
 import {MatDividerModule} from '@angular/material/divider';
 import {assertDefined} from 'common/assert_utils';
 import {TimestampType} from 'common/time';
-import {
-  CrossPlatform,
-  ShellTransitionData,
-  Transition,
-  TransitionChange,
-  TransitionType,
-  WmTransitionData,
-} from 'flickerlib/common';
 import {TracePositionUpdate} from 'messaging/winscope_event';
+import {PropertyTreeBuilder} from 'test/unit/property_tree_builder';
 import {UnitTestUtils} from 'test/unit/utils';
+import {Parser} from 'trace/parser';
 import {Trace} from 'trace/trace';
 import {Traces} from 'trace/traces';
 import {TracePosition} from 'trace/trace_position';
 import {TraceType} from 'trace/trace_type';
-import {TreeComponentLegacy} from 'viewers/components/legacy/tree_component';
-import {TreeNodeComponentLegacy} from 'viewers/components/legacy/tree_node_component';
-import {TreeNodeDataViewComponentLegacy} from 'viewers/components/legacy/tree_node_data_view_component';
-import {TreeNodePropertiesDataViewComponentLegacy} from 'viewers/components/legacy/tree_node_properties_data_view_component';
+import {Transition} from 'trace/transition';
+import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
+import {TreeComponent} from 'viewers/components/tree_component';
+import {TreeNodeComponent} from 'viewers/components/tree_node_component';
+import {TreeNodePropertiesDataViewComponent} from 'viewers/components/tree_node_properties_data_view_component';
 import {Events} from './events';
 import {Presenter} from './presenter';
 import {UiData} from './ui_data';
@@ -54,10 +49,9 @@
       imports: [MatDividerModule, ScrollingModule],
       declarations: [
         ViewerTransitionsComponent,
-        TreeComponentLegacy,
-        TreeNodeComponentLegacy,
-        TreeNodeDataViewComponentLegacy,
-        TreeNodePropertiesDataViewComponentLegacy,
+        TreeComponent,
+        TreeNodeComponent,
+        TreeNodePropertiesDataViewComponent,
       ],
       schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
     }).compileComponents();
@@ -77,10 +71,9 @@
   it('renders entries', () => {
     expect(htmlElement.querySelector('.scroll')).toBeTruthy();
 
-    const entry = htmlElement.querySelector('.scroll .entry');
-    expect(entry).toBeTruthy();
-    expect(entry!.innerHTML).toContain('TO_FRONT');
-    expect(entry!.innerHTML).toContain('10ns');
+    const entry = assertDefined(htmlElement.querySelector('.scroll .entry'));
+    expect(entry.innerHTML).toContain('TO_FRONT');
+    expect(entry.innerHTML).toContain('10ns');
   });
 
   it('shows message when no transition is selected', () => {
@@ -103,29 +96,29 @@
     expect(emitEventSpy).not.toHaveBeenCalled();
 
     const id0 = assertDefined(entry1.querySelector('.id')).textContent;
-    expect(id0).toBe('0');
+    expect(id0).toEqual('0');
     entry1.click();
     fixture.detectChanges();
 
     expect(emitEventSpy).toHaveBeenCalled();
     expect(emitEventSpy).toHaveBeenCalledWith(Events.TransitionSelected, jasmine.any(Object));
-    expect(emitEventSpy.calls.mostRecent().args[1].id).toBe(0);
+    expect(emitEventSpy.calls.mostRecent().args[1].getChildByName('id')?.getValue()).toEqual(0);
 
     const id1 = assertDefined(entry2.querySelector('.id')).textContent;
-    expect(id1).toBe('1');
+    expect(id1).toEqual('1');
     entry2.click();
     fixture.detectChanges();
 
     expect(emitEventSpy).toHaveBeenCalled();
     expect(emitEventSpy).toHaveBeenCalledWith(Events.TransitionSelected, jasmine.any(Object));
-    expect(emitEventSpy.calls.mostRecent().args[1].id).toBe(1);
+    expect(emitEventSpy.calls.mostRecent().args[1].getChildByName('id')?.getValue()).toEqual(1);
   });
 
   it('updates tree view on TracePositionUpdate event', async () => {
-    const parser = await UnitTestUtils.getTracesParser([
+    const parser = (await UnitTestUtils.getTracesParser([
       'traces/elapsed_and_real_timestamp/wm_transition_trace.pb',
       'traces/elapsed_and_real_timestamp/shell_transition_trace.pb',
-    ]);
+    ])) as Parser<PropertyTreeNode>;
     const trace = Trace.fromParser(parser, TimestampType.REAL);
     const traces = new Traces();
     traces.setTrace(TraceType.TRANSITION, trace);
@@ -141,13 +134,17 @@
     const selectedTransitionEntry = assertDefined(
       traces.getTrace(TraceType.TRANSITION)?.getEntry(2)
     );
-    const selectedTransition = (await selectedTransitionEntry.getValue()) as Transition;
+    const selectedTransition = await selectedTransitionEntry.getValue();
+    const selectedTransitionId = assertDefined(selectedTransition.getChildByName('id')).getValue();
     await presenter.onAppEvent(
       new TracePositionUpdate(TracePosition.fromTraceEntry(selectedTransitionEntry))
     );
 
-    expect(component.uiData.selectedTransition.id).toBe(selectedTransition.id);
-    expect(component.uiData.selectedTransitionPropertiesTree).toBeTruthy();
+    expect(
+      assertDefined(
+        component.uiData.selectedTransition?.getChildByName('wmData')?.getChildByName('id')
+      ).getValue()
+    ).toEqual(selectedTransitionId);
 
     fixture.detectChanges();
 
@@ -155,7 +152,7 @@
       fixture.nativeElement.querySelector('.container-properties')
     ) as any as HTMLElement;
     const textContentWithoutWhitespaces = treeView.textContent?.replace(/(\s|\t|\n)*/g, '');
-    expect(textContentWithoutWhitespaces).toContain(`id:${selectedTransition.id}`);
+    expect(textContentWithoutWhitespaces).toContain(`id:${selectedTransitionId}`);
   });
 });
 
@@ -163,54 +160,37 @@
   let mockTransitionIdCounter = 0;
 
   const transitions = [
-    createMockTransition(10, 20, 30, mockTransitionIdCounter++),
-    createMockTransition(40, 42, 50, mockTransitionIdCounter++),
-    createMockTransition(45, 46, 49, mockTransitionIdCounter++),
-    createMockTransition(55, 58, 70, mockTransitionIdCounter++),
+    createMockTransition(20, 30, mockTransitionIdCounter++),
+    createMockTransition(42, 50, mockTransitionIdCounter++),
+    createMockTransition(46, 49, mockTransitionIdCounter++),
+    createMockTransition(58, 70, mockTransitionIdCounter++),
   ];
 
-  const selectedTransition = undefined;
-  const selectedTransitionPropertiesTree = undefined;
-  const timestampType = TimestampType.REAL;
-
-  return new UiData(
-    transitions,
-    selectedTransition,
-    timestampType,
-    selectedTransitionPropertiesTree
-  );
+  return new UiData(transitions, undefined);
 }
 
 function createMockTransition(
-  createTimeNanos: number,
   sendTimeNanos: number,
   finishTimeNanos: number,
   id: number
 ): Transition {
-  const createTime = CrossPlatform.timestamp.fromString(createTimeNanos.toString(), null, null);
-  const sendTime = CrossPlatform.timestamp.fromString(sendTimeNanos.toString(), null, null);
-  const abortTime = null;
-  const finishTime = CrossPlatform.timestamp.fromString(finishTimeNanos.toString(), null, null);
-  const startingWindowRemoveTime = null;
+  const transitionTree = new PropertyTreeBuilder()
+    .setIsRoot(true)
+    .setRootId('TransitionTraceEntry')
+    .setName('transition')
+    .setChildren([{name: 'id', value: id}])
+    .build();
 
-  const startTransactionId = '-1';
-  const finishTransactionId = '-1';
-  const type = TransitionType.TO_FRONT;
-  const changes: TransitionChange[] = [];
-
-  return new Transition(
+  return {
     id,
-    new WmTransitionData(
-      createTime,
-      sendTime,
-      abortTime,
-      finishTime,
-      startingWindowRemoveTime,
-      startTransactionId,
-      finishTransactionId,
-      type,
-      changes
-    ),
-    new ShellTransitionData()
-  );
+    type: 'TO_FRONT',
+    sendTime: sendTimeNanos.toString() + 'ns',
+    finishTime: finishTimeNanos.toString() + 'ns',
+    duration: (finishTimeNanos - sendTimeNanos).toString() + 'ns',
+    merged: false,
+    aborted: false,
+    played: false,
+    realToElapsedTimeOffsetNs: undefined,
+    propertiesTree: transitionTree,
+  };
 }