From 1b326cbe62b99ffd2e600ee8d3c204d3f4d04156 Mon Sep 17 00:00:00 2001 From: adamelmore <2363879+adamdottv@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:18:23 -0600 Subject: [PATCH] fix(app): stack overflow in filetree --- packages/app/src/components/file-tree.tsx | 94 ++++++++++++++++------- 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx index 5552cc90b8e4..758f5a83f532 100644 --- a/packages/app/src/components/file-tree.tsx +++ b/packages/app/src/components/file-tree.tsx @@ -21,6 +21,8 @@ import { import { Dynamic } from "solid-js/web" import type { FileNode } from "@opencode-ai/sdk/v2" +const MAX_DEPTH = 128 + function pathToFileUrl(filepath: string): string { return `file://${encodeFilePath(filepath)}` } @@ -260,12 +262,20 @@ export default function FileTree(props: { _marks?: Set _deeps?: Map _kinds?: ReadonlyMap + _chain?: readonly string[] }) { const file = useFile() const level = props.level ?? 0 const draggable = () => props.draggable ?? true const tooltip = () => props.tooltip ?? true + const key = (p: string) => + file + .normalize(p) + .replace(/[\\/]+$/, "") + .replaceAll("\\", "/") + const chain = props._chain ? [...props._chain, key(props.path)] : [key(props.path)] + const filter = createMemo(() => { if (props._filter) return props._filter @@ -307,23 +317,45 @@ export default function FileTree(props: { const out = new Map() - const visit = (dir: string, lvl: number): number => { - const expanded = file.tree.state(dir)?.expanded ?? false - if (!expanded) return -1 + const root = props.path + if (!(file.tree.state(root)?.expanded ?? false)) return out + + const seen = new Set() + const stack: { dir: string; lvl: number; i: number; kids: string[]; max: number }[] = [] + + const push = (dir: string, lvl: number) => { + const id = key(dir) + if (seen.has(id)) return + seen.add(id) - const nodes = file.tree.children(dir) - const max = nodes.reduce((max, node) => { - if (node.type !== "directory") return max - const open = file.tree.state(node.path)?.expanded ?? false - if (!open) return max - return Math.max(max, visit(node.path, lvl + 1)) - }, lvl) + const kids = file.tree + .children(dir) + .filter((node) => node.type === "directory" && (file.tree.state(node.path)?.expanded ?? false)) + .map((node) => node.path) - out.set(dir, max) - return max + stack.push({ dir, lvl, i: 0, kids, max: lvl }) + } + + push(root, level - 1) + + while (stack.length > 0) { + const top = stack[stack.length - 1]! + + if (top.i < top.kids.length) { + const next = top.kids[top.i]! + top.i++ + push(next, top.lvl + 1) + continue + } + + out.set(top.dir, top.max) + stack.pop() + + const parent = stack[stack.length - 1] + if (!parent) continue + parent.max = Math.max(parent.max, top.max) } - visit(props.path, level - 1) return out }) @@ -459,21 +491,27 @@ export default function FileTree(props: { }} style={`left: ${Math.max(0, 8 + level * 12 - 4) + 8}px`} /> - + ...} + > + +