feat: v0.7.0 files pane: browse, preview, diff, Claude's edits
- devdesk attach --fs-list/--fs-read/--git-status/--git-show/--asset (libexec/devdesk-files.sh), confined to the session folder. - The state hook records files Claude edits (Edit/Write/MultiEdit/ NotebookEdit) for the Changes tab's "C" badge; rename/end carry it. - Files pane: folders, CodeMirror read-only view, sanitized markdown preview in a sandboxed iframe with inlined relative images, images, Changes tab with line counts and diff against HEAD, "→ Claude" @path, resizable split. - The editor bundle ships as /usr/share/devdesk/assets/editor.js, is fetched over ttyd on first use, verified against the page's SHA-256 and cached in IndexedDB. - Fix: the state hook's field split collapsed empty fields. Closes #19 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -491,3 +491,106 @@ posts() { [ -f "$TMP/posts" ] && wc -l <"$TMP/posts" || echo 0; }
|
||||
run claude_args alpha
|
||||
[ "$output" = "-n alpha --settings $REPO/share/claude-settings.json --remote-control devdesk-alpha" ]
|
||||
}
|
||||
|
||||
# Decode a blob reply (header line, base64 between markers) into $TMP/blob.
|
||||
blob_body() {
|
||||
sed -n '/^DEVDESK-BLOB-BEGIN$/,/^DEVDESK-BLOB-END$/p' <<<"$1" | sed '1d;$d' | base64 -d >"$TMP/blob"
|
||||
}
|
||||
|
||||
@test "fs-list lists one level, folders first, and stays inside the session folder (#19)" {
|
||||
p="$DEVDESK_PROJECTS_ROOT/alpha"
|
||||
mkdir -p "$p/src/deep" "$p/.git"
|
||||
echo hi >"$p/README.md"
|
||||
echo x >"$p/src/a.sh"
|
||||
ln -s "$TMP/outside" "$p/escape-link"
|
||||
ln -s src "$p/src-link"
|
||||
run devdesk attach --fs-list alpha
|
||||
[ "$status" -eq 0 ]
|
||||
# Folders first (a symlink to a folder counts, even one that escapes: opening it fails).
|
||||
[ "$(jq -r '[.entries[].n] | join(",")' <<<"$output")" = "escape-link,src,src-link,README.md" ]
|
||||
run devdesk attach --fs-list alpha src
|
||||
[ "$(jq -r '.dir + ":" + ([.entries[].n] | join(","))' <<<"$output")" = "src:deep,a.sh" ]
|
||||
for bad in ../ /etc escape-link "src/../../alpha/../../outside"; do
|
||||
run devdesk attach --fs-list alpha "$bad"
|
||||
[ "$status" -ne 0 ]
|
||||
done
|
||||
run devdesk attach --fs-list alpha src-link
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "fs-read returns text, images and refuses binaries, big files and escapes (#19)" {
|
||||
p="$DEVDESK_PROJECTS_ROOT/alpha"
|
||||
printf 'line 1\r\nüñí\n' >"$p/notes.md"
|
||||
printf '\x89PNG\r\n\x1a\n\0\0\0' >"$p/pic.png"
|
||||
head -c 100 /dev/zero >"$p/blob.bin"
|
||||
head -c 3000 /dev/zero | tr '\0' a >"$p/big.txt"
|
||||
run devdesk attach --fs-read alpha notes.md
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(head -n1 <<<"$output" | jq -r '[.path, .binary, .utf8, .body] | @tsv')" = "$(printf 'notes.md\tfalse\ttrue\ttrue')" ]
|
||||
blob_body "$output"
|
||||
cmp "$TMP/blob" "$p/notes.md"
|
||||
run devdesk attach --fs-read alpha pic.png
|
||||
[ "$(head -n1 <<<"$output" | jq -r '[.mime, .body] | @tsv')" = "$(printf 'image/png\ttrue')" ]
|
||||
run devdesk attach --fs-read alpha blob.bin
|
||||
[ "$(head -n1 <<<"$output" | jq -r '[.binary, .body] | @tsv')" = "$(printf 'true\tfalse')" ]
|
||||
run env DEVDESK_FS_MAX=1000 "$REPO/bin/devdesk" attach --fs-read alpha big.txt
|
||||
[ "$(head -n1 <<<"$output" | jq -r '.tooLarge')" = true ]
|
||||
run devdesk attach --fs-read alpha ../alpha/../../outside
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "git-status and git-show report changes, line counts and Claude's edits (#19)" {
|
||||
p="$DEVDESK_PROJECTS_ROOT/alpha"
|
||||
git -C "$p" init -q -b main
|
||||
git -C "$p" -c user.name=t -c user.email=t@t commit -q --allow-empty -m init
|
||||
printf 'one\ntwo\n' >"$p/kept.txt"
|
||||
printf 'old\n' >"$p/moved.txt"
|
||||
git -C "$p" add . && git -C "$p" -c user.name=t -c user.email=t@t commit -q -m files
|
||||
printf 'one\nTWO\nthree\n' >"$p/kept.txt"
|
||||
git -C "$p" mv moved.txt renamed.txt
|
||||
echo new >"$p/fresh.txt"
|
||||
mkdir -p "$DEVDESK_STATE_DIR/edits"
|
||||
printf '%s\n' "$p/kept.txt" "$p/notes-outside-git.txt" >"$DEVDESK_STATE_DIR/edits/alpha.txt"
|
||||
run devdesk attach --git-status alpha
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(jq -r '.files | map("\(.x)\(.y) \(.path) +\(.add) -\(.del) \(.claude)") | sort | join("|")' <<<"$output")" = \
|
||||
" notes-outside-git.txt +null -null true| M kept.txt +2 -1 true|?? fresh.txt +null -null false|R renamed.txt +null -null false" ]
|
||||
[ "$(jq -r '.files[] | select(.path == "renamed.txt") | .from' <<<"$output")" = moved.txt ]
|
||||
run devdesk attach --git-show alpha kept.txt
|
||||
blob_body "$output"
|
||||
[ "$(cat "$TMP/blob")" = "$(printf 'one\ntwo')" ]
|
||||
run devdesk attach --git-show alpha fresh.txt
|
||||
[ "$(head -n1 <<<"$output" | jq -r '.size')" = 0 ]
|
||||
run devdesk attach --git-status alpha
|
||||
run env DEVDESK_PROJECTS_ROOT="$TMP/outside" "$REPO/bin/devdesk" attach --git-status alpha
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "the state hook logs Claude's edits; rename moves and end removes the log (#19)" {
|
||||
start_server
|
||||
devdesk new alpha
|
||||
pane=$(tmux -S "$DEVDESK_SOCKET" display -p -t =alpha:shell '#{pane_id}')
|
||||
hook() { TMUX="$DEVDESK_SOCKET,1,0" TMUX_PANE="$pane" "$REPO/libexec/devdesk-state-hook"; }
|
||||
echo '{"hook_event_name":"PostToolUse","tool_name":"Edit","tool_input":{"file_path":"/x/a.txt"}}' | hook
|
||||
echo '{"hook_event_name":"PostToolUse","tool_name":"Write","tool_input":{"file_path":"/x/a.txt"}}' | hook
|
||||
echo '{"hook_event_name":"PostToolUse","tool_name":"Bash","tool_input":{"command":"ls"}}' | hook
|
||||
echo '{"hook_event_name":"PostToolUse","tool_name":"NotebookEdit","tool_input":{"notebook_path":"/x/n.ipynb"}}' | hook
|
||||
[ "$(cat "$DEVDESK_STATE_DIR/edits/alpha.txt")" = "$(printf '/x/a.txt\n/x/n.ipynb')" ]
|
||||
devdesk rename alpha beta
|
||||
[ -f "$DEVDESK_STATE_DIR/edits/beta.txt" ] && [ ! -e "$DEVDESK_STATE_DIR/edits/alpha.txt" ]
|
||||
devdesk end beta
|
||||
[ ! -e "$DEVDESK_STATE_DIR/edits/beta.txt" ]
|
||||
}
|
||||
|
||||
@test "asset serves only named .js files from the assets folder (#19)" {
|
||||
mkdir -p "$TMP/assets"
|
||||
echo 'window.X = 1;' >"$TMP/assets/editor.js"
|
||||
run env DEVDESK_ASSETS="$TMP/assets" "$REPO/bin/devdesk" attach --asset editor.js
|
||||
[ "$status" -eq 0 ]
|
||||
blob_body "$output"
|
||||
cmp "$TMP/blob" "$TMP/assets/editor.js"
|
||||
for bad in ../etc/passwd editor.css nope.js; do
|
||||
run env DEVDESK_ASSETS="$TMP/assets" "$REPO/bin/devdesk" attach --asset "$bad"
|
||||
[ "$status" -ne 0 ]
|
||||
done
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user