Web dashboard step 7: docs, users page, polish

Users page, README and config.example updates, implementation notes, the
rollout runbook, site-layout 404/500 pages, human-readable download sizes,
dark-mode and narrow-screen polish, and a smoke test over every dashboard
route; also removes zdiff3 ancestor markers left by earlier merges.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM
This commit is contained in:
2026-09-03 18:19:08 +00:00
co-authored by Claude Fable 5.1
parent 10cbe9ed79
commit eb960f1b57
16 changed files with 599 additions and 50 deletions
+28 -1
View File
@@ -25,6 +25,7 @@ pub struct Download {
pub label: String,
pub href: String,
pub size_bytes: u64,
pub size: String,
}
#[derive(Debug, Clone)]
@@ -192,9 +193,26 @@ fn download(
label: label.to_string(),
href: format!("/files/{kind}/{}", crate::web::encode_component(name)),
size_bytes: metadata.len(),
size: format_file_size(metadata.len()),
})
}
fn format_file_size(bytes: u64) -> String {
const KB: f64 = 1024.0;
const MB: f64 = KB * 1024.0;
const GB: f64 = MB * 1024.0;
let bytes_float = bytes as f64;
if bytes_float >= GB {
format!("{:.1} GB", bytes_float / GB)
} else if bytes_float >= MB {
format!("{:.1} MB", bytes_float / MB)
} else if bytes_float >= KB {
format!("{:.1} KB", bytes_float / KB)
} else {
format!("{bytes} B")
}
}
#[derive(Debug)]
struct FullEntry {
title: String,
@@ -1072,7 +1090,7 @@ mod tests {
Edition::Standard,
"epub",
));
std::fs::write(&standard, b"epub").unwrap();
std::fs::write(&standard, vec![0; 2 * 1024]).unwrap();
let mut config = crate::config::Config::default();
config.publish.epub_dir = epub_dir;
let app = crate::server::router(crate::server::AppState::new(db, config, None));
@@ -1089,10 +1107,19 @@ mod tests {
.unwrap();
let issue = response_text(issue).await;
assert!(issue.contains("Download EPUB"));
assert!(issue.contains("2.0 KB"));
assert!(!issue.contains("2048 bytes"));
assert!(!issue.contains("Download X4 EPUB"));
assert!(!issue.contains("Download XTC"));
}
#[test]
fn file_sizes_are_human_readable() {
assert_eq!(format_file_size(42), "42 B");
assert_eq!(format_file_size(1536), "1.5 KB");
assert_eq!(format_file_size(5 * 1024 * 1024), "5.0 MB");
}
#[tokio::test]
async fn rating_post_supports_json_forms_attribution_fallback_and_clear() {
let (_dir, db, source) = seeded_issue(true).await;