From a3725fdeddbd77d8e5eef2cc97fa299b36bcb4be Mon Sep 17 00:00:00 2001 From: Evan Purkhiser Date: Tue, 20 Oct 2026 22:04:55 -0400 Subject: [PATCH 1/2] Render project mutations with iocraft --- src/commands/projects.rs | 84 +++++++++++++------ trycmd/projects/new_compatible_payload.trycmd | 3 +- 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/src/commands/projects.rs b/src/commands/projects.rs index 93efe0e..92f501e 100644 --- a/src/commands/projects.rs +++ b/src/commands/projects.rs @@ -7,11 +7,13 @@ use iocraft::prelude::*; use crate::{ app::Cli, commands::{Command, TagDeltaArgs, detailed_json_conflict, write_json}, - common::{ - DIM, GREEN, ICONS, colored, day_to_timestamp, parse_day, resolve_tag_ids, task6_note, - }, + common::{day_to_timestamp, parse_day, resolve_tag_ids, task6_note}, ids::ThingsId, + store::{ThingsStore, fold_item}, ui::{ + components::{ + mutation_result::MutationResult, project_item::ProjectItem, tasks::TaskOptions, + }, render_element_to_string, views::{ json::common::build_tasks_json, @@ -307,7 +309,8 @@ impl Command for ProjectsArgs { return Ok(()); } - let store = cli.load_store()?; + let mut state = cli.load_state()?; + let store = ThingsStore::from_raw_state(&state); let now = ctx.now_timestamp(); let mut props = TaskProps { title: title.to_string(), @@ -383,21 +386,40 @@ impl Command for ProjectsArgs { let mut changes = BTreeMap::new(); changes.insert(uuid.clone(), WireObject::create(EntityType::Task7, props)); + fold_item(changes.clone(), &mut state); if let Err(e) = ctx.commit_changes(changes, None) { eprintln!("Failed to create project: {e}"); return Ok(()); } - writeln!( - out, - "{} {} {}", - colored(format!("{} Created", ICONS.done), &[GREEN], cli.no_color), - title, - colored(&uuid, &[DIM], cli.no_color) - )?; + let store = Arc::new(ThingsStore::from_raw_state(&state)); + let project = store + .tasks_by_uuid + .get(&uuid.parse()?) + .cloned() + .ok_or_else(|| { + anyhow::anyhow!("created project missing from planned changes") + })?; + let options = TaskOptions { + show_area: true, + show_today_markers: true, + ..Default::default() + }; + let mut ui = element! { + ContextProvider(value: Context::owned(store)) { + ContextProvider(value: Context::owned(ctx.today())) { + MutationResult(action: "Created") { + ProjectItem(project: &project, options) + } + } + } + }; + let rendered = render_element_to_string(&mut ui, cli.no_color); + writeln!(out, "{rendered}")?; } Some(ProjectsSubcommand::Edit(args)) => { - let store = cli.load_store()?; + let mut state = cli.load_state()?; + let store = ThingsStore::from_raw_state(&state); let plan = match build_projects_edit_plan(args, &store, ctx.now_timestamp()) { Ok(plan) => plan, Err(err) => { @@ -411,24 +433,36 @@ impl Command for ProjectsArgs { plan.project.uuid.to_string(), WireObject::update(EntityType::Task7, plan.update.clone()), ); + fold_item(changes.clone(), &mut state); if let Err(e) = ctx.commit_changes(changes, None) { eprintln!("Failed to edit project: {e}"); return Ok(()); } - let title = plan.update.title.as_deref().unwrap_or(&plan.project.title); - writeln!( - out, - "{} {} {} {}", - colored(format!("{} Edited", ICONS.done), &[GREEN], cli.no_color), - title, - colored(&plan.project.uuid, &[DIM], cli.no_color), - colored( - format!("({})", plan.labels.join(", ")), - &[DIM], - cli.no_color - ) - )?; + let store = Arc::new(ThingsStore::from_raw_state(&state)); + let project = store + .tasks_by_uuid + .get(&plan.project.uuid) + .cloned() + .ok_or_else(|| { + anyhow::anyhow!("edited project missing from planned changes") + })?; + let options = TaskOptions { + show_area: true, + show_today_markers: true, + ..Default::default() + }; + let mut ui = element! { + ContextProvider(value: Context::owned(store)) { + ContextProvider(value: Context::owned(ctx.today())) { + MutationResult(action: "Edited", detail: Some(plan.labels.join(", "))) { + ProjectItem(project: &project, options) + } + } + } + }; + let rendered = render_element_to_string(&mut ui, cli.no_color); + writeln!(out, "{rendered}")?; } } Ok(()) diff --git a/trycmd/projects/new_compatible_payload.trycmd b/trycmd/projects/new_compatible_payload.trycmd index 377f8eb..d34ae90 100644 --- a/trycmd/projects/new_compatible_payload.trycmd +++ b/trycmd/projects/new_compatible_payload.trycmd @@ -1,6 +1,7 @@ ```trycmd $ run.sh things3 --today-ts 1700000000 --now-ts 1700000000.0 projects new 'HV-MRF-01' -✓ Created HV-MRF-01 [..] +✓ Created +◯ HV-MRF-01 { "ancestor_index": 0, "changes": { From 29387d1000dec989cc1f027b54b549e0c5aca357 Mon Sep 17 00:00:00 2001 From: Evan Purkhiser Date: Tue, 20 Oct 2026 22:31:29 -0400 Subject: [PATCH 2/2] Show the created project short ID --- src/commands/projects.rs | 3 ++- trycmd/projects/new_compatible_payload.trycmd | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/commands/projects.rs b/src/commands/projects.rs index 92f501e..6d42f54 100644 --- a/src/commands/projects.rs +++ b/src/commands/projects.rs @@ -400,6 +400,7 @@ impl Command for ProjectsArgs { .ok_or_else(|| { anyhow::anyhow!("created project missing from planned changes") })?; + let id_prefix_len = store.short_id(&project.uuid).len(); let options = TaskOptions { show_area: true, show_today_markers: true, @@ -409,7 +410,7 @@ impl Command for ProjectsArgs { ContextProvider(value: Context::owned(store)) { ContextProvider(value: Context::owned(ctx.today())) { MutationResult(action: "Created") { - ProjectItem(project: &project, options) + ProjectItem(project: &project, options, id_prefix_len) } } } diff --git a/trycmd/projects/new_compatible_payload.trycmd b/trycmd/projects/new_compatible_payload.trycmd index d34ae90..79495cf 100644 --- a/trycmd/projects/new_compatible_payload.trycmd +++ b/trycmd/projects/new_compatible_payload.trycmd @@ -1,7 +1,7 @@ ```trycmd $ run.sh things3 --today-ts 1700000000 --now-ts 1700000000.0 projects new 'HV-MRF-01' ✓ Created -◯ HV-MRF-01 +[..] ◯ HV-MRF-01 { "ancestor_index": 0, "changes": {