memory
ALWAYS ACTIVE — Selective persistent memory: preserve verified, durable knowledge worth reconstructing, with safe topic updates…
Bubbletea TUI patterns for Gentleman.Dots installer. Trigger: When editing Go files in installer/internal/tui/, working on TUI screens, or adding new UI features.
Claude Code
$ npx -y skills add Gentleman-Programming/engram --skill gentleman-bubbletea --agent claude-code
Codex
$ npx skills add Gentleman-Programming/engram --skill gentleman-bubbletea -a codex -y
Cursor
$ npx skills add Gentleman-Programming/engram --skill gentleman-bubbletea -a cursor -y
Gemini CLI
$ npx skills add Gentleman-Programming/engram --skill gentleman-bubbletea -a gemini-cli -y
GitHub Copilot
$ npx skills add Gentleman-Programming/engram --skill gentleman-bubbletea -a github-copilot -y
OpenCode
$ npx skills add Gentleman-Programming/engram --skill gentleman-bubbletea -a opencode -y
Windsurf
$ npx skills add Gentleman-Programming/engram --skill gentleman-bubbletea -a windsurf -y
name: gentleman-bubbletea description: > Bubbletea TUI patterns for Gentleman.Dots installer. Trigger: When editing Go files in installer/internal/tui/, working on TUI screens, or adding new UI features. license: Apache-2.0 metadata: author: gentleman-programming version: "1.0"
Use this skill when:
---
All screens MUST be defined as `Screen` constants in `model.go`:
type Screen int
const (
ScreenWelcome Screen = iota
ScreenMainMenu
ScreenOSSelect
// ... new screens go here
ScreenNewFeature // Add new screen
ScreenNewFeatureCat // Add category screen if needed
)The `Model` struct in `model.go` holds ALL application state:
type Model struct {
Screen Screen
PrevScreen Screen // For back navigation
Width int
Height int
Cursor int
// Add new state here
NewFeatureData []SomeType
NewFeatureScroll int
}All input handling goes through `Update()` with a type switch:
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
return m.handleKeyPress(msg)
case tea.WindowSizeMsg:
m.Width = msg.Width
m.Height = msg.Height
return m, nil
case customMsg:
// Handle custom messages
return m, nil
}
return m, nil
}Separate handler per screen, always return `(tea.Model, tea.Cmd)`:
func (m Model) handleNewFeatureKeys(key string) (tea.Model, tea.Cmd) {
options := m.GetCurrentOptions()
switch key {
case "up", "k":
if m.Cursor > 0 {
m.Cursor--
// Skip separator
if strings.HasPrefix(options[m.Cursor], "───") && m.Cursor > 0 {
m.Cursor--
}
}
case "down", "j":
if m.Cursor < len(options)-1 {
m.Cursor++
if strings.HasPrefix(options[m.Cursor], "───") && m.Cursor < len(options)-1 {
m.Cursor++
}
}
case "enter", " ":
// Handle selection
return m.handleNewFeatureSelection()
case "esc":
m.Screen = m.PrevScreen
m.Cursor = 0
}
return m, nil
}---
Adding a new screen?
├── Define Screen constant in model.go
├── Add state fields to Model struct
├── Add handler in handleKeyPress switch
├── Create handle{Screen}Keys function in update.go
├── Add view case in view.go
└── Add title in GetScreenTitle()
Adding navigation to existing screen?
├── Use m.PrevScreen for back navigation
├── Reset m.Cursor = 0 on screen change
└── Save scroll position if scrollable
Adding scrollable content?
├── Add {Screen}Scroll int to Model
├── Calculate visibleItems from m.Height
├── Handle up/down for scroll position
└── Reset scroll on screen exit---
// In handleKeyPress switch statement:
case ScreenNewFeature:
return m.handleNewFeatureKeys(key)
case ScreenNewFeatureCat:
return m.handleNewFeatureCatKeys(key)func (m Model) GetCurrentOptions() []string {
switch m.Screen {
case ScreenNewFeature:
categories := make([]string, len(m.NewFeatureData)+2)
for i, item := range m.NewFeatureData {
categories[i] = item.Name
}
categories[len(m.NewFeatureData)] = "─────────────"
categories[len(m.NewFeatureData)+1] = "← Back"
return categories
// ...
}
}func (m Model) handleNewFeatureCatKeys(key string) (tea.Model, tea.Cmd) {
data := m.NewFeatureData[m.SelectedNewFeature]
visibleItems := m.Height - 9
if visibleItems < 5 {
visibleItems = 5
}
maxScroll := len(data.Items) - visibleItems
if maxScroll < 0 {
maxScroll = 0
}
switch key {
case "up", "k":
if m.NewFeatureScroll > 0 {
m.NewFeatureScroll--
}
case "down", "j":
if m.NewFeatureScroll < maxScroll {
m.NewFeatureScroll++
}
case "esc", "q", "enter", " ":
m.Screen = ScreenNewFeature
m.NewFeatureScroll = 0
}
return m, nil
}// Define message type
type newFeatureLoadedMsg struct {
data []SomeType
err error
}
// Send message from command
func loadNewFeatureCmd() tea.Cmd {
return func() tea.Msg {
data, err := loadData()
return newFeatureLoadedMsg{data: data, err: err}
}
}
// Handle in Update
case newFeatureLoadedMsg:
if msg.err != nil {
m.ErrorMsg = msg.err.Error()
return m, nil
}
m.NewFeatureData = msg.data
return m, nil---
cd installer && go build ./cmd/gentleman-installer # Build installer cd installer && go test ./internal/tui/... # Run TUI tests cd installer && go test -run TestNewFeature # Run specific test
---
Persistent memory system for AI coding agents. Agent-agnostic Go binary with SQLite + FTS5, MCP server, HTTP API, CLI, and TUI.
ALWAYS ACTIVE — Selective persistent memory: preserve verified, durable knowledge worth reconstructing, with safe topic updates…
Architecture guardrails for Engram across local store, cloud sync, dashboard, and plugins. Trigger: Any change that affects…
Backlog triage protocol for Engram: audit open issues and PRs, classify each item, infer maintainer ideology from comments, and…
PR creation workflow for Engram following the issue-first enforcement system. Trigger: When creating a pull request, opening a…
Product and business-rule guardrails for Engram. Trigger: Any change that affects sync behavior, project controls, permissions,…
Commit and branch naming standards for Engram contributors, enforced by GitHub rulesets. Trigger: Any commit creation, review, or…
Believable agent cost report for any period, default the last 7 full days PT, not counting today. Measured tokens from Claude…
Keep a durable record of work passed between Bots in the shared Hindsight memory bank. Use when handing work to another Bot, when…
Install, configure, and operate MemPalace, including a private local palace, a shared-brain hub, or a client joining an existing…
Install, configure, and operate MemPalace, including a private local palace, a shared-brain hub, or a client joining an existing…