Add file drag-and-drop onto the terminal
Build App / compute-version (pull_request) Successful in 6s
Build Container / build-container (pull_request) Successful in 1m25s
Build App / build-macos (pull_request) Successful in 2m31s
Build App / build-windows (pull_request) Successful in 4m14s
Build App / build-linux (pull_request) Successful in 4m54s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped

Drop files onto a terminal pane and they're copied into the container and
their in-container paths typed into the prompt, so Claude Code can read
them for reference — mirroring the existing image-paste flow.

Backend: upload_host_file_to_terminal reads the dropped host file and
writes it under /tmp/triple-c-drops/<name> in the session's container,
returning that path. Rejects directories and unreadable paths.

Frontend: TerminalView subscribes to Tauri's webview onDragDropEvent
(OS file drops are intercepted at the webview level, so HTML5 ondrop
wouldn't expose paths). The window-wide event is guarded by the pane's
`active` flag plus a bounds hit-test so a drop only affects the terminal
it landed on; multiple files are uploaded and their paths inserted
space-separated (quoted when they contain spaces).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 14:11:09 -07:00
parent 10e689eaa6
commit 84e0bdf7b4
4 changed files with 101 additions and 3 deletions
@@ -183,6 +183,41 @@ pub async fn paste_image_to_terminal(
.await
}
/// Copy a host file (e.g. dragged onto the terminal) into the container so
/// Claude Code can read it, and return the in-container path. Mirrors the
/// image-paste flow: the file is placed under /tmp/triple-c-drops/ keeping its
/// original name. Returns an error for paths that aren't readable regular files
/// (e.g. a dropped directory).
#[tauri::command]
pub async fn upload_host_file_to_terminal(
session_id: String,
host_path: String,
state: State<'_, AppState>,
) -> Result<String, String> {
let container_id = state.exec_manager.get_container_id(&session_id).await?;
let meta = std::fs::metadata(&host_path)
.map_err(|e| format!("Cannot access {}: {}", host_path, e))?;
if meta.is_dir() {
return Err(format!("{} is a directory — drop individual files", host_path));
}
let data =
std::fs::read(&host_path).map_err(|e| format!("Failed to read {}: {}", host_path, e))?;
let base = std::path::Path::new(&host_path)
.file_name()
.map(|s| s.to_string_lossy().to_string())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "dropped-file".to_string());
let file_name = format!("triple-c-drops/{}", base);
state
.exec_manager
.write_file_to_container(&container_id, &file_name, &data)
.await
}
#[tauri::command]
pub async fn start_audio_bridge(
session_id: String,