- name
- build-tui-view
- description
- Provides instructions for building Hatchet TUI views in the Hatchet CLI.
- version
- 1
- last_updated
- 2026-01-09T00:00:00.000Z
- self_updating
- true
**📝 SELF-UPDATING DOCUMENT**: This skill automatically updates itself when inaccuracies are discovered or new patterns are learned. Always verify information against the actual codebase and update this file when needed.
## Overview
This skill provides instructions for creating and maintaining Terminal User Interface (TUI) views in the Hatchet CLI using bubbletea and lipgloss. The TUI system uses a modular view architecture where individual views are isolated in separate files within the `views/` directory.
**IMPORTANT**: Always start by finding the corresponding view in the frontend application to understand the structure, columns, and API calls.
## Self-Updating Skill Instructions
**CRITICAL - READ FIRST**: This skill document is designed to be continuously improved and kept accurate.
### When to Update This Skill
You MUST update this skill file in the following situations:
1. **Discovering Inaccuracies**
- When you find incorrect file paths or directory structures
- When code examples don't compile or don't match actual implementations
- When API signatures have changed
- When referenced files don't exist at specified locations
2. **Learning New Patterns**
- When implementing a new view and discovering better approaches
- When the user teaches you new conventions or patterns
- When you find reusable patterns that should be documented
- When you discover common pitfalls that should be warned about
3. **Finding Missing Information**
- When you need information that isn't documented here
- When new components or utilities are added to the codebase
- When new bubbletea/lipgloss patterns are adopted
4. **User Corrections**
- When the user corrects any information in this document
- When the user provides updated approaches or conventions
- When the user points out outdated information
### How to Update This Skill
When updating this skill:
1. **Verify Before Adding**: Always verify paths, code, and API signatures against the actual codebase before adding to this document
2. **Use Read/Glob/Grep**: Check the actual files to ensure accuracy
3. **Test Code Examples**: Ensure code examples compile and follow current patterns
4. **Be Specific**: Include exact file paths, function signatures, and working code examples
5. **Update Immediately**: Make updates as soon as inaccuracies are discovered, not at the end of a session
6. **Preserve Structure**: Maintain the existing document structure and formatting
7. **Add Context**: When adding new sections, explain why the pattern is recommended
### Verification Checklist
Before using information from this skill, verify:
- [ ] File paths exist and are correct
- [ ] Code examples match current implementations
- [ ] API signatures match the generated REST client
- [ ] Reusable components are correctly referenced
- [ ] Directory structures are accurate
### Self-Correction Process
If you discover an inaccuracy while working:
1. Immediately note the issue
2. Verify the correct information by reading the actual files
3. Update this skill document with the correction
4. Continue with the user's task using the corrected information
**Remember**: This skill should be a living document that grows more accurate and comprehensive with each use.
## Project Context
- **Framework**: [bubbletea](https://github.com/charmbracelet/bubbletea) (TUI framework)
- **Styling**: [lipgloss](https://github.com/charmbracelet/lipgloss) (style definitions)
- **TUI Command Location**: `cmd/hatchet-cli/cli/tui.go`
- **Views Location**: `cmd/hatchet-cli/cli/tui/` directory
- **Theme**: Pre-defined Hatchet theme in `cmd/hatchet-cli/cli/internal/styles/styles.go`
- **Frontend Reference**: `frontend/app/src/pages/main/v1/` directory
## Finding Frontend Prior Art
**CRITICAL FIRST STEP**: Before implementing any TUI view, locate the corresponding frontend view to understand:
1. Column structure and names
2. API endpoints and query parameters
3. Data types and fields used
4. Filtering and sorting logic
### Process for Finding Frontend Reference:
1. **Locate the Frontend View**
```bash
# Navigate to frontend pages
cd frontend/app/src/pages/main/v1/
# Find views related to your feature (e.g., workflow-runs, tasks, events)
ls -la
```
2. **Study the Column Definitions**
- Look for files like `{feature}-columns.tsx`
- Note the column keys, titles, and accessors
- Example: `frontend/app/src/pages/main/v1/workflow-runs-v1/components/v1/task-runs-columns.tsx`
```typescript
export const TaskRunColumn = {
taskName: "Task Name",
status: "Status",
workflow: "Workflow",
createdAt: "Created At",
startedAt: "Started At",
duration: "Duration",
};
```
3. **Identify the Data Hook**
- Look for `use-{feature}.tsx` files in the `hooks/` directory
- These contain the API query logic
- Example: `frontend/app/src/pages/main/v1/workflow-runs-v1/hooks/use-runs.tsx`
4. **Find the API Query**
- Check `frontend/app/src/lib/api/queries.ts` for the query definition
- Note the endpoint name and parameters
- Example:
```typescript
v1WorkflowRuns: {
list: (tenant: string, query: V2ListWorkflowRunsQuery) => ({
queryKey: ['v1:workflow-run:list', tenant, query],
queryFn: async () => (await api.v1WorkflowRunList(tenant, query)).data,
}),
}
```
5. **Map to Go REST Client**
- The frontend `api.v1WorkflowRunList()` maps to Go's `client.API().V1WorkflowRunListWithResponse()`
- Frontend query parameters map to Go struct parameters
- Example mapping:
```typescript
// Frontend
api.v1WorkflowRunList(tenantId, {
offset: 0,
limit: 100,
since: createdAfter,
only_tasks: true,
})
// Go equivalent
client.API().V1WorkflowRunListWithResponse(
ctx,
client.TenantId(),
&rest.V1WorkflowRunListParams{
Offset: int64Ptr(0),
Limit: int64Ptr(100),
Since: &since,
OnlyTasks: true,
},
)
```
### Example: Implementing Tasks View from Frontend Reference
1. **Frontend Structure**: `frontend/app/src/pages/main/v1/workflow-runs-v1/`
- Columns: `task-runs-columns.tsx`
- Hook: `use-runs.tsx`
- Table: `runs-table.tsx`
2. **Extract Column Names**:
```typescript
taskName, status, workflow, createdAt, startedAt, duration;
```
3. **Identify API Call**:
```typescript
queries.v1WorkflowRuns.list(tenantId, {
offset,
limit,
statuses,
workflow_ids,
since,
until,
only_tasks: true,
});
```
4. **Implement in TUI**:
```go
// Create matching columns
columns := []table.Column{
{Title: "Task Name", Width: 30},
{Title: "Status", Width: 12},
{Title: "Workflow", Width: 25},
{Title: "Created At", Width: 16},
{Title: "Started At", Width: 16},
{Title: "Duration", Width: 12},
}
// Call matching API endpoint
response, err := client.API().V1WorkflowRunListWithResponse(
ctx,
client.TenantId(),
&rest.V1WorkflowRunListParams{
Offset: int64Ptr(0),
Limit: int64Ptr(100),
Since: &since,
OnlyTasks: true,
},
)
```
## Reusable Components (CRITICAL - READ FIRST)
**IMPORTANT**: All TUI views MUST use the standardized reusable components defined in `view.go` to ensure consistency across the application. **DO NOT** copy-paste header/footer styling code.
### Header Component
**CRITICAL**: ALL headers throughout the TUI use the magenta highlight color (`styles.HighlightColor`) for the title to provide consistent visual emphasis across all views (primary views, detail views, modals, etc.).
#### For Detail Views and Modals
**Always use `RenderHeader()` for detail views, modals, and secondary screens:**
```go
header := RenderHeader("Workflow Details", v.Ctx.ProfileName, v.Width)
header := RenderHeader("Task Details", v.Ctx.ProfileName, v.Width)
header := RenderHeader("Filter Tasks", v.Ctx.ProfileName, v.Width)
```
#### For Primary Views
**Use `RenderHeaderWithViewIndicator()` for primary/list views:**
```go
// For primary list views - shows just the view name, no repetitive "Hatchet Workflows [Workflows]"
header := RenderHeaderWithViewIndicator("Runs", v.Ctx.ProfileName, v.Width)
header := RenderHeaderWithViewIndicator("Workflows", v.Ctx.ProfileName, v.Width)
```
This function renders just the view name (e.g., "Runs" or "Workflows") in the highlight color, keeping it simple and non-repetitive.
**Features of both header functions:**
- Title rendered in magenta highlight color (`styles.HighlightColor`) - **consistent across ALL views**
- Includes the logo (text-based: "HATCHET TUI") on the right
- Shows profile name
- Bordered bottom edge
- Responsive to terminal width
**❌ NEVER do this:**
```go
// Bad: Copy-pasting header styles
headerStyle := lipgloss.NewStyle().
Bold(true).
Foreground(styles.AccentColor).
BorderStyle(lipgloss.NormalBorder()).
// ... more styling
header := headerStyle.Render(fmt.Sprintf("My View - Profile: %s", profile))
// Bad: Calling RenderHeaderWithLogo directly (bypasses highlight color)
header := RenderHeaderWithLogo(fmt.Sprintf("My View - Profile: %s", profile), v.Width)
```
**✅ ALWAYS do this:**
```go
// Good: Use the reusable component for detail views
header := RenderHeader("Task Details", v.Ctx.ProfileName, v.Width)
// Good: Use the view indicator variant for primary views
header := RenderHeaderWithViewIndicator("Runs", v.Ctx.ProfileName, v.Width)
```
### Instructions Component
Use `RenderInstructions()` to display contextual help text:
```go
instructions := RenderInstructions(
"Your instructions here • Use bullets to separate items",
v.Width,
)
```
**Features:**
- Muted color styling for reduced visual noise
- Automatically handles width constraints
- Consistent padding
- Uses bullet separators (•)
### Footer Component
**Always use `RenderFooter()` for navigation/control hints:**
```go
footer := RenderFooter([]string{
"↑/↓: Navigate",
"Enter: Select",
"Esc: Cancel",
"q: Quit",
}, v.Width)
```
**Features:**
- Consistent styling with top border
- Automatically joins control items with bullets (•)
- Muted color for non-intrusive display
- Responsive to terminal width
## Standard View Structure
**Every view should follow this consistent structure:**
```go
func (v *YourView) View() string {
var b strings.Builder
// 1. Header (always) - USE REUSABLE COMPONENT
header := RenderHeader("View Title", v.Ctx.ProfileName, v.Width)
b.WriteString(header)
b.WriteString("\n\n")
// 2. Instructions (when helpful) - USE REUSABLE COMPONENT
instructions := RenderInstructions("Your instructions", v.Width)
b.WriteString(instructions)
b.WriteString("\n\n")
// 3. Main content
// ... your view-specific content ...
// 4. Footer (always) - USE REUSABLE COMPONENT
footer := RenderFooter([]string{
"control1: Action1",
"control2: Action2",
}, v.Width)
b.WriteString(footer)
return b.String()
}
```
## Architecture
### Root TUI Model (`tui.go`)
The root TUI command is responsible for:
1. Profile selection and validation
2. Initializing the Hatchet client
3. Creating the view context
4. Managing the current view
5. Delegating updates to views
### View System (`views/` directory)
Each view is a separate file that implements the `View` interface:
- `view.go` - Base view interface, context, and **reusable components**
- `{viewname}.go` - Individual view implementations (e.g., `tasks.go`)
## Core Principles
### 1. File Structure
#### TUI Command File
- File: `cmd/hatchet-cli/cli/tui.go`
- Purpose: Command setup, profile selection, client initialization, view management
#### View Files
- Location: `cmd/hatchet-cli/cli/tui/`
- Files:
- `view.go` - View interface and base types
- `{viewname}.go` - Individual view implementations
### 2. View Interface
All views must implement this interface (defined in `views/view.go`):
```go
package views
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/hatchet-dev/hatchet/pkg/client"
)
// ViewContext contains the shared context passed to all views
type ViewContext struct {
// Profile name for display
ProfileName string
// Hatchet client for API calls
Client client.Client
// Terminal dimensions
Width int
Height int
}
// View represents a TUI view component
type View interface {
// Init initializes the view and returns any initial commands
Init() tea.Cmd
// Update handles messages and updates the view state
Update(msg tea.Msg) (View, tea.Cmd)
// View renders the view to a string
View() string
// SetSize updates the view dimensions
SetSize(width, height int)
}
```
### 3. Base Model Pattern
Use `BaseModel` for common view fields:
```go
// BaseModel contains common fields for all views
type BaseModel struct {
Ctx ViewContext
Width int
Height int
Err error
}
// Your view embeds BaseModel
type YourView struct {
BaseModel
// Your view-specific fields
table table.Model
items []YourDataType
}
```
### 4. Creating a New View
#### Step 1: Create View File
Create `cmd/hatchet-cli/cli/tui/{viewname}.go`:
```go
package views
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/hatchet-dev/hatchet/cmd/hatchet-cli/cli/internal/styles"
"github.com/hatchet-dev/hatchet/pkg/client/rest"
)
type YourView struct {
BaseModel
// View-specific fields
}
// NewYourView creates a new instance of your view
func NewYourView(ctx ViewContext) *YourView {
v := &YourView{
BaseModel: BaseModel{
Ctx: ctx,
},
}
// Initialize view components
return v
}
Ver no GitHub