rename queued state to ready
Migration 012 updates existing rows from 'queued' to 'ready'.
Migration 013 recreates the tasks table with a CHECK constraint
for the new set of valid states (icebox, ready, in_progress, done).

Enum variant renamed from Queued to Ready, as_str returns 'ready'.
Parsing still accepts 'queued' for backward compatibility. CSS
classes and web UI updated to match.
change utmlrwsspqnknzloyvqqqvrymmozrwqy
commit 32520926e89db927ce4b3a6e1ede3a61e2ecb854
author Alpha Chen <alpha@kejadlen.dev>
date
parent slomzpyw
diff --git a/migrations/012_rename_queued_to_ready.sql b/migrations/012_rename_queued_to_ready.sql
new file mode 100644
index 0000000..2d29052
--- /dev/null
+++ b/migrations/012_rename_queued_to_ready.sql
@@ -0,0 +1 @@
+UPDATE tasks SET state = 'ready' WHERE state = 'queued';
diff --git a/migrations/013_add_state_check.sql b/migrations/013_add_state_check.sql
new file mode 100644
index 0000000..7442be9
--- /dev/null
+++ b/migrations/013_add_state_check.sql
@@ -0,0 +1,27 @@
+-- Re-add CHECK constraint for valid state values (now includes 'ready').
+PRAGMA foreign_keys = OFF;
+
+CREATE TABLE tasks_new (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    key TEXT NOT NULL UNIQUE,
+    backlog_id INTEGER NOT NULL REFERENCES backlogs(id) ON DELETE CASCADE,
+    title TEXT NOT NULL,
+    description TEXT,
+    state TEXT NOT NULL DEFAULT 'icebox'
+        CHECK (state IN ('icebox', 'ready', 'in_progress', 'done')),
+    position TEXT NOT NULL,
+    archived INTEGER NOT NULL DEFAULT 0,
+    created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
+    updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
+    done_at TEXT,
+    CHECK ((state = 'done') = (done_at IS NOT NULL))
+);
+
+INSERT INTO tasks_new (id, key, backlog_id, title, description, state, position, archived, created_at, updated_at, done_at)
+SELECT id, key, backlog_id, title, description, state, position, archived, created_at, updated_at, done_at
+FROM tasks;
+
+DROP TABLE tasks;
+ALTER TABLE tasks_new RENAME TO tasks;
+
+PRAGMA foreign_keys = ON;
diff --git a/src/bin/ranger/commands/backlog.rs b/src/bin/ranger/commands/backlog.rs
index a611656..2842ee5 100644
--- a/src/bin/ranger/commands/backlog.rs
+++ b/src/bin/ranger/commands/backlog.rs
@@ -74,7 +74,7 @@ pub async fn run(pool: &SqlitePool, command: BacklogCommands, json: bool) -> Res
             let states: Vec<State> = if done {
                 vec![State::Done]
             } else {
-                vec![State::InProgress, State::Queued, State::Icebox]
+                vec![State::InProgress, State::Ready, State::Icebox]
             };
 
             if json {
diff --git a/src/bin/ranger/commands/serve.rs b/src/bin/ranger/commands/serve.rs
index 703a481..8320255 100644
--- a/src/bin/ranger/commands/serve.rs
+++ b/src/bin/ranger/commands/serve.rs
@@ -104,13 +104,13 @@ async fn render_board(state: &AppState, backlog_name: &str) -> color_eyre::Resul
     let prefixes = key::unique_prefix_lengths(&all_keys);
 
     let mut in_progress = Vec::new();
-    let mut queued = Vec::new();
+    let mut ready = Vec::new();
     let mut icebox = Vec::new();
     let mut done = Vec::new();
 
     for s in [
         ranger::models::State::InProgress,
-        ranger::models::State::Queued,
+        ranger::models::State::Ready,
         ranger::models::State::Icebox,
         ranger::models::State::Done,
     ] {
@@ -122,14 +122,14 @@ async fn render_board(state: &AppState, backlog_name: &str) -> color_eyre::Resul
         let views = to_task_views(&tasks, &prefixes, &mut conn).await?;
         match s {
             ranger::models::State::InProgress => in_progress = views,
-            ranger::models::State::Queued => queued = views,
+            ranger::models::State::Ready => ready = views,
             ranger::models::State::Icebox => icebox = views,
             ranger::models::State::Done => done = views,
         }
     }
 
-    let total = in_progress.len() + queued.len() + icebox.len() + done.len();
-    let active = in_progress.len() + queued.len();
+    let total = in_progress.len() + ready.len() + icebox.len() + done.len();
+    let active = in_progress.len() + ready.len();
 
     Ok(html! {
         (DOCTYPE)
@@ -170,7 +170,7 @@ async fn render_board(state: &AppState, backlog_name: &str) -> color_eyre::Resul
                     div.counts { (active) " active · " (total) " total" }
                 }
                 div.board {
-                    (render_backlog_panel(&in_progress, &queued))
+                    (render_backlog_panel(&in_progress, &ready))
                     (render_column_panel("Icebox", "state-icebox", &icebox))
                     (render_column_panel("Done", "state-done", &done))
                 }
@@ -180,15 +180,15 @@ async fn render_board(state: &AppState, backlog_name: &str) -> color_eyre::Resul
     })
 }
 
-fn render_backlog_panel(in_progress: &[TaskView], queued: &[TaskView]) -> Markup {
-    let count = in_progress.len() + queued.len();
+fn render_backlog_panel(in_progress: &[TaskView], ready: &[TaskView]) -> Markup {
+    let count = in_progress.len() + ready.len();
     html! {
         div.panel {
             div.panel-header {
                 h2 { "Backlog" }
                 span.count { (count) }
             }
-            @if in_progress.is_empty() && queued.is_empty() {
+            @if in_progress.is_empty() && ready.is_empty() {
                 div.empty { "No active tasks" }
             } @else {
                 @if !in_progress.is_empty() {
@@ -198,9 +198,9 @@ fn render_backlog_panel(in_progress: &[TaskView], queued: &[TaskView]) -> Markup
                         }
                     }
                 }
-                @if !queued.is_empty() {
-                    div.state-queued {
-                        @for task in queued {
+                @if !ready.is_empty() {
+                    div.state-ready {
+                        @for task in ready {
                             (render_task(task))
                         }
                     }
diff --git a/src/models.rs b/src/models.rs
index 3a73072..45426c2 100644
--- a/src/models.rs
+++ b/src/models.rs
@@ -7,7 +7,7 @@ use crate::timestamp::Timestamp;
 #[serde(rename_all = "snake_case")]
 pub enum State {
     Icebox,
-    Queued,
+    Ready,
     InProgress,
     Done,
 }
@@ -16,7 +16,7 @@ impl State {
     pub fn as_str(&self) -> &'static str {
         match self {
             State::Icebox => "icebox",
-            State::Queued => "queued",
+            State::Ready => "ready",
             State::InProgress => "in_progress",
             State::Done => "done",
         }
@@ -26,7 +26,7 @@ impl State {
     pub fn rank(&self) -> u8 {
         match self {
             State::Icebox => 0,
-            State::Queued => 1,
+            State::Ready => 1,
             State::InProgress => 2,
             State::Done => 3,
         }
@@ -42,7 +42,7 @@ impl std::str::FromStr for State {
     fn from_str(s: &str) -> Result<Self, Self::Err> {
         match s {
             "icebox" => Ok(State::Icebox),
-            "queued" | "ready" => Ok(State::Queued),
+            "ready" | "queued" => Ok(State::Ready),
             "in_progress" => Ok(State::InProgress),
             "done" => Ok(State::Done),
             _ => Err(InvalidStateError(s.to_string())),
@@ -123,7 +123,7 @@ mod tests {
     fn state_roundtrips_through_display_and_parse() {
         for (state, expected) in [
             (State::Icebox, "icebox"),
-            (State::Queued, "queued"),
+            (State::Ready, "ready"),
             (State::InProgress, "in_progress"),
             (State::Done, "done"),
         ] {
@@ -135,9 +135,9 @@ mod tests {
     }
 
     #[test]
-    fn ready_parses_as_queued() {
-        let parsed: State = "ready".parse().unwrap();
-        assert_eq!(parsed, State::Queued);
+    fn queued_parses_as_ready() {
+        let parsed: State = "queued".parse().unwrap();
+        assert_eq!(parsed, State::Ready);
     }
 
     #[test]
diff --git a/src/ops/task.rs b/src/ops/task.rs
index 31c774b..8ca48fa 100644
--- a/src/ops/task.rs
+++ b/src/ops/task.rs
@@ -597,7 +597,7 @@ mod tests {
             CreateTask {
                 title: "Queued task",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -621,7 +621,7 @@ mod tests {
             &mut conn,
             bl.id,
             &ListFilter {
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 ..Default::default()
             },
         )
@@ -676,14 +676,14 @@ mod tests {
             task.id,
             Some("Updated"),
             Some("A description"),
-            Some(State::Queued),
+            Some(State::Ready),
         )
         .await
         .unwrap();
 
         assert_eq!(updated.title, "Updated");
         assert_eq!(updated.description.as_deref(), Some("A description"));
-        assert_eq!(updated.state, State::Queued);
+        assert_eq!(updated.state, State::Ready);
     }
 
     #[tokio::test]
@@ -1027,7 +1027,7 @@ mod tests {
             CreateTask {
                 title: "Q",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1048,7 +1048,7 @@ mod tests {
         let err = move_task(&mut conn, &queued, Placement::Before(&done))
             .await
             .unwrap_err();
-        assert!(err.to_string().contains("queued"));
+        assert!(err.to_string().contains("ready"));
         assert!(err.to_string().contains("done"));
     }
 
@@ -1086,7 +1086,7 @@ mod tests {
             CreateTask {
                 title: "Queued 1",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1130,7 +1130,7 @@ mod tests {
             CreateTask {
                 title: "Queued 1",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1141,7 +1141,7 @@ mod tests {
             CreateTask {
                 title: "Queued 2",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1160,16 +1160,16 @@ mod tests {
         .unwrap();
 
         // Move in_progress task to queued — should land before Queued 1
-        let updated = edit(&mut conn, ip.id, None, None, Some(State::Queued))
+        let updated = edit(&mut conn, ip.id, None, None, Some(State::Ready))
             .await
             .unwrap();
-        assert_eq!(updated.state, State::Queued);
+        assert_eq!(updated.state, State::Ready);
 
         let queued = list(
             &mut conn,
             bl.id,
             &ListFilter {
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 ..Default::default()
             },
         )
@@ -1195,7 +1195,7 @@ mod tests {
             CreateTask {
                 title: "First",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1206,7 +1206,7 @@ mod tests {
             CreateTask {
                 title: "Second",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1216,7 +1216,7 @@ mod tests {
         let original_pos = t1.position.clone();
 
         // Edit to same state — position should not change
-        let updated = edit(&mut conn, t1.id, None, None, Some(State::Queued))
+        let updated = edit(&mut conn, t1.id, None, None, Some(State::Ready))
             .await
             .unwrap();
         assert_eq!(updated.position, original_pos);
@@ -1225,7 +1225,7 @@ mod tests {
             &mut conn,
             bl.id,
             &ListFilter {
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 ..Default::default()
             },
         )
@@ -1246,7 +1246,7 @@ mod tests {
             CreateTask {
                 title: "Queued task",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1484,7 +1484,7 @@ mod tests {
             CreateTask {
                 title: "Tagged queued",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1495,7 +1495,7 @@ mod tests {
             CreateTask {
                 title: "Untagged queued",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1508,7 +1508,7 @@ mod tests {
             &mut conn,
             bl.id,
             &ListFilter {
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 tag: Some("bug".to_string()),
                 ..Default::default()
             },
@@ -1553,7 +1553,7 @@ mod tests {
             CreateTask {
                 title: "Queued task",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1577,7 +1577,7 @@ mod tests {
             CreateTask {
                 title: "Task",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1613,7 +1613,7 @@ mod tests {
         .unwrap();
         assert!(task.done_at.is_some());
 
-        let updated = edit(&mut conn, task.id, None, None, Some(State::Queued))
+        let updated = edit(&mut conn, task.id, None, None, Some(State::Ready))
             .await
             .unwrap();
         assert!(
@@ -1634,7 +1634,7 @@ mod tests {
             CreateTask {
                 title: "First done",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1645,7 +1645,7 @@ mod tests {
             CreateTask {
                 title: "Second done",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
@@ -1656,7 +1656,7 @@ mod tests {
             CreateTask {
                 title: "Third done",
                 backlog_id: bl.id,
-                state: Some(State::Queued),
+                state: Some(State::Ready),
                 description: None,
             },
         )
diff --git a/static/style.css b/static/style.css
index 53d432b..72c57b7 100644
--- a/static/style.css
+++ b/static/style.css
@@ -24,13 +24,13 @@
 
   /* ── Color tokens: state ── */
   --color-in-progress: #629a3e;
-  --color-queued:      #3d6098;
+  --color-ready:      #3d6098;
   --color-done:        #8ba83b;
   --color-icebox:      #8a8878;
 
   /* ── State row tints (12 % opacity) ── */
   --tint-in-progress:  rgba(210, 170, 30, 0.12);
-  --tint-queued:       rgba(160, 160, 160, 0.12);
+  --tint-ready:       rgba(160, 160, 160, 0.12);
   --tint-done:         rgba(98, 154, 62, 0.24);
   --tint-icebox:       rgba(61, 96, 152, 0.20);
 
@@ -237,7 +237,7 @@ header .counts {
 }
 
 .section-label-in-progress { color: var(--color-in-progress); }
-.section-label-queued      { color: var(--color-queued); }
+.section-label-ready      { color: var(--color-ready); }
 .section-label-done        { color: var(--color-done); }
 .section-label-icebox      { color: var(--color-icebox); }
 
@@ -384,7 +384,7 @@ details.task[open] > summary .expand-icon {
 
 /* === State-specific row tints === */
 .state-in-progress .task { background: #fdf6e3; }
-.state-queued .task      { background: #f5f5f3; }
+.state-ready .task      { background: #f5f5f3; }
 .state-done .task        { background: #eef5e6; }
 .state-icebox .task      { background: #e8eef6; }
 
diff --git a/tests/cli.rs b/tests/cli.rs
index d238423..6717270 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -259,7 +259,7 @@ fn full_workflow() {
     // Should show task state sections
     assert!(
         stdout.contains("[in_progress]")
-            || stdout.contains("[queued]")
+            || stdout.contains("[ready]")
             || stdout.contains("[icebox]")
     );
 
@@ -333,10 +333,7 @@ fn full_workflow() {
         !stdout.contains("[in_progress]"),
         "--done should not show in_progress"
     );
-    assert!(
-        !stdout.contains("[queued]"),
-        "--done should not show queued"
-    );
+    assert!(!stdout.contains("[ready]"), "--done should not show ready");
     assert!(
         !stdout.contains("[icebox]"),
         "--done should not show icebox"
@@ -350,7 +347,7 @@ fn full_workflow() {
     assert!(output.status.success());
     let detail: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
     assert!(detail["tasks"]["done"].is_array());
-    assert!(detail["tasks"]["queued"].is_null());
+    assert!(detail["tasks"]["ready"].is_null());
     assert!(detail["tasks"]["in_progress"].is_null());
 
     // Backlog show JSON without --done excludes done tasks