Render the same expandable tree, but as a flat list — one <ul> of sibling <li>s, no nested DOM. The visual nesting comes entirely from indentation keyed to each row's depth. The trick is to flatten the tree into an ordered array of visible rows first, then render that array linearly.
type FileNode = { name: string; children?: FileNode[] };
type Row = { node: FileNode; path: string; depth: number; isFolder: boolean; isOpen: boolean };
function flatten(nodes: FileNode[], open: Set<string>): Row[];
open = { src }
flat rows: depth
▾ 📁 src 0 ← paddingLeft 0
▸ 📁 components 1 ← paddingLeft 18
📄 index.ts 1
▸ 📁 public 0
📄 package.json 0
DOM is a single flat <ul>; "components" only LOOKS nested because depth=1
adds left padding. Closed folders contribute no rows.
depth.paddingLeft = depth * step — the only thing that conveys hierarchy.