summaryrefslogtreecommitdiff
path: root/internal/ui
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-07 19:53:58 +0300
committerPaul Buetow <paul@buetow.org>2026-04-07 19:53:58 +0300
commit361a2a542fa60d7ab04e820cbf3d0f11ffc5531f (patch)
tree67ba158b1174e6f75eb2d3f888330169c5ced8ef /internal/ui
parentefb531343046a5e32c177136c70afda97169c8fb (diff)
ui: subtle started indicator and hide empty priority in ultra mode
- Revert whole-card yellow background for started tasks; instead render only the ID badge (#N) with yellow bg + black text as a low-noise "in progress" cue - Hide the priority field from the status line when no priority is set, removing the distracting "-" that appeared next to every unset task Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/ui')
-rw-r--r--internal/ui/ultra.go111
1 files changed, 59 insertions, 52 deletions
diff --git a/internal/ui/ultra.go b/internal/ui/ultra.go
index 53bc677..de9a301 100644
--- a/internal/ui/ultra.go
+++ b/internal/ui/ultra.go
@@ -548,24 +548,21 @@ func (m *Model) ultraCursorStatus(tasks []task.Task) string {
return fmt.Sprintf("%d/%d", cursor+1, len(tasks))
}
-// ultraStatusText returns the plain-text representation of the single
-// consolidated status line shown per card: ID, priority, status, urgency,
-// due date, project, and tags. Age, recur, and start are omitted — started
-// tasks are highlighted in yellow, and the remaining fields are available in
-// the detail view.
+// ultraStatusText returns the plain-text representation of the consolidated
+// status line for search indexing. Priority is omitted when unset.
func (m *Model) ultraStatusText(t task.Task) string {
- return strings.Join(
- []string{
- fmt.Sprintf("#%d", t.ID),
- ultraOrDash(t.Priority),
- ultraOrDash(t.Status),
- fmt.Sprintf("%.1f", t.Urgency),
- "due: " + ultraDueValue(m, t.Due),
- "proj: " + ultraOrDash(t.Project),
- "tags: " + ultraOrDash(strings.Join(t.Tags, " ")),
- },
- " | ",
+ parts := []string{fmt.Sprintf("#%d", t.ID)}
+ if t.Priority != "" {
+ parts = append(parts, t.Priority)
+ }
+ parts = append(parts,
+ ultraOrDash(t.Status),
+ fmt.Sprintf("%.1f", t.Urgency),
+ "due: "+ultraDueValue(m, t.Due),
+ "proj: "+ultraOrDash(t.Project),
+ "tags: "+ultraOrDash(strings.Join(t.Tags, " ")),
)
+ return strings.Join(parts, " | ")
}
func (m *Model) ultraDescriptionLines(t task.Task, width int) []string {
@@ -640,8 +637,7 @@ func (m *Model) renderUltraCard(t task.Task, width int, selected bool, re *regex
lines[0] = "! " + lines[0]
card = strings.Join(lines, "\n")
}
- started := t.Start != ""
- return ultraCardStyle(m.theme, width, selected, started, blink).Render(card)
+ return ultraCardStyle(m.theme, width, selected, blink).Render(card)
}
// renderUltraStatus renders the consolidated single-line card status (no selection bg).
@@ -659,56 +655,74 @@ func (m *Model) renderUltraAnnotations(t task.Task, width int) string {
return m.renderUltraAnnotationsWithRegex(t, width, m.ultraSearchRegex, "")
}
-// renderUltraStatusWithRegex renders a single consolidated status line combining
-// the former header (ID, priority, status, urgency) and meta (due, project, tags)
-// fields. Age, recur, and start are omitted for compactness.
+// renderUltraStatusWithRegex renders a single consolidated status line:
+//
+// #ID [pri] | status | urgency | due: X | proj: X | tags: X
+//
+// Priority is omitted when unset. When the task is started, the ID badge
+// gets a yellow background with black text for a subtle "in progress" cue
+// without flooding the whole card with colour.
+// Age, recur, and start are omitted for compactness.
func (m *Model) renderUltraStatusWithRegex(t task.Task, width int, re *regexp.Regexp, bg string) string {
_ = width
idText := fmt.Sprintf("#%d", t.ID)
- priorityText := ultraOrDash(t.Priority)
statusText := ultraOrDash(t.Status)
urgencyText := fmt.Sprintf("%.1f", t.Urgency)
due := ultraDueValue(m, t.Due)
project := ultraOrDash(t.Project)
tags := ultraOrDash(strings.Join(t.Tags, " "))
- // Priority badges render as 3-char wide pills in the styled path (Width(3)
- // + Center). Pad the plain text to match so whole-line and styled paths
- // produce the same visible text after ANSI stripping.
- priorityPadded := priorityText
+ // Build plain-text line for whole-line search matching.
+ // Priority badges render as 3-char pills (Width(3)+Center) in the styled
+ // path, so pad the plain text to match for consistent ANSI-stripped output.
+ plainParts := []string{idText}
if t.Priority == "H" || t.Priority == "M" || t.Priority == "L" {
- priorityPadded = " " + t.Priority + " "
+ plainParts = append(plainParts, " "+t.Priority+" ")
}
+ plainParts = append(plainParts, statusText, urgencyText,
+ "due: "+due, "proj: "+project, "tags: "+tags)
+ line := strings.Join(plainParts, " | ")
- line := strings.Join([]string{
- idText, priorityPadded, statusText, urgencyText,
- "due: " + due, "proj: " + project, "tags: " + tags,
- }, " | ")
- // Fall back to whole-line rendering when the regex spans a field separator
- // or a full "key: value" pair (e.g. "proj: home") that can't be matched by
- // checking label and value individually.
+ // Fall back to whole-line rendering when the regex spans a separator or a
+ // full "key: value" pair that can't be matched by individual field checks.
if re != nil && re.MatchString(line) && !ultraRegexMatchesAny(re,
- idText, priorityText, statusText, urgencyText, due, project, tags,
+ idText, t.Priority, statusText, urgencyText, due, project, tags,
) {
return m.renderUltraSearchLine(line, lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("253")), re, bg)
}
sep := ultraFieldSep(bg)
- id := m.ultraStyledText(re, lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("253")), idText, bg)
- // H/M/L badges keep their own coloured background; plain "-" uses the card bg.
- priorityBG := bg
- if t.Priority == "H" || t.Priority == "M" || t.Priority == "L" {
- priorityBG = ""
+
+ // ID badge: yellow bg + black text when the task is started; otherwise bold white.
+ var idStyle lipgloss.Style
+ if t.Start != "" {
+ idStyle = lipgloss.NewStyle().Bold(true).
+ Foreground(lipgloss.Color("0")).
+ Background(lipgloss.Color(m.theme.UltraStartedBG))
+ } else {
+ idStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("253"))
+ }
+ // Don't pass bg into the started ID badge — it has its own background.
+ idRendered := idStyle.Render(idText)
+ if re != nil && re.MatchString(idText) {
+ idRendered = idStyle.Render(m.highlightMatches(idText, re))
}
- priority := m.ultraStyledText(re, ultraPriorityStyle(m.theme, t.Priority), priorityText, priorityBG)
+
status := m.ultraStyledText(re, lipgloss.NewStyle().Foreground(lipgloss.Color("246")), statusText, bg)
urgency := m.ultraStyledText(re, lipgloss.NewStyle().Foreground(lipgloss.Color("214")), urgencyText, bg)
- parts := []string{
- id, priority, status, urgency,
+
+ parts := []string{idRendered}
+ // Only include priority when it is actually set (H/M/L).
+ if t.Priority != "" {
+ priorityText := t.Priority
+ parts = append(parts, m.ultraStyledText(re, ultraPriorityStyle(m.theme, t.Priority), priorityText, ""))
+ }
+ parts = append(parts,
+ status, urgency,
m.ultraKeyValue(re, "due", due, bg),
m.ultraKeyValue(re, "proj", project, bg),
m.ultraKeyValue(re, "tags", tags, bg),
- }
+ )
return strings.Join(parts, sep)
}
@@ -837,16 +851,9 @@ func (m *Model) ultraKeyValue(re *regexp.Regexp, label, value, bg string) string
return m.ultraStyledText(re, labelStyle, label+":", bg) + space + m.ultraStyledText(re, valueStyle, value, bg)
}
-func ultraCardStyle(theme Theme, width int, selected, started, blink bool) lipgloss.Style {
+func ultraCardStyle(theme Theme, width int, selected, blink bool) lipgloss.Style {
style := lipgloss.NewStyle().Width(width)
- if started {
- // Amber yellow background marks in-progress tasks; selection overrides it
- // when the cursor is also on this card.
- fg := contrastColor(theme.UltraStartedBG)
- style = style.Foreground(lipgloss.Color(fg)).Background(lipgloss.Color(theme.UltraStartedBG))
- }
if selected {
- // Selection highlight takes priority over the started colour.
style = style.Foreground(lipgloss.Color(theme.SelectedFG)).Background(lipgloss.Color(theme.SelectedBG))
}
if blink {