package tui import ( "filepass/internal/pages" "filepass/internal/services" "charm.land/bubbles/v2/textinput" ) type page int const ( pageHome page = iota pageConfig pageAddServer pageSelectServer pageServerActions pageFileAction pageSend pageCleanAll pageRemoveServer pageSelectEditServer pageEditServer ) type TUIInterface struct { Services *services.ServicesStore Page page MenuItems []pages.MenuItem Selected int Servers map[string]services.Server ServerNames []string // sorted, stable order for list rendering NoServers bool InitErr error FlashMsg string Form addServerForm FormErr string // inline field error (e.g. duplicate name) Quitting bool WindowWidth int WindowHeight int // server actions page ActiveServer string LocalDir string // user's cwd, destination for received files StorageFiles []string StorageLoading bool StorageErr error FileSelected int // cursor within StorageFiles FileFocused bool // true = ↑↓ drives file list, false = action menu FileScrollOff int // first visible row in StorageFiles list FileViewHeight int // available visible rows for StorageFiles list // file action page ActiveFile string FileOpLoading bool FileOpErr error FileOpSuccess string // edit server page EditingServer string // original name of server being edited // clean all confirmation page CleanInput textinput.Model CleanOpLoading bool CleanOpErr error // send / file picker page Picker picker } func NewTUIInterface(store *services.ServicesStore, localDir string) TUIInterface { return TUIInterface{ Services: store, Page: pageHome, MenuItems: pages.HomeMenuItems(), LocalDir: localDir, } } // fileListHeight calculates how many rows can fit in the server storage file list. func (m TUIInterface) fileListHeight() int { h := m.WindowHeight if h == 0 { return 0 // unconstrained until first WindowSizeMsg } // Card chrome overhead (border top+bottom, inner padding top+bottom) const cardOverhead = 6 // Header (title + subtitle + margins) const headerLines = 4 // Footer (border + content) const footerLines = 2 // Server actions menu rows (Send / Clean All) actionLines := len(m.MenuItems) if actionLines < 1 { actionLines = 2 } // File section chrome: section margin+border+padding + local-dir label+margin const fileSectionOverhead = 5 used := cardOverhead + headerLines + footerLines + actionLines + fileSectionOverhead available := h - used if available < 1 { available = 1 } return available }