web: task row state coloring and key prefix highlighting
- Split task keys into bold prefix (accent color) + dimmed remainder
- Add dot indicators (●) to section labels (In Progress, Queued)
- Fade icebox tasks to 80% opacity
- Done tasks remain at 75% opacity
diff --git a/src/bin/ranger/commands/serve.rs b/src/bin/ranger/commands/serve.rs
index 636c8b9..5113e22 100644
--- a/src/bin/ranger/commands/serve.rs
+++ b/src/bin/ranger/commands/serve.rs
@@ -53,7 +53,8 @@ async fn index(State(state): State<AppState>) -> Html<String> {
}
struct TaskView {
- short_key: String,
+ key_prefix: String,
+ key_rest: String,
title: String,
description: Option<String>,
has_subtasks: bool,
@@ -140,7 +141,7 @@ fn render_backlog_panel(in_progress: &[TaskView], queued: &[TaskView]) -> String
} else {
if !in_progress.is_empty() {
html.push_str(
- r#"<div class="section-label section-label-in-progress">In Progress</div>"#,
+ r#"<div class="section-label section-label-in-progress"><span class="dot">●</span> In Progress</div>"#,
);
html.push_str(r#"<div class="state-in-progress">"#);
for task in in_progress {
@@ -150,7 +151,7 @@ fn render_backlog_panel(in_progress: &[TaskView], queued: &[TaskView]) -> String
}
if !queued.is_empty() {
- html.push_str(r#"<div class="section-label section-label-queued">Queued</div>"#);
+ html.push_str(r#"<div class="section-label section-label-queued"><span class="dot">●</span> Queued</div>"#);
html.push_str(r#"<div class="state-queued">"#);
for task in queued {
html.push_str(&render_task(task));
@@ -210,8 +211,9 @@ fn render_task(task: &TaskView) -> String {
html.push_str(r#"<div class="task">"#);
html.push_str(r#"<div class="task-header">"#);
html.push_str(&format!(
- r#"<span class="key">{}</span>"#,
- html_escape(&task.short_key)
+ r#"<span class="key"><span class="key-prefix">{}</span><span class="key-rest">{}</span></span>"#,
+ html_escape(&task.key_prefix),
+ html_escape(&task.key_rest)
));
html.push_str(&format!(
r#"<span class="title">{}</span>"#,
@@ -239,16 +241,9 @@ async fn to_task_views(
let mut views = Vec::with_capacity(tasks.len());
for task in tasks {
let prefix_len = prefixes.get(&task.key).copied().unwrap_or(8);
- let short_key = task.key[..8.min(task.key.len())].to_string();
- let short_key = format!(
- "{}{}",
- &short_key[..prefix_len.min(short_key.len())],
- if prefix_len < short_key.len() {
- &short_key[prefix_len..]
- } else {
- ""
- }
- );
+ let display_len = 8.min(task.key.len());
+ let key_prefix = task.key[..prefix_len.min(display_len)].to_string();
+ let key_rest = task.key[prefix_len.min(display_len)..display_len].to_string();
// Check for subtasks
let subtasks: Vec<Task> = sqlx::query_as(
@@ -267,7 +262,8 @@ async fn to_task_views(
.count();
views.push(TaskView {
- short_key,
+ key_prefix,
+ key_rest,
title: task.title.clone(),
description: task.description.clone(),
has_subtasks,
diff --git a/static/style.css b/static/style.css
index 77e771b..24d46a7 100644
--- a/static/style.css
+++ b/static/style.css
@@ -155,6 +155,10 @@ header .counts {
margin-top: 0;
}
+.section-label .dot {
+ margin-right: 4px;
+}
+
.section-label-in-progress { color: var(--color-in-progress); }
.section-label-queued { color: var(--color-queued); }
.section-label-done { color: var(--color-done); }
@@ -179,10 +183,19 @@ header .counts {
.task .key {
font-family: var(--font-mono);
font-size: var(--step-mono);
- color: var(--color-accent);
flex-shrink: 0;
}
+.task .key-prefix {
+ color: var(--color-accent);
+ font-weight: 700;
+}
+
+.task .key-rest {
+ color: var(--color-text-muted);
+ font-weight: 400;
+}
+
.task .title {
font-size: var(--step-0);
font-weight: 500;
@@ -216,6 +229,11 @@ header .counts {
opacity: 0.75;
}
+/* Icebox tasks slightly faded */
+.state-icebox .task {
+ opacity: 0.8;
+}
+
/* === Empty states === */
.empty {