feat: complete TUI redesign with cyberpunk theme (#1)
Some checks failed
Stable Release / stable (push) Failing after 22s

- Dark theme with red accents (cyberpunk aesthetic)
- Epuré cyberpunk style: clean dark backgrounds, sharp red highlights
- Full cyberpunk animations: glitch effect, scan line, typewriter
- Mixed Unicode + ASCII icons
- Rounded borders (╭ ╮ ╯ ╰) on cards and panels
- ASCII art block titles (■) with red styling
- Header: MUYUE branding, status indicators, live clock
- Footer: shortcuts, version, update indicator
- Tab transitions: glitch → scan → typewriter sequence
- Extracted header.go, footer.go, animations.go as new files

Controls unchanged: ctrl+t tabs, ctrl+s sidebar, ctrl+a AI panel

file changes:
- styles.go: new color palette (cyberRed, bgVoid, dimRed), block titles
- types.go: added transition state, clock tick, glitch/scan/done messages
- animations.go: new file with glitch, scan, typewriter, hex stream effects
- header.go: new file with logo, tabs, status dots, live clock
- footer.go: new file with shortcuts, version, update indicator
- model.go: integrated transition state machine, clock updates
- dashboard.go, studio.go, terminal.go, config_tab.go: updated icons/styles

Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>

Co-authored-by: Augustin <muyue@legion-muyue.fr>
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-04-20 20:17:06 +00:00
parent 8ea7418684
commit cb8e3d0d26
12 changed files with 771 additions and 417 deletions

View File

@@ -15,16 +15,16 @@ func (m Model) renderDashboard() string {
var left, right strings.Builder
left.WriteString(renderSectionWithIcon("System", ""))
left.WriteString(renderSectionHeader("SYSTEM", "[*]"))
left.WriteString("\n")
if m.scanResult != nil {
sysInfo := m.scanResult.System.String()
left.WriteString(" ")
left.WriteString(lipgloss.NewStyle().Foreground(textColor).Render(sysInfo))
left.WriteString(lipgloss.NewStyle().Foreground(textMain).Render(sysInfo))
}
left.WriteString("\n\n")
left.WriteString(renderSectionWithIcon("Installed Tools", ""))
left.WriteString(renderSectionHeader("INSTALLED TOOLS", "[+]"))
left.WriteString("\n")
if m.scanResult != nil {
installed := 0
@@ -33,14 +33,14 @@ func (m Model) renderDashboard() string {
if t.Installed {
installed++
left.WriteString(" ")
left.WriteString(itemOKStyle.Render(" "))
left.WriteString(lipgloss.NewStyle().Foreground(textColor).Render(t.Name))
left.WriteString(lipgloss.NewStyle().Foreground(dimColor).Render(fmt.Sprintf(" %s", extractVersion(t.Version))))
left.WriteString(itemOKStyle.Render("[OK] "))
left.WriteString(lipgloss.NewStyle().Foreground(textMain).Render(t.Name))
left.WriteString(lipgloss.NewStyle().Foreground(dimRed).Render(fmt.Sprintf(" %s", extractVersion(t.Version))))
left.WriteString("\n")
} else {
left.WriteString(" ")
left.WriteString(itemMissingStyle.Render(" "))
left.WriteString(lipgloss.NewStyle().Foreground(mutedColor).Render(t.Name))
left.WriteString(itemMissingStyle.Render("[--] "))
left.WriteString(lipgloss.NewStyle().Foreground(textMuted).Render(t.Name))
left.WriteString(itemPendingStyle.Render(" (missing)"))
left.WriteString("\n")
}
@@ -51,14 +51,14 @@ func (m Model) renderDashboard() string {
if total > 0 {
pct = (installed * barWidth) / total
}
bar := lipgloss.NewStyle().Foreground(primaryColor).Render(strings.Repeat("█", pct)) +
lipgloss.NewStyle().Foreground(dimColor).Render(strings.Repeat("░", barWidth-pct))
bar := lipgloss.NewStyle().Foreground(cyberRed).Bold(true).Render(strings.Repeat("█", pct)) +
lipgloss.NewStyle().Foreground(dimRed).Render(strings.Repeat("░", barWidth-pct))
left.WriteString(fmt.Sprintf("\n %s %d/%d\n", bar, installed, total))
}
left.WriteString("\n")
if m.installing {
left.WriteString(renderSectionWithIcon("Installing", ""))
left.WriteString(renderSectionHeader("INSTALLING", "[~]"))
left.WriteString("\n")
progBar := m.progressBar.View()
label := ""
@@ -72,7 +72,7 @@ func (m Model) renderDashboard() string {
}
if len(m.installLog) > 0 {
left.WriteString(renderSectionWithIcon("Install Log", "📋"))
left.WriteString(renderSectionHeader("INSTALL LOG", "[#]"))
left.WriteString("\n")
for _, l := range m.installLog {
left.WriteString(l + "\n")
@@ -80,27 +80,27 @@ func (m Model) renderDashboard() string {
left.WriteString("\n")
}
right.WriteString(renderSectionWithIcon("Quick Actions", ""))
right.WriteString(renderSectionHeader("QUICK ACTIONS", "[!]"))
right.WriteString("\n")
actions := []struct {
key string
desc string
color lipgloss.Color
}{
{"i", "Install missing tools", primaryColor},
{"u", "Check for updates", warmColor},
{"s", "Rescan system", roseColor},
{"l", "Scan LSP servers", accentColor},
{"m", "Configure MCP servers", roseLightColor},
{"i", "Install missing tools", cyberRed},
{"u", "Check for updates", neonRed},
{"s", "Rescan system", cyberPink},
{"l", "Scan LSP servers", cyberRose},
{"m", "Configure MCP servers", brightRed},
}
for _, a := range actions {
right.WriteString(fmt.Sprintf(" %s %s\n",
lipgloss.NewStyle().Foreground(a.color).Bold(true).Render("["+a.key+"]"),
lipgloss.NewStyle().Foreground(textColor).Render(a.desc)))
lipgloss.NewStyle().Foreground(textMain).Render(a.desc)))
}
right.WriteString("\n")
right.WriteString(renderSectionWithIcon("Active Agents", ""))
right.WriteString(renderSectionHeader("ACTIVE AGENTS", "[*]"))
right.WriteString("\n")
agents := []struct {
@@ -111,24 +111,24 @@ func (m Model) renderDashboard() string {
}
for _, a := range agents {
right.WriteString(" ")
right.WriteString(lipgloss.NewStyle().Foreground(dimColor).Render(" "))
right.WriteString(lipgloss.NewStyle().Foreground(mutedColor).Render(a.name + " "))
right.WriteString(itemPendingStyle.Render("stopped"))
right.WriteString(lipgloss.NewStyle().Foreground(dimRed).Render(">> "))
right.WriteString(lipgloss.NewStyle().Foreground(textMuted).Render(a.name + " "))
right.WriteString(itemPendingStyle.Render("[stopped]"))
right.WriteString("\n")
}
right.WriteString("\n")
if len(m.updateStatus) > 0 {
right.WriteString(renderSectionWithIcon("Updates", ""))
right.WriteString(renderSectionHeader("UPDATES", "[^]"))
right.WriteString("\n")
for _, s := range m.updateStatus {
if s.NeedsUpdate {
right.WriteString(" ")
right.WriteString(itemWarnStyle.Render(" "))
right.WriteString(fmt.Sprintf("%s: %s %s\n", s.Tool, s.Current, s.Latest))
right.WriteString(itemWarnStyle.Render("[!!] "))
right.WriteString(fmt.Sprintf("%s: %s -> %s\n", s.Tool, s.Current, s.Latest))
} else if s.Error == "" {
right.WriteString(" ")
right.WriteString(itemOKStyle.Render(" "))
right.WriteString(itemOKStyle.Render("[OK] "))
right.WriteString(fmt.Sprintf("%s: up to date\n", s.Tool))
}
}
@@ -136,18 +136,18 @@ func (m Model) renderDashboard() string {
}
if len(m.lspServers) > 0 {
right.WriteString(renderSectionWithIcon("LSP Servers", "§"))
right.WriteString(renderSectionHeader("LSP SERVERS", "[L]"))
right.WriteString("\n")
lspInstalled := 0
for _, s := range m.lspServers {
if s.Installed {
lspInstalled++
right.WriteString(" ")
right.WriteString(itemOKStyle.Render(" "))
right.WriteString(itemOKStyle.Render("[OK] "))
right.WriteString(fmt.Sprintf("%-22s (%s)\n", s.Name, s.Language))
} else {
right.WriteString(" ")
right.WriteString(itemPendingStyle.Render(" "))
right.WriteString(itemPendingStyle.Render("[ ] "))
right.WriteString(fmt.Sprintf("%-22s (%s)\n", s.Name, s.Language))
}
}
@@ -155,16 +155,16 @@ func (m Model) renderDashboard() string {
right.WriteString("\n")
}
mcpStatus := itemPendingStyle.Render(" not configured")
mcpStatus := itemPendingStyle.Render("[ ] not configured")
if m.mcpConfigured {
mcpStatus = itemOKStyle.Render(" configured")
mcpStatus = itemOKStyle.Render("[OK] configured")
}
right.WriteString(fmt.Sprintf(" MCP: %s\n", mcpStatus))
if m.daemon != nil {
daemonStatus := itemPendingStyle.Render(" stopped")
daemonStatus := itemPendingStyle.Render("[ ] stopped")
if m.daemon.IsRunning() {
daemonStatus = itemOKStyle.Render(" running")
daemonStatus = itemOKStyle.Render("[OK] running")
}
right.WriteString(fmt.Sprintf(" Daemon: %s\n", daemonStatus))
}
@@ -174,8 +174,3 @@ func (m Model) renderDashboard() string {
return lipgloss.JoinHorizontal(lipgloss.Top, leftCol, rightCol)
}
func renderSectionWithIcon(title string, icon string) string {
return lipgloss.NewStyle().Foreground(primaryColor).Render(icon+" ") +
sectionStyle.Render(title)
}