From b65585b7391ed252f5769585f4f53623eaa72966 Mon Sep 17 00:00:00 2001 From: tolelom <98kimsungmin@naver.com> Date: Thu, 26 Mar 2026 11:43:29 +0900 Subject: [PATCH] docs: add asset browser implementation plan Co-Authored-By: Claude Opus 4.6 (1M context) --- .../plans/2026-03-26-asset-browser.md | 259 ++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-26-asset-browser.md diff --git a/docs/superpowers/plans/2026-03-26-asset-browser.md b/docs/superpowers/plans/2026-03-26-asset-browser.md new file mode 100644 index 0000000..dd8e63d --- /dev/null +++ b/docs/superpowers/plans/2026-03-26-asset-browser.md @@ -0,0 +1,259 @@ +# Asset Browser Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a file system asset browser panel to the editor that displays files/folders in a flat list with navigation. + +**Architecture:** `AssetBrowser` struct manages directory state (current path, entries cache). `asset_browser_panel` function renders the UI. Uses `std::fs::read_dir` for file listing. No external dependencies needed. + +**Tech Stack:** Rust, std::fs, voltex_editor (UiContext, Rect, LayoutState, widgets) + +**Spec:** `docs/superpowers/specs/2026-03-26-asset-browser-design.md` + +--- + +### Task 1: AssetBrowser struct + logic + tests + +**Files:** +- Create: `crates/voltex_editor/src/asset_browser.rs` +- Modify: `crates/voltex_editor/src/lib.rs` + +- [ ] **Step 1: Write tests and implementation** + +Create `crates/voltex_editor/src/asset_browser.rs` with the full AssetBrowser implementation and tests. The struct handles directory scanning, navigation, and path safety. + +Key implementation details: +- `new(root)` → canonicalize root, set current=root, refresh +- `refresh()` → read_dir, collect into Vec, sort (dirs first, then by name) +- `navigate_to(dir_name)` → join path, check starts_with(root) && is_dir, refresh +- `go_up()` → parent path, check >= root, refresh +- `relative_path()` → strip_prefix(root) display +- `format_size(bytes)` → "B" / "KB" / "MB" formatting + +Tests using `std::fs` temp directories: +- test_new_scans_entries +- test_navigate_to +- test_go_up +- test_go_up_at_root +- test_root_guard +- test_entries_sorted (dirs before files) +- test_format_size + +- [ ] **Step 2: Add module to lib.rs** + +```rust +pub mod asset_browser; +pub use asset_browser::{AssetBrowser, asset_browser_panel}; +``` + +- [ ] **Step 3: Run tests** + +Run: `cargo test -p voltex_editor --lib asset_browser -- --nocapture` +Expected: all tests PASS + +- [ ] **Step 4: Commit** + +```bash +git add crates/voltex_editor/src/asset_browser.rs crates/voltex_editor/src/lib.rs +git commit -m "feat(editor): add AssetBrowser with directory navigation and file listing" +``` + +--- + +### Task 2: asset_browser_panel UI function + +**Files:** +- Modify: `crates/voltex_editor/src/asset_browser.rs` + +- [ ] **Step 1: Write test** + +```rust +#[test] +fn test_panel_draws_commands() { + let dir = std::env::temp_dir().join("voltex_ab_ui_test"); + let _ = std::fs::create_dir_all(&dir); + std::fs::write(dir.join("test.txt"), "hello").unwrap(); + + let mut browser = AssetBrowser::new(dir.clone()); + let mut ui = UiContext::new(800.0, 600.0); + let rect = Rect { x: 0.0, y: 0.0, w: 300.0, h: 400.0 }; + + ui.begin_frame(0.0, 0.0, false); + asset_browser_panel(&mut ui, &mut browser, &rect); + ui.end_frame(); + + assert!(ui.draw_list.commands.len() > 0); + + let _ = std::fs::remove_dir_all(&dir); +} +``` + +- [ ] **Step 2: Implement asset_browser_panel** + +```rust +pub fn asset_browser_panel( + ui: &mut UiContext, + browser: &mut AssetBrowser, + rect: &Rect, +) { + ui.layout = LayoutState::new(rect.x + PADDING, rect.y + PADDING); + + // Current path + let path_text = format!("Path: /{}", browser.relative_path()); + ui.text(&path_text); + + // Go up button (not at root) + if browser.current != browser.root { + if ui.button("[..]") { + browser.go_up(); + return; // entries changed, redraw next frame + } + } + + // Entry list + let gw = ui.font.glyph_width as f32; + let gh = ui.font.glyph_height as f32; + let line_h = gh + PADDING; + let mut clicked_dir: Option = None; + + for entry in browser.entries() { + let y = ui.layout.cursor_y; + let x = rect.x + PADDING; + + // Highlight selected file + if !entry.is_dir { + if browser.selected_file.as_deref() == Some(&entry.name) { + ui.draw_list.add_rect(rect.x, y, rect.w, line_h, COLOR_SELECTED); + } + } + + // Click detection + if ui.mouse_clicked && ui.mouse_in_rect(rect.x, y, rect.w, line_h) { + if entry.is_dir { + clicked_dir = Some(entry.name.clone()); + } else { + browser.selected_file = Some(entry.name.clone()); + } + } + + // Draw label + let label = if entry.is_dir { + format!("[D] {}", entry.name) + } else { + format!(" {}", entry.name) + }; + + let text_y = y + (line_h - gh) * 0.5; + let mut cx = x; + for ch in label.chars() { + let (u0, v0, u1, v1) = ui.font.glyph_uv(ch); + ui.draw_list.add_rect_uv(cx, text_y, gw, gh, u0, v0, u1, v1, COLOR_TEXT); + cx += gw; + } + + ui.layout.cursor_y += line_h; + } + + // Navigate after iteration (avoids borrow conflict) + if let Some(dir) = clicked_dir { + browser.navigate_to(&dir); + } + + // Selected file info + if let Some(ref file_name) = browser.selected_file.clone() { + ui.text("-- File Info --"); + ui.text(&format!("Name: {}", file_name)); + if let Some(entry) = browser.entries.iter().find(|e| e.name == *file_name) { + ui.text(&format!("Size: {}", format_size(entry.size))); + if let Some(ext) = file_name.rsplit('.').next() { + if file_name.contains('.') { + ui.text(&format!("Type: .{}", ext)); + } + } + } + } +} +``` + +Note: `selected_file` needs to be `pub(crate)` or accessed via a method. Also `entries` field needs to be accessible. Simplest: make `selected_file` and `entries` pub. + +- [ ] **Step 3: Run tests** + +Run: `cargo test -p voltex_editor --lib asset_browser -- --nocapture` +Expected: all tests PASS + +- [ ] **Step 4: Commit** + +```bash +git add crates/voltex_editor/src/asset_browser.rs +git commit -m "feat(editor): add asset_browser_panel UI function" +``` + +--- + +### Task 3: Integrate into editor_demo + +**Files:** +- Modify: `examples/editor_demo/src/main.rs` + +- [ ] **Step 1: Replace Console panel with AssetBrowser** + +Add import: +```rust +use voltex_editor::{..., AssetBrowser, asset_browser_panel}; +``` + +Add to AppState: +```rust +asset_browser: AssetBrowser, +``` + +In `resumed`, initialize: +```rust +let asset_browser = AssetBrowser::new(std::env::current_dir().unwrap_or_default()); +``` + +In the panel loop, replace panel 3 (Console): +```rust +3 => { + asset_browser_panel(&mut state.ui, &mut state.asset_browser, rect); +} +``` + +- [ ] **Step 2: Build and test** + +Run: `cargo build -p editor_demo` +Run: `cargo test -p voltex_editor` + +- [ ] **Step 3: Commit** + +```bash +git add examples/editor_demo/src/main.rs +git commit -m "feat(editor): integrate asset browser into editor_demo" +``` + +--- + +### Task 4: Update docs + +**Files:** +- Modify: `docs/STATUS.md` +- Modify: `docs/DEFERRED.md` + +- [ ] **Step 1: Update STATUS.md** + +Add: `- voltex_editor: AssetBrowser (file system browsing, directory navigation)` +Update test count. + +- [ ] **Step 2: Update DEFERRED.md** + +``` +- ~~**에셋 브라우저**~~ ✅ 파일 목록 + 디렉토리 탐색 완료. 에셋 로딩/프리뷰 미구현. +``` + +- [ ] **Step 3: Commit** + +```bash +git add docs/STATUS.md docs/DEFERRED.md +git commit -m "docs: update STATUS.md and DEFERRED.md with asset browser" +```