feat: files pane like VS Code: tree, tabs, go to file
build-deb / Lint, test, package (push) Successful in 2m28s
build-deb / Deploy to CT 126 (push) Successful in 3s
build-deb / Notify on failure (push) Skipped

- Explorer tree with inline expand/collapse and keyboard navigation, git
  colours, change dots, dimmed gitignored entries (fs-list now flags them),
  file-type badges, resizable explorer.
- Editor tabs: preview tab semantics, instant switching with scroll kept,
  in-place reload when Claude changes an open file.
- Go to file: devdesk attach --fs-find (git ls-files or capped find) and a
  fuzzy-ranked palette (CommandPalette gained custom search/async lists).
- Per-session memory of folders and tabs; empty state with recents.

Closes #37

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 01:40:47 -04:00
co-authored by Claude Opus 5.5
parent 73f8525db2
commit e345bf4640
10 changed files with 999 additions and 391 deletions
+32 -7
View File
@@ -72,20 +72,45 @@ dd_blob() {
}
# --fs-list SESSION [REL]: one directory level, folders first, .git skipped.
# In a git repository, entries .gitignore covers are flagged (the UI dims them).
cmd_fs_list() {
if [ $# -lt 1 ] || [ $# -gt 2 ]; then dd_die "usage: devdesk attach --fs-list SESSION [PATH]"; fi
local root dir rel=${2:-.}
local root dir rel=${2:-.} tmp ignored=""
root=$(dd_session_root "$1")
dir=$(dd_fs_path "$root" "$rel")
[ -d "$dir" ] || dd_die "not a folder: $rel"
tmp=$(mktemp)
# %y: type, %Y: type behind a symlink, %s: size, %T@: mtime, %f: name.
find "$dir" -mindepth 1 -maxdepth 1 ! -name .git -printf '%y\t%Y\t%s\t%T@\t%f\0' 2>/dev/null |
head -z -n "$((DEVDESK_FS_LIST_MAX + 1))" |
jq -cMRs --arg root "$root" --arg rel "${dir#"$root"}" --argjson max "$DEVDESK_FS_LIST_MAX" '
(split("\u0000") | map(select(length > 0) | split("\t") |
{t: .[0], to: .[1], s: (.[2] | tonumber), m: (.[3] | tonumber | floor), n: (.[4:] | join("\t"))})) as $all
| {root: $root, dir: ($rel | ltrimstr("/")), truncated: ($all | length > $max),
entries: ($all[:$max] | sort_by([(if .t == "d" or .to == "d" then 0 else 1 end), (.n | ascii_downcase)]))}'
head -z -n "$((DEVDESK_FS_LIST_MAX + 1))" >"$tmp"
if git --no-optional-locks -C "$dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
ignored=$(tr '\0' '\n' <"$tmp" | cut -f5- | git --no-optional-locks -C "$dir" check-ignore --stdin 2>/dev/null || true)
fi
jq -cMRs --arg root "$root" --arg rel "${dir#"$root"}" --argjson max "$DEVDESK_FS_LIST_MAX" --arg ign "$ignored" '
($ign | split("\n") | map(select(length > 0) | rtrimstr("/")) | INDEX(.)) as $ignored
| (split("\u0000") | map(select(length > 0) | split("\t") |
{t: .[0], to: .[1], s: (.[2] | tonumber), m: (.[3] | tonumber | floor), n: (.[4:] | join("\t"))}
| if $ignored[.n] then . + {ig: true} else . end)) as $all
| {root: $root, dir: ($rel | ltrimstr("/")), truncated: ($all | length > $max),
entries: ($all[:$max] | sort_by([(if .t == "d" or .to == "d" then 0 else 1 end), (.n | ascii_downcase)]))}' <"$tmp"
rm -f "$tmp"
}
# --fs-find SESSION: every file under the session folder, for "go to file".
# git ls-files in a repository (tracked + untracked, .gitignore honoured), else
# find without node_modules and .git; capped at DEVDESK_FS_FIND_MAX.
cmd_fs_find() {
[ $# -eq 1 ] || dd_die "usage: devdesk attach --fs-find SESSION"
local root max=${DEVDESK_FS_FIND_MAX:-20000}
root=$(dd_session_root "$1")
if git --no-optional-locks -C "$root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
git --no-optional-locks -C "$root" ls-files -co --exclude-standard -z 2>/dev/null
else
(cd "$root" && find . -maxdepth 8 \( -name node_modules -o -name .git -o -name __pycache__ -o -name .venv \) -prune \
-o -type f -printf '%P\0' 2>/dev/null)
fi | head -z -n "$((max + 1))" |
jq -cMRs --argjson max "$max" --arg root "$root" \
'(split("\u0000") | map(select(length > 0))) as $f | {root: $root, truncated: ($f | length > $max), files: $f[:$max]}'
}
# --fs-read SESSION REL