Detailed changes
@@ -112,6 +112,10 @@ pub enum Command {
/// Set description
#[arg(short = 'd', long = "desc")]
desc: Option<String>,
+
+ /// Set task type (e.g. task, bug, feature)
+ #[arg(long = "type")]
+ task_type: Option<String>,
},
/// Mark task(s) as closed
@@ -94,6 +94,7 @@ pub fn dispatch(cli: &Cli) -> Result<()> {
effort,
title,
desc,
+ task_type,
} => {
let root = require_root()?;
let pri = priority.as_deref().map(Priority::parse).transpose()?;
@@ -107,6 +108,7 @@ pub fn dispatch(cli: &Cli) -> Result<()> {
effort: eff,
title: title.as_deref(),
desc: desc.as_deref(),
+ task_type: task_type.as_deref(),
json: cli.json,
},
)
@@ -12,6 +12,7 @@ pub struct Opts<'a> {
pub effort: Option<Effort>,
pub title: Option<&'a str>,
pub desc: Option<&'a str>,
+ pub task_type: Option<&'a str>,
pub json: bool,
}
@@ -30,6 +31,7 @@ pub fn run(root: &Path, id: &str, opts: Opts) -> Result<()> {
&& opts.effort.is_none()
&& opts.title.is_none()
&& opts.desc.is_none()
+ && opts.task_type.is_none()
{
let interactive = std::env::var("TD_FORCE_EDITOR").is_ok()
|| std::io::IsTerminal::is_terminal(&std::io::stdin());
@@ -73,6 +75,7 @@ pub fn run(root: &Path, id: &str, opts: Opts) -> Result<()> {
effort: opts.effort,
title: title_override.map(String::from),
description: desc_override.map(String::from),
+ task_type: opts.task_type.map(String::from),
},
)?;
@@ -60,6 +60,8 @@ pub(in crate::cmd::webui) struct UpdateForm {
#[serde(default)]
effort: Option<String>,
#[serde(default)]
+ task_type: Option<String>,
+ #[serde(default)]
redirect: Option<String>,
}
@@ -197,6 +199,7 @@ pub(in crate::cmd::webui) async fn update_handler(
.transpose()?,
title: form.title.filter(|s| !s.is_empty()),
description: form.description,
+ task_type: form.task_type.filter(|s| !s.is_empty()),
},
)?;
@@ -83,6 +83,7 @@ pub struct UpdateOpts {
pub effort: Option<Effort>,
pub title: Option<String>,
pub description: Option<String>,
+ pub task_type: Option<String>,
}
/// Update task fields and return the refreshed task.
@@ -108,6 +109,9 @@ pub fn update_task(store: &Store, task_id: &TaskId, opts: UpdateOpts) -> Result<
if let Some(ref d) = opts.description {
task.insert("description", d.as_str())?;
}
+ if let Some(ref tt) = opts.task_type {
+ task.insert("type", tt.as_str())?;
+ }
task.insert("updated_at", ts.clone())?;
Ok(())
})?;
@@ -129,6 +129,25 @@ fn update_changes_effort() {
assert_eq!(t["effort"].as_str().unwrap(), "high");
}
+#[test]
+fn update_changes_task_type() {
+ let tmp = init_tmp();
+ let id = create_task(&tmp, "Reclassify me");
+
+ // Default type is "task"
+ let t = get_task_json(&tmp, &id);
+ assert_eq!(t["type"].as_str().unwrap(), "task");
+
+ td(&tmp)
+ .args(["update", &id, "--type", "bug"])
+ .current_dir(&tmp)
+ .assert()
+ .success();
+
+ let t = get_task_json(&tmp, &id);
+ assert_eq!(t["type"].as_str().unwrap(), "bug");
+}
+
// ── done ─────────────────────────────────────────────────────────────
#[test]