7.2 KiB
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, refreshrefresh()→ read_dir, collect into Vec, sort (dirs first, then by name)navigate_to(dir_name)→ join path, check starts_with(root) && is_dir, refreshgo_up()→ parent path, check >= root, refreshrelative_path()→ strip_prefix(root) displayformat_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
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
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
#[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
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<String> = 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
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:
use voltex_editor::{..., AssetBrowser, asset_browser_panel};
Add to AppState:
asset_browser: AssetBrowser,
In resumed, initialize:
let asset_browser = AssetBrowser::new(std::env::current_dir().unwrap_or_default());
In the panel loop, replace panel 3 (Console):
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
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
git add docs/STATUS.md docs/DEFERRED.md
git commit -m "docs: update STATUS.md and DEFERRED.md with asset browser"