Skip to content

Commit 011e28e

Browse files
committed
feat: aesthetic icons and multi-theme support (unicode, nerdfont, octicons)
1 parent 631f782 commit 011e28e

File tree

10 files changed

+361
-174
lines changed

10 files changed

+361
-174
lines changed

ROADMAP.md

Lines changed: 33 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,33 @@
1-
# Rapide Roadmap
2-
3-
This document outlines the planned trajectory for Rapide beyond the initial local CLI release.
4-
5-
## v2.0.0: Interactive TUI (Released 🗿🚀)
6-
The goal is to transition Rapide from a purely command-driven tool to an interactive experience for reviewing and managing logs.
7-
8-
- **Infrastructure**: Implement a Terminal User Interface (TUI) using [Charm Bubble Tea](https://github.com/charmbracelet/bubbletea) and [Charm Lipgloss](https://github.com/charmbracelet/lipgloss).
9-
- **Features**:
10-
- **Interactive List View**: Scroll through entries, filter in real-time, and toggle "Done" states with hotkeys.
11-
- **Editor Integration**: Open entries in an interactive multi-line editor for faster journaling.
12-
- **Visual Board**: A "Dash" view showing a high-level summary of your collections and pending tasks.
13-
14-
## v2.5.0: Git Sync (Bridge) (Released 🌉🚀)
15-
Bridging the gap between local-only and cloud-hosted logs by leveraging private Git repositories.
16-
17-
- **Infrastructure**: Lightweight Git automation for the `~/.rapide/` directory.
18-
- **Features**:
19-
- **Single Command Sync**: `rapide sync` to pull, merge (rebase), and push logs to a configured remote.
20-
- **Optional Autosync**: Configurable setting to automatically sync on every write or TUI exit.
21-
- **Setup Wizard**: `rapide sync --setup` to easily link a private repository.
22-
23-
## v2.6.0: Pinning (Released 📌🚀)
24-
- **Features**:
25-
- **Pinning**: `rapide pin <id>` to keep critical entries or current projects at the top of the list.
26-
27-
## v2.6.1: Polish & Organization (Released 🛠️🚀)
28-
- **Features**:
29-
- **TUI Config Editor**: `c` hotkey to manage Git URL and Autosync directly in the UI.
30-
- **Clipboard Support**: Robust handling for multi-character pastes in the terminal.
31-
- **Dynamic Layout**: Constrained collection column width with automatic truncation.
32-
33-
## v2.7.2: Security Fix (Released 🔒🚀)
34-
- **Fixes**:
35-
- **esbuild**: Resolved a security vulnerability in `esbuild` using npm overrides in the documentation site.
36-
37-
## v2.7.1: Build & Security (Released 🛠️🚀)
38-
- **Fixes**:
39-
- **CI/CD Align**: Updated Go to 1.25 and Node to 22 in GitHub Actions.
40-
- **Security**: Added Dependabot configuration for automated dependency tracking.
41-
42-
## v2.7.0: Documentation & Onboarding (Released 📖🚀)
43-
The goal is to lower the barrier to entry and ensure Rapide feels accessible to new users while maintaining its professional edge.
44-
45-
- **Infrastructure**: Refined internal documentation and self-documenting CLI help.
46-
- **Features**:
47-
- **`rapide init`**: Interactive setup wizard to seed your journal.
48-
- **TUI Help Overlay**: In-app quick reference (press `?`).
49-
- **VitePress Documentation**: Dedicated docs site on GitHub Pages.
50-
51-
## v3.0.0: Rapide MCP (Model Context Protocol) (Released 🗿🚀)
52-
Bridging the gap between your logs and AI agents by making Rapide a first-class MCP server via a hidden `mcp start` command.
53-
54-
- **Infrastructure**: Implement a Model Context Protocol (MCP) server within the binary.
55-
- **Features**:
56-
- **Contextual Search**: Allow AI agents to search and retrieve relevant journal entries to inform their tasks.
57-
- **Automated Logging**: Enable agents to "log a thought" or "record a milestone" directly into Rapide.
58-
- **Tool Integration**: Expose `rapide` commands (list, done, migrate) as MCP tools.
59-
- **Privacy First**: Local-first MCP server ensuring your journal stays under your control.
60-
61-
## v3.0.2: CI/CD Maintenance (Released 🛠️🚀)
62-
Maintenance release focusing on fixing build warnings and keeping CI/CD healthy.
63-
- **Infrastructure**:
64-
- Update GoReleaser configuration to use specified version (`~> v2`).
65-
- Opt actions runner into Node.js 24 runtime to future-proof workflows.
66-
67-
## v3.0.3: GoReleaser Deprecation Fix (Released 🛠️🚀)
68-
- **Infrastructure**:
69-
- Address GoReleaser v2 deprecations by renaming `format` to `formats` in `.goreleaser.yaml`.
70-
71-
72-
---
73-
74-
*Inspired by the philosophy of Rapid Logging and the aesthetics of the Charm toolchain.*
1+
# Rapide Roadmap 🗿🚀
2+
3+
## v1.0: Core Foundation (Completed)
4+
- [x] Basic rapid logging (tasks, notes, events).
5+
- [x] JSONL storage backbone.
6+
- [x] Primitive CLI list/add.
7+
8+
## v2.0: TUI & Refinement (Completed)
9+
- [x] Full interactive TUI with `bubbletea`.
10+
- [x] Real-time filtering and status bars.
11+
- [x] The `./dev` wrapper for isolated development.
12+
13+
## v2.5: Power User Features (Current)
14+
- [x] **Pinning**: Keep important items at the top (`p` in TUI).
15+
- [x] **Priority**: Visual highlighting for urgent tasks (`!` suffix).
16+
- [x] **Edit Mode**: Quick entry correction (`e` in TUI).
17+
- [x] **Archive/Trim**: Keep the journal lean by moving old items.
18+
19+
## v3.0: MCP & Agentic Workflows (Planned)
20+
- [x] **MCP Server**: Expose Rapide tools to local AI agents (Antigravity).
21+
- [x] **Agent Discovery**: Allow agents to read and search private logs securely.
22+
- [x] **Sync Bridge**: Robust Git synchronization for multi-device workflows.
23+
24+
## v3.1.0: Frictionless Time Logging (Completed ◔🚀)
25+
- [x] **Numpad Trigger**: Implicitly open time entry mode by typing numbers in the TUI.
26+
- [x] **Auto-Formatting**: Smart `HH:MM` input with real-time validation.
27+
- [x] **Origin Indicators**: Visual distinction between user-created (◔) and agent-created (◇) time entries.
28+
- [x] **Aesthetic Icons**: Replaced clunky emojis with refined Unicode symbols (◆, ◔, ◇).
29+
30+
## v4.0: Beyond the Binary (Future)
31+
- [ ] **Plugins**: WASM-based extensions for custom bullets or export formats.
32+
- [ ] **Dashboards**: A summary view of weekly velocity and open tasks.
33+
- [ ] **Mobile Companion**: Lightweight helper app for logging on the go.

cmd/list.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"rapide/internal/model"
77
"rapide/internal/storage"
8+
"rapide/internal/tui"
89
"sort"
910
"strings"
1011
"time"
@@ -115,8 +116,9 @@ var listCmd = &cobra.Command{
115116
}
116117

117118
filtered = append(filtered, e)
118-
if len(e.MarginKey) > maxMargin {
119-
maxMargin = len(e.MarginKey)
119+
mk := tui.StripIcons(e.MarginKey)
120+
if len(mk) > maxMargin {
121+
maxMargin = len(mk)
120122
}
121123
}
122124

cmd/root.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,30 @@ import (
55
"os"
66
"rapide/internal"
77
"rapide/internal/storage"
8+
"rapide/internal/tui"
89

910
"github.com/charmbracelet/lipgloss"
1011
"github.com/spf13/cobra"
1112
)
1213

13-
var Version = "3.0.3"
14+
func init() {
15+
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
16+
cfg, _ := storage.LoadConfig()
17+
if cfg != nil {
18+
theme := cfg.IconTheme
19+
// Migration path for legacy nerd_fonts boolean
20+
if theme == "" && cfg.NerdFonts {
21+
theme = "nerdfont"
22+
}
23+
if theme == "" {
24+
theme = "unicode"
25+
}
26+
tui.SetIconTheme(theme)
27+
}
28+
}
29+
}
30+
31+
var Version = "3.1.0"
1432

1533
var successStyle = lipgloss.NewStyle().
1634
Bold(true).

cmd/ui.go

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,66 +3,91 @@ package cmd
33
import (
44
"fmt"
55
"rapide/internal/model"
6+
"rapide/internal/tui"
7+
"strings"
68

79
"github.com/charmbracelet/lipgloss"
810
)
911

1012
var (
11-
timestampStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#757575"))
12-
marginStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#00ADD8")).Bold(true)
13+
timestampStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Faint(true) // SecondaryColor
14+
marginStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("6")).Bold(true) // PrimaryColor
1315
bulletStyle = lipgloss.NewStyle().Bold(true)
14-
priorityStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000")).Bold(true)
16+
priorityStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("208")).Bold(true) // AccentColor
1517
noteStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#EEEEEE"))
16-
idStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#555555")).Italic(true).Width(5)
17-
doneStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#444444")).Strikethrough(true)
18-
pinnedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#00FF00")).Bold(true)
18+
idStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Faint(true) // DimmedIDStyle
19+
doneStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Strikethrough(true)
20+
pinnedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("3")).Bold(true) // HighlightColor
1921
)
2022

2123
func renderEntry(e model.Entry, marginWidth int) {
2224
if marginWidth < 2 {
2325
marginWidth = 2
2426
}
27+
if marginWidth > 12 {
28+
marginWidth = 12
29+
}
30+
31+
// TUI Format: [ID] 02 Jan 15:04 📌 MarginKey Bullet Content
32+
rawTS := e.Timestamp.Format("02 Jan 15:04")
33+
// Indicator column (Pin > Time > Agent)
34+
icon := tui.GetIcon(e.Pinned, e.MarginKey)
35+
if icon == "" {
36+
icon = " "
37+
}
38+
indicatorStr := pinnedStyle.Render(icon)
2539

26-
rawTS := e.Timestamp.Format("2006-01-02 15:04")
27-
rawMK := fmt.Sprintf("%-*s", marginWidth, e.MarginKey)
40+
displayMK := tui.StripIcons(e.MarginKey)
41+
if len(displayMK) > marginWidth {
42+
displayMK = displayMK[:marginWidth-1] + "…"
43+
}
44+
rawMK := fmt.Sprintf("%-*s", marginWidth, displayMK)
2845
rawBlt := e.Bullet
2946
rawCnt := e.Content
30-
rawID := e.ID
47+
rawID := fmt.Sprintf("[%s]", e.ID)
3148
rawPrio := ""
3249
if e.Priority {
3350
rawPrio = "!"
3451
}
3552

36-
// If done, overwrite components with dimmed style
37-
if e.Bullet == "x" {
38-
ts := doneStyle.Render(rawTS)
39-
mk := doneStyle.Render(rawMK)
40-
blt := doneStyle.Render(rawBlt)
41-
cnt := doneStyle.Render(rawCnt)
42-
id := doneStyle.Render(fmt.Sprintf("%-5s", rawID))
43-
prio := doneStyle.Render(rawPrio)
44-
pn := " "
45-
if e.Pinned {
46-
pn = doneStyle.Render("📌")
47-
}
48-
fmt.Printf("%s | %s | %s | %s %s %s %s\n", id, ts, mk, pn, blt, cnt, prio)
49-
return
50-
}
51-
52-
// Default styling
53+
// Default colors/styles
5354
ts := timestampStyle.Render(rawTS)
54-
mk := marginStyle.Render(rawMK)
55-
blt := bulletStyle.Render(rawBlt)
55+
mk := " "
56+
if displayMK != "" {
57+
mk = marginStyle.Render(rawMK)
58+
} else {
59+
mk = strings.Repeat(" ", marginWidth)
60+
}
61+
bltStyle := bulletStyle
62+
switch e.Bullet {
63+
case "•":
64+
bltStyle = bltStyle.Foreground(lipgloss.Color("#FFFFFF"))
65+
case "O":
66+
bltStyle = bltStyle.Foreground(lipgloss.Color("#AFEEEE"))
67+
case "-", "—":
68+
bltStyle = bltStyle.Foreground(lipgloss.Color("#C0C0C0"))
69+
case ">":
70+
bltStyle = bltStyle.Foreground(lipgloss.Color("#D3D3D3"))
71+
}
72+
blt := bltStyle.Render(rawBlt)
5673
cnt := noteStyle.Render(rawCnt)
57-
id := idStyle.Render(rawID)
74+
id := idStyle.Render(fmt.Sprintf("%-6s", rawID))
5875
prio := ""
5976
if e.Priority {
6077
prio = priorityStyle.Render(rawPrio)
6178
}
62-
pn := " "
63-
if e.Pinned {
64-
pn = pinnedStyle.Render("📌")
79+
80+
if e.Bullet == "x" {
81+
ts = doneStyle.Render(rawTS)
82+
mk = doneStyle.Render(rawMK)
83+
blt = doneStyle.Render("x")
84+
cnt = doneStyle.Render(rawCnt)
85+
id = doneStyle.Render(fmt.Sprintf("%-6s", rawID))
86+
prio = doneStyle.Render(rawPrio)
87+
if e.Pinned {
88+
indicatorStr = doneStyle.Render("📌")
89+
}
6590
}
6691

67-
fmt.Printf("%s | %s | %s | %s %s %s %s\n", id, ts, mk, pn, blt, cnt, prio)
92+
fmt.Printf("%s %s %s %s %s %s%s\n", id, ts, indicatorStr, mk, blt, cnt, prio)
6893
}

demo/config.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2-
"remote_url": "git@github.com:user/journal.git",
3-
"auto_sync": false
2+
"auto_sync": false,
3+
"auto_hide_days": 14,
4+
"icon_theme": "nerdfont"
45
}

internal/mcp/adapter.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"rapide/internal/model"
66
"rapide/internal/storage"
7+
"rapide/internal/tui"
78
"strings"
89
"time"
910
)
@@ -32,6 +33,23 @@ func (a *storageAdapter) AddAgentEntry(ctx context.Context, content string) (*mo
3233
Pinned: false,
3334
}
3435

36+
// Detect if content starts with HH:MM | or just HH:MM
37+
parts := strings.SplitN(content, " ", 2)
38+
potentialTime := strings.TrimSuffix(parts[0], "|")
39+
if len(potentialTime) == 5 && potentialTime[2] == ':' {
40+
hh := potentialTime[:2]
41+
mm := potentialTime[3:]
42+
// Simple validation
43+
if hh >= "00" && hh <= "23" && mm >= "00" && mm <= "59" {
44+
entry.MarginKey = tui.PrefixAgent + potentialTime
45+
if len(parts) > 1 {
46+
entry.Content = strings.TrimPrefix(parts[1], "| ")
47+
} else {
48+
entry.Content = ""
49+
}
50+
}
51+
}
52+
3553
id, err := a.storage.Append(entry)
3654
if err != nil {
3755
return nil, err
@@ -49,7 +67,8 @@ func (a *storageAdapter) SearchAgentEntries(ctx context.Context, query string) (
4967
var results []model.Entry
5068
q := strings.ToLower(query)
5169
for _, e := range all {
52-
if e.MarginKey == "AGENT" && strings.Contains(strings.ToLower(e.Content), q) {
70+
isAgent := e.MarginKey == "AGENT" || strings.HasPrefix(e.MarginKey, tui.IconAgent) || strings.HasPrefix(e.MarginKey, "🤖")
71+
if isAgent && strings.Contains(strings.ToLower(e.Content), q) {
5372
results = append(results, e)
5473
}
5574
}
@@ -64,7 +83,8 @@ func (a *storageAdapter) ListRecentAgentEntries(ctx context.Context, limit int)
6483

6584
var agentEntries []model.Entry
6685
for i := len(all) - 1; i >= 0; i-- {
67-
if all[i].MarginKey == "AGENT" {
86+
isAgent := all[i].MarginKey == "AGENT" || strings.HasPrefix(all[i].MarginKey, tui.IconAgent) || strings.HasPrefix(all[i].MarginKey, "🤖")
87+
if isAgent {
6888
agentEntries = append(agentEntries, all[i])
6989
if len(agentEntries) >= limit {
7090
break

0 commit comments

Comments
 (0)