196 lines
4.8 KiB
Markdown
196 lines
4.8 KiB
Markdown
# Architecture
|
|
|
|
## Why this shape
|
|
|
|
The project should not become a giant `main.go` that mixes rendering, key handling, filesystem I/O and business rules. The architecture is split so the UI remains reactive and file operations stay isolated.
|
|
|
|
## High-level modules
|
|
|
|
- `cmd/vcom`
|
|
Entry point, config loading, program startup.
|
|
- `internal/config`
|
|
Config schema, defaults, search paths and TOML parsing.
|
|
- `internal/fs`
|
|
Filesystem model, directory scanning, previews and file operations.
|
|
- `internal/theme`
|
|
Theme presets and style tokens.
|
|
- `internal/ui`
|
|
Bubble Tea model, key map, pane rendering, modal flow and layout.
|
|
|
|
## State model
|
|
|
|
The Bubble Tea root model owns:
|
|
|
|
- terminal dimensions
|
|
- full config
|
|
- active browser pane
|
|
- left and right pane state
|
|
- center preview state
|
|
- transient modal state
|
|
- busy/status state for async work
|
|
|
|
This keeps the Elm-style update loop simple:
|
|
|
|
1. key or resize event arrives
|
|
2. model decides whether to mutate local state or start async work
|
|
3. async work returns a typed message
|
|
4. view renders from plain state
|
|
|
|
## Pane model
|
|
|
|
Each side pane stores:
|
|
|
|
- current path
|
|
- scanned entries
|
|
- cursor index
|
|
- scroll offset
|
|
- cached directory sizes for entries calculated on demand
|
|
|
|
This is intentionally independent from rendering details, so the pane can be unit-tested later without Lip Gloss.
|
|
|
|
## Preview pipeline
|
|
|
|
The center pane is driven only by the current selection from the active side pane.
|
|
|
|
Preview strategies:
|
|
|
|
- directory preview
|
|
Shows child entries and summary data.
|
|
- text preview
|
|
Reads up to a configured byte limit and displays text safely.
|
|
- image preview
|
|
Detects dimensions and format through standard Go image decoders.
|
|
- binary fallback
|
|
Avoids dumping junk bytes into the terminal.
|
|
|
|
Metadata shown above the preview:
|
|
|
|
- kind
|
|
- full path
|
|
- size
|
|
- created time
|
|
- modified time
|
|
- permissions
|
|
|
|
Directory size is expensive, so it is not calculated eagerly. Pressing `Space` starts an async scan and updates both:
|
|
|
|
- side-pane size column
|
|
- preview metadata widget
|
|
|
|
## Configuration design
|
|
|
|
TOML is used because:
|
|
|
|
- it is readable in terminal workflows
|
|
- comments are first-class
|
|
- optional settings can stay commented out for manual enable/disable
|
|
|
|
The example config enables only MC-like columns by default:
|
|
|
|
- `name`
|
|
- `size`
|
|
- `modified`
|
|
|
|
Additional columns are left commented out so users can literally uncomment them:
|
|
|
|
- `created`
|
|
- `permissions`
|
|
- `extension`
|
|
|
|
Other useful config areas:
|
|
|
|
- startup paths for left and right panes
|
|
- theme preset
|
|
- hidden file visibility
|
|
- sort field and reverse mode
|
|
- center pane width ratio
|
|
- confirmation behavior
|
|
- preview byte limits
|
|
- text wrapping in preview
|
|
- compact path display
|
|
|
|
## Rendering strategy
|
|
|
|
The side panes are custom-rendered rather than built on top of the generic table bubble.
|
|
|
|
Reasoning:
|
|
|
|
- MC-like directory panes are not just tables
|
|
- we need tight control over selection styling, truncation and empty states
|
|
- the center pane uses a different rendering model than the side panes
|
|
|
|
Bubble usage:
|
|
|
|
- `bubbletea`
|
|
event loop and async commands
|
|
- `bubbles/key`
|
|
declarative key bindings
|
|
- `bubbles/textinput`
|
|
mkdir modal
|
|
- `bubbles/viewport`
|
|
preview scrolling surface
|
|
- `lipgloss`
|
|
all layout and styling
|
|
|
|
## Operations
|
|
|
|
The first operational slice is intentionally MC-core:
|
|
|
|
- copy selected entry to passive pane directory
|
|
- move selected entry to passive pane directory
|
|
- create directory in active pane
|
|
- delete selected entry
|
|
- refresh active pane
|
|
|
|
These operations are dispatched asynchronously to avoid freezing the TUI on large directories.
|
|
|
|
Overwrite handling is decided before the async operation begins:
|
|
|
|
- if the target does not exist, the operation runs directly
|
|
- if the target exists and overwrite confirmation is enabled, the UI opens a modal
|
|
- if overwrite confirmation is disabled, the operation replaces the target immediately
|
|
|
|
## Theme system
|
|
|
|
Themes are token-based rather than style-object based.
|
|
|
|
Each preset defines semantic colors:
|
|
|
|
- background
|
|
- panel
|
|
- panel inactive
|
|
- border
|
|
- border active
|
|
- text
|
|
- muted text
|
|
- accent
|
|
- selection
|
|
- warning
|
|
- danger
|
|
- footer key
|
|
|
|
This allows future user-defined themes without touching UI logic.
|
|
|
|
The current UI also supports runtime theme cycling, but config remains the source of truth for default startup appearance.
|
|
|
|
## Extension points
|
|
|
|
The architecture is designed to accept later additions without a rewrite:
|
|
|
|
- multi-select and batch operations
|
|
- file viewer/editor integration
|
|
- terminal image protocols
|
|
- tab history per pane
|
|
- bookmarks and quick-jump
|
|
- sort modes
|
|
- archive browsing
|
|
- search/filter overlay
|
|
- pluggable preview providers
|
|
|
|
## Constraints worth keeping
|
|
|
|
- never calculate directory sizes during normal navigation
|
|
- never read full large files into memory for preview
|
|
- keep filesystem code out of `View()`
|
|
- keep styling decisions out of `internal/fs`
|
|
- prefer typed Tea messages over stringly-typed status events
|