ui

package
v1.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 8, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package ui provides the interactive Terminal User Interface (TUI) for gemtracker using BubbleTea.

The TUI displays gem dependency analysis across multiple views (gem list, details, search, vulnerabilities, project info) with real-time background updates for gem health, outdated versions, and health scoring. It handles user keyboard input, manages application state, and coordinates async data fetching.

Index

Constants

View Source
const (
	ColorBg           = "235" // #262626 - base background
	ColorSurface      = "237" // #3a3a3a - cards/panels
	ColorBorder       = "240" // #585858 - default borders
	ColorBorderActive = "74"  // #5fafd7 - focused border (slate blue)
	ColorText         = "252" // #d0d0d0 - primary text
	ColorTextMuted    = "244" // #808080 - secondary text
	ColorTextSubtle   = "240" // #585858 - hints, tree connectors
	ColorPrimary      = "74"  // #5fafd7 - app accent
	ColorSuccess      = "71"  // #5faf5f - latest/up to date
	ColorWarning      = "178" // #d7af00 - outdated
	ColorDanger       = "160" // #d70000 - vulnerable
	ColorSelected     = "24"  // #005f87 - selected row background
	ColorTabActive    = "74"  // same as Primary
	ColorTabInactive  = "244" // same as TextMuted
)

Color palette - dark slate/blue theme (256-color ANSI)

View Source
const (
	FixedChrome     = 3 // header (1) + tabbar (1) + statusbar (1)
	HeaderHeight    = 3
	TabBarHeight    = 1
	StatusBarHeight = 1
)

Layout constants

Variables

View Source
var AppHeaderStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorPrimary)).
	Bold(true).
	Background(lipgloss.Color(ColorSurface)).
	Padding(0, 2)
View Source
var AppVersionStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorTextMuted))
View Source
var BadgeCriticalDotStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorDanger))
View Source
var BadgeErrorStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorDanger))
View Source
var BadgeHealthyDotStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorSuccess))
View Source
var BadgeLoadingStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorTextMuted))
View Source
var BadgeOKStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorSuccess))
View Source
var BadgeOutdatedStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorWarning))
View Source
var BadgeVulnerableStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorDanger))
View Source
var BadgeWarningDotStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorWarning))
View Source
var ErrorBoxStyle = lipgloss.NewStyle().
	Border(lipgloss.RoundedBorder()).
	BorderForeground(lipgloss.Color(ColorDanger)).
	Foreground(lipgloss.Color(ColorDanger)).
	Bold(true).
	Padding(1, 2)
View Source
var ErrorMessageStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorText))
View Source
var ErrorTitleStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorDanger)).
	Bold(true)
View Source
var InputBoxStyle = lipgloss.NewStyle().
	Border(lipgloss.RoundedBorder()).
	BorderForeground(lipgloss.Color(ColorBorder)).
	Padding(0, 1)
View Source
var KeyHintDescStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorTextMuted))
View Source
var KeyHintKeyStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorPrimary)).
	Bold(true)
View Source
var LoadingMessageStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorTextMuted))
View Source
var PanelBorderActiveStyle = lipgloss.NewStyle().
	Border(lipgloss.RoundedBorder()).
	BorderForeground(lipgloss.Color(ColorBorderActive)).
	Padding(0, 1)
View Source
var PanelBorderStyle = lipgloss.NewStyle().
	Border(lipgloss.RoundedBorder()).
	BorderForeground(lipgloss.Color(ColorBorder)).
	Padding(0, 1)
View Source
var PanelTitleStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorPrimary)).
	Bold(true)
View Source
var ProjectPathStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorTextMuted)).
	Padding(0, 2)
View Source
var RowMutedStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorTextMuted))
View Source
var RowNormalStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorText))
View Source
var RowSelectedStyle = lipgloss.NewStyle().
	Background(lipgloss.Color(ColorSelected)).
	Foreground(lipgloss.Color(ColorText)).
	Bold(true)
View Source
var SearchBoxStyle = lipgloss.NewStyle().
	Border(lipgloss.RoundedBorder()).
	BorderForeground(lipgloss.Color(ColorBorderActive)).
	Padding(0, 1)
View Source
var SearchPromptStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorPrimary)).
	Bold(true)
View Source
var SpinnerStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorPrimary)).
	Bold(true)
View Source
var StatusBarStyle = lipgloss.NewStyle().
	Background(lipgloss.Color(ColorSurface)).
	Foreground(lipgloss.Color(ColorTextMuted)).
	Padding(0, 2)
View Source
var TabActiveStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorTabActive)).
	Bold(true).
	Padding(0, 2).
	Background(lipgloss.Color(ColorSurface)).
	Underline(true)
View Source
var TabStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorTabInactive)).
	Padding(0, 2).
	Background(lipgloss.Color(ColorSurface))
View Source
var TableHeaderStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorTextMuted)).
	Bold(true).
	BorderBottom(true).
	BorderStyle(lipgloss.NormalBorder()).
	BorderForeground(lipgloss.Color(ColorBorder))
View Source
var TreeConnectorStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorTextSubtle))
View Source
var TreeGemNameStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorText))
View Source
var TreeVersionStyle = lipgloss.NewStyle().
	Foreground(lipgloss.Color(ColorTextMuted))
View Source
var UpdateBarStyle = lipgloss.NewStyle().
	Background(lipgloss.Color(ColorSurface)).
	Foreground(lipgloss.Color(ColorWarning)).
	Padding(0, 2)

Functions

This section is empty.

Types

type AnalysisCompleteMsg

type AnalysisCompleteMsg struct {
	Result          *gemfile.AnalysisResult
	Error           error
	OutdatedChecker *gemfile.OutdatedChecker
}

type CVECompleteMsg added in v1.1.5

type CVECompleteMsg struct {
	Vulnerabilities []*gemfile.Vulnerability
	Error           error
}

type CVELoadFromCacheMsg added in v1.1.5

type CVELoadFromCacheMsg struct {
	Vulnerabilities []*gemfile.Vulnerability
	CacheAge        time.Duration
	CacheTTL        time.Duration
}

type CVEProgressMsg added in v1.1.5

type CVEProgressMsg struct {
	GemsProcessed int
	TotalGems     int
}

type CVEScanStartedMsg added in v1.1.5

type CVEScanStartedMsg struct{}

type DependencyCompleteMsg

type DependencyCompleteMsg struct {
	Result *gemfile.DependencyResult
	Error  error
}

type GemReport added in v1.1.2

type GemReport struct {
	// Name is the gem name
	Name string
	// Version is the currently installed version
	Version string
	// Groups lists the bundle groups this gem belongs to
	Groups []string
	// IsFirstLevel indicates whether this is a directly required gem
	IsFirstLevel bool
	// IsOutdated indicates whether a newer version is available
	IsOutdated bool
	// LatestVersion is the latest available version (if IsOutdated is true)
	LatestVersion string
	// IsVulnerable indicates whether known CVEs affect this version
	IsVulnerable bool
	// VulnerabilityInfo contains CVE ID and description (if IsVulnerable is true)
	VulnerabilityInfo string
	// HomepageURL is the gem's homepage or source code URL
	HomepageURL string
	// Description is the gem description from rubygems.org
	Description string
}

GemReport represents a gem's details in the generated report, including status and metadata.

type GitHubBatchCompleteMsg added in v1.1.2

type GitHubBatchCompleteMsg struct {
	Error error
}

type HealthCompleteMsg added in v1.1.0

type HealthCompleteMsg struct{}

type HealthItemMsg added in v1.1.0

type HealthItemMsg struct {
	GemName string
	Health  *gemfile.GemHealth
	Error   error
}

type HealthRateLimitedMsg added in v1.1.0

type HealthRateLimitedMsg struct {
	StoppedAt string // gem name where rate limiting occurred
}

type Model

type Model struct {
	// Window dimensions (updated on resize)
	Width  int
	Height int

	// CurrentView is the screen currently being displayed
	CurrentView ViewMode
	// ActiveTab persists the tab (ViewGemList, ViewUpgradeable, ViewCVE, ViewProjectInfo)
	// across navigation away and back, restoring state when returning
	ActiveTab ViewMode

	// Data
	AnalysisResult   *gemfile.AnalysisResult
	DependencyResult *gemfile.DependencyResult

	// Gem List screen state
	FirstLevelGems     []*gemfile.GemStatus
	GemListCursor      int
	GemListOffset      int
	UnfilteredGems     []*gemfile.GemStatus // All first-level gems (for filter operations)
	SelectedGroups     map[string]bool      // Groups to filter by (if empty, show all)
	ShowOnlyUpgradable bool                 // Filter to show only gems with updates
	AvailableGroups    []string             // All unique groups found in gems

	// Filter Menu screen state
	FilterMenuCursor int // Position in the filter menu (0 = upgradable, 1+ = groups)

	// Gem Detail screen state
	SelectedGem             *gemfile.GemStatus
	DetailSection           int // 0 = forward deps, 1 = reverse deps
	DetailForwardOffset     int
	DetailReverseOffset     int
	DetailTreeCursor        int                     // Selected line in current tree panel
	DetailForwardLines      []string                // Gem names at each line in forward tree
	DetailReverseLines      []string                // Gem names at each line in reverse tree
	DetailCurrentlyViewing  *gemfile.GemStatus      // The gem currently being viewed in detail (may differ from SelectedGem)
	DetailCurrentReverseDep *gemfile.DependencyInfo // Current gem's reverse dependencies

	// Search screen state
	SearchInput   textinput.Model
	SearchQuery   string
	SearchResults []*gemfile.GemStatus
	SearchCursor  int
	SearchOffset  int

	// Upgradeable screen state
	UpgradeableGems           []*gemfile.GemStatus
	UpgradeableFrameworkGems  []*gemfile.GemStatus
	UpgradeableTransitiveDeps []*gemfile.GemStatus // Transitive dependency gems that can be upgraded
	UpgradeableCursor         int
	UpgradeableOffset         int

	// CVE screen state
	VulnerableGems       []*gemfile.GemStatus
	CVECursor            int
	CVEOffset            int
	CVEVulnerabilities   []*gemfile.Vulnerability // Actual vulnerability data from OSV.dev
	LastGemsSignature    string                   // SHA256 of last scanned gems
	CVERefreshInProgress bool                     // Is a CVE refresh happening in background?
	CVELastScanTime      time.Time                // When was CVE data last scanned?
	CVECacheLoadedAt     time.Time                // When was cache loaded?
	CVECacheTTL          time.Duration            // Default: 1 hour
	CVELastError         string                   // Last error message if scan failed

	// Project Info screen state
	RubyVersion       string
	RailsVersion      string
	BundleVersion     string
	OtherFramework    string // For non-Rails projects
	TotalGems         int
	FirstLevelCount   int
	TransitiveDeps    int
	FrameworkDetected string // The name of the framework detected

	// Path selection modal
	PathInput textinput.Model

	// Loading state
	Loading              bool
	LoadingMessage       string
	AnimationFrame       int
	AnalysisStage        string // "parsing", "checking-updates", "scanning-cves"
	AnalysisPercentage   int    // 0-100
	AnalysisCurrentCount int    // Current item in stage
	AnalysisTotalCount   int    // Total items for stage

	// Health loading state
	HealthLoading     bool
	HealthRateLimited bool
	HealthLoadedCount int
	HealthTotalCount  int
	HealthPending     []*gemfile.GemStatus // Queue for sequential fetching
	HealthChecker     *gemfile.HealthChecker
	OutdatedChecker   *gemfile.OutdatedChecker // Reused for health data extraction

	// Outdated checking state
	OutdatedLoading     bool
	OutdatedPending     []*gemfile.GemStatus // Queue for sequential fetching
	OutdatedErrorCount  int
	OutdatedRateLimited bool

	// Error state
	ErrorMessage string

	// Project state
	ProjectPath     string
	GemfileLockPath string
	GemfileSource   string // "Gemfile.lock", "gems.locked", ".gemspec", etc.

	// App metadata
	Version             string
	Commit              string
	Date                string
	NewVersionAvailable string // empty = no update, otherwise holds latest version tag
	Quitting            bool
	NoCache             bool // Skip cache and force fresh analysis
	Verbose             bool // Enable verbose logging
}

Model is the central BubbleTea model that manages all TUI state, including the current screen, gem data, navigation, filtering, async operations (health checks, outdated version checks), and error states. It implements the tea.Model interface and coordinates all UI updates.

func NewModel

func NewModel(version, commit, date, projectPath string, noCache, verbose bool) *Model

NewModel creates a new TUI Model and loads the project from the given path. If the path contains a Gemfile.lock, gems.locked, or .gemspec file, analysis starts automatically. The version, commit, and date are displayed in the UI header.

func (*Model) Init

func (m *Model) Init() tea.Cmd

Init implements the tea.Model interface and starts the initial command queue. If a Gemfile.lock or .gemspec is found, it begins project analysis automatically.

func (*Model) Update

func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (*Model) View

func (m *Model) View() string

type OutdatedCompleteMsg added in v1.1.0

type OutdatedCompleteMsg struct{}

type OutdatedItemMsg added in v1.1.0

type OutdatedItemMsg struct {
	GemName       string
	IsOutdated    bool
	LatestVersion string
	HomepageURL   string
	Description   string
	Error         error
}

type ProgressMsg added in v1.0.6

type ProgressMsg struct {
	Stage      string // "parsing", "checking-updates", "scanning-cves", "complete"
	Percentage int    // 0-100
	Message    string // Status message
}

type ProgressTickMsg added in v1.0.6

type ProgressTickMsg struct{}

type ReportData added in v1.1.2

type ReportData struct {
	// GeneratedAt is the timestamp when the report was generated
	GeneratedAt string
	// ProjectPath is the path to the analyzed Ruby project
	ProjectPath string
	// TotalGems is the count of all gems (first-level and transitive)
	TotalGems int
	// FirstLevelGems is the count of directly required gems
	FirstLevelGems int
	// OutdatedGems lists gems with available updates
	OutdatedGems []*GemReport
	// VulnerableGems lists gems with known CVEs
	VulnerableGems []*GemReport
	// AllGems lists all gems in the project
	AllGems []*GemReport
	// Summary is a brief text summary of findings
	Summary string
	// OutdatedCount is the count of gems with updates available
	OutdatedCount int
	// VulnerableCount is the count of gems with known vulnerabilities
	VulnerableCount int
}

ReportData holds structured gem analysis data suitable for export in multiple formats.

type ReportGenerator added in v1.1.2

type ReportGenerator struct {
	// contains filtered or unexported fields
}

ReportGenerator generates gem dependency reports in multiple formats (text, CSV, JSON) for non-interactive CI/CD integration and compliance use cases.

func NewReportGenerator added in v1.1.2

func NewReportGenerator(projectPath string, noCache, verbose bool) *ReportGenerator

NewReportGenerator creates a new ReportGenerator for the given project path.

func (*ReportGenerator) Generate added in v1.1.2

func (rg *ReportGenerator) Generate(format, outputPath string) error

Generate analyzes the project and generates a report in the specified format (text, csv, or json). If outputPath is empty, writes to stdout. Returns an error if analysis or report writing fails.

type SpinnerTickMsg

type SpinnerTickMsg struct{}

type StageUpdateMsg added in v1.0.6

type StageUpdateMsg struct {
	Stage          string                  // "parsing", "checking-updates", "scanning-cves"
	CurrentCount   int                     // Current gems processed
	TotalCount     int                     // Total gems to process
	Percentage     int                     // 0-100
	Result         *gemfile.AnalysisResult // Accumulated results so far
	OutdatedGems   []*gemfile.GemStatus    // Updated gems with version info
	VulnerableGems []*gemfile.GemStatus    // Updated with CVE info
}

type VersionCheckMsg added in v1.0.3

type VersionCheckMsg struct {
	LatestVersion string
	HasUpdate     bool
}

type ViewMode

type ViewMode int

ViewMode represents the current screen being displayed in the TUI.

const (
	// ViewLoading displays the loading/progress screen while analyzing the project
	ViewLoading ViewMode = iota
	// ViewGemList displays all first-level gems (directly required dependencies)
	ViewGemList
	// ViewGemDetail displays forward and reverse dependencies for a selected gem
	ViewGemDetail
	// ViewSearch displays search results for a gem query
	ViewSearch
	// ViewUpgradeable displays gems with available updates, organized by type
	ViewUpgradeable
	// ViewCVE displays vulnerable gems with CVE information
	ViewCVE
	// ViewProjectInfo displays project metadata (Ruby version, framework, gem counts, etc.)
	ViewProjectInfo
	// ViewFilterMenu displays options to filter gems by group or upgradability
	ViewFilterMenu
	// ViewSelectPath displays an input prompt to select a project directory
	ViewSelectPath
	// ViewError displays an error message
	ViewError
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL