Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
e850ee3
feat(ui): persist trace selection in URL
hi-ogawa Aug 18, 2026
1b7d618
Merge remote-tracking branch 'upstream/main' into fix/issue-10769
hi-ogawa Aug 18, 2026
c60eca3
Merge branch 'main' into fix/issue-10769
hi-ogawa Aug 19, 2026
62ba71a
Merge branch 'main' into fix/issue-10769
hi-ogawa Aug 19, 2026
23f9dcc
test(ui): update trace selection assertion
hi-ogawa Aug 19, 2026
9b5db55
test(ui): cover live trace URL state
hi-ogawa Aug 19, 2026
c467a21
nit
hi-ogawa Aug 19, 2026
2a05d45
refactor(ui): qualify trace URL params
hi-ogawa Aug 19, 2026
21d7faa
test(ui): simplify trace URL assertions
hi-ogawa Aug 19, 2026
858cd85
test(ui): parse trace params from page URL
hi-ogawa Aug 19, 2026
6c1a11b
test(ui): explain trace URL scenarios
hi-ogawa Aug 19, 2026
41e2e3d
nit
hi-ogawa Aug 19, 2026
8ca9b63
test(ui): retain exact trace test ID
hi-ogawa Aug 19, 2026
6b871f5
test(ui): contrast trace URL states
hi-ogawa Aug 19, 2026
94554ad
test(ui): construct invalid trace URL locally
hi-ogawa Aug 19, 2026
fcdfaea
test(ui): contrast trace attempt states
hi-ogawa Aug 19, 2026
6a8f18f
test(ui): align trace persistence checks
hi-ogawa Aug 19, 2026
ff911a8
nit
hi-ogawa Aug 19, 2026
4459f4c
nit
hi-ogawa Aug 19, 2026
c86e2bb
chore(ui): fix URL param lint
hi-ogawa Aug 19, 2026
0039e0c
refactor(ui): pass complete trace selection
hi-ogawa Aug 19, 2026
ac5d8e6
refactor(ui): keep default trace attempt implicit
hi-ogawa Aug 19, 2026
4a78da9
refactor(ui): inline default trace selection
hi-ogawa Aug 19, 2026
bef9ded
fix(ui): preserve implicit trace attempt
hi-ogawa Aug 19, 2026
b3695cb
refactor(ui): simplify config readiness check
hi-ogawa Aug 21, 2026
fcfbbce
refactor(ui): use map for trace attempts
hi-ogawa Aug 21, 2026
061ea52
refactor(ui): derive selected trace test
hi-ogawa Aug 21, 2026
fbe61ce
Merge branch 'main' into fix/issue-10769
hi-ogawa Aug 21, 2026
136a924
refactor(ui): camelcase trace URL params
hi-ogawa Aug 21, 2026
42a1fea
fix(ui): close rebuilt trace document
hi-ogawa Aug 21, 2026
b89ac56
nit
hi-ogawa Aug 21, 2026
bd2f221
refactor(ui): own trace view initialization
hi-ogawa Aug 21, 2026
045da8d
feat(ui): persist trace pane sizes
hi-ogawa Aug 21, 2026
69316b8
Merge upstream/main into feat/trace-split-persistence
hi-ogawa Aug 24, 2026
b2be1b2
fix(ui): persist trace replay pane sizes
hi-ogawa Aug 24, 2026
631aafb
refactor(ui): clarify trace split size name
hi-ogawa Aug 24, 2026
4ff4bd3
refactor(ui): use splitpanes payload type
hi-ogawa Aug 24, 2026
969eb9e
nit
hi-ogawa Aug 24, 2026
0b12b82
nit
hi-ogawa Aug 24, 2026
6990a8b
nit
hi-ogawa Aug 24, 2026
1e0865f
test(ui): assert persisted trace pane width
hi-ogawa Aug 24, 2026
f7270c8
test(ui): resize trace from visible bounds
hi-ogawa Aug 24, 2026
69ed006
test(ui): assert trace pane geometry
hi-ogawa Aug 24, 2026
b1530ae
test(ui): make trace resize explicit
hi-ogawa Aug 24, 2026
4067293
test(ui): assert trace resize changes geometry
hi-ogawa Aug 24, 2026
72b5cbd
test(ui): clarify trace resize assertions
hi-ogawa Aug 24, 2026
c36c4fd
nit
hi-ogawa Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions packages/ui/client/components/trace/TraceView.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<script setup lang="ts">
import type { SplitpanesResizedPayload } from 'splitpanes'
import type { NormalizedBrowserTraceData, NormalizedBrowserTraceEntry, TraceSelection } from '~/composables/trace-view'
import { createCache, createMirror, rebuild } from 'rrweb-snapshot'
import { Pane, Splitpanes } from 'splitpanes'
import { computed, ref, watch } from 'vue'
import { openLocation } from '~/composables/location'
import { traceViewSplitSizes } from '~/composables/navigation'
import { getTraceEntryClass, selectActiveTraceStep } from '~/composables/trace-view'

const props = defineProps<{
Expand Down Expand Up @@ -172,13 +174,20 @@ function formatStepName(step: NormalizedBrowserTraceEntry) {
function isTraceStepInProgress(step: NormalizedBrowserTraceEntry) {
return step.range?.phase === 'start'
}

function onSplitpanesResized({ panes }: SplitpanesResizedPayload) {
if (panes.length === 2) {
traceViewSplitSizes.value = [panes[0].size, panes[1].size]
}
}
</script>

<template>
<Splitpanes
class="h-full min-h-0"
@resized="onSplitpanesResized"
>
<Pane :size="30" min-size="20">
<Pane :size="traceViewSplitSizes[0]" min-size="20">
<div
class="h-full min-h-0 p-4"
flex="~ col gap-1"
Expand Down Expand Up @@ -237,7 +246,7 @@ function isTraceStepInProgress(step: NormalizedBrowserTraceEntry) {
</button>
</div>
</Pane>
<Pane :size="70" min-size="20">
<Pane :size="traceViewSplitSizes[1]" min-size="20">
<div class="h-full min-h-0" flex="~ col" overflow-auto>
<iframe
v-if="selectedStep"
Expand Down
4 changes: 4 additions & 0 deletions packages/ui/client/composables/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export const detailSizes = useLocalStorage<[left: number, right: number]>(
67,
],
)
export const traceViewSplitSizes = useLocalStorage<[steps: number, iframe: number]>(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

local storage is not persistent between different runs, is it? we also have the same issue with dark mode. these settings should probably be also stored on the device somewhere

not blocking though

'vitest-ui_splitpanes-traceViewSplitSizes',
[30, 70],
)

export const detailsPanelVisible = useLocalStorage<boolean>(
'vitest-ui_details-panel-visible',
Expand Down
75 changes: 75 additions & 0 deletions test/ui/test/trace.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ test.describe('ui', () => {
test('persists attempt in URL', async ({ page }) => {
await testPersistsAttemptInURL(page)
})

test('persists resized trace panes across reloads', async ({ page }) => {
await testPersistsResizedTracePanes(page)
})
})

test.describe('html reporter', () => {
Expand Down Expand Up @@ -154,6 +158,10 @@ test.describe('html reporter', () => {
test('persists attempt in URL', async ({ page }) => {
await testPersistsAttemptInURL(page)
})

test('persists resized trace panes across reloads', async ({ page }) => {
await testPersistsResizedTracePanes(page)
})
})

async function testBasic(page: Page) {
Expand Down Expand Up @@ -485,3 +493,70 @@ function getHashParams(page: Page) {
const hash = new URL(page.url()).hash
return Object.fromEntries(new URLSearchParams(hash.split('?')[1]))
}

async function testPersistsResizedTracePanes(page: Page) {
// Opening a trace renders resizable step list and iframe panes.
await openExplorerItem(page, 'simple')

const traceView = page.getByTestId('trace-view')
const traceSteps = traceView.getByRole('listbox', { name: 'Trace steps' })
const splitpanes = traceView.locator('.splitpanes').first()
const splitter = splitpanes.locator('.splitpanes__splitter').first()
await expect(traceView).toBeVisible()
await expect(splitter).toBeVisible()

const initialTraceStepsBox = await traceSteps.boundingBox()
const traceViewBox = await traceView.boundingBox()
const splitterBox = await splitter.boundingBox()
if (!initialTraceStepsBox || !traceViewBox || !splitterBox) {
throw new Error('Trace split panes are not visible')
}

// Resize the step list from its 30% default to roughly 60% and persist both pane sizes.
expect(initialTraceStepsBox.width).toBeLessThan(traceViewBox.width / 2)
await page.mouse.move(
splitterBox.x + splitterBox.width / 2,
splitterBox.y + splitterBox.height / 2,
)
await page.mouse.down()
await page.mouse.move(
traceViewBox.x + traceViewBox.width * 0.6,
splitterBox.y + splitterBox.height / 2,
{ steps: 5 },
)
await page.mouse.up()

// wait for storage update
await expect.poll(async () => (await getStoredTracePaneSizes(page))?.[0]).toBeGreaterThan(55)
const expectedTraceStepsBox = await traceSteps.boundingBox()
if (!expectedTraceStepsBox) {
throw new Error('Trace steps are not visible')
}
// The resize visibly expands the step list without moving it.
expect(expectedTraceStepsBox).toEqual({
x: expect.closeTo(initialTraceStepsBox.x, 1),
y: expect.closeTo(initialTraceStepsBox.y, 1),
width: expect.any(Number),
height: expect.closeTo(initialTraceStepsBox.height, 1),
})
expect(expectedTraceStepsBox.width).toBeGreaterThan(traceViewBox.width / 2)

// Reloading repeatedly preserves the resized trace step geometry.
for (let i = 0; i < 2; i++) {
await page.reload()
await expect(traceView).toBeVisible()
await expect.poll(() => traceSteps.boundingBox()).toEqual({
x: expect.closeTo(expectedTraceStepsBox.x, 1),
y: expect.closeTo(expectedTraceStepsBox.y, 1),
width: expect.closeTo(expectedTraceStepsBox.width, 1),
height: expect.closeTo(expectedTraceStepsBox.height, 1),
})
}
}

function getStoredTracePaneSizes(page: Page): Promise<number[] | null> {
return page.evaluate(() => {
const value = localStorage.getItem('vitest-ui_splitpanes-traceViewSplitSizes')
return value ? JSON.parse(value) : null
})
}
Loading