1use std::{cmp::Reverse, collections::BTreeMap};
2
3use anyhow::Result;
4use chrono::{TimeZone, Utc};
5use clap::Args;
6use serde_json::json;
7
8use crate::{
9 app::Cli,
10 commands::Command,
11 common::{
12 DIM,
13 GREEN,
14 ICONS,
15 colored,
16 day_to_timestamp,
17 parse_day,
18 resolve_tag_ids,
19 task6_note,
20 },
21 store::Task,
22 wire::{
23 task::{TaskProps, TaskStart, TaskStatus, TaskType},
24 wire_object::{EntityType, WireObject},
25 },
26};
27
28#[derive(Debug, Args)]
29#[command(about = "Create a new task")]
30pub struct NewArgs {
31 pub title: String,
33 #[arg(
34 long = "in",
35 default_value = "inbox",
36 help = "Container: inbox, clear, project UUID/prefix, or area UUID/prefix"
37 )]
38 pub in_target: String,
39 #[arg(
40 long,
41 help = "Schedule: anytime, someday, today, evening, or YYYY-MM-DD"
42 )]
43 pub when: Option<String>,
44 #[arg(long = "before", help = "Insert before this sibling task UUID/prefix")]
45 pub before_id: Option<String>,
46 #[arg(long = "after", help = "Insert after this sibling task UUID/prefix")]
47 pub after_id: Option<String>,
48 #[arg(long, default_value = "", help = "Task notes")]
49 pub notes: String,
50 #[arg(long, help = "Comma-separated tags (titles or UUID prefixes)")]
51 pub tags: Option<String>,
52 #[arg(long = "deadline", help = "Deadline date (YYYY-MM-DD)")]
53 pub deadline_date: Option<String>,
54}
55
56fn base_new_props(title: &str, now: f64) -> TaskProps {
57 TaskProps {
58 title: title.to_string(),
59 item_type: TaskType::Todo,
60 status: TaskStatus::Incomplete,
61 start_location: TaskStart::Inbox,
62 creation_date: Some(now),
63 modification_date: Some(now),
64 conflict_overrides: Some(json!({"_t": "oo", "sn": {}})),
65 ..Default::default()
66 }
67}
68
69fn task_bucket(task: &Task, store: &crate::store::ThingsStore) -> Vec<String> {
70 if task.is_heading() {
71 return vec![
72 "heading".to_string(),
73 task.project
74 .clone()
75 .map(|v| v.to_string())
76 .unwrap_or_default(),
77 ];
78 }
79 if task.is_project() {
80 return vec![
81 "project".to_string(),
82 task.area.clone().map(|v| v.to_string()).unwrap_or_default(),
83 ];
84 }
85 if let Some(project_uuid) = store.effective_project_uuid(task) {
86 return vec![
87 "task-project".to_string(),
88 project_uuid.to_string(),
89 task.action_group
90 .clone()
91 .map(|v| v.to_string())
92 .unwrap_or_default(),
93 ];
94 }
95 if let Some(area_uuid) = store.effective_area_uuid(task) {
96 return vec![
97 "task-area".to_string(),
98 area_uuid.to_string(),
99 i32::from(task.start).to_string(),
100 ];
101 }
102 vec!["task-root".to_string(), i32::from(task.start).to_string()]
103}
104
105fn props_bucket(props: &TaskProps) -> Vec<String> {
106 if let Some(project_uuid) = props.parent_project_ids.first() {
107 return vec![
108 "task-project".to_string(),
109 project_uuid.to_string(),
110 String::new(),
111 ];
112 }
113 if let Some(area_uuid) = props.area_ids.first() {
114 let st = i32::from(props.start_location);
115 return vec![
116 "task-area".to_string(),
117 area_uuid.to_string(),
118 st.to_string(),
119 ];
120 }
121 let st = i32::from(props.start_location);
122 vec!["task-root".to_string(), st.to_string()]
123}
124
125fn plan_ix_insert(ordered: &[Task], insert_at: usize) -> (i32, Vec<(String, i32, String)>) {
126 let prev_ix = if insert_at > 0 {
127 Some(ordered[insert_at - 1].index)
128 } else {
129 None
130 };
131 let next_ix = if insert_at < ordered.len() {
132 Some(ordered[insert_at].index)
133 } else {
134 None
135 };
136 let mut updates = Vec::new();
137
138 if prev_ix.is_none() && next_ix.is_none() {
139 return (0, updates);
140 }
141 if prev_ix.is_none() {
142 return (next_ix.unwrap_or(0) - 1, updates);
143 }
144 if next_ix.is_none() {
145 return (prev_ix.unwrap_or(0) + 1, updates);
146 }
147 if prev_ix.unwrap_or(0) + 1 < next_ix.unwrap_or(0) {
148 return ((prev_ix.unwrap_or(0) + next_ix.unwrap_or(0)) / 2, updates);
149 }
150
151 let stride = 1024;
152 let mut new_index = stride;
153 let mut idx = 1;
154 for i in 0..=ordered.len() {
155 let target_ix = idx * stride;
156 if i == insert_at {
157 new_index = target_ix;
158 idx += 1;
159 continue;
160 }
161 let source_idx = if i < insert_at { i } else { i - 1 };
162 if source_idx < ordered.len() {
163 let entry = &ordered[source_idx];
164 if entry.index != target_ix {
165 updates.push((entry.uuid.to_string(), target_ix, entry.entity.clone()));
166 }
167 idx += 1;
168 }
169 }
170 (new_index, updates)
171}
172
173#[derive(Debug, Clone)]
174struct NewPlan {
175 new_uuid: String,
176 changes: BTreeMap<String, WireObject>,
177 title: String,
178}
179
180fn build_new_plan(
181 args: &NewArgs,
182 store: &crate::store::ThingsStore,
183 now: f64,
184 today_ts: i64,
185 next_id: &mut dyn FnMut() -> String,
186) -> std::result::Result<NewPlan, String> {
187 let today = Utc
188 .timestamp_opt(today_ts, 0)
189 .single()
190 .unwrap_or_else(Utc::now)
191 .date_naive()
192 .and_hms_opt(0, 0, 0)
193 .map(|d| Utc.from_utc_datetime(&d))
194 .unwrap_or_else(Utc::now);
195 let title = args.title.trim();
196 if title.is_empty() {
197 return Err("Task title cannot be empty.".to_string());
198 }
199
200 let mut props = base_new_props(title, now);
201 if !args.notes.is_empty() {
202 props.notes = Some(task6_note(&args.notes));
203 }
204
205 let anchor_id = args.before_id.as_ref().or(args.after_id.as_ref());
206 let mut anchor: Option<Task> = None;
207 if let Some(anchor_id) = anchor_id {
208 let (task, err, _ambiguous) = store.resolve_task_identifier(anchor_id);
209 if task.is_none() {
210 return Err(err);
211 }
212 anchor = task;
213 }
214
215 let in_target = args.in_target.trim();
216 if !in_target.eq_ignore_ascii_case("inbox") {
217 let (project, _, _) = store.resolve_mark_identifier(in_target);
218 let (area, _, _) = store.resolve_area_identifier(in_target);
219 let project_uuid = project.as_ref().and_then(|p| {
220 if p.is_project() {
221 Some(p.uuid.clone())
222 } else {
223 None
224 }
225 });
226 let area_uuid = area.map(|a| a.uuid);
227
228 if project_uuid.is_some() && area_uuid.is_some() {
229 return Err(format!(
230 "Ambiguous --in target '{}' (matches project and area).",
231 in_target
232 ));
233 }
234
235 if project.is_some() && project_uuid.is_none() {
236 return Err("--in target must be inbox, a project ID, or an area ID.".to_string());
237 }
238
239 if let Some(project_uuid) = project_uuid {
240 props.parent_project_ids = vec![project_uuid.into()];
241 props.start_location = TaskStart::Anytime;
242 } else if let Some(area_uuid) = area_uuid {
243 props.area_ids = vec![area_uuid.into()];
244 props.start_location = TaskStart::Anytime;
245 } else {
246 return Err(format!("Container not found: {}", in_target));
247 }
248 }
249
250 if let Some(when_raw) = &args.when {
251 let when = when_raw.trim();
252 if when.eq_ignore_ascii_case("anytime") {
253 props.start_location = TaskStart::Anytime;
254 props.scheduled_date = None;
255 } else if when.eq_ignore_ascii_case("someday") {
256 props.start_location = TaskStart::Someday;
257 props.scheduled_date = None;
258 } else if when.eq_ignore_ascii_case("today") {
259 props.start_location = TaskStart::Anytime;
260 props.scheduled_date = Some(today_ts);
261 props.today_index_reference = Some(today_ts);
262 } else {
263 let parsed = match parse_day(Some(when), "--when") {
264 Ok(Some(day)) => day,
265 Ok(None) => {
266 return Err(
267 "--when requires anytime, someday, today, or YYYY-MM-DD".to_string()
268 );
269 }
270 Err(err) => return Err(err),
271 };
272 let day_ts = day_to_timestamp(parsed);
273 props.start_location = TaskStart::Someday;
274 props.scheduled_date = Some(day_ts);
275 props.today_index_reference = Some(day_ts);
276 }
277 }
278
279 if let Some(tags) = &args.tags {
280 let (tag_ids, tag_err) = resolve_tag_ids(store, tags);
281 if !tag_err.is_empty() {
282 return Err(tag_err);
283 }
284 props.tag_ids = tag_ids;
285 }
286
287 if let Some(deadline_date) = &args.deadline_date {
288 let parsed = match parse_day(Some(deadline_date), "--deadline") {
289 Ok(Some(day)) => day,
290 Ok(None) => return Err("--deadline requires YYYY-MM-DD".to_string()),
291 Err(err) => return Err(err),
292 };
293 props.deadline = Some(day_to_timestamp(parsed) as i64);
294 }
295
296 let anchor_is_today = anchor
297 .as_ref()
298 .map(|a| a.start == TaskStart::Anytime && (a.is_today(&today) || a.evening))
299 .unwrap_or(false);
300 let target_bucket = props_bucket(&props);
301
302 if let Some(anchor) = &anchor
303 && !anchor_is_today
304 && task_bucket(anchor, store) != target_bucket
305 {
306 return Err(
307 "Cannot place new task relative to an item in a different container/list.".to_string(),
308 );
309 }
310
311 let mut index_updates: Vec<(String, i32, String)> = Vec::new();
312 let mut siblings = store
313 .tasks_by_uuid
314 .values()
315 .filter(|t| {
316 !t.trashed
317 && t.status == TaskStatus::Incomplete
318 && task_bucket(t, store) == target_bucket
319 })
320 .cloned()
321 .collect::<Vec<_>>();
322 siblings.sort_by_key(|t| (t.index, t.uuid.clone()));
323
324 let mut structural_insert_at = 0usize;
325 if let Some(anchor) = &anchor
326 && task_bucket(anchor, store) == target_bucket
327 {
328 let anchor_pos = siblings.iter().position(|t| t.uuid == anchor.uuid);
329 let Some(anchor_pos) = anchor_pos else {
330 return Err("Anchor not found in target list.".to_string());
331 };
332 structural_insert_at = if args.before_id.is_some() {
333 anchor_pos
334 } else {
335 anchor_pos + 1
336 };
337 }
338
339 let (structural_ix, structural_updates) = plan_ix_insert(&siblings, structural_insert_at);
340 props.sort_index = structural_ix;
341 index_updates.extend(structural_updates);
342
343 let new_is_today = props.start_location == TaskStart::Anytime
344 && props.scheduled_date.map_or(false, |sr| sr <= today_ts);
345 if new_is_today && anchor_is_today {
346 let mut section_evening = if props.evening_bit != 0 { 1 } else { 0 };
347
348 if anchor_is_today && let Some(anchor) = &anchor {
349 section_evening = if anchor.evening { 1 } else { 0 };
350 props.evening_bit = section_evening;
351 }
352
353 let mut today_siblings = store
354 .tasks_by_uuid
355 .values()
356 .filter(|t| {
357 !t.trashed
358 && t.status == TaskStatus::Incomplete
359 && t.start == TaskStart::Anytime
360 && (t.is_today(&today) || t.evening)
361 && (if t.evening { 1 } else { 0 }) == section_evening
362 })
363 .cloned()
364 .collect::<Vec<_>>();
365 today_siblings.sort_by_key(|task| {
366 let tir = task.today_index_reference.unwrap_or(0);
367 (Reverse(tir), task.today_index, Reverse(task.index))
368 });
369
370 let mut today_insert_at = 0usize;
371 if anchor_is_today
372 && let Some(anchor) = &anchor
373 && (if anchor.evening { 1 } else { 0 }) == section_evening
374 && let Some(anchor_pos) = today_siblings.iter().position(|t| t.uuid == anchor.uuid)
375 {
376 today_insert_at = if args.before_id.is_some() {
377 anchor_pos
378 } else {
379 anchor_pos + 1
380 };
381 }
382
383 let prev_today = if today_insert_at > 0 {
384 today_siblings.get(today_insert_at - 1)
385 } else {
386 None
387 };
388 let next_today = today_siblings.get(today_insert_at);
389
390 if let Some(next_today) = next_today {
391 let next_tir = next_today.today_index_reference.unwrap_or(today_ts);
392 props.today_index_reference = Some(next_tir);
393 props.today_sort_index = next_today.today_index - 1;
394 } else if let Some(prev_today) = prev_today {
395 let prev_tir = prev_today.today_index_reference.unwrap_or(today_ts);
396 props.today_index_reference = Some(prev_tir);
397 props.today_sort_index = prev_today.today_index + 1;
398 } else {
399 props.today_index_reference = Some(today_ts);
400 props.today_sort_index = 0;
401 }
402 }
403
404 let new_uuid = next_id();
405
406 let mut changes = BTreeMap::new();
407 changes.insert(
408 new_uuid.clone(),
409 WireObject::create(EntityType::Task6, props.clone()),
410 );
411
412 for (task_uuid, task_index, task_entity) in index_updates {
413 use crate::wire::task::TaskPatch;
414 changes.insert(
415 task_uuid,
416 WireObject::update(
417 EntityType::from(task_entity),
418 TaskPatch {
419 sort_index: Some(task_index),
420 modification_date: Some(now),
421 ..Default::default()
422 },
423 ),
424 );
425 }
426
427 Ok(NewPlan {
428 new_uuid,
429 changes,
430 title: title.to_string(),
431 })
432}
433
434impl Command for NewArgs {
435 fn run_with_ctx(
436 &self,
437 cli: &Cli,
438 out: &mut dyn std::io::Write,
439 ctx: &mut dyn crate::cmd_ctx::CmdCtx,
440 ) -> Result<()> {
441 let store = cli.load_store()?;
442 let now = ctx.now_timestamp();
443 let today = ctx.today_timestamp();
444 let mut id_gen = || ctx.next_id();
445 let plan = match build_new_plan(self, &store, now, today, &mut id_gen) {
446 Ok(plan) => plan,
447 Err(err) => {
448 eprintln!("{err}");
449 return Ok(());
450 }
451 };
452
453 if let Err(e) = ctx.commit_changes(plan.changes, None) {
454 eprintln!("Failed to create task: {e}");
455 return Ok(());
456 }
457
458 writeln!(
459 out,
460 "{} {} {}",
461 colored(&format!("{} Created", ICONS.done), &[GREEN], cli.no_color),
462 plan.title,
463 colored(&plan.new_uuid, &[DIM], cli.no_color)
464 )?;
465 Ok(())
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use serde_json::json;
472
473 use super::*;
474 use crate::{
475 store::{ThingsStore, fold_items},
476 wire::{
477 area::AreaProps,
478 tags::TagProps,
479 task::{TaskProps, TaskStart, TaskStatus, TaskType},
480 },
481 };
482
483 const NOW: f64 = 1_700_000_000.0;
484 const NEW_UUID: &str = "MpkEei6ybkFS2n6SXvwfLf";
485 const INBOX_ANCHOR_UUID: &str = "A7h5eCi24RvAWKC3Hv3muf";
486 const INBOX_OTHER_UUID: &str = "KGvAPpMrzHAKMdgMiERP1V";
487 const PROJECT_UUID: &str = "JFdhhhp37fpryAKu8UXwzK";
488 const AREA_UUID: &str = "74rgJf6Qh9wYp2TcVk8mNB";
489 const TAG_A_UUID: &str = "By8mN2qRk5Wv7Xc9Dt3HpL";
490 const TAG_B_UUID: &str = "Cv9nP3sTk6Xw8Yd4Eu5JqM";
491 const TODAY: i64 = 1_700_000_000;
492
493 fn build_store(entries: Vec<(String, WireObject)>) -> ThingsStore {
494 let mut item = BTreeMap::new();
495 for (uuid, obj) in entries {
496 item.insert(uuid, obj);
497 }
498 ThingsStore::from_raw_state(&fold_items([item]))
499 }
500
501 fn task(
502 uuid: &str,
503 title: &str,
504 st: i32,
505 ix: i32,
506 sr: Option<i64>,
507 tir: Option<i64>,
508 ti: i32,
509 ) -> (String, WireObject) {
510 (
511 uuid.to_string(),
512 WireObject::create(
513 EntityType::Task6,
514 TaskProps {
515 title: title.to_string(),
516 item_type: TaskType::Todo,
517 status: TaskStatus::Incomplete,
518 start_location: TaskStart::from(st),
519 sort_index: ix,
520 scheduled_date: sr,
521 today_index_reference: tir,
522 today_sort_index: ti,
523 creation_date: Some(1.0),
524 modification_date: Some(1.0),
525 ..Default::default()
526 },
527 ),
528 )
529 }
530
531 fn project(uuid: &str, title: &str) -> (String, WireObject) {
532 (
533 uuid.to_string(),
534 WireObject::create(
535 EntityType::Task6,
536 TaskProps {
537 title: title.to_string(),
538 item_type: TaskType::Project,
539 status: TaskStatus::Incomplete,
540 start_location: TaskStart::Anytime,
541 sort_index: 0,
542 creation_date: Some(1.0),
543 modification_date: Some(1.0),
544 ..Default::default()
545 },
546 ),
547 )
548 }
549
550 fn area(uuid: &str, title: &str) -> (String, WireObject) {
551 (
552 uuid.to_string(),
553 WireObject::create(
554 EntityType::Area3,
555 AreaProps {
556 title: title.to_string(),
557 sort_index: 0,
558 ..Default::default()
559 },
560 ),
561 )
562 }
563
564 fn tag(uuid: &str, title: &str) -> (String, WireObject) {
565 (
566 uuid.to_string(),
567 WireObject::create(
568 EntityType::Tag4,
569 TagProps {
570 title: title.to_string(),
571 sort_index: 0,
572 ..Default::default()
573 },
574 ),
575 )
576 }
577
578 #[test]
579 fn new_payload_parity_cases() {
580 let mut id_gen = || NEW_UUID.to_string();
581
582 let bare = build_new_plan(
583 &NewArgs {
584 title: "Ship release".to_string(),
585 in_target: "inbox".to_string(),
586 when: None,
587 before_id: None,
588 after_id: None,
589 notes: String::new(),
590 tags: None,
591 deadline_date: None,
592 },
593 &build_store(vec![]),
594 NOW,
595 TODAY,
596 &mut id_gen,
597 )
598 .expect("bare");
599 let bare_json = serde_json::to_value(bare.changes).expect("to value");
600 assert_eq!(bare_json[NEW_UUID]["t"], json!(0));
601 assert_eq!(bare_json[NEW_UUID]["e"], json!("Task6"));
602 assert_eq!(bare_json[NEW_UUID]["p"]["tt"], json!("Ship release"));
603 assert_eq!(bare_json[NEW_UUID]["p"]["st"], json!(0));
604 assert_eq!(bare_json[NEW_UUID]["p"]["cd"], json!(NOW));
605 assert_eq!(bare_json[NEW_UUID]["p"]["md"], json!(NOW));
606
607 let when_today = build_new_plan(
608 &NewArgs {
609 title: "Task today".to_string(),
610 in_target: "inbox".to_string(),
611 when: Some("today".to_string()),
612 before_id: None,
613 after_id: None,
614 notes: String::new(),
615 tags: None,
616 deadline_date: None,
617 },
618 &build_store(vec![]),
619 NOW,
620 TODAY,
621 &mut id_gen,
622 )
623 .expect("today");
624 let p = &serde_json::to_value(when_today.changes).expect("to value")[NEW_UUID]["p"];
625 assert_eq!(p["st"], json!(1));
626 assert_eq!(p["sr"], json!(TODAY));
627 assert_eq!(p["tir"], json!(TODAY));
628
629 let full_store = build_store(vec![
630 project(PROJECT_UUID, "Roadmap"),
631 area(AREA_UUID, "Work"),
632 tag(TAG_A_UUID, "urgent"),
633 tag(TAG_B_UUID, "backend"),
634 ]);
635 let in_project = build_new_plan(
636 &NewArgs {
637 title: "Project task".to_string(),
638 in_target: PROJECT_UUID.to_string(),
639 when: None,
640 before_id: None,
641 after_id: None,
642 notes: "line one".to_string(),
643 tags: Some("urgent,backend".to_string()),
644 deadline_date: Some("2032-05-06".to_string()),
645 },
646 &full_store,
647 NOW,
648 TODAY,
649 &mut id_gen,
650 )
651 .expect("in project");
652 let p = &serde_json::to_value(in_project.changes).expect("to value")[NEW_UUID]["p"];
653 let deadline_ts = day_to_timestamp(
654 parse_day(Some("2032-05-06"), "--deadline")
655 .expect("parse")
656 .expect("day"),
657 );
658 assert_eq!(p["pr"], json!([PROJECT_UUID]));
659 assert_eq!(p["st"], json!(1));
660 assert_eq!(p["tg"], json!([TAG_A_UUID, TAG_B_UUID]));
661 assert_eq!(p["dd"], json!(deadline_ts));
662 }
663
664 #[test]
665 fn new_after_gap_and_rebalance() {
666 let mut id_gen = || NEW_UUID.to_string();
667 let gap_store = build_store(vec![
668 task(INBOX_ANCHOR_UUID, "Anchor", 0, 1024, None, None, 0),
669 task(INBOX_OTHER_UUID, "Other", 0, 2048, None, None, 0),
670 ]);
671 let gap = build_new_plan(
672 &NewArgs {
673 title: "Inserted".to_string(),
674 in_target: "inbox".to_string(),
675 when: None,
676 before_id: None,
677 after_id: Some(INBOX_ANCHOR_UUID.to_string()),
678 notes: String::new(),
679 tags: None,
680 deadline_date: None,
681 },
682 &gap_store,
683 NOW,
684 TODAY,
685 &mut id_gen,
686 )
687 .expect("gap");
688 assert_eq!(
689 serde_json::to_value(gap.changes).expect("to value")[NEW_UUID]["p"]["ix"],
690 json!(1536)
691 );
692
693 let rebalance_store = build_store(vec![
694 task(INBOX_ANCHOR_UUID, "Anchor", 0, 1024, None, None, 0),
695 task(INBOX_OTHER_UUID, "Other", 0, 1025, None, None, 0),
696 ]);
697 let rebalance = build_new_plan(
698 &NewArgs {
699 title: "Inserted".to_string(),
700 in_target: "inbox".to_string(),
701 when: None,
702 before_id: None,
703 after_id: Some(INBOX_ANCHOR_UUID.to_string()),
704 notes: String::new(),
705 tags: None,
706 deadline_date: None,
707 },
708 &rebalance_store,
709 NOW,
710 TODAY,
711 &mut id_gen,
712 )
713 .expect("rebalance");
714 let rb = serde_json::to_value(rebalance.changes).expect("to value");
715 assert_eq!(rb[NEW_UUID]["p"]["ix"], json!(2048));
716 assert_eq!(rb[INBOX_OTHER_UUID]["p"], json!({"ix":3072,"md":NOW}));
717 }
718
719 #[test]
720 fn new_rejections() {
721 let mut id_gen = || NEW_UUID.to_string();
722 let empty_title = build_new_plan(
723 &NewArgs {
724 title: " ".to_string(),
725 in_target: "inbox".to_string(),
726 when: None,
727 before_id: None,
728 after_id: None,
729 notes: String::new(),
730 tags: None,
731 deadline_date: None,
732 },
733 &build_store(vec![]),
734 NOW,
735 TODAY,
736 &mut id_gen,
737 )
738 .expect_err("empty title");
739 assert_eq!(empty_title, "Task title cannot be empty.");
740
741 let unknown_container = build_new_plan(
742 &NewArgs {
743 title: "Ship".to_string(),
744 in_target: "nope".to_string(),
745 when: None,
746 before_id: None,
747 after_id: None,
748 notes: String::new(),
749 tags: None,
750 deadline_date: None,
751 },
752 &build_store(vec![]),
753 NOW,
754 TODAY,
755 &mut id_gen,
756 )
757 .expect_err("unknown container");
758 assert_eq!(unknown_container, "Container not found: nope");
759 }
760}