git.lucas.co / cce-files
file manager
git clone https://git.lucas.co/cce-files.git

commit10bb6b67b288c3983832d7cc2b366b9cfffbf296
parentef02c36b61
authorLucas Galante <[email protected]>
date2026-07-06 22:56
refactor: fix wheel/preview bug, dedupe helpers, harden config paths

- Fix mouse-wheel preview hit-region to use the preview widget's actual
  laid-out rect instead of a hardcoded 50/50 split (broke after dragging
  the list/preview splitter).
- Serialize HOME-mutating tests with serial_test to stop the parallel race.
- Deduplicate format_size/format_permissions into src/util.rs; share the
  ~/.local/bin command-spawn logic via spawn_command_for_path; extract
  clip_to_viewport/occlude_against helpers and dialog/menu geometry.
- Add BrowseState::selected_path and path_to_segment helpers.
- Consistent XDG_CONFIG_HOME handling via cce_config_dir; safe HOME-unset
  fallback in get_default_application.
- Remove dead code (NetworkMessage, Message::KeyboardEvent, unused
  AppWidget/FilesystemApp fields) and both allow(dead_code) attributes.
- Add unit tests for the previously-untested browse::update handlers.
- Add CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

 CLAUDE.md            |  56 +++++
 Cargo.toml           |   3 +
 src/lib.rs           |   3 +-
 src/main.rs          | 695 ++++++++++++++++++++++++++++++---------------------
 src/pages/browse.rs  | 240 ++++++++++++------
 src/pages/network.rs |   7 -
 src/services/fs.rs   | 137 +++++-----
 src/util.rs          |  51 ++++
 8 files changed, 738 insertions(+), 454 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..5d20901
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,56 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## What this is
+
+`cce-files` is a Wayland-native file manager, one app in the larger **CCE** desktop-environment ecosystem (the sibling `cce-*` crates under `../`). It renders directly with wgpu + glyphon on a Wayland surface — there is no GTK/Qt/web layer. All GUI primitives come from the sibling crate **`cce-ui`** (`../cce-ui`, a path dependency), which owns the windowing/event loop, the widget toolkit, layout, fonts, and colors.
+
+## Build / run / test
+
+```sh
+make build      # cargo build --release
+make install    # build + install binary to ~/.local/bin/cce-files
+make run        # cargo run  (needs a live Wayland compositor)
+cargo test      # run unit tests (fs and browse modules have them)
+cargo test test_is_project_dir_detection    # run a single test by name
+```
+
+Running the binary requires a Wayland session — it will not run headless. Edition is **2024**; the `cce-ui` sibling is edition 2021. When touching layout/widget behavior, the actual widget implementations live in `../cce-ui/src/widget/`, not here.
+
+## Architecture
+
+### Elm-style app on the cce-ui engine
+`main.rs` defines `FilesystemApp`, which implements `cce_ui::engine::Application`. That trait drives everything through a message loop:
+- **`Message`** (`lib.rs`) is the single top-level event enum; page-specific messages nest inside it (`Message::Browse(BrowseMessage)`, `Message::Preview(PreviewMessage)`).
+- **`update()`** mutates state in response to a `Message` and may dispatch async work to `FsService`.
+- **`view` / `rebuild_layout()`** produce the frame. cce-ui calls `view_rounded_quads()` / `text_items()` to pull the rendered geometry.
+
+Each page has a `state` struct and a `view()` that returns a `PageContent` (`pages/mod.rs`) — a flat list of rects, texts, and buttons — which `rebuild_layout` later translates into GPU quads and glyphon `TextItem`s. Interactive pages also carry their own `Message` enum + `update()` (`pages/browse.rs`, `pages/preview.rs`); Network is view-only, driven directly from `BrowseState` and pointer/graph events, so it has no message type of its own.
+
+Shared, page-independent formatting helpers (`format_size`, `format_permissions`) live in `src/util.rs`.
+
+### FsService: all filesystem IO is async and off-thread
+`services/fs.rs` runs a Tokio task that receives `FsRequest`s (read dir, refresh, read preview, delete, load/save last dir), performs the blocking IO, and sends results back into the app as `Message`s over a `calloop::channel::Sender`. `update()` never does blocking IO directly — it sends an `FsRequest` and handles the resulting message later. A `notify` watcher (`start_watching`) debounces filesystem events and triggers `RefreshDirectory`.
+
+### rebuild_layout is the render heart (main.rs)
+`rebuild_layout()` gathers geometry from five sources — the root window, page content, popovers, the context menu, and the open-with dialog — and flattens them into `self.widgets` + `self.text_items`. Two non-obvious concerns live here:
+- **Viewport clipping**: page content is clipped to the content region so scrolled rows/text don't overflow into the breadcrumb or selection bar.
+- **Overlay occlusion**: text/buttons under a popover, context menu, or dialog are either discarded or bound-clipped so they don't bleed through overlays. This is the logic behind commits like "Fix text rendering through popovers/overlays."
+
+### Widget hierarchy uses raw pointers
+`BrowseContainer` and `NetworkContainer` are composite widgets whose children (`breadcrumb`, `list_box`, `save_name_box`, `graph`) are held as `*mut dyn Element` and wired up in `set_parent` via `ctx.register_widget` / `ctx.link_ids`. This mirrors the cce-ui widget model; the containers are `unsafe impl Send/Sync`. When adding a child widget to a container, replicate the register + link + `set_parent` sequence, and clear it in `rebuild_layout`'s teardown block.
+
+### Two pages, one preview
+- **Browse** — the `List` widget (columnar, integrated search box) plus a `Breadcrumb`. The right pane is a `Preview` widget, split from the list by a `SplitBox`.
+- **Network** — a `Graph` view of the same directory (nodes = entries), also split against the preview.
+The active page is picked by `view_dropdown` next to the breadcrumb (there is no sidebar — it was removed; `has_sidebar` is hardcoded `false`).
+
+## Domain specifics
+
+- **CCE projects**: a directory containing `state.json` or `state.kdl` is treated as a *project* (`is_project_dir`), gets MIME `application/x-cce-project`, and on double-click is opened by its handler rather than entered. "Enter Directory" in the context menu overrides this.
+- **Opening files**: `open_file()` resolves a handler via `get_mime_type` → `get_default_application`, which checks (1) `~/.config/cce/mime.kdl` custom associations, then (2) `xdg-mime` + `.desktop` parsing. Bare command names are resolved against `~/.local/bin` before falling back to `xdg-open`. Always launch via `cce_ui::process::spawn_detached`.
+- **Chooser modes**: launched with `--select`, `--select-dir`, or `--save`, the app becomes a file picker for other CCE apps — it shows a bottom action bar, prints the chosen path to stdout, and `std::process::exit(0)` on selection (or exit code 1 on cancel). This is why `SelectOpen`/`SelectCancel` call `process::exit` directly.
+- **Persistence**: the last-visited directory is saved to `~/.config/cce/cce-files/cce-files-last-dir.txt` and restored on launch.
+- **Double-click**: opening is temporal — `last_click_time` / `last_clicked_idx` in `update()` detect a double-click within 500ms rather than relying on a windowing double-click event.
+- **Fonts**: `cce_ui::create_font_system()` loads fonts from `/home/lsgalante/Dropbox/Fonts` (hardcoded in cce-ui). Set `CCE_LOAD_SYSTEM_FONTS` to also load system fonts.
diff --git a/Cargo.toml b/Cargo.toml
index 570f53d..b50d3fa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,9 @@ kdl = "6.7.1"
 
 
 
+[dev-dependencies]
+serial_test = "3"
+
 [lib]
 name = "cce_files"
 path = "src/lib.rs"
diff --git a/src/lib.rs b/src/lib.rs
index e17c894..4a19d45 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,7 +1,7 @@
 pub mod pages;
 pub mod services;
+pub mod util;
 
-use cce_ui::widget::KeyEvent;
 use pages::Page;
 
 #[derive(Debug, Clone)]
@@ -9,7 +9,6 @@ pub enum Message {
     SwitchPage(Page),
     Browse(pages::browse::BrowseMessage),
     Preview(pages::preview::PreviewMessage),
-    KeyboardEvent(KeyEvent),
     SelectOpen,
     SelectCancel,
     PromptOpenWith(std::path::PathBuf),
diff --git a/src/main.rs b/src/main.rs
index 7e09764..1efb294 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,18 +11,116 @@ use cce_files::{Message, pages, services};
 use cce_files::pages::Page;
 use cce_files::pages::browse::is_project_dir;
 
+// ── Layout constants ────────────────────────────────────────────────
+
+const ROW_H: f32 = 24.0;        // context-menu / breadcrumb row height
+const DIALOG_W: f32 = 400.0;
+const DIALOG_H: f32 = 160.0;
+const MENU_MIN_W: f32 = 120.0;
+const MENU_CHAR_W: f32 = 7.5;   // approximate glyph advance used for menu sizing
+
+/// Context-menu width/height for a given set of options.
+fn context_menu_size(options: &[(String, Option<Message>)]) -> (f32, f32) {
+    let max_len = options.iter().map(|(s, _)| s.len()).max().unwrap_or(0);
+    let w = ((max_len as f32 * MENU_CHAR_W) + 24.0).max(MENU_MIN_W);
+    let h = options.len() as f32 * ROW_H;
+    (w, h)
+}
+
+/// Computed rects for the "Open with…" modal, so layout and hit-testing agree.
+struct OpenWithRects {
+    x: f32,
+    y: f32,
+    w: f32,
+    h: f32,
+    tb: (f32, f32, f32, f32),
+    cancel: (f32, f32, f32, f32),
+    open: (f32, f32, f32, f32),
+}
+
+fn open_with_rects(win_w: f32, win_h: f32) -> OpenWithRects {
+    let x = (win_w - DIALOG_W) / 2.0;
+    let y = (win_h - DIALOG_H) / 2.0;
+    let tb_h = cce_ui::layout::textbox_height();
+    let btn_h = cce_ui::layout::button_height();
+    OpenWithRects {
+        x,
+        y,
+        w: DIALOG_W,
+        h: DIALOG_H,
+        tb: (x + 20.0, y + 60.0, DIALOG_W - 40.0, tb_h),
+        cancel: (x + DIALOG_W - 180.0, y + DIALOG_H - btn_h - 16.0, 70.0, btn_h),
+        open: (x + DIALOG_W - 100.0, y + DIALOG_H - btn_h - 16.0, 80.0, btn_h),
+    }
+}
+
+/// Clip a vertical span `[y, y+h)` to the viewport `[top, bottom)`.
+/// Returns the clipped `(y, h)`, or `None` if fully outside.
+fn clip_to_viewport(y: f32, h: f32, top: f32, bottom: f32) -> Option<(f32, f32)> {
+    if y >= bottom || y + h <= top {
+        return None;
+    }
+    let mut ny = y;
+    let mut nh = h;
+    if ny < top {
+        let diff = top - ny;
+        ny = top;
+        nh = (nh - diff).max(0.0);
+    }
+    if ny + nh > bottom {
+        nh = (bottom - ny).max(0.0);
+    }
+    Some((ny, nh))
+}
+
+/// Clip a text/label box against overlay rects so it does not bleed through
+/// popovers/menus/dialogs. Returns the adjusted clip bounds, or `None` if the
+/// box is fully covered (should be discarded).
+fn occlude_against(
+    mut bounds: [f32; 4],
+    t_min_x: f32,
+    t_max_x: f32,
+    t_min_y: f32,
+    t_max_y: f32,
+    overlays: &[&pages::PageContent],
+) -> Option<[f32; 4]> {
+    for overlay_pc in overlays {
+        for (_, ox, oy, ow, oh, _, _) in &overlay_pc.rects {
+            let o_min_x = *ox;
+            let o_max_x = *ox + *ow;
+            let o_min_y = *oy;
+            let o_max_y = *oy + *oh;
+
+            if t_max_x > o_min_x && t_min_x < o_max_x && t_max_y > o_min_y && t_min_y < o_max_y {
+                if t_min_x >= o_min_x && t_max_x <= o_max_x && t_min_y >= o_min_y && t_max_y <= o_max_y {
+                    return None;
+                }
+                if o_min_x > t_min_x && o_min_x < t_max_x {
+                    bounds[2] = bounds[2].min(o_min_x);
+                }
+                if o_max_x > t_min_x && o_max_x < t_max_x {
+                    bounds[0] = bounds[0].max(o_max_x);
+                }
+                if o_min_y > t_min_y && o_min_y < t_max_y {
+                    bounds[3] = bounds[3].min(o_min_y);
+                }
+                if o_max_y > t_min_y && o_max_y < t_max_y {
+                    bounds[1] = bounds[1].max(o_max_y);
+                }
+            }
+        }
+    }
+    Some(bounds)
+}
+
 // ── State ───────────────────────────────────────────────────────────
 
-#[allow(dead_code)]
 struct AppWidget {
     x: f32,
     y: f32,
     w: f32,
     h: f32,
     color: [f32; 4],
-    hover_color: [f32; 4],
-    hovering: bool,
-    action: Option<Message>,
     radius: f32,
     corners: (bool, bool, bool, bool),
 }
@@ -38,7 +136,150 @@ struct ContextMenu {
     hovered: Option<usize>,
 }
 
-#[allow(dead_code)]
+struct BrowseContainer {
+    pub base: cce_ui::widget::Widget,
+    pub parent: Option<*mut (dyn cce_ui::widget::Element + 'static)>,
+    pub breadcrumb: *mut cce_ui::widget::Breadcrumb,
+    pub list_box: *mut cce_ui::widget::List,
+    pub save_name_box: *mut cce_ui::widget::TextBox,
+    pub select_mode: bool,
+}
+
+impl cce_ui::widget::Element for BrowseContainer {
+    cce_ui::impl_widget_base!(BrowseContainer);
+
+    fn color(&self) -> [f32; 4] {
+        [0.0, 0.0, 0.0, 0.0]
+    }
+
+    fn children(&self, _ctx: &cce_ui::widget::UiContext) -> Vec<*mut (dyn cce_ui::widget::Element + 'static)> {
+        let mut list = vec![self.breadcrumb as *mut (dyn cce_ui::widget::Element + 'static), self.list_box as *mut (dyn cce_ui::widget::Element + 'static)];
+        if self.select_mode {
+            list.push(self.save_name_box as *mut (dyn cce_ui::widget::Element + 'static));
+        }
+        list
+    }
+
+    fn parent(&self, _ctx: &cce_ui::widget::UiContext) -> Option<*mut (dyn cce_ui::widget::Element + 'static)> {
+        self.parent
+    }
+
+    fn set_parent(&mut self, parent: Option<*mut (dyn cce_ui::widget::Element + 'static)>, ctx: &mut cce_ui::widget::UiContext) {
+        self.parent = parent;
+        if parent.is_some() {
+            let self_ptr = self as *mut Self;
+            let self_id = self.base.id();
+            unsafe {
+                let bc_id = (*self.breadcrumb).base().unwrap().id();
+                ctx.register_widget(bc_id, self.breadcrumb as *mut (dyn cce_ui::widget::Element + 'static));
+                ctx.link_ids(self_id, bc_id);
+                (*self.breadcrumb).set_parent(Some(self_ptr), ctx);
+
+                let lb_id = (*self.list_box).base().unwrap().id();
+                ctx.register_widget(lb_id, self.list_box as *mut (dyn cce_ui::widget::Element + 'static));
+                ctx.link_ids(self_id, lb_id);
+                (*self.list_box).set_parent(Some(self_ptr), ctx);
+
+                if self.select_mode {
+                    let sn_id = (*self.save_name_box).base().unwrap().id();
+                    ctx.register_widget(sn_id, self.save_name_box as *mut (dyn cce_ui::widget::Element + 'static));
+                    ctx.link_ids(self_id, sn_id);
+                    (*self.save_name_box).set_parent(Some(self_ptr), ctx);
+                }
+            }
+        }
+    }
+
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
+        self.base.x = x;
+        self.base.y = y;
+        self.base.w = w;
+        self.base.h = h;
+
+        let gap = 12.0;
+        let breadcrumb_h = cce_ui::layout::button_height();
+        let textbox_h = 24.0;
+
+        unsafe {
+            (*self.breadcrumb).set_rect(x, y, w, breadcrumb_h);
+            let list_h = if self.select_mode {
+                h - breadcrumb_h - textbox_h - 2.0 * gap
+            } else {
+                h - breadcrumb_h - gap
+            };
+            (*self.list_box).set_rect(x, y + breadcrumb_h + gap, w, list_h);
+
+            if self.select_mode {
+                (*self.save_name_box).set_rect(x, y + h - textbox_h, w, textbox_h);
+                (*self.save_name_box).set_row_rect(x, w);
+            }
+        }
+    }
+}
+
+unsafe impl Send for BrowseContainer {}
+unsafe impl Sync for BrowseContainer {}
+
+struct NetworkContainer {
+    pub base: cce_ui::widget::Widget,
+    pub parent: Option<*mut (dyn cce_ui::widget::Element + 'static)>,
+    pub breadcrumb: *mut cce_ui::widget::Breadcrumb,
+    pub graph: *mut cce_ui::widget::Graph,
+}
+
+impl cce_ui::widget::Element for NetworkContainer {
+    cce_ui::impl_widget_base!(NetworkContainer);
+
+    fn color(&self) -> [f32; 4] {
+        [0.0, 0.0, 0.0, 0.0]
+    }
+
+    fn children(&self, _ctx: &cce_ui::widget::UiContext) -> Vec<*mut (dyn cce_ui::widget::Element + 'static)> {
+        vec![self.breadcrumb as *mut (dyn cce_ui::widget::Element + 'static), self.graph as *mut (dyn cce_ui::widget::Element + 'static)]
+    }
+
+    fn parent(&self, _ctx: &cce_ui::widget::UiContext) -> Option<*mut (dyn cce_ui::widget::Element + 'static)> {
+        self.parent
+    }
+
+    fn set_parent(&mut self, parent: Option<*mut (dyn cce_ui::widget::Element + 'static)>, ctx: &mut cce_ui::widget::UiContext) {
+        self.parent = parent;
+        if parent.is_some() {
+            let self_ptr = self as *mut Self;
+            let self_id = self.base.id();
+            unsafe {
+                let bc_id = (*self.breadcrumb).base().unwrap().id();
+                ctx.register_widget(bc_id, self.breadcrumb as *mut (dyn cce_ui::widget::Element + 'static));
+                ctx.link_ids(self_id, bc_id);
+                (*self.breadcrumb).set_parent(Some(self_ptr), ctx);
+
+                let g_id = (*self.graph).base().unwrap().id();
+                ctx.register_widget(g_id, self.graph as *mut (dyn cce_ui::widget::Element + 'static));
+                ctx.link_ids(self_id, g_id);
+                (*self.graph).set_parent(Some(self_ptr), ctx);
+            }
+        }
+    }
+
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
+        self.base.x = x;
+        self.base.y = y;
+        self.base.w = w;
+        self.base.h = h;
+
+        let gap = 12.0;
+        let breadcrumb_h = cce_ui::layout::button_height();
+
+        unsafe {
+            (*self.breadcrumb).set_rect(x, y, w, breadcrumb_h);
+            (*self.graph).set_rect(x, y + breadcrumb_h + gap, w, h - breadcrumb_h - gap);
+        }
+    }
+}
+
+unsafe impl Send for NetworkContainer {}
+unsafe impl Sync for NetworkContainer {}
+
 struct FilesystemApp {
     current_page: Page,
     browse: pages::browse::BrowseState,
@@ -58,7 +299,6 @@ struct FilesystemApp {
     width: u32,
     height: u32,
     scale_factor: f64,
-    sender: calloop::channel::Sender<Message>,
     page_buttons: Vec<(cce_ui::widget::Button, Message)>,
     cursor_x: f32,
     cursor_y: f32,
@@ -71,6 +311,10 @@ struct FilesystemApp {
     context_menu: ContextMenu,
     open_with_dialog: Option<(std::path::PathBuf, cce_ui::widget::TextBox)>,
     root_window: cce_ui::widget::Backplate,
+    browse_splitter: cce_ui::widget::SplitBox,
+    network_splitter: cce_ui::widget::SplitBox,
+    browse_container: BrowseContainer,
+    network_container: NetworkContainer,
     last_click_time: std::time::Instant,
     last_clicked_idx: Option<usize>,
 }
@@ -146,6 +390,11 @@ impl FilesystemApp {
         self.view_dropdown.clear_children(&mut self.ui_context); self.view_dropdown.set_parent(None, &mut self.ui_context);
         self.preview.clear_children(&mut self.ui_context); self.preview.set_parent(None, &mut self.ui_context);
 
+        self.browse_splitter.set_parent(None, &mut self.ui_context);
+        self.network_splitter.set_parent(None, &mut self.ui_context);
+        self.browse_container.clear_children(&mut self.ui_context); self.browse_container.set_parent(None, &mut self.ui_context);
+        self.network_container.clear_children(&mut self.ui_context); self.network_container.set_parent(None, &mut self.ui_context);
+
         self.browse.save_name_box.clear_children(&mut self.ui_context); self.browse.save_name_box.set_parent(None, &mut self.ui_context);
         self.browse.list_box.clear_children(&mut self.ui_context); self.browse.list_box.set_parent(None, &mut self.ui_context);
         self.browse.breadcrumb.clear_children(&mut self.ui_context); self.browse.breadcrumb.set_parent(None, &mut self.ui_context);
@@ -161,9 +410,6 @@ impl FilesystemApp {
         let sidebar_w = if has_sidebar { self.paginator.sidebar_w() } else { 0.0 };
         let browse_x = if has_sidebar { sidebar_w + 17.0 } else { 16.0 };
         let usable_w = self.width as f32 - sidebar_w - (if has_sidebar { 1.0 } else { 0.0 }) - 32.0;
-        let browse_w = (usable_w - 12.0) * 0.5;
-        let preview_w = (usable_w - 12.0) * 0.5;
-        let preview_x = browse_x + browse_w + 12.0;
         let content_y = 16.0;
 
         let select_bar_h = 48.0;
@@ -177,19 +423,23 @@ impl FilesystemApp {
             link_parent_child(&mut self.root_window, &mut self.paginator, &mut self.ui_context);
         }
         link_parent_child(&mut self.root_window, &mut self.view_dropdown, &mut self.ui_context);
-        link_parent_child(&mut self.root_window, &mut self.preview, &mut self.ui_context);
 
         match self.current_page {
             Page::Browse => {
-                link_parent_child(&mut self.root_window, &mut self.browse.breadcrumb, &mut self.ui_context);
-                link_parent_child(&mut self.root_window, &mut self.browse.list_box, &mut self.ui_context);
-                if self.select_mode {
-                    link_parent_child(&mut self.root_window, &mut self.browse.save_name_box, &mut self.ui_context);
+                if self.browse_splitter.children.is_empty() {
+                    self.browse_splitter.add_child_with_proportion(&mut self.browse_container, 0.49, 100.0);
+                    self.browse_splitter.add_child_with_proportion(&mut self.preview, 0.51, 100.0);
                 }
+                link_parent_child(&mut self.root_window, &mut self.browse_splitter, &mut self.ui_context);
+                self.browse_splitter.set_rect(browse_x, content_y, usable_w, content_h);
             }
             Page::Network => {
-                link_parent_child(&mut self.root_window, &mut self.network.breadcrumb, &mut self.ui_context);
-                link_parent_child(&mut self.root_window, &mut self.network.graph, &mut self.ui_context);
+                if self.network_splitter.children.is_empty() {
+                    self.network_splitter.add_child_with_proportion(&mut self.network_container, 0.49, 100.0);
+                    self.network_splitter.add_child_with_proportion(&mut self.preview, 0.51, 100.0);
+                }
+                link_parent_child(&mut self.root_window, &mut self.network_splitter, &mut self.ui_context);
+                self.network_splitter.set_rect(browse_x, content_y, usable_w, content_h);
             }
         }
 
@@ -204,9 +454,6 @@ impl FilesystemApp {
             self.paginator.set_selected_page(page_idx);
             cce_ui::layout::render_widget(&mut dummy_pc, &mut self.paginator, 0.0, 0.0, sidebar_w, self.height as f32, &mut self.ui_context);
         }
-        if self.current_page == Page::Browse || self.current_page == Page::Network {
-            cce_ui::layout::render_widget(&mut dummy_pc, &mut self.preview, preview_x, content_y, preview_w, content_h, &mut self.ui_context);
-        }
 
         // Render root window recursively
         let mut window_pc = pages::PageContent::new();
@@ -216,15 +463,16 @@ impl FilesystemApp {
         let mut pc = pages::PageContent::new();
         match self.current_page {
             Page::Browse => {
-
-                let browse_pc = pages::browse::view(&mut self.browse, &mut self.view_dropdown, browse_x, content_y, browse_w, content_h, self.select_mode, &mut self.ui_context);
+                let (bx, by, bw, bh) = self.browse_container.rect();
+                let browse_pc = pages::browse::view(&mut self.browse, &mut self.view_dropdown, bx, by, bw, bh, self.select_mode, &mut self.ui_context);
 
                 pc.rects.extend(browse_pc.rects);
                 pc.texts.extend(browse_pc.texts);
                 pc.buttons.extend(browse_pc.buttons);
             }
             Page::Network => {
-                let network_pc = pages::network::view(&mut self.network, &self.browse, &mut self.view_dropdown, browse_x, content_y, browse_w, content_h, &mut self.ui_context);
+                let (nx, ny, nw, nh) = self.network_container.rect();
+                let network_pc = pages::network::view(&mut self.network, &self.browse, &mut self.view_dropdown, nx, ny, nw, nh, &mut self.ui_context);
 
                 pc.rects.extend(network_pc.rects);
                 pc.texts.extend(network_pc.texts);
@@ -292,12 +540,12 @@ impl FilesystemApp {
             context_menu_pc.rect(cce_ui::color::popover_bg_color(), cx + 1.0, cy + 1.0, cw - 2.0, ch - 2.0);
             // Hover highlight
             if let Some(h_idx) = self.context_menu.hovered {
-                let iy = cy + h_idx as f32 * 24.0;
+                let iy = cy + h_idx as f32 * ROW_H;
                 context_menu_pc.rect([0.20, 0.40, 0.65, 0.6], cx + 2.0, iy + 2.0, cw - 4.0, 20.0);
             }
             // Text options
             for (idx, (opt, _)) in self.context_menu.options.iter().enumerate() {
-                let iy = cy + idx as f32 * 24.0 + (24.0 - 12.0) / 2.0;
+                let iy = cy + idx as f32 * ROW_H + (ROW_H - 12.0) / 2.0;
                 let text_color = if idx == 0 {
                     [0.44, 0.44, 0.47, 1.0]
                 } else if self.context_menu.hovered == Some(idx) {
@@ -312,10 +560,11 @@ impl FilesystemApp {
         // Gather open-with dialog backdrop & dialog panel if active (open_with_dialog uses textbox rendering manually but we can gather its other quads/texts)
         let mut dialog_pc = pages::PageContent::new();
         if let Some((_path, textbox)) = &mut self.open_with_dialog {
-            let dialog_w = 400.0;
-            let dialog_h = 160.0;
-            let dialog_x = (self.width as f32 - dialog_w) / 2.0;
-            let dialog_y = (self.height as f32 - dialog_h) / 2.0;
+            let r = open_with_rects(self.width as f32, self.height as f32);
+            let (dialog_x, dialog_y, dialog_w, dialog_h) = (r.x, r.y, r.w, r.h);
+            let (tb_x, tb_y, tb_w, tb_h) = r.tb;
+            let (btn_cancel_x, btn_cancel_y, btn_cancel_w, btn_cancel_h) = r.cancel;
+            let (btn_open_x, btn_open_y, btn_open_w, btn_open_h) = r.open;
 
             // Semi-transparent backdrop overlay
             dialog_pc.rect([0.02, 0.02, 0.03, 0.6], 0.0, 0.0, self.width as f32, self.height as f32);
@@ -330,24 +579,8 @@ impl FilesystemApp {
             dialog_pc.text("Enter command:", dialog_x + 20.0, dialog_y + 42.0, 11.0, [0.54, 0.54, 0.58, 1.0]);
 
             // Set textbox position dynamically using configured textbox height
-            let tb_x = dialog_x + 20.0;
-            let tb_y = dialog_y + 60.0;
-            let tb_w = dialog_w - 40.0;
-            let tb_h = cce_ui::layout::textbox_height();
             textbox.set_rect(tb_x, tb_y, tb_w, tb_h);
 
-            // Render Buttons: Cancel & Open
-            let btn_h = cce_ui::layout::button_height();
-            let btn_cancel_x = dialog_x + dialog_w - 180.0;
-            let btn_cancel_y = dialog_y + dialog_h - btn_h - 16.0;
-            let btn_cancel_w = 70.0;
-            let btn_cancel_h = btn_h;
-
-            let btn_open_x = dialog_x + dialog_w - 100.0;
-            let btn_open_y = dialog_y + dialog_h - btn_h - 16.0;
-            let btn_open_w = 80.0;
-            let btn_open_h = btn_h;
-
             let cancel_hover = self.cursor_x >= btn_cancel_x && self.cursor_x <= btn_cancel_x + btn_cancel_w
                 && self.cursor_y >= btn_cancel_y && self.cursor_y <= btn_cancel_y + btn_cancel_h;
             let cancel_bg = if cancel_hover { [0.35, 0.15, 0.15, 0.8] } else { [0.25, 0.12, 0.12, 0.5] };
@@ -373,18 +606,9 @@ impl FilesystemApp {
                 let mut wh = *h;
 
                 if is_page_content {
-                    let viewport_top = content_y;
-                    let viewport_bottom = content_y + content_h;
-                    if wy >= viewport_bottom || wy + wh <= viewport_top {
-                        continue;
-                    }
-                    if wy < viewport_top {
-                        let diff = viewport_top - wy;
-                        wy = viewport_top;
-                        wh = (wh - diff).max(0.0);
-                    }
-                    if wy + wh > viewport_bottom {
-                        wh = (viewport_bottom - wy).max(0.0);
+                    match clip_to_viewport(wy, wh, content_y, content_y + content_h) {
+                        Some((cy, ch)) => { wy = cy; wh = ch; }
+                        None => continue,
                     }
                 }
 
@@ -394,11 +618,8 @@ impl FilesystemApp {
                     w: ww,
                     h: wh,
                     color: *c,
-                    hover_color: *c,
-                    hovering: false,
                     radius: *r,
                     corners: *corners,
-                    action: None,
                 });
             }
             for (btn, action) in &pc_part.buttons {
@@ -415,18 +636,9 @@ impl FilesystemApp {
                 let mut wh = base.h;
 
                 if is_page_content {
-                    let viewport_top = content_y;
-                    let viewport_bottom = content_y + content_h;
-                    if wy >= viewport_bottom || wy + wh <= viewport_top {
-                        continue;
-                    }
-                    if wy < viewport_top {
-                        let diff = viewport_top - wy;
-                        wy = viewport_top;
-                        wh = (wh - diff).max(0.0);
-                    }
-                    if wy + wh > viewport_bottom {
-                        wh = (viewport_bottom - wy).max(0.0);
+                    match clip_to_viewport(wy, wh, content_y, content_y + content_h) {
+                        Some((cy, ch)) => { wy = cy; wh = ch; }
+                        None => continue,
                     }
                 }
 
@@ -440,11 +652,8 @@ impl FilesystemApp {
                     w: ww,
                     h: wh,
                     color: col,
-                    hover_color: hover_bg,
-                    hovering,
                     radius: 4.0, // standard button radius
                     corners: (true, true, true, true),
-                    action: Some(action.clone()),
                 });
 
                 let text_x = if btn.justify == cce_ui::widget::Justification::Left {
@@ -455,63 +664,32 @@ impl FilesystemApp {
                 };
                 let text_y = base.y + (base.h - label_size * 1.4) / 2.0;
 
-                let mut final_button_bounds = if is_page_content {
-                    Some([
-                        0.0,
-                        content_y,
-                        self.width as f32,
-                        content_y + content_h,
-                    ])
+                let start_bounds = if is_page_content {
+                    [0.0, content_y, self.width as f32, content_y + content_h]
                 } else {
-                    None
+                    [0.0, 0.0, self.width as f32, self.height as f32]
                 };
-
-                let mut current_bounds = final_button_bounds.unwrap_or([0.0, 0.0, self.width as f32, self.height as f32]);
-                let mut discard = false;
                 let text_w = label.chars().count() as f32 * label_size * 0.65;
                 let text_h = label_size * 1.4;
-                let t_min_x = text_x;
-                let t_max_x = text_x + text_w;
-                let t_min_y = text_y;
-                let t_max_y = text_y + text_h;
-
-                if part_idx < 2 {
-                    for overlay_pc in [&popover_pc, &context_menu_pc, &dialog_pc] {
-                        for (_, ox, oy, ow, oh, _, _) in &overlay_pc.rects {
-                            let o_min_x = *ox;
-                            let o_max_x = *ox + *ow;
-                            let o_min_y = *oy;
-                            let o_max_y = *oy + *oh;
-
-                            if t_max_x > o_min_x && t_min_x < o_max_x && t_max_y > o_min_y && t_min_y < o_max_y {
-                                if t_min_x >= o_min_x && t_max_x <= o_max_x && t_min_y >= o_min_y && t_max_y <= o_max_y {
-                                    discard = true;
-                                    break;
-                                }
-                                if o_min_x > t_min_x && o_min_x < t_max_x {
-                                    current_bounds[2] = current_bounds[2].min(o_min_x);
-                                }
-                                if o_max_x > t_min_x && o_max_x < t_max_x {
-                                    current_bounds[0] = current_bounds[0].max(o_max_x);
-                                }
-                                if o_min_y > t_min_y && o_min_y < t_max_y {
-                                    current_bounds[3] = current_bounds[3].min(o_min_y);
-                                }
-                                if o_max_y > t_min_y && o_max_y < t_max_y {
-                                    current_bounds[1] = current_bounds[1].max(o_max_y);
-                                }
-                            }
-                        }
-                        if discard {
-                            break;
-                        }
+                let occluded = if part_idx < 2 {
+                    occlude_against(
+                        start_bounds,
+                        text_x,
+                        text_x + text_w,
+                        text_y,
+                        text_y + text_h,
+                        &[&popover_pc, &context_menu_pc, &dialog_pc],
+                    )
+                } else {
+                    Some(start_bounds)
+                };
+                let final_button_bounds = match occluded {
+                    Some(b) => Some(b),
+                    None => {
+                        page_buttons.push((btn.clone(), action.clone()));
+                        continue;
                     }
-                }
-                if discard {
-                    page_buttons.push((btn.clone(), action.clone()));
-                    continue;
-                }
-                final_button_bounds = Some(current_bounds);
+                };
 
                 text_items.push(TextItem::new(
                     &mut self.font_system,
@@ -531,7 +709,7 @@ impl FilesystemApp {
                 page_buttons.push((btn.clone(), action.clone()));
             }
             for (text, size, x, y, col, font, bounds) in &pc_part.texts {
-                let mut final_bounds = if is_page_content {
+                let clamped_bounds = if is_page_content {
                     let viewport_top = content_y;
                     let viewport_bottom = content_y + content_h;
                     match bounds {
@@ -552,51 +730,25 @@ impl FilesystemApp {
                     *bounds
                 };
 
-                let mut current_bounds = final_bounds.unwrap_or([0.0, 0.0, self.width as f32, self.height as f32]);
-                let mut discard = false;
+                let start_bounds = clamped_bounds.unwrap_or([0.0, 0.0, self.width as f32, self.height as f32]);
                 let text_w = text.chars().count() as f32 * size * 0.65;
                 let text_h = *size * 1.4;
-                let t_min_x = *x;
-                let t_max_x = *x + text_w;
-                let t_min_y = *y;
-                let t_max_y = *y + text_h;
-
-                if part_idx < 2 {
-                    for overlay_pc in [&popover_pc, &context_menu_pc, &dialog_pc] {
-                        for (_, ox, oy, ow, oh, _, _) in &overlay_pc.rects {
-                            let o_min_x = *ox;
-                            let o_max_x = *ox + *ow;
-                            let o_min_y = *oy;
-                            let o_max_y = *oy + *oh;
-
-                            if t_max_x > o_min_x && t_min_x < o_max_x && t_max_y > o_min_y && t_min_y < o_max_y {
-                                if t_min_x >= o_min_x && t_max_x <= o_max_x && t_min_y >= o_min_y && t_max_y <= o_max_y {
-                                    discard = true;
-                                    break;
-                                }
-                                if o_min_x > t_min_x && o_min_x < t_max_x {
-                                    current_bounds[2] = current_bounds[2].min(o_min_x);
-                                }
-                                if o_max_x > t_min_x && o_max_x < t_max_x {
-                                    current_bounds[0] = current_bounds[0].max(o_max_x);
-                                }
-                                if o_min_y > t_min_y && o_min_y < t_max_y {
-                                    current_bounds[3] = current_bounds[3].min(o_min_y);
-                                }
-                                if o_max_y > t_min_y && o_max_y < t_max_y {
-                                    current_bounds[1] = current_bounds[1].max(o_max_y);
-                                }
-                            }
-                        }
-                        if discard {
-                            break;
-                        }
-                    }
-                }
-                if discard {
-                    continue;
-                }
-                final_bounds = Some(current_bounds);
+                let occluded = if part_idx < 2 {
+                    occlude_against(
+                        start_bounds,
+                        *x,
+                        *x + text_w,
+                        *y,
+                        *y + text_h,
+                        &[&popover_pc, &context_menu_pc, &dialog_pc],
+                    )
+                } else {
+                    Some(start_bounds)
+                };
+                let final_bounds = match occluded {
+                    Some(b) => Some(b),
+                    None => continue,
+                };
 
                 text_items.push(TextItem::new(
                     &mut self.font_system,
@@ -656,6 +808,15 @@ impl Application for FilesystemApp {
                 }
             }
         }
+        if self.current_page == Page::Browse {
+            if self.browse_splitter.dragging_idx.is_some() || self.browse_splitter.hovered_idx.is_some() {
+                return false;
+            }
+        } else if self.current_page == Page::Network {
+            if self.network_splitter.dragging_idx.is_some() || self.network_splitter.hovered_idx.is_some() {
+                return false;
+            }
+        }
         // 5. Fallback to ui_context's check for registered widgets
         self.ui_context.is_movable_backplate_at(px, py)
     }
@@ -701,7 +862,6 @@ impl Application for FilesystemApp {
             width: initial_w,
             height: initial_h,
             scale_factor: 1.0,
-            sender: sender.clone(),
             page_buttons: Vec::new(),
             cursor_x: 0.0,
             cursor_y: 0.0,
@@ -722,10 +882,34 @@ impl Application for FilesystemApp {
             },
             open_with_dialog: None,
             root_window,
+            browse_splitter: cce_ui::widget::SplitBox::new(cce_ui::widget::SplitDirection::Horizontal, 12.0),
+            network_splitter: cce_ui::widget::SplitBox::new(cce_ui::widget::SplitDirection::Horizontal, 12.0),
+            browse_container: BrowseContainer {
+                base: cce_ui::widget::Widget::new(),
+                parent: None,
+                breadcrumb: std::ptr::null_mut(),
+                list_box: std::ptr::null_mut(),
+                save_name_box: std::ptr::null_mut(),
+                select_mode,
+            },
+            network_container: NetworkContainer {
+                base: cce_ui::widget::Widget::new(),
+                parent: None,
+                breadcrumb: std::ptr::null_mut(),
+                graph: std::ptr::null_mut(),
+            },
             last_click_time: std::time::Instant::now(),
             last_clicked_idx: None,
         };
 
+        // Initialize container references
+        app.browse_container.breadcrumb = &mut app.browse.breadcrumb;
+        app.browse_container.list_box = &mut app.browse.list_box;
+        app.browse_container.save_name_box = &mut app.browse.save_name_box;
+
+        app.network_container.breadcrumb = &mut app.network.breadcrumb;
+        app.network_container.graph = &mut app.network.graph;
+
         // Start initial directory loading via FsService
         app.fs_service.send(services::fs::FsRequest::ReadLastDir);
 
@@ -801,11 +985,7 @@ impl Application for FilesystemApp {
                 }
 
                 // If NavigateTo or SelectEntry happened, update Preview path
-                let selected_path = if let Some(idx) = self.browse.selected {
-                    self.browse.entries.get(idx).map(|e| e.path.clone())
-                } else {
-                    None
-                };
+                let selected_path = self.browse.selected_path();
                 if let Some(path) = selected_path {
                     self.fs_service.send(services::fs::FsRequest::ReadPreview(path));
                 } else {
@@ -842,14 +1022,9 @@ impl Application for FilesystemApp {
                 *needs_rebuild = true;
                 self.needs_rebuild = true;
             }
-            Message::KeyboardEvent(_) => {}
             Message::SelectOpen => {
                 if self.select_directory {
-                    let selected_path = if let Some(idx) = self.browse.selected {
-                        self.browse.entries.get(idx).map(|e| e.path.clone())
-                    } else {
-                        None
-                    };
+                    let selected_path = self.browse.selected_path();
                     let path = selected_path.filter(|p| p.is_dir()).unwrap_or_else(|| self.browse.current_dir.clone());
                     println!("{}", path.display());
                     std::process::exit(0);
@@ -872,11 +1047,7 @@ impl Application for FilesystemApp {
                             }
                         }
                     } else {
-                        let selected_path = if let Some(idx) = self.browse.selected {
-                            self.browse.entries.get(idx).map(|e| e.path.clone())
-                        } else {
-                            None
-                        };
+                        let selected_path = self.browse.selected_path();
                         if let Some(path) = selected_path {
                             if path.is_dir() && !is_project_dir(&path) {
                                 self.fs_service.send(services::fs::FsRequest::ReadDirectory(path));
@@ -923,25 +1094,7 @@ impl Application for FilesystemApp {
                         textbox.text.trim().to_string()
                     };
                     if !cmd_str.is_empty() {
-                        let parts: Vec<&str> = cmd_str.split_whitespace().collect();
-                        if !parts.is_empty() {
-                            let program = parts[0];
-                            let mut program_path = std::path::PathBuf::from(program);
-                            if !program_path.is_absolute() && !program.contains('/') {
-                                if let Ok(home) = std::env::var("HOME") {
-                                    let local_bin = std::path::PathBuf::from(home).join(".local").join("bin").join(program);
-                                    if local_bin.exists() {
-                                        program_path = local_bin;
-                                    }
-                                }
-                            }
-                            let mut command = std::process::Command::new(program_path);
-                            for arg in &parts[1..] {
-                                command.arg(arg);
-                            }
-                            command.arg(&path);
-                            let _ = cce_ui::process::spawn_detached(command);
-                        }
+                        crate::services::fs::spawn_command_for_path(&cmd_str, &path);
                     }
                 }
                 *needs_rebuild = true;
@@ -1029,7 +1182,7 @@ impl Application for FilesystemApp {
             let was_hovered = self.context_menu.hovered;
             self.context_menu.hovered = None;
             if pos.x >= cx && pos.x <= cx + cw && pos.y >= cy && pos.y <= cy + ch {
-                let idx = ((pos.y - cy) / 24.0) as usize;
+                let idx = ((pos.y - cy) / ROW_H) as usize;
                 if idx < self.context_menu.options.len() && idx > 0 {
                     self.context_menu.hovered = Some(idx);
                 }
@@ -1044,6 +1197,16 @@ impl Application for FilesystemApp {
             return;
         }
 
+        if self.current_page == Page::Browse {
+            if self.browse_splitter.on_cursor_moved(pos.x, pos.y, &mut self.ui_context) {
+                changed = true;
+            }
+        } else if self.current_page == Page::Network {
+            if self.network_splitter.on_cursor_moved(pos.x, pos.y, &mut self.ui_context) {
+                changed = true;
+            }
+        }
+
         if !self.select_mode && self.paginator.cursor_moved(pos.x, pos.y, &mut self.ui_context) {
             changed = true;
         }
@@ -1099,26 +1262,11 @@ impl Application for FilesystemApp {
         }
 
         if let Some((_path, textbox)) = &mut self.open_with_dialog {
-            let dialog_w = 400.0;
-            let dialog_h = 160.0;
-            let dialog_x = (self.width as f32 - dialog_w) / 2.0;
-            let dialog_y = (self.height as f32 - dialog_h) / 2.0;
-
-            let tb_x = dialog_x + 20.0;
-            let tb_y = dialog_y + 60.0;
-            let tb_w = dialog_w - 40.0;
-            let tb_h = cce_ui::layout::textbox_height();
-
-            let btn_h = cce_ui::layout::button_height();
-            let btn_cancel_x = dialog_x + dialog_w - 180.0;
-            let btn_cancel_y = dialog_y + dialog_h - btn_h - 16.0;
-            let btn_cancel_w = 70.0;
-            let btn_cancel_h = btn_h;
-
-            let btn_open_x = dialog_x + dialog_w - 100.0;
-            let btn_open_y = dialog_y + dialog_h - btn_h - 16.0;
-            let btn_open_w = 80.0;
-            let btn_open_h = btn_h;
+            let r = open_with_rects(self.width as f32, self.height as f32);
+            let (dialog_x, dialog_y, dialog_w, dialog_h) = (r.x, r.y, r.w, r.h);
+            let (tb_x, tb_y, tb_w, tb_h) = r.tb;
+            let (btn_cancel_x, btn_cancel_y, btn_cancel_w, btn_cancel_h) = r.cancel;
+            let (btn_open_x, btn_open_y, btn_open_w, btn_open_h) = r.open;
 
             if state == ElementState::Pressed {
                 let clicked_inside = pos.x >= dialog_x && pos.x <= dialog_x + dialog_w && pos.y >= dialog_y && pos.y <= dialog_y + dialog_h;
@@ -1170,7 +1318,7 @@ impl Application for FilesystemApp {
 
                 let mut clicked_option = None;
                 if pos.x >= cx && pos.x <= cx + cw && pos.y >= cy && pos.y <= cy + ch {
-                    let idx = ((pos.y - cy) / 24.0) as usize;
+                    let idx = ((pos.y - cy) / ROW_H) as usize;
                     if idx < self.context_menu.options.len() && idx > 0 {
                         clicked_option = self.context_menu.options[idx].1.clone();
                     }
@@ -1208,9 +1356,7 @@ impl Application for FilesystemApp {
                         ("Copy Path".to_string(), Some(Message::CopyPath(path_str))),
                     ];
 
-                    let max_len = options.iter().map(|(s, _)| s.len()).max().unwrap_or(0);
-                    let menu_w = ((max_len as f32 * 7.5) + 24.0).max(120.0);
-                    let menu_h = options.len() as f32 * 24.0;
+                    let (menu_w, menu_h) = context_menu_size(&options);
 
                     self.context_menu = ContextMenu {
                         visible: true,
@@ -1272,9 +1418,7 @@ impl Application for FilesystemApp {
                         options.push(("Delete".to_string(), Some(Message::Browse(pages::browse::BrowseMessage::DeleteEntry(idx)))));
 
                         // Calculate width
-                        let max_len = options.iter().map(|(s, _)| s.len()).max().unwrap_or(0);
-                        let menu_w = ((max_len as f32 * 7.5) + 24.0).max(120.0);
-                        let menu_h = options.len() as f32 * 24.0;
+                        let (menu_w, menu_h) = context_menu_size(&options);
 
                         self.context_menu = ContextMenu {
                             visible: true,
@@ -1319,6 +1463,20 @@ impl Application for FilesystemApp {
             return None;
         }
 
+        if self.current_page == Page::Browse {
+            if self.browse_splitter.mouse_input(button, state, pos.x, pos.y, &mut self.ui_context) {
+                *needs_rebuild = true;
+                self.needs_rebuild = true;
+                return None;
+            }
+        } else if self.current_page == Page::Network {
+            if self.network_splitter.mouse_input(button, state, pos.x, pos.y, &mut self.ui_context) {
+                *needs_rebuild = true;
+                self.needs_rebuild = true;
+                return None;
+            }
+        }
+
         if self.current_page == Page::Browse {
             if self.select_mode {
                 if self.browse.save_name_box.mouse_input(button, state, pos.x, pos.y, &mut self.ui_context) {
@@ -1343,21 +1501,7 @@ impl Application for FilesystemApp {
                 if self.browse.breadcrumb.hit_test(pos.x, pos.y, &self.ui_context) {
                     if self.browse.breadcrumb.mouse_input(button, state, pos.x, pos.y, &mut self.ui_context) {
                         if let Some(seg) = self.browse.breadcrumb.path_click() {
-                            let mut target_path = std::path::PathBuf::new();
-                            let mut current_idx = 0;
-                            for component in self.browse.current_dir.components() {
-                                target_path.push(component);
-                                if component == std::path::Component::RootDir {
-                                    if seg == 0 {
-                                        break;
-                                    }
-                                } else {
-                                    current_idx += 1;
-                                    if current_idx == seg {
-                                        break;
-                                    }
-                                }
-                            }
+                            let target_path = pages::browse::path_to_segment(&self.browse.current_dir, seg);
                             self.fs_service.send(services::fs::FsRequest::ReadDirectory(target_path));
                             *needs_rebuild = true;
                             self.needs_rebuild = true;
@@ -1371,21 +1515,7 @@ impl Application for FilesystemApp {
                     if self.network.breadcrumb.hit_test(pos.x, pos.y, &self.ui_context) {
                         if self.network.breadcrumb.mouse_input(button, state, pos.x, pos.y, &mut self.ui_context) {
                             if let Some(seg) = self.network.breadcrumb.path_click() {
-                                let mut target_path = std::path::PathBuf::new();
-                                let mut current_idx = 0;
-                                for component in self.browse.current_dir.components() {
-                                    target_path.push(component);
-                                    if component == std::path::Component::RootDir {
-                                        if seg == 0 {
-                                            break;
-                                        }
-                                    } else {
-                                        current_idx += 1;
-                                        if current_idx == seg {
-                                            break;
-                                        }
-                                    }
-                                }
+                                let target_path = pages::browse::path_to_segment(&self.browse.current_dir, seg);
                                 self.fs_service.send(services::fs::FsRequest::ReadDirectory(target_path));
                                 changed = true;
                             }
@@ -1423,10 +1553,7 @@ impl Application for FilesystemApp {
                                     self.browse.save_name_box.edit_buffer = entry.name.clone();
                                 }
                             }
-                            let selected_path = Some(entry.path.clone());
-                            if let Some(path) = selected_path {
-                                self.fs_service.send(services::fs::FsRequest::ReadPreview(path));
-                            }
+                            self.fs_service.send(services::fs::FsRequest::ReadPreview(entry.path.clone()));
                         }
                         changed = true;
                     }
@@ -1510,27 +1637,17 @@ impl Application for FilesystemApp {
 
     fn handle_mouse_wheel(&mut self, delta: &MouseScrollDelta, pos: LogicalPosition, needs_rebuild: &mut bool) {
         if self.current_page == Page::Browse || self.current_page == Page::Network {
-            let has_sidebar = false;
-            let sidebar_w = if has_sidebar { self.paginator.sidebar_w() } else { 0.0 };
-            let browse_x = if has_sidebar { sidebar_w + 17.0 } else { 16.0 };
-            let usable_w = self.width as f32 - sidebar_w - (if has_sidebar { 1.0 } else { 0.0 }) - 32.0;
-            let browse_w = (usable_w - 12.0) * 0.5;
-            let preview_w = (usable_w - 12.0) * 0.5;
-            let preview_x = browse_x + browse_w + 12.0;
-            let content_y = 16.0;
-
-            let select_bar_h = 48.0;
-            let content_h = if self.select_mode {
-                self.height as f32 - 32.0 - select_bar_h
-            } else {
-                self.height as f32 - 32.0
-            };
-
-            let half_h = content_h * 0.5;
-            let px = preview_x + 12.0;
-            let py = content_y + 32.0;
-            let pw = preview_w - 24.0;
-            let ph = half_h - 40.0;
+            // Hit-test against the preview widget's actual laid-out rect. Recomputing a
+            // hardcoded 50/50 split here was wrong once the list/preview splitter had been
+            // dragged off-center, so wheel events over the preview were misrouted.
+            let (prev_x, prev_y, prev_w, prev_h) = self.preview.rect();
+            let content_h = prev_h;
+
+            // Inner content-preview region (below the metadata header).
+            let px = prev_x + 12.0;
+            let py = prev_y + 32.0;
+            let pw = prev_w - 24.0;
+            let ph = prev_h * 0.5 - 40.0;
 
             if pos.x as f32 >= px && pos.x as f32 <= px + pw && pos.y as f32 >= py && pos.y as f32 <= py + ph {
                 if self.preview.handle_mouse_wheel(delta, content_h) {
diff --git a/src/pages/browse.rs b/src/pages/browse.rs
index 045147d..d554a21 100644
--- a/src/pages/browse.rs
+++ b/src/pages/browse.rs
@@ -76,6 +76,11 @@ impl Default for BrowseState {
 }
 
 impl BrowseState {
+    /// Path of the currently selected entry, if any.
+    pub fn selected_path(&self) -> Option<PathBuf> {
+        self.selected.and_then(|idx| self.entries.get(idx).map(|e| e.path.clone()))
+    }
+
     pub fn update_breadcrumb(&mut self) {
         let mut segments = Vec::new();
         for component in self.current_dir.components() {
@@ -112,38 +117,34 @@ pub fn is_project_dir(path: &Path) -> bool {
     path.is_dir() && (path.join("state.json").exists() || path.join("state.kdl").exists())
 }
 
+/// Reconstruct the ancestor path for a clicked breadcrumb segment index.
+/// `seg == 0` is the root `/`; each subsequent index adds one path component.
+pub fn path_to_segment(current_dir: &Path, seg: usize) -> PathBuf {
+    let mut target_path = PathBuf::new();
+    let mut current_idx = 0;
+    for component in current_dir.components() {
+        target_path.push(component);
+        if component == std::path::Component::RootDir {
+            if seg == 0 {
+                break;
+            }
+        } else {
+            current_idx += 1;
+            if current_idx == seg {
+                break;
+            }
+        }
+    }
+    target_path
+}
+
 // ── Helpers ─────────────────────────────────────────────────────────
 
 pub fn read_directory(path: &Path) -> Vec<DirEntry> {
     crate::services::fs::read_directory_internal(path)
 }
 
-fn format_size(size: u64) -> String {
-    if size < 1024 {
-        format!("{} B", size)
-    } else if size < 1024 * 1024 {
-        format!("{:.1} K", size as f64 / 1024.0)
-    } else if size < 1024 * 1024 * 1024 {
-        format!("{:.1} M", size as f64 / (1024.0 * 1024.0))
-    } else {
-        format!("{:.1} G", size as f64 / (1024.0 * 1024.0 * 1024.0))
-    }
-}
-
-fn format_permissions(mode: u32) -> String {
-    let mut s = String::with_capacity(10);
-    s.push(if mode & 0o40000 != 0 { 'd' } else { '-' });
-    s.push(if mode & 0o400 != 0 { 'r' } else { '-' });
-    s.push(if mode & 0o200 != 0 { 'w' } else { '-' });
-    s.push(if mode & 0o100 != 0 { 'x' } else { '-' });
-    s.push(if mode & 0o040 != 0 { 'r' } else { '-' });
-    s.push(if mode & 0o020 != 0 { 'w' } else { '-' });
-    s.push(if mode & 0o010 != 0 { 'x' } else { '-' });
-    s.push(if mode & 0o004 != 0 { 'r' } else { '-' });
-    s.push(if mode & 0o002 != 0 { 'w' } else { '-' });
-    s.push(if mode & 0o001 != 0 { 'x' } else { '-' });
-    s
-}
+use crate::util::{format_size, format_permissions};
 
 fn entry_icon(is_dir: bool, name: &str) -> &'static str {
     if is_dir {
@@ -458,22 +459,6 @@ mod tests {
         assert_eq!(next_selection_index(&state, BrowseNavigation::Down), Some(2));
     }
 
-    #[test]
-    fn format_size_units() {
-        assert_eq!(format_size(0), "0 B");
-        assert_eq!(format_size(512), "512 B");
-        assert_eq!(format_size(1024), "1.0 K");
-        assert_eq!(format_size(1048576), "1.0 M");
-        assert_eq!(format_size(1073741824), "1.0 G");
-    }
-
-    #[test]
-    fn format_permissions_string() {
-        assert_eq!(format_permissions(0o40755), "drwxr-xr-x");
-        assert_eq!(format_permissions(0o100644), "-rw-r--r--");
-        assert_eq!(format_permissions(0o644), "-rw-r--r--");
-    }
-
     #[test]
     fn apply_filters_hides_dotfiles() {
         let mut state = BrowseState::default();
@@ -499,6 +484,130 @@ mod tests {
         assert_eq!(state.entries.len(), 2);
     }
 
+    fn entry(name: &str, path: &str, is_dir: bool) -> DirEntry {
+        DirEntry {
+            name: name.to_string(),
+            path: PathBuf::from(path),
+            is_dir,
+            size: 0,
+            permissions: 0o644,
+            modified: String::new(),
+        }
+    }
+
+    #[test]
+    fn search_changed_filters_entries() {
+        let mut state = BrowseState::default();
+        state.all_entries = vec![entry("apple", "/a/apple", false), entry("banana", "/a/banana", false)];
+        let req = update(&mut state, BrowseMessage::SearchChanged("ban".to_string()));
+        assert!(req.is_none());
+        assert_eq!(state.entries.len(), 1);
+        assert_eq!(state.entries[0].name, "banana");
+        assert_eq!(state.selected, Some(0));
+    }
+
+    #[test]
+    fn navigate_to_path_reads_directory() {
+        let mut state = BrowseState::default();
+        let req = update(&mut state, BrowseMessage::NavigateToPath(PathBuf::from("/tmp")));
+        assert!(matches!(req, Some(crate::services::fs::FsRequest::ReadDirectory(p)) if p == PathBuf::from("/tmp")));
+    }
+
+    #[test]
+    fn navigate_to_plain_directory_reads_it() {
+        let dir = std::env::temp_dir().join(format!("cce_nav_{}", chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)));
+        std::fs::create_dir_all(&dir).unwrap();
+        let mut state = BrowseState::default();
+        state.entries = vec![entry("sub", dir.to_str().unwrap(), true)];
+        let req = update(&mut state, BrowseMessage::NavigateTo(0));
+        assert!(matches!(req, Some(crate::services::fs::FsRequest::ReadDirectory(_))));
+        let _ = std::fs::remove_dir_all(&dir);
+    }
+
+    #[test]
+    fn navigate_to_project_directory_does_not_enter() {
+        let dir = std::env::temp_dir().join(format!("cce_proj_{}", chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)));
+        std::fs::create_dir_all(&dir).unwrap();
+        std::fs::write(dir.join("state.json"), "{}").unwrap();
+        let mut state = BrowseState::default();
+        state.entries = vec![entry("proj", dir.to_str().unwrap(), true)];
+        let req = update(&mut state, BrowseMessage::NavigateTo(0));
+        assert!(req.is_none());
+        let _ = std::fs::remove_dir_all(&dir);
+    }
+
+    #[test]
+    fn navigate_to_file_does_nothing() {
+        let mut state = BrowseState::default();
+        state.entries = vec![entry("f.txt", "/a/f.txt", false)];
+        let req = update(&mut state, BrowseMessage::NavigateTo(0));
+        assert!(req.is_none());
+    }
+
+    #[test]
+    fn directory_loaded_populates_and_saves() {
+        let mut state = BrowseState::default();
+        let entries = vec![entry("x", "/d/x", false), entry("y", "/d/y", true)];
+        let req = update(&mut state, BrowseMessage::DirectoryLoaded(PathBuf::from("/d"), entries));
+        assert_eq!(state.current_dir, PathBuf::from("/d"));
+        assert_eq!(state.entries.len(), 2);
+        assert!(matches!(req, Some(crate::services::fs::FsRequest::SaveLastDir(p)) if p == PathBuf::from("/d")));
+    }
+
+    #[test]
+    fn directory_refreshed_preserves_selection_by_path() {
+        let mut state = BrowseState::default();
+        state.current_dir = PathBuf::from("/d");
+        state.all_entries = vec![entry("a", "/d/a", false), entry("b", "/d/b", false)];
+        apply_filters(&mut state);
+        state.selected = Some(1); // "b"
+        let new_entries = vec![entry("b", "/d/b", false), entry("a", "/d/a", false), entry("c", "/d/c", false)];
+        let req = update(&mut state, BrowseMessage::DirectoryRefreshed(PathBuf::from("/d"), new_entries));
+        assert!(req.is_none());
+        assert_eq!(
+            state.selected.and_then(|i| state.entries.get(i)).map(|e| e.name.as_str()),
+            Some("b")
+        );
+    }
+
+    #[test]
+    fn directory_refreshed_ignores_other_dir() {
+        let mut state = BrowseState::default();
+        state.current_dir = PathBuf::from("/d");
+        state.all_entries = vec![entry("a", "/d/a", false)];
+        apply_filters(&mut state);
+        let req = update(&mut state, BrowseMessage::DirectoryRefreshed(PathBuf::from("/other"), vec![entry("z", "/other/z", false)]));
+        assert!(req.is_none());
+        assert_eq!(state.entries.len(), 1);
+        assert_eq!(state.entries[0].name, "a");
+    }
+
+    #[test]
+    fn toggle_hidden_shows_dotfiles() {
+        let mut state = BrowseState::default();
+        state.all_entries = vec![entry(".hidden", "/a/.hidden", false), entry("visible", "/a/visible", false)];
+        apply_filters(&mut state);
+        assert_eq!(state.entries.len(), 1);
+        let req = update(&mut state, BrowseMessage::ToggleHidden);
+        assert!(req.is_none());
+        assert!(state.show_hidden);
+        assert_eq!(state.entries.len(), 2);
+    }
+
+    #[test]
+    fn last_dir_loaded_some_reads_that_dir() {
+        let mut state = BrowseState::default();
+        let req = update(&mut state, BrowseMessage::LastDirLoaded(Some(PathBuf::from("/some/dir"))));
+        assert!(matches!(req, Some(crate::services::fs::FsRequest::ReadDirectory(p)) if p == PathBuf::from("/some/dir")));
+    }
+
+    #[test]
+    fn last_dir_loaded_none_falls_back() {
+        let mut state = BrowseState::default();
+        let req = update(&mut state, BrowseMessage::LastDirLoaded(None));
+        assert!(matches!(req, Some(crate::services::fs::FsRequest::ReadDirectory(_))));
+    }
+
     #[test]
     fn test_is_project_dir_detection() {
         let unique_dir = std::env::temp_dir().join(format!("clear_test_dir_{}", chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)));
@@ -534,6 +643,7 @@ mod tests {
     }
 
     #[test]
+    #[serial_test::serial]
     fn test_directory_persistence() {
         let temp_dir = std::env::temp_dir();
         let original_home = std::env::var("HOME");
@@ -638,44 +748,12 @@ mod tests {
 
     #[test]
     fn test_component_reconstruction() {
-        let current_dir = PathBuf::from("/home/lsgalante/documents");
-        
-        // Let's say seg is 1 (meaning "home/")
-        let seg = 1;
-        let mut target_path = std::path::PathBuf::new();
-        let mut current_idx = 0;
-        for component in current_dir.components() {
-            target_path.push(component);
-            if component == std::path::Component::RootDir {
-                if seg == 0 {
-                    break;
-                }
-            } else {
-                current_idx += 1;
-                if current_idx == seg {
-                    break;
-                }
-            }
-        }
-        assert_eq!(target_path, PathBuf::from("/home"));
-        
-        // Let's say seg is 0 (meaning "/")
-        let seg = 0;
-        let mut target_path = std::path::PathBuf::new();
-        let mut current_idx = 0;
-        for component in current_dir.components() {
-            target_path.push(component);
-            if component == std::path::Component::RootDir {
-                if seg == 0 {
-                    break;
-                }
-            } else {
-                current_idx += 1;
-                if current_idx == seg {
-                    break;
-                }
-            }
-        }
-        assert_eq!(target_path, PathBuf::from("/"));
+        let current_dir = PathBuf::from("/home/user/documents");
+
+        // seg 0 is the root, then each index adds a component.
+        assert_eq!(path_to_segment(&current_dir, 0), PathBuf::from("/"));
+        assert_eq!(path_to_segment(&current_dir, 1), PathBuf::from("/home"));
+        assert_eq!(path_to_segment(&current_dir, 2), PathBuf::from("/home/user"));
+        assert_eq!(path_to_segment(&current_dir, 3), PathBuf::from("/home/user/documents"));
     }
 }
diff --git a/src/pages/network.rs b/src/pages/network.rs
index b62819a..b27f557 100644
--- a/src/pages/network.rs
+++ b/src/pages/network.rs
@@ -32,10 +32,6 @@ impl Default for NetworkState {
     }
 }
 
-#[allow(dead_code)]
-#[derive(Debug, Clone)]
-pub enum NetworkMessage {}
-
 impl NetworkState {
     pub fn populate_graph(&mut self, current_dir: &Path, entries: &[DirEntry]) {
         let mut nodes = Vec::new();
@@ -170,9 +166,6 @@ pub fn view(state: &mut NetworkState, browse: &BrowseState, view_dropdown: &mut
     pc
 }
 
-#[allow(dead_code)]
-pub fn update(_state: &mut NetworkState, _msg: NetworkMessage) {}
-
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/src/services/fs.rs b/src/services/fs.rs
index ffe5aa4..c481f24 100644
--- a/src/services/fs.rs
+++ b/src/services/fs.rs
@@ -3,6 +3,7 @@ use std::os::unix::fs::PermissionsExt;
 use std::path::{Path, PathBuf};
 use tokio::sync::mpsc;
 use crate::pages::browse::DirEntry;
+use crate::util::{format_size, format_permissions};
 use image::GenericImageView;
 use cce_ui::widget::ImagePreviewData;
 
@@ -287,33 +288,6 @@ fn load_preview_data_internal(path: &Path) -> PreviewData {
     }
 }
 
-fn format_size(size: u64) -> String {
-    if size < 1024 {
-        format!("{} B", size)
-    } else if size < 1024 * 1024 {
-        format!("{:.1} K", size as f64 / 1024.0)
-    } else if size < 1024 * 1024 * 1024 {
-        format!("{:.1} M", size as f64 / (1024.0 * 1024.0))
-    } else {
-        format!("{:.1} G", size as f64 / (1024.0 * 1024.0 * 1024.0))
-    }
-}
-
-fn format_permissions(mode: u32) -> String {
-    let mut s = String::with_capacity(10);
-    s.push(if mode & 0o40000 != 0 { 'd' } else { '-' });
-    s.push(if mode & 0o400 != 0 { 'r' } else { '-' });
-    s.push(if mode & 0o200 != 0 { 'w' } else { '-' });
-    s.push(if mode & 0o100 != 0 { 'x' } else { '-' });
-    s.push(if mode & 0o040 != 0 { 'r' } else { '-' });
-    s.push(if mode & 0o020 != 0 { 'w' } else { '-' });
-    s.push(if mode & 0o010 != 0 { 'x' } else { '-' });
-    s.push(if mode & 0o004 != 0 { 'r' } else { '-' });
-    s.push(if mode & 0o002 != 0 { 'w' } else { '-' });
-    s.push(if mode & 0o001 != 0 { 'x' } else { '-' });
-    s
-}
-
 fn infer_file_type(name: &str, is_dir: bool) -> String {
     if is_dir {
         return "Directory".to_string();
@@ -348,19 +322,18 @@ fn infer_file_type(name: &str, is_dir: bool) -> String {
     }
 }
 
-fn get_last_dir_file_path() -> Option<PathBuf> {
-    let dir = if let Ok(xdg_config) = std::env::var("XDG_CONFIG_HOME") {
-        if !xdg_config.is_empty() {
-            PathBuf::from(xdg_config)
-        } else {
-            let home = std::env::var("HOME").ok()?;
-            PathBuf::from(home).join(".config")
-        }
-    } else {
-        let home = std::env::var("HOME").ok()?;
-        PathBuf::from(home).join(".config")
+/// Base config directory for cce: `$XDG_CONFIG_HOME/cce`, else `$HOME/.config/cce`.
+/// Returns `None` only when neither variable is usable.
+pub fn cce_config_dir() -> Option<PathBuf> {
+    let base = match std::env::var("XDG_CONFIG_HOME") {
+        Ok(xdg) if !xdg.is_empty() => PathBuf::from(xdg),
+        _ => PathBuf::from(std::env::var("HOME").ok()?).join(".config"),
     };
-    let dir = dir.join("cce").join("cce-files");
+    Some(base.join("cce"))
+}
+
+fn get_last_dir_file_path() -> Option<PathBuf> {
+    let dir = cce_config_dir()?.join("cce-files");
     let _ = fs::create_dir_all(&dir);
     Some(dir.join("cce-files-last-dir.txt"))
 }
@@ -410,8 +383,7 @@ pub fn get_mime_type(path: &Path) -> Option<String> {
 }
 
 pub fn load_kdl_associations() -> Option<std::collections::HashMap<String, String>> {
-    let home = std::env::var("HOME").ok()?;
-    let path = PathBuf::from(home).join(".config").join("cce").join("mime.kdl");
+    let path = cce_config_dir()?.join("mime.kdl");
     if !path.exists() {
         return None;
     }
@@ -469,13 +441,15 @@ pub fn get_default_application(mime: &str) -> Option<(String, String)> {
         return None;
     }
 
-    // Search for .desktop file in common directories
-    let home = std::env::var("HOME").ok().unwrap_or_default();
-    let search_paths = vec![
-        PathBuf::from(&home).join(".local/share/applications"),
+    // Search for .desktop file in common directories. Skip the user-local path
+    // entirely when HOME is unset rather than emitting a bogus relative path.
+    let mut search_paths = vec![
         PathBuf::from("/usr/share/applications"),
         PathBuf::from("/usr/local/share/applications"),
     ];
+    if let Ok(home) = std::env::var("HOME") {
+        search_paths.insert(0, PathBuf::from(home).join(".local/share/applications"));
+    }
 
     let mut desktop_path = None;
     for dir in search_paths {
@@ -517,35 +491,38 @@ pub fn get_default_application(mime: &str) -> Option<(String, String)> {
     }
 }
 
-pub fn open_file(path: &Path) {
-    let mut opened = false;
-    if let Some(mime) = get_mime_type(path) {
-        if let Some((_, cmd)) = get_default_application(&mime) {
-            if !cmd.is_empty() {
-                let parts: Vec<&str> = cmd.split_whitespace().collect();
-                if !parts.is_empty() {
-                    let program = parts[0];
-                    let mut program_path = PathBuf::from(program);
-                    if !program_path.is_absolute() && !program.contains('/') {
-                        if let Ok(home) = std::env::var("HOME") {
-                            let local_bin = PathBuf::from(home).join(".local").join("bin").join(program);
-                            if local_bin.exists() {
-                                program_path = local_bin;
-                            }
-                        }
-                    }
-                    let mut command = std::process::Command::new(program_path);
-                    for arg in &parts[1..] {
-                        command.arg(arg);
-                    }
-                    command.arg(path);
-                    if cce_ui::process::spawn_detached(command).is_ok() {
-                        opened = true;
-                    }
-                }
+/// Parse a command string, resolve a bare program name against `~/.local/bin`,
+/// append `path` as the final argument, and spawn it detached.
+/// Returns `true` if a process was spawned.
+pub fn spawn_command_for_path(cmd: &str, path: &Path) -> bool {
+    let parts: Vec<&str> = cmd.split_whitespace().collect();
+    if parts.is_empty() {
+        return false;
+    }
+    let program = parts[0];
+    let mut program_path = PathBuf::from(program);
+    if !program_path.is_absolute() && !program.contains('/') {
+        if let Ok(home) = std::env::var("HOME") {
+            let local_bin = PathBuf::from(home).join(".local").join("bin").join(program);
+            if local_bin.exists() {
+                program_path = local_bin;
             }
         }
     }
+    let mut command = std::process::Command::new(program_path);
+    for arg in &parts[1..] {
+        command.arg(arg);
+    }
+    command.arg(path);
+    cce_ui::process::spawn_detached(command).is_ok()
+}
+
+pub fn open_file(path: &Path) {
+    let opened = get_mime_type(path)
+        .and_then(|mime| get_default_application(&mime))
+        .map(|(_, cmd)| !cmd.is_empty() && spawn_command_for_path(&cmd, path))
+        .unwrap_or(false);
+
     if !opened {
         let mut command = std::process::Command::new("xdg-open");
         command.arg(path);
@@ -558,6 +535,7 @@ mod tests {
     use super::*;
 
     #[test]
+    #[serial_test::serial]
     fn test_kdl() {
         let unique_dir_name = format!("cce_test_kdl_{}", std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos());
         let temp_path = std::env::temp_dir().join(unique_dir_name);
@@ -567,18 +545,27 @@ mod tests {
         std::fs::write(&mime_file, "associations { association \"text/plain\" \"cce-text-editor\" }").unwrap();
         
         let old_home = std::env::var("HOME").ok();
-        unsafe { std::env::set_var("HOME", &temp_path); }
-        
+        let old_xdg = std::env::var("XDG_CONFIG_HOME").ok();
+        unsafe {
+            std::env::set_var("HOME", &temp_path);
+            std::env::remove_var("XDG_CONFIG_HOME");
+        }
+
         let assoc = load_kdl_associations();
-        
+
         unsafe {
             if let Some(ref h) = old_home {
                 std::env::set_var("HOME", h);
             } else {
                 std::env::remove_var("HOME");
             }
+            if let Some(ref x) = old_xdg {
+                std::env::set_var("XDG_CONFIG_HOME", x);
+            } else {
+                std::env::remove_var("XDG_CONFIG_HOME");
+            }
         }
-        
+
         let _ = std::fs::remove_dir_all(&temp_path);
         
         println!("Parsed associations: {:?}", assoc);
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..fdd3fae
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,51 @@
+//! Small formatting helpers shared across pages and services.
+
+/// Format a byte count as a short human-readable size (e.g. `1.5 M`).
+pub fn format_size(size: u64) -> String {
+    if size < 1024 {
+        format!("{} B", size)
+    } else if size < 1024 * 1024 {
+        format!("{:.1} K", size as f64 / 1024.0)
+    } else if size < 1024 * 1024 * 1024 {
+        format!("{:.1} M", size as f64 / (1024.0 * 1024.0))
+    } else {
+        format!("{:.1} G", size as f64 / (1024.0 * 1024.0 * 1024.0))
+    }
+}
+
+/// Format a Unix mode into a 10-character `drwxr-xr-x`-style permission string.
+pub fn format_permissions(mode: u32) -> String {
+    let mut s = String::with_capacity(10);
+    s.push(if mode & 0o40000 != 0 { 'd' } else { '-' });
+    s.push(if mode & 0o400 != 0 { 'r' } else { '-' });
+    s.push(if mode & 0o200 != 0 { 'w' } else { '-' });
+    s.push(if mode & 0o100 != 0 { 'x' } else { '-' });
+    s.push(if mode & 0o040 != 0 { 'r' } else { '-' });
+    s.push(if mode & 0o020 != 0 { 'w' } else { '-' });
+    s.push(if mode & 0o010 != 0 { 'x' } else { '-' });
+    s.push(if mode & 0o004 != 0 { 'r' } else { '-' });
+    s.push(if mode & 0o002 != 0 { 'w' } else { '-' });
+    s.push(if mode & 0o001 != 0 { 'x' } else { '-' });
+    s
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn format_size_units() {
+        assert_eq!(format_size(0), "0 B");
+        assert_eq!(format_size(512), "512 B");
+        assert_eq!(format_size(1024), "1.0 K");
+        assert_eq!(format_size(1048576), "1.0 M");
+        assert_eq!(format_size(1073741824), "1.0 G");
+    }
+
+    #[test]
+    fn format_permissions_string() {
+        assert_eq!(format_permissions(0o40755), "drwxr-xr-x");
+        assert_eq!(format_permissions(0o100644), "-rw-r--r--");
+        assert_eq!(format_permissions(0o644), "-rw-r--r--");
+    }
+}