Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
"ignoreDeps": [
"@types/vscode"
]
}
}
3 changes: 3 additions & 0 deletions shared/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ export type SaveNames = z.infer<typeof saveNames>
export const childrenById = z.object({
message: z.literal('childrenById'),
id: z.number(),
limit: z.number().optional().default(50),
offset: z.number().optional().default(0),
Comment on lines +148 to +149
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

The message schema accepts any numeric limit/offset. To avoid abuse and accidental UI freezes, constrain these (e.g., non-negative offset, reasonable max limit) at the schema level (zod refinements) and/or in the message handler before calling getChildrenById.

Suggested change
limit: z.number().optional().default(50),
offset: z.number().optional().default(0),
limit: z.number().int().min(0).max(500).optional().default(50),
offset: z.number().int().min(0).optional().default(0),

Copilot uses AI. Check for mistakes.
children: z.array(zodTree).optional(),
total: z.number().optional(),
})
export type ChildrenById = z.infer<typeof childrenById>

Expand Down
3 changes: 2 additions & 1 deletion src/handleMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export function handleMessage(panel: vscode.WebviewPanel, message: unknown): voi
break
}
case 'childrenById': {
postMessage({ ...data, children: getChildrenById(data.id) }) // TODO: stream these
const { children, total } = getChildrenById(data.id, data.limit, data.offset)
postMessage({ ...data, children, total })
break
}
case 'typesById': {
Expand Down
10 changes: 6 additions & 4 deletions src/traceTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,16 @@ export function showTree(startsWith: string, sourceFileName: string, position: n
return nodes
}

export function getChildrenById(id: number) {
export function getChildrenById(id: number, limit: number = 50, offset: number = 0) {
const nodes = treeIdNodes.get(id)?.children ?? []
const ret: typeof nodes = []
nodes.forEach((node) => {
const total = nodes.length
const paginatedNodes = nodes.slice(offset, offset + limit)
const ret: typeof paginatedNodes = []
paginatedNodes.forEach((node) => {
Comment on lines +136 to +141
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

limit/offset are used directly to slice the children array without validation. Negative values or very large limits can produce unexpected paging behavior or reintroduce large payloads (UI freeze) if a webview sends a large limit. Clamp limit to a reasonable max and ensure offset >= 0 before slicing.

Copilot uses AI. Check for mistakes.
treeIdNodes.set(node.id, node)
ret.push({ ...node, children: [], types: [] })
})
return ret
return { children: ret, total }
}

export function getTypesById(id: number) {
Expand Down
21 changes: 19 additions & 2 deletions ui/components/TreeNode.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
<script setup lang="ts">
import type { Tree } from '../../src/traceTree'
import { childrenById, typesById } from '~/src/appState'
import { childrenById, childrenTotalById, typesById } from '~/src/appState'

const props = defineProps<{ tree: Tree, depth: number }>()

const sendMessage = useNuxtApp().$sendMessage

const children = computed(() => childrenById.get(props.tree.id) ?? [])
const totalChildren = computed(() => childrenTotalById.get(props.tree.id))
const types = computed(() => typesById.get(props.tree.id) ?? [])

// Pagination state
const offset = ref(0)
const limit = 50

function fetchChildren() {
if (children.value.length === 0)
sendMessage('childrenById', { id: props.tree.id })
sendMessage('childrenById', { id: props.tree.id, limit, offset: offset.value })
}

function loadMoreChildren() {
// Load next batch of children
const newOffset = offset.value + limit
offset.value = newOffset
sendMessage('childrenById', { id: props.tree.id, limit, offset: newOffset })
Comment on lines +13 to +26
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

offset is kept as local component state but the actual loaded count is stored globally in childrenById. If this component is ever remounted/reused (e.g., collapse/unmount behavior), offset will reset to 0 while childrenById may already contain items, causing duplicate page fetches. Consider deriving the next offset from children.value.length (or syncing offset from it) instead of incrementing a standalone ref.

Copilot uses AI. Check for mistakes.
}

function fetchTypes() {
Expand Down Expand Up @@ -78,6 +90,11 @@ const insetClass = `border-e min-w-2 border-[var(--vscode-tree-inactiveIndentGui
<template v-for="(node, idx) of children" :key="idx">
<TreeNode :depth="depth + 1" :tree="node" />
</template>
<div v-if="totalChildren !== undefined && children.length + offset < totalChildren" class="flex justify-center py-2 pl-8">
<button class="px-4 py-1 bg-[var(--vscode-button-background)] text-[var(--vscode-button-foreground)] rounded hover:opacity-80 transition-opacity text-sm" @click="loadMoreChildren">
Load More ({{ children.length + offset }}/{{ totalChildren }})
Comment on lines +93 to +95
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

The “Load More” visibility/progress math is incorrect: children.length already represents the number of loaded children (since childrenById appends). Adding offset double-counts and will hide the button early (roughly after half the children) and display wrong progress after the first load-more.

Suggested change
<div v-if="totalChildren !== undefined && children.length + offset < totalChildren" class="flex justify-center py-2 pl-8">
<button class="px-4 py-1 bg-[var(--vscode-button-background)] text-[var(--vscode-button-foreground)] rounded hover:opacity-80 transition-opacity text-sm" @click="loadMoreChildren">
Load More ({{ children.length + offset }}/{{ totalChildren }})
<div v-if="totalChildren !== undefined && children.length < totalChildren" class="flex justify-center py-2 pl-8">
<button class="px-4 py-1 bg-[var(--vscode-button-background)] text-[var(--vscode-button-foreground)] rounded hover:opacity-80 transition-opacity text-sm" @click="loadMoreChildren">
Load More ({{ children.length }}/{{ totalChildren }})

Copilot uses AI. Check for mistakes.
</button>
</div>
</UExpand>
</div>
</template>
3 changes: 3 additions & 0 deletions ui/src/appState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Tree } from '../../src/traceTree'
import * as Messages from '../../shared/src/messages'

export const childrenById = shallowReactive(new Map<number, Tree[]>())
export const childrenTotalById = shallowReactive(new Map<number, number>())
export const typesById = shallowReactive(new Map<number, TypeLine[]>())
Comment on lines 5 to 7
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

childrenTotalById is introduced but none of the per-id caches (childrenById, typesById, childrenTotalById) are cleared when a new tree is shown or the trace file list is reset. Since node IDs restart from 1 for each new trace (src/traceTree.ts resets id = 0), stale cached children/total can be applied to unrelated nodes in later traces. Clear these maps when handling showTree step start and/or traceFileLoaded with resetFileList.

Copilot uses AI. Check for mistakes.
export const nodes = ref([] as Tree[])
export const sortBy = ref('Timestamp' as keyof typeof sortValue)
Expand Down Expand Up @@ -40,6 +41,8 @@ function handleMessage(e: MessageEvent<unknown>) {
const id = parsed.data.id
const children = childrenById.get(id) ?? []
childrenById.set(id, [...children, ...parsed.data.children])
if (parsed.data.total !== undefined)
childrenTotalById.set(id, parsed.data.total)
break
}
case 'typesById': {
Expand Down
Loading