Initial vcom TUI prototype
This commit is contained in:
commit
059f925e00
16 changed files with 3227 additions and 0 deletions
196
docs/architecture.md
Normal file
196
docs/architecture.md
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
# 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue