feat(viewer): IPC types, wrappers and Rust module skeleton for the file viewer
Task 0 of the terminal file viewer plan: the shared interfaces that seven later tasks build against in parallel. Adds ViewerLocation/ViewerTargetState/ ViewerState/ViewerFile/ViewerPoll to types.ts and their invoke() wrappers to tauri-commands.ts, creates the file_viewer Rust module (mod.rs with MAX_VIEWER_WINDOWS/VIEWER_LABEL_PREFIX/is_viewer_label, plus placeholder registry/resolve/poll/write/window submodules), wires it into lib.rs, and loosens visibility on the file_commands.rs helpers the viewer commands will reuse (MAX_READ_BYTES, validate_container_path, validate_container_write_path, FetchedFile, fetch_container_file, require_running, clip_container_text). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,7 +46,7 @@ pub struct FileContents {
|
||||
/// Hard ceiling on a single viewer read, whatever the caller asks for. The tar
|
||||
/// path buffers the whole payload in host RAM, so a caller-supplied cap is not
|
||||
/// something to take on trust.
|
||||
const MAX_READ_BYTES: u64 = 8 * 1024 * 1024;
|
||||
pub(crate) const MAX_READ_BYTES: u64 = 8 * 1024 * 1024;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_container_files(
|
||||
@@ -352,7 +352,7 @@ const CONTAINER_WRITE_ROOTS: &[&str] = &["/workspace", "/home/claude", "/tmp"];
|
||||
///
|
||||
/// `what` names the parameter in the error, because these messages are shown to
|
||||
/// a user who is looking at a folder, not at argv.
|
||||
fn validate_container_path(what: &str, path: &str) -> Result<(), String> {
|
||||
pub(crate) fn validate_container_path(what: &str, path: &str) -> Result<(), String> {
|
||||
if path.is_empty() {
|
||||
return Err(format!("{} path cannot be empty", what));
|
||||
}
|
||||
@@ -394,7 +394,7 @@ fn validate_container_path(what: &str, path: &str) -> Result<(), String> {
|
||||
/// directly. What it buys is that the *panel* keeps its promise — the roots
|
||||
/// named in the refusal are the roots it writes to — and that a mis-aimed drop
|
||||
/// cannot quietly land outside them.
|
||||
fn validate_container_write_path(what: &str, path: &str) -> Result<(), String> {
|
||||
pub(crate) fn validate_container_write_path(what: &str, path: &str) -> Result<(), String> {
|
||||
validate_container_path(what, path)?;
|
||||
if CONTAINER_WRITE_ROOTS
|
||||
.iter()
|
||||
@@ -1178,12 +1178,12 @@ fn push_capped(buf: &mut String, frame: &[u8]) {
|
||||
}
|
||||
|
||||
/// One regular file's bytes, pulled out of a container.
|
||||
struct FetchedFile {
|
||||
bytes: Vec<u8>,
|
||||
pub(crate) struct FetchedFile {
|
||||
pub(crate) bytes: Vec<u8>,
|
||||
/// The size the tar header declared, i.e. the file's real size — which is
|
||||
/// not `bytes.len()` once `max_bytes` has cut the read short.
|
||||
size: u64,
|
||||
truncated: bool,
|
||||
pub(crate) size: u64,
|
||||
pub(crate) truncated: bool,
|
||||
}
|
||||
|
||||
/// Fetch a single regular file from a container as exact bytes.
|
||||
@@ -1202,7 +1202,7 @@ struct FetchedFile {
|
||||
/// file — or the whole *directory tree*, since the type check happens after the
|
||||
/// read — landed in host RAM twice. This function buffers, so every caller of
|
||||
/// it must name a ceiling.
|
||||
async fn fetch_container_file(
|
||||
pub(crate) async fn fetch_container_file(
|
||||
container_id: &str,
|
||||
container_path: &str,
|
||||
max_bytes: u64,
|
||||
@@ -1456,7 +1456,7 @@ pub async fn create_container_directory(
|
||||
/// upload it surfaces even less usefully: `resolve_container_dir`'s `realpath`
|
||||
/// is the first thing to touch the container, so a stopped project fails inside
|
||||
/// path *validation* and reads like the path was the problem.
|
||||
async fn require_running(container_id: &str, action: &str) -> Result<(), String> {
|
||||
pub(crate) async fn require_running(container_id: &str, action: &str) -> Result<(), String> {
|
||||
let docker = get_docker()?;
|
||||
let running = docker
|
||||
.inspect_container(container_id, None)
|
||||
@@ -2011,7 +2011,7 @@ async fn upload_one(
|
||||
/// call site for why each of those three matters; the short version is that
|
||||
/// this text ends up inside a toast that renders above every modal, and its
|
||||
/// author is the container.
|
||||
fn clip_container_text(text: &str) -> String {
|
||||
pub(crate) fn clip_container_text(text: &str) -> String {
|
||||
const MAX: usize = 200;
|
||||
let flattened: String = text
|
||||
.trim()
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
//! The terminal file viewer: one OS window per clicked path.
|
||||
//!
|
||||
//! Every window is a `file-viewer-<n>` label registered in [`registry::ViewerRegistry`];
|
||||
//! the commands in `commands/file_viewer_commands.rs` gate on the label and act only on
|
||||
//! the caller's own entry, which is why nothing here takes a path from a window.
|
||||
|
||||
pub mod poll;
|
||||
pub mod registry;
|
||||
pub mod resolve;
|
||||
pub mod window;
|
||||
pub mod write;
|
||||
|
||||
/// Spec §3: the 21st click is refused with a toast.
|
||||
pub const MAX_VIEWER_WINDOWS: usize = 20;
|
||||
pub const VIEWER_LABEL_PREFIX: &str = "file-viewer-";
|
||||
|
||||
pub fn is_viewer_label(label: &str) -> bool {
|
||||
label
|
||||
.strip_prefix(VIEWER_LABEL_PREFIX)
|
||||
.is_some_and(|rest| !rest.is_empty() && rest.bytes().all(|b| b.is_ascii_digit()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn only_numbered_viewer_labels_pass() {
|
||||
assert!(is_viewer_label("file-viewer-1"));
|
||||
assert!(is_viewer_label("file-viewer-20"));
|
||||
assert!(!is_viewer_label("file-viewer-"));
|
||||
assert!(!is_viewer_label("file-viewer-x"));
|
||||
assert!(!is_viewer_label("main"));
|
||||
assert!(!is_viewer_label("browser-view-abc"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
//! Filled in by Task N.
|
||||
@@ -0,0 +1 @@
|
||||
//! Filled in by Task N.
|
||||
@@ -0,0 +1 @@
|
||||
//! Filled in by Task N.
|
||||
@@ -0,0 +1 @@
|
||||
//! Filled in by Task N.
|
||||
@@ -0,0 +1 @@
|
||||
//! Filled in by Task N.
|
||||
@@ -2,6 +2,7 @@ mod auth_bridge;
|
||||
mod browser_view;
|
||||
mod commands;
|
||||
mod docker;
|
||||
pub mod file_viewer;
|
||||
mod install_helper;
|
||||
mod logging;
|
||||
mod models;
|
||||
|
||||
Reference in New Issue
Block a user