Documentation
¶
Overview ¶
Package dashboard implements the `thoughtline ui` Bubbletea TUI.
It opens the same SQLite store the MCP server uses, calls storage.Stats, and renders a multi-tab dashboard so a developer can see at a glance what's stored, browse it, search it, and review session activity.
Index ¶
- Variables
- func ApplyTheme(t Theme)
- func Run(ctx context.Context, st *storage.Storage, cfg Config) error
- func RunLegacy(ctx context.Context, st *storage.Storage, cfg Config) error
- func StatusGlyph(status string) string
- type BrowseProjectsScreen
- type Config
- type CubeConfig
- type CubeMode
- type CubeModel
- type DashboardScreen
- type DetailScreen
- type Milestone
- type Model
- type PendingScreen
- type RecentScreen
- type Screen
- type SearchScreen
- type Theme
- type WorkstationScreen
Constants ¶
This section is empty.
Variables ¶
var AllThemes = []Theme{ThemeBrand, ThemeZBrush, ThemeMono}
AllThemes is the cycle order used by the [t] hotkey.
var ThemeBrand = Theme{ Name: "brand", Brand: lipgloss.AdaptiveColor{Light: "#6D28D9", Dark: "#A78BFA"}, BrandSolid: lipgloss.Color("#7C3AED"), Accent: lipgloss.AdaptiveColor{Light: "#0891B2", Dark: "#22D3EE"}, Success: lipgloss.AdaptiveColor{Light: "#047857", Dark: "#34D399"}, Warning: lipgloss.AdaptiveColor{Light: "#B45309", Dark: "#FBBF24"}, Danger: lipgloss.AdaptiveColor{Light: "#B91C1C", Dark: "#F87171"}, Text: lipgloss.AdaptiveColor{Light: "#1F2937", Dark: "#E5E7EB"}, TextOnBrand: lipgloss.Color("#FFFFFF"), Muted: lipgloss.AdaptiveColor{Light: "#6B7280", Dark: "#9CA3AF"}, Subtle: lipgloss.AdaptiveColor{Light: "#9CA3AF", Dark: "#6B7280"}, Border: lipgloss.AdaptiveColor{Light: "#D1D5DB", Dark: "#374151"}, Surface: lipgloss.AdaptiveColor{Light: "#F3F4F6", Dark: "#1F2937"}, }
ThemeBrand — the original violet+cyan SaaS-tech palette. Default.
var ThemeMono = Theme{ Name: "mono", Brand: lipgloss.AdaptiveColor{Light: "#000000", Dark: "#FFFFFF"}, BrandSolid: lipgloss.Color("#000000"), Accent: lipgloss.AdaptiveColor{Light: "#374151", Dark: "#D1D5DB"}, Success: lipgloss.AdaptiveColor{Light: "#000000", Dark: "#FFFFFF"}, Warning: lipgloss.AdaptiveColor{Light: "#374151", Dark: "#D1D5DB"}, Danger: lipgloss.AdaptiveColor{Light: "#000000", Dark: "#FFFFFF"}, Text: lipgloss.AdaptiveColor{Light: "#111827", Dark: "#F9FAFB"}, TextOnBrand: lipgloss.Color("#FFFFFF"), Muted: lipgloss.AdaptiveColor{Light: "#6B7280", Dark: "#9CA3AF"}, Subtle: lipgloss.AdaptiveColor{Light: "#9CA3AF", Dark: "#6B7280"}, Border: lipgloss.AdaptiveColor{Light: "#D1D5DB", Dark: "#4B5563"}, Surface: lipgloss.AdaptiveColor{Light: "#F3F4F6", Dark: "#1F2937"}, }
ThemeMono — minimalist grayscale. Useful for screenshots, slides, and low-color terminals.
var ThemeZBrush = Theme{ Name: "zbrush", Brand: lipgloss.AdaptiveColor{Light: "#D68A3C", Dark: "#D68A3C"}, BrandSolid: lipgloss.Color("#D68A3C"), Accent: lipgloss.AdaptiveColor{Light: "#E8A858", Dark: "#E8A858"}, Success: lipgloss.AdaptiveColor{Light: "#B5A642", Dark: "#B5A642"}, Warning: lipgloss.AdaptiveColor{Light: "#E8A858", Dark: "#E8A858"}, Danger: lipgloss.AdaptiveColor{Light: "#C25450", Dark: "#C25450"}, Text: lipgloss.AdaptiveColor{Light: "#E8DCC4", Dark: "#E8DCC4"}, TextOnBrand: lipgloss.Color("#1A1A1A"), Muted: lipgloss.AdaptiveColor{Light: "#A89F8C", Dark: "#A89F8C"}, Subtle: lipgloss.AdaptiveColor{Light: "#7A7163", Dark: "#7A7163"}, Border: lipgloss.AdaptiveColor{Light: "#4A443D", Dark: "#4A443D"}, Surface: lipgloss.AdaptiveColor{Light: "#2D2A26", Dark: "#2D2A26"}, }
ThemeZBrush — warm tactile palette inspired by Pixologic ZBrush. Warm grays tending to brown, signature amber/orange accents, cream text. Dark-only by design (Light values mirror Dark).
Functions ¶
func ApplyTheme ¶
func ApplyTheme(t Theme)
ApplyTheme rebuilds every package-level style from the given theme. Call this once at startup and again whenever the user cycles themes. Concurrency note: Bubbletea runs Update on a single goroutine, so we don't lock here.
func Run ¶
Run blocks the calling goroutine while the dashboard TUI is on screen. It returns nil on a clean quit (q / ctrl+c / esc) and a wrapped error otherwise. The caller (cmd/thoughtline) is responsible for opening the storage and providing the Config.
Uses the new flatModel (engram-style screen stack). The legacy Model in model.go is preserved for its test suite but no longer drives the binary. To boot the legacy UI for comparison, call RunLegacy instead.
func RunLegacy ¶
RunLegacy boots the v1 tab-based dashboard. Kept as an escape hatch in case the new flat dashboard regresses on a specific terminal — invoke it from cmd/thoughtline behind a hidden flag if needed. Not exposed by default.
func StatusGlyph ¶
StatusGlyph returns a one-character emoji-free marker for a milestone status — keeps the TUI rendering portable across terminals that handle emoji unevenly.
Types ¶
type BrowseProjectsScreen ¶
type BrowseProjectsScreen struct {
// contains filtered or unexported fields
}
BrowseProjectsScreen shows all distinct projects, sorted alphabetically. Pressing enter drills into a project-scoped RecentScreen.
func (*BrowseProjectsScreen) Init ¶
func (b *BrowseProjectsScreen) Init() tea.Cmd
func (*BrowseProjectsScreen) OnFocus ¶
func (b *BrowseProjectsScreen) OnFocus() tea.Cmd
func (*BrowseProjectsScreen) Title ¶
func (b *BrowseProjectsScreen) Title() string
func (*BrowseProjectsScreen) View ¶
func (b *BrowseProjectsScreen) View(width, height int, p palette) string
type Config ¶
type Config struct {
Version string
DBPath string
Project string
// ThemeName is preserved on Config for backwards compatibility with
// the existing CLI flag --theme. The new screen-stack dashboard
// ignores it and renders a single palette; the old tab-based view
// is still wired through this field until the migration completes.
ThemeName string
// Splash + SplashDuration drive the v1 intro animation that lives in
// the old tab-based view. The new flat dashboard skips the splash.
// These fields stay so --no-splash and --splash-ms keep parsing while
// the migration is in flight.
Splash bool
SplashDuration time.Duration
// CheckUpdates toggles the GitHub release lookup. Default true in
// the binary, false in tests so we don't hit the network.
CheckUpdates bool
}
Config carries the wiring the dashboard needs from main(). DBPath and Version come from the binary's runtime; Project is the auto-detected default ("" means "show all projects").
type CubeConfig ¶
type CubeConfig struct {
Enabled bool
Mode CubeMode
FrameDelay time.Duration // default 200ms when zero
}
CubeConfig is the set of knobs an embedder can tune. Zero value = disabled cube, no animation, no draw cost.
type CubeMode ¶
type CubeMode int
CubeMode picks the glyph set used by the cube. Plain is pure ASCII (`+ - | / \`) — safe in Windows CMD, conhost, and other legacy terminals. Fancy uses box-drawing characters for sharper edges; requires a font that supports them, which is most modern terminals.
type CubeModel ¶
type CubeModel struct {
// contains filtered or unexported fields
}
CubeModel is a Bubbletea sub-model that renders an animated 3D wireframe cube. It oscillates through the frames forward and back to give the impression of gentle rotation between -15° and +15°.
The component is self-contained: it owns its own tick command, its own frame buffer, and its own forward/backward direction. Embed it as a field on the parent Model and forward cubeTickMsg in your Update.
func NewCube ¶
func NewCube(cfg CubeConfig) CubeModel
NewCube builds a CubeModel from cfg. If cfg.FrameDelay is zero we fall back to 200ms which feels meditative without being distracting.
func (CubeModel) Init ¶
Init returns the first tick command. If the cube is disabled, no command is returned and no work is scheduled.
type DashboardScreen ¶
type DashboardScreen struct {
// contains filtered or unexported fields
}
DashboardScreen is the root screen of the Thoughtline TUI. It shows the ASCII logo, a 4-cell stat card, top projects, and a 5-item action menu.
func NewDashboardScreen ¶
func NewDashboardScreen(st *storage.Storage, project, version string) *DashboardScreen
NewDashboardScreen creates a new DashboardScreen bound to the given storage.
func (*DashboardScreen) Init ¶
func (d *DashboardScreen) Init() tea.Cmd
Init starts the 30-second ticker and kicks off the initial data load.
func (*DashboardScreen) OnFocus ¶
func (d *DashboardScreen) OnFocus() tea.Cmd
OnFocus is called when the dashboard returns to the top of the stack. Re-issues data-load so the view is fresh on re-entry.
func (*DashboardScreen) Title ¶
func (d *DashboardScreen) Title() string
func (*DashboardScreen) View ¶
func (d *DashboardScreen) View(width, height int, p palette) string
View renders all 7 sections of the dashboard.
type DetailScreen ¶
type DetailScreen struct {
// contains filtered or unexported fields
}
DetailScreen shows the full content of a memory or pending event payload. It uses a scrollable viewport for long content.
func (*DetailScreen) Init ¶
func (d *DetailScreen) Init() tea.Cmd
func (*DetailScreen) OnFocus ¶
func (d *DetailScreen) OnFocus() tea.Cmd
func (*DetailScreen) Title ¶
func (d *DetailScreen) Title() string
func (*DetailScreen) View ¶
func (d *DetailScreen) View(width, height int, p palette) string
type Milestone ¶
type Milestone struct {
ID string `yaml:"id"` // "M0", "M1", ...
Name string `yaml:"name"` // short title
Status string `yaml:"status"` // "done", "in-progress", "next", "deferred"
}
Milestone is a roadmap entry rendered in the Overview panel of the TUI.
type Model ¶
type Model struct {
Quitting bool
// contains filtered or unexported fields
}
Model is the Bubbletea Model for the dashboard. It owns the per-tab bubbles components (lists, textinput, viewport) and a coarse "loaded" map so each non-overview tab fetches its data lazily on first visit.
The fields stats, loaded, loading, err, width, height, Quitting are kept at the top level (not inside a sub-struct) so the v1 dashboard tests in model_test.go that read them directly still compile unchanged.
func New ¶
New constructs a dashboard Model. The Init() command triggers the first stats load. All bubbles widgets are created in a default-sized state and resized to fit on the first WindowSizeMsg.
func (Model) Init ¶
Init kicks off the first stats fetch. The ticker starts when the first statsLoadedMsg arrives so the test harness in model_test.go (which calls cmd() once and feeds the message into Update) keeps working without having to handle tea.BatchMsg.
func (Model) Update ¶
Update is the dashboard message dispatcher. The shape is:
- Window resize: distribute width/height to all child components.
- Async results (stats, browse, search, tags, detail, tick): fold into Model state; surface errors via the err field.
- Key events: handled by handleKey, which routes to per-tab logic.
Side effects (re-fetching, ticking, focused list scrolling) come back as returned tea.Cmds so Update stays pure and unit-testable.
type PendingScreen ¶
type PendingScreen struct {
// contains filtered or unexported fields
}
PendingScreen lists pending events for the current project.
func (*PendingScreen) Init ¶
func (p *PendingScreen) Init() tea.Cmd
func (*PendingScreen) OnFocus ¶
func (p *PendingScreen) OnFocus() tea.Cmd
func (*PendingScreen) Title ¶
func (p *PendingScreen) Title() string
func (*PendingScreen) View ¶
func (p *PendingScreen) View(width, height int, pal palette) string
type RecentScreen ¶
type RecentScreen struct {
// contains filtered or unexported fields
}
RecentScreen displays up to 20 recently updated memories, optionally scoped to a project (filterProject != "" → project-scoped; "" → cross-project).
func (*RecentScreen) Init ¶
func (r *RecentScreen) Init() tea.Cmd
func (*RecentScreen) OnFocus ¶
func (r *RecentScreen) OnFocus() tea.Cmd
func (*RecentScreen) Title ¶
func (r *RecentScreen) Title() string
func (*RecentScreen) View ¶
func (r *RecentScreen) View(width, height int, p palette) string
type Screen ¶
type Screen interface {
// Init is called once when the screen is pushed onto the stack.
// Return a tea.Cmd to kick off any async work needed before first render.
Init() tea.Cmd
// Update handles a single message. Return the (possibly new) Screen and
// any follow-up command. The screen may return a pushScreenCmd or
// popScreenCmd to navigate.
Update(msg tea.Msg) (Screen, tea.Cmd)
// View renders the screen into a string. width and height are the
// available terminal dimensions; p is the active palette.
View(width, height int, p palette) string
// Title returns a short human-readable name shown in breadcrumbs or logs.
Title() string
// OnFocus is called when this screen returns to the top of the stack
// after a child screen is popped. Use it to re-issue data-load commands
// so the view is always fresh on re-entry.
OnFocus() tea.Cmd
}
Screen is the interface every drill-in screen must implement. The root Model dispatches keyboard input to the top of the stack and calls View on the same screen each frame.
type SearchScreen ¶
type SearchScreen struct {
// contains filtered or unexported fields
}
SearchScreen shows a text-input field and a list of search results.
func (*SearchScreen) Init ¶
func (s *SearchScreen) Init() tea.Cmd
func (*SearchScreen) OnFocus ¶
func (s *SearchScreen) OnFocus() tea.Cmd
func (*SearchScreen) Title ¶
func (s *SearchScreen) Title() string
func (*SearchScreen) View ¶
func (s *SearchScreen) View(width, height int, p palette) string
type Theme ¶
type Theme struct {
Name string
// Identity
Brand lipgloss.AdaptiveColor // primary brand color (header pills, key glyphs)
BrandSolid lipgloss.Color // single hex for components that can't take adaptive
// Accent + semantic
Accent lipgloss.AdaptiveColor
Success lipgloss.AdaptiveColor
Warning lipgloss.AdaptiveColor
Danger lipgloss.AdaptiveColor
// Surfaces and text
Text lipgloss.AdaptiveColor
TextOnBrand lipgloss.Color
Muted lipgloss.AdaptiveColor
Subtle lipgloss.AdaptiveColor
Border lipgloss.AdaptiveColor
Surface lipgloss.AdaptiveColor
}
Theme is a named palette. All package-level styles in styles.go are derived from `currentTheme` — call ApplyTheme to swap and rebuild.
Colors use AdaptiveColor so a single theme renders well on both light and dark terminal backgrounds. The "ZBrush" theme breaks that convention deliberately: it is a dark-only theme, so its Light side just keeps the dark values (we want the same warm look regardless of the user's terminal bg).
func ThemeByName ¶
ThemeByName returns the theme with the matching Name, or ThemeBrand when no match is found. Comparison is case-insensitive on the caller side — pass the name lowercased.
type WorkstationScreen ¶
type WorkstationScreen struct {
// contains filtered or unexported fields
}
WorkstationScreen is the v2 root: a 3-pane developer workstation layout modelled on Warp / Linear / LazyGit. Replaces DashboardScreen as the root.
Layout:
┌─ thoughtline · <project> ─────────── DB:99% · Nmem · Ns · uptime ─┐ │ Sidebar │ Center │ Right │ │ ▸ Projects │ <list of items per section> │ <detail of cursor>│ │ Recent │ │ │ │ Pending │ │ │ │ Search │ │ │ │ Sessions │ │ │ └──────────────── ┴ ──────────────────────────── ┴ ─────────────────┘ idx:up · last-save:14:32 · fts5:ok · v0.0.1 :cmd /search
func NewWorkstationScreen ¶
func NewWorkstationScreen(st *storage.Storage, project, version string) *WorkstationScreen
NewWorkstationScreen returns the root workstation screen ready to be the top of the screen stack.
func (*WorkstationScreen) Init ¶
func (w *WorkstationScreen) Init() tea.Cmd
func (*WorkstationScreen) OnFocus ¶
func (w *WorkstationScreen) OnFocus() tea.Cmd
func (*WorkstationScreen) Title ¶
func (w *WorkstationScreen) Title() string
func (*WorkstationScreen) View ¶
func (w *WorkstationScreen) View(width, height int, p palette) string
Source Files
¶
- commands.go
- cube.go
- dashboard_screen.go
- detail_screen.go
- diskfree_unix.go
- flat_model.go
- items.go
- logo.go
- model.go
- pending_screen.go
- projects_screen.go
- recent_screen.go
- roadmap.go
- run.go
- screen.go
- screen_stubs.go
- search_screen.go
- splash.go
- status.go
- styles.go
- theme.go
- themes.go
- update.go
- updatecheck.go
- view.go
- workstation_screen.go