ModsCode / Gallery / Designs / File tree with changes
File tree with changes
A project tree in Box and Text: muted glyphs, bold folders, and one column of git status letters, adds and deletes in the theme's colours.


hooks/designs/file-tree.mjs
// file-tree: a working tree with its git status, as Box and Text only. 18 rows counting the root:
// the tree glyphs (4 columns a level) are muted so the names carry the shape, folders are bold, and
// the status letter sits alone at column 48 so the changes read down one column, as in git status
// --short: M plain, A success, D error, ? subtle. M is not in warning: six Claude orange letters would be six
// accents and drown the adds and deletes. Unchanged files get no letter, so the column is sparse
// enough to scan. The rows are drawn from a nested list, so a glyph is never typed by hand.
// 6 changed (M), 2 added, 2 deleted, 1 untracked; the footer is counted from the rows.
// Same frame as the other text designs: the root is the bold subject line with a muted SAMPLE at
// column 48, and a muted summary closes it. Sample data only.
const ROOT = 'agent-pool/'
const WIDTH = 48
const TAG = 'SAMPLE'
// [name, status] for a file, [name, [children]] for a folder
const TREE = [
['.claude/', [['settings.json', 'M']]],
['src/', [
['agents/', [['budget.ts', 'A'], ['pool.ts', 'M'], ['retry.ts', 'D']]],
['hooks/', [['pre-commit.ts', 'M']]],
['index.ts', ''],
['tokens.ts', 'M'],
]],
['test/', [['budget.spec.ts', 'A'], ['pool.spec.ts', 'M'], ['retry.spec.ts', 'D']]],
['CLAUDE.md', 'M'],
['scratch.log', '?'],
]
const STATUS = { M: null, A: 'success', D: 'error', '?': 'subtle' }
const WORD = { M: 'changed', A: 'added', D: 'deleted', '?': 'untracked' }
// Depth-first rows: the glyphs above a row are '│ ' while that ancestor has later siblings
function rows(items, lead = '', out = []) {
items.forEach(([name, rest], i) => {
const last = i === items.length - 1
const folder = Array.isArray(rest)
out.push({ glyph: lead + (last ? '└── ' : '├── '), name, folder, status: folder ? '' : rest })
if (folder) rows(rest, lead + (last ? ' ' : '│ '), out)
})
return out
}
const ROWS = rows(TREE)
const SUMMARY = Object.keys(WORD)
.map(s => `${ROWS.filter(r => r.status === s).length} ${WORD[s]}`)
.join(', ')
export default {
id: 'file-tree',
title: 'File tree with changes',
category: 'code',
description: 'A project tree in Box and Text: muted glyphs, bold folders, and one column of git status letters, adds and deletes in the theme\'s colours.',
element: ui => {
const { Box, Text } = ui
return Box({
flexDirection: 'column',
children: [
Text({
key: 'root',
children: [
Text({ key: 'name', bold: true, children: [ROOT.padEnd(WIDTH - TAG.length)] }),
Text({ key: 'tag', dimColor: true, children: [TAG] }),
],
}),
...ROWS.map((r, i) => Text({
key: `r${i}`,
children: [
Text({ key: 'g', dimColor: true, children: [r.glyph] }),
r.status
? Text({ key: 'n', children: [r.name.padEnd(WIDTH - 1 - r.glyph.length)] })
: Text({ key: 'n', bold: r.folder, children: [r.name] }),
r.status && Text({ key: 's', ...(STATUS[r.status] && { color: STATUS[r.status] }), children: [r.status] }),
],
})),
Text({ key: 'sum', dimColor: true, children: [`-- ${SUMMARY} --`] }),
],
})
},
}
To use it in a mod: copy the file to your mod's hooks/designs/ and return its element(ui) from a pane's ui.render. How a pane is built.
Code: PolyForm Noncommercial License 1.0.0: noncommercial use only. Required Notice: Copyright 2026 Haruki Nakada (https://modscode.com).