Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions bin/claude-close-diff.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
INPUT="$(cat)"
CWD="$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)"
TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null || true)"
TRANSCRIPT_PATH="$(echo "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null || true)"
TOOL_USE_ID="$(echo "$INPUT" | jq -r '.tool_use_id // empty' 2>/dev/null || true)"

# Discover Neovim socket (prefer instance whose cwd matches project) and load RPC helpers
source "$SCRIPT_DIR/nvim-socket.sh" "$CWD" 2>/dev/null
source "$SCRIPT_DIR/nvim-send.sh"
source "$SCRIPT_DIR/claude-preview-transcript-watch.sh"

# For Bash tool (rm detection), only clear deletion markers — don't touch edit markers or diff tab
if [[ "$TOOL_NAME" == "Bash" ]]; then
Expand All @@ -38,4 +41,9 @@ fi
# Clean up temp files
rm -f "${TMPDIR:-/tmp}/claude-diff-original" "${TMPDIR:-/tmp}/claude-diff-proposed"

# Stop the transcript watcher (if one was spawned by PreToolUse)
if [[ -n "$TRANSCRIPT_PATH" && -n "$TOOL_USE_ID" ]]; then
claude_preview_stop_transcript_watcher "$TRANSCRIPT_PATH" "$TOOL_USE_ID" || true
fi

exit 0
11 changes: 11 additions & 0 deletions bin/claude-preview-diff.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ INPUT="$(cat)"

TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name')"
CWD="$(echo "$INPUT" | jq -r '.cwd')"
TRANSCRIPT_PATH="$(echo "$INPUT" | jq -r '.transcript_path // empty')"
TOOL_USE_ID="$(echo "$INPUT" | jq -r '.tool_use_id // empty')"

# Discover Neovim socket (prefer instance whose cwd matches project) and load RPC helpers
source "$SCRIPT_DIR/nvim-socket.sh" "$CWD" 2>/dev/null || true
source "$SCRIPT_DIR/nvim-send.sh"
source "$SCRIPT_DIR/claude-preview-transcript-watch.sh"

HAS_NVIM=true
if [[ -z "${NVIM_SOCKET:-}" ]]; then
Expand Down Expand Up @@ -190,6 +193,14 @@ if [[ "$HAS_NVIM" == "true" ]]; then
fi

nvim_send "require('claude-preview.diff').show_diff('$ORIG_ESC', '$PROP_ESC', '$DISPLAY_ESC')" || true

# Spawn transcript watcher to close diff on rejection
if [[ -n "$TRANSCRIPT_PATH" && -n "$TOOL_USE_ID" ]]; then
(
trap '' HUP
claude_preview_watch_transcript "$TRANSCRIPT_PATH" "$TOOL_USE_ID"
) >/dev/null 2>&1 &
fi
fi

# --- Always ask for user confirmation ---
Expand Down
156 changes: 156 additions & 0 deletions bin/claude-preview-transcript-watch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#!/usr/bin/env bash
# claude-preview-transcript-watch.sh — shared transcript watcher helpers
#
# Sourced by claude-preview-diff.sh and claude-close-diff.sh.

claude_preview_watch_state_key() {
local transcript_path="$1"
local tool_use_id="$2"
printf '%s\0%s' "$transcript_path" "$tool_use_id" | cksum | awk '{print $1}'
}

claude_preview_watch_state_dir() {
local transcript_path="$1"
local tool_use_id="$2"
local key
key="$(claude_preview_watch_state_key "$transcript_path" "$tool_use_id")"
printf '%s/claude-preview-watch-%s' "${TMPDIR:-/tmp}" "$key"
}

claude_preview_watch_pidfile() {
local transcript_path="$1"
local tool_use_id="$2"
printf '%s/pid' "$(claude_preview_watch_state_dir "$transcript_path" "$tool_use_id")"
}

claude_preview_watch_stopfile() {
local transcript_path="$1"
local tool_use_id="$2"
printf '%s/stop' "$(claude_preview_watch_state_dir "$transcript_path" "$tool_use_id")"
}

claude_preview_watch_fifo() {
local transcript_path="$1"
local tool_use_id="$2"
printf '%s/transcript.fifo' "$(claude_preview_watch_state_dir "$transcript_path" "$tool_use_id")"
}

claude_preview_stop_transcript_watcher() {
local transcript_path="$1"
local tool_use_id="$2"
local state_dir pidfile stopfile pid

state_dir="$(claude_preview_watch_state_dir "$transcript_path" "$tool_use_id")"
pidfile="$(claude_preview_watch_pidfile "$transcript_path" "$tool_use_id")"
stopfile="$(claude_preview_watch_stopfile "$transcript_path" "$tool_use_id")"

if [[ ! -d "$state_dir" ]]; then
return 0
fi

: > "$stopfile"

if [[ -r "$pidfile" ]]; then
pid="$(cat "$pidfile" 2>/dev/null || true)"
if [[ "$pid" =~ ^[0-9]+$ ]]; then
kill -TERM "$pid" 2>/dev/null || true
fi
fi
}

claude_preview_line_is_rejection() {
local line="$1"
local tool_use_id="$2"

printf '%s\n' "$line" | jq -e --arg tool_use_id "$tool_use_id" '
(.message.content // []) |
any(.tool_use_id == $tool_use_id and (.is_error // false) == true)
' >/dev/null 2>&1
}

claude_preview_nvim_diff_is_open() {
[[ -n "${NVIM_SOCKET:-}" ]] || return 1

local result
result="$(
nvim --server "$NVIM_SOCKET" --remote-expr \
"luaeval(\"require('claude-preview.diff').is_open() and 1 or 0\")" \
2>/dev/null
)" || return 1

[[ "$result" == "1" ]]
}

claude_preview_close_diff_for_rejection() {
[[ -n "${NVIM_SOCKET:-}" ]] || return 1
nvim_send "if require('claude-preview.diff').is_open() then pcall(function() require('claude-preview.changes').clear_all() end) pcall(function() require('claude-preview.diff').close_diff() end) end"
}

claude_preview_watch_transcript() {
local transcript_path="$1"
local tool_use_id="$2"
local state_dir pidfile stopfile fifo tail_pid deadline next_probe line

state_dir="$(claude_preview_watch_state_dir "$transcript_path" "$tool_use_id")"
pidfile="$(claude_preview_watch_pidfile "$transcript_path" "$tool_use_id")"
stopfile="$(claude_preview_watch_stopfile "$transcript_path" "$tool_use_id")"
fifo="$(claude_preview_watch_fifo "$transcript_path" "$tool_use_id")"

mkdir -p "$state_dir"
rm -f "$pidfile" "$stopfile" "$fifo"

trap '
if [[ -n "${tail_pid:-}" ]]; then
kill "$tail_pid" 2>/dev/null || true
wait "$tail_pid" 2>/dev/null || true
fi
rm -f "$pidfile" "$stopfile" "$fifo"
rm -rf "$state_dir" 2>/dev/null || true
' EXIT
trap '' HUP

mkfifo "$fifo"
tail -n0 -F "$transcript_path" >"$fifo" 2>/dev/null &
tail_pid=$!
exec 3<"$fifo"
rm -f "$fifo"

deadline=$((SECONDS + 120))
next_probe=$SECONDS

while (( SECONDS < deadline )); do
if [[ -f "$stopfile" ]]; then
break
fi

if (( SECONDS >= next_probe )); then
if ! claude_preview_nvim_diff_is_open; then
break
fi
next_probe=$((SECONDS + 2))
fi

if ! IFS= read -r -t 1 line <&3; then
if ! kill -0 "$tail_pid" 2>/dev/null; then
break
fi
continue
fi

if claude_preview_line_is_rejection "$line" "$tool_use_id"; then
rm -f "${TMPDIR:-/tmp}/claude-diff-original" "${TMPDIR:-/tmp}/claude-diff-proposed"
claude_preview_close_diff_for_rejection || true
break
fi
done
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
set -euo pipefail
if [[ $# -lt 2 ]]; then
echo "usage: $0 TRANSCRIPT_PATH TOOL_USE_ID" >&2
exit 2
fi
source "$(dirname "$0")/nvim-send.sh"
claude_preview_watch_transcript "$1" "$2"
fi
27 changes: 27 additions & 0 deletions bin/claude-user-prompt-cleanup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# claude-user-prompt-cleanup.sh — UserPromptSubmit hook for Claude Code
# Belt-and-suspenders fallback: closes any orphaned diff preview tab
# when the user sends their next message. Catches anything the
# transcript watcher missed (e.g. watcher died, nvim socket changed).

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

INPUT="$(cat)"
CWD="$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)"

source "$SCRIPT_DIR/nvim-socket.sh" "$CWD" 2>/dev/null || true
source "$SCRIPT_DIR/nvim-send.sh"

if [[ -z "${NVIM_SOCKET:-}" ]]; then
exit 0
fi

# Fast path: single RPC to check if a diff is open. <100ms when nothing is open.
DIFF_OPEN=$(nvim --server "$NVIM_SOCKET" --remote-expr "luaeval(\"require('claude-preview.diff').is_open() and 1 or 0\")" 2>/dev/null || echo "0")

if [[ "$DIFF_OPEN" == "1" ]]; then
nvim_send "if require('claude-preview.diff').is_open() then pcall(function() require('claude-preview.changes').clear_all() end) pcall(function() require('claude-preview.diff').close_diff() end) end" || true
rm -f "${TMPDIR:-/tmp}/claude-diff-original" "${TMPDIR:-/tmp}/claude-diff-proposed"
fi

exit 0
Loading
Loading