dashboard

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: May 7, 2026 License: MIT Imports: 21 Imported by: 0

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

Constants

This section is empty.

Variables

AllThemes is the cycle order used by the [t] hotkey.

View Source
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.

View Source
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.

View Source
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

func Run(ctx context.Context, st *storage.Storage, cfg Config) error

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

func RunLegacy(ctx context.Context, st *storage.Storage, cfg Config) error

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

func StatusGlyph(status string) string

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) Update

func (b *BrowseProjectsScreen) Update(msg tea.Msg) (Screen, tea.Cmd)

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.

const (
	CubePlain CubeMode = iota
	CubeFancy
)

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

func (m CubeModel) Init() tea.Cmd

Init returns the first tick command. If the cube is disabled, no command is returned and no work is scheduled.

func (CubeModel) Update

func (m CubeModel) Update(msg tea.Msg) (CubeModel, tea.Cmd)

Update advances the frame index. The cube oscillates: it walks forward to the last frame, flips direction, walks back to frame 0, flips direction, and repeats. That mimics a gentle back-and-forth rotation rather than a one-way spin.

func (CubeModel) View

func (m CubeModel) View() string

View returns the current frame, optionally colorized with the brand palette so the cube reads as part of the dashboard's identity rather than a foreign sprite.

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) Update

func (d *DashboardScreen) Update(msg tea.Msg) (Screen, tea.Cmd)

Update handles key events and async data messages.

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) Update

func (d *DetailScreen) Update(msg tea.Msg) (Screen, tea.Cmd)

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.

func Roadmap

func Roadmap() []Milestone

Roadmap returns the parsed milestone list, embedded from roadmap.yaml at build time. The first call parses; subsequent calls return the cached slice. Tests that mutate the slice should copy first — Roadmap shares memory by design (cheap, read-only data).

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

func New(st *storage.Storage, cfg Config) Model

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

func (m Model) Init() tea.Cmd

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

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

Update is the dashboard message dispatcher. The shape is:

  1. Window resize: distribute width/height to all child components.
  2. Async results (stats, browse, search, tags, detail, tick): fold into Model state; surface errors via the err field.
  3. 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.

func (Model) View

func (m Model) View() string

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) Update

func (p *PendingScreen) Update(msg tea.Msg) (Screen, tea.Cmd)

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) Update

func (r *RecentScreen) Update(msg tea.Msg) (Screen, tea.Cmd)

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) Update

func (s *SearchScreen) Update(msg tea.Msg) (Screen, tea.Cmd)

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

func ThemeByName(name string) Theme

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) Update

func (w *WorkstationScreen) Update(msg tea.Msg) (Screen, tea.Cmd)

func (*WorkstationScreen) View

func (w *WorkstationScreen) View(width, height int, p palette) string

Jump to

Keyboard shortcuts

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