Compare commits
7 Commits
45b8f9cf0f
...
b544616801
Author | SHA1 | Date |
---|---|---|
xeruf | b544616801 | |
xeruf | 1e0cc319b8 | |
xeruf | 8588fb9a04 | |
xeruf | bbfbb2d8b0 | |
xeruf | f5aca3eca8 | |
xeruf | be582cb536 | |
xeruf | 68d5c101e9 |
82
README.md
82
README.md
|
@ -99,7 +99,7 @@ To stop time-tracking completely, simply move to the root of all tasks.
|
|||
+ match by task name prefix: if one or more tasks match, filter / activate (tries case-sensitive then case-insensitive)
|
||||
+ no match: create & activate task
|
||||
- `.2` - set view depth to `2`, which can be substituted for any number (how many subtask levels to show, default 1)
|
||||
- `/[TEXT]` - like `.`, but never creates a task
|
||||
- `/[TEXT]` - like `.`, but never creates a task and filters beyond currently visible tasks
|
||||
- `||TASK` - create and activate a new task procedure (where subtasks automatically depend on the previously created task)
|
||||
- `|[TASK]` - (un)mark current task as procedure or create a sibling task depending on the current one and move up
|
||||
|
||||
|
@ -109,12 +109,13 @@ Dots and slashes can be repeated to move to parent tasks.
|
|||
- `::[PROP]` - Sort by property PROP (multiple space-separated values allowed)
|
||||
- `([TIME]` - insert timetracking with the specified offset in minutes (empty: list tracked times)
|
||||
- `)[TIME]` - stop timetracking with the specified offset in minutes - convenience helper to move to root (empty: stop now)
|
||||
- `>[TEXT]` - complete active task and move up, with optional state description
|
||||
- `<[TEXT]` - close active task and move up, with optional state description
|
||||
- `!TEXT` - set state for current task from text and move up
|
||||
- `>[TEXT]` - complete active task and move up, with optional status description
|
||||
- `<[TEXT]` - close active task and move up, with optional status description
|
||||
- `!TEXT` - set status for current task from text and move up (empty to open)
|
||||
- `,TEXT` - add text note (comment / description)
|
||||
- TBI: `*[INT]` - set priority - can also be used in task, with any digit
|
||||
- `@` - undoes last action (moving in place or upwards or waiting a minute confirms pending actions)
|
||||
- TBI: `*[INT]` - set priority - can also be used in task creation, with any digit
|
||||
- TBI: status history and creation with attribution
|
||||
- `&` - undo last action (moving in place or upwards confirms pending actions)
|
||||
- `wss://...` - switch or subscribe to relay (prefix with space to forcibly add a new one)
|
||||
|
||||
Property Filters:
|
||||
|
@ -123,6 +124,8 @@ Property Filters:
|
|||
- `+TAG` - add tag filter
|
||||
- `-TAG` - remove tag filters
|
||||
- `?STATUS` - filter by status (type or description) - plain `?` to reset, `??` to show all
|
||||
- `@AUTHOR` - filter by author (`@` for self, id prefix, name prefix)
|
||||
- TBI: Filter by time
|
||||
|
||||
Status descriptions can be used for example for Kanban columns or review flows.
|
||||
An active tag or status filter will also set that attribute for newly created tasks.
|
||||
|
@ -148,6 +151,11 @@ An active tag or status filter will also set that attribute for newly created ta
|
|||
|
||||
For debugging: `props`, `alltags`, `descriptions`
|
||||
|
||||
### Notes
|
||||
|
||||
- TBI = To Be Implemented
|
||||
- `. TASK` - create and enter a new task even if the name matches an existing one
|
||||
|
||||
## Nostr reference
|
||||
|
||||
Mostr mainly uses the following NIPs:
|
||||
|
@ -164,17 +172,26 @@ Considering to use Calendar: https://github.com/nostr-protocol/nips/blob/master/
|
|||
|
||||
## Plans
|
||||
|
||||
- Remove state filter when moving up?
|
||||
- Local Database Cache, Negentropy Reconciliation
|
||||
-> Offline Use!
|
||||
- Scheduling
|
||||
- Remove status filter when moving up?
|
||||
- Task markdown support? - colored
|
||||
- Time tracking: Ability to postpone task and add planned timestamps (calendar entry)
|
||||
- Parse Hashtag tags from task name
|
||||
- Unified Filter object
|
||||
-> include subtasks of matched tasks
|
||||
- Relay Switching
|
||||
- Speedup: Offline caching & Expiry (no need to fetch potential years of history)
|
||||
+ Fetch most recent tasks first
|
||||
+ Relay: compress tracked time for old tasks, filter closed tasks
|
||||
+ Relay: filter out task state updates within few seconds, also on client side
|
||||
+ Relay: filter out task status updates within few seconds, also on client side
|
||||
|
||||
### Command
|
||||
|
||||
- Open Command characters: `_^\=$%~'"`, `{}[]`
|
||||
- Remove colon from task creation syntax
|
||||
- reassign undo to `&` and use `@` for people
|
||||
- maybe use `;` for sorting instead of `::`
|
||||
|
||||
### Conceptual
|
||||
|
||||
|
@ -182,9 +199,10 @@ The following features are not ready to be implemented
|
|||
because they need conceptualization.
|
||||
Suggestions welcome!
|
||||
|
||||
- Special commands: help, exit, tutorial, change log level
|
||||
- Duplicate task (subtasks? timetracking?)
|
||||
- What if I want to postpone a procedure, i.e. make it pending, or move it across kanban, does this make sense?
|
||||
- Priorities
|
||||
- Dependencies (change from tags to properties so they can be added later? or maybe as a state?)
|
||||
- Dependencies (change from tags to properties so they can be added later? or maybe as a status?)
|
||||
- Templates
|
||||
- Ownership
|
||||
- Combined formatting and recursion specifiers
|
||||
|
@ -194,11 +212,45 @@ Suggestions welcome!
|
|||
|
||||
### Interfaces
|
||||
|
||||
- TUI: Clear terminal? Refresh on empty prompt after timeout?
|
||||
- TUI: Clear Terminal? Refresh on empty prompt after timeout?
|
||||
- Kanban, GANTT, Calendar
|
||||
- Web Interface, Messenger integrations
|
||||
|
||||
## Notes
|
||||
## Exemplary Workflows
|
||||
|
||||
- TBI = To Be Implemented
|
||||
- `. TASK` - create and enter a new task even if the name matches an existing one
|
||||
- Freelancer
|
||||
- Family Chore management
|
||||
- Inter-Disciplinary Project Team -> Company with multiple projects and multiple relays
|
||||
+ Permissions via status or assignment (reassignment?)
|
||||
+ Tasks can be blocked while having a status (e.g. kanban column)
|
||||
+ A meeting can be worked on (tracked) before it starts
|
||||
+ Schedule for multiple people
|
||||
- Tracking Daily Routines / Habits
|
||||
|
||||
### Contexts
|
||||
|
||||
A context is a custom set of filters such as status, tags, assignee
|
||||
so that the visible tasks are always relevant
|
||||
and newly created tasks are less of a hassle to type out
|
||||
since they will automatically take on that context.
|
||||
By automating these contexts based on triggers, scripts or time,
|
||||
relevant tasks can be surfaced automatically.
|
||||
|
||||
#### Example
|
||||
|
||||
In the morning, your groggy brain is good at divergent thinking,
|
||||
and you like to do sports in the morning.
|
||||
So for that time, mostr can show you tasks tagged for divergent thinking,
|
||||
since you are easily distracted filter out those that require the internet,
|
||||
as well as anything sportsy.
|
||||
After you come back from sports and had breakfast,
|
||||
for example detected through a period of inactivity on your device,
|
||||
you are ready for work, so the different work projects are shown and you delve into one.
|
||||
After 90 minutes you reach a natural low in your focus,
|
||||
so mostr surfaces break activities -
|
||||
such as a short walk, a small workout, some instrument practice
|
||||
or simply grabbing a snack and drink.
|
||||
After lunch you like to take an extended afternoon break,
|
||||
so your call list pops up -
|
||||
you can give a few people a call as you make a market run,
|
||||
before going for siesta.
|
||||
|
|
|
@ -7,6 +7,7 @@ pub fn some_non_empty(str: &str) -> Option<String> {
|
|||
if str.is_empty() { None } else { Some(str.to_string()) }
|
||||
}
|
||||
|
||||
// TODO as macro so that log comes from appropriate module
|
||||
pub fn or_print<T, U: Display>(result: Result<T, U>) -> Option<T> {
|
||||
match result {
|
||||
Ok(value) => Some(value),
|
||||
|
|
125
src/main.rs
125
src/main.rs
|
@ -13,7 +13,7 @@ use std::sync::mpsc::RecvTimeoutError;
|
|||
use std::sync::mpsc::Sender;
|
||||
use std::time::Duration;
|
||||
|
||||
use colored::Colorize;
|
||||
use colored::{ColoredString, Colorize};
|
||||
use env_logger::Builder;
|
||||
use itertools::Itertools;
|
||||
use log::{debug, error, info, LevelFilter, trace, warn};
|
||||
|
@ -23,7 +23,6 @@ use xdg::BaseDirectories;
|
|||
|
||||
use crate::helpers::*;
|
||||
use crate::kinds::{KINDS, PROPERTY_COLUMNS, TRACKING_KIND};
|
||||
use crate::MostrMessage::AddTasks;
|
||||
use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State};
|
||||
use crate::tasks::{PropertyCollection, StateFilter, Tasks};
|
||||
|
||||
|
@ -80,7 +79,9 @@ impl EventSender {
|
|||
debug!("Flushing {} events from queue", self.queue.borrow().len());
|
||||
let values = self.clear();
|
||||
self.url.as_ref().map(|url| {
|
||||
or_print(self.tx.send(AddTasks(url.clone(), values)));
|
||||
self.tx.send(MostrMessage::AddTasks(url.clone(), values)).inspect_err(|e| {
|
||||
error!("Nostr communication thread failure, changes will not be persisted: {}", e)
|
||||
})
|
||||
});
|
||||
}
|
||||
/// Sends all pending events if there is a non-tracking event
|
||||
|
@ -149,6 +150,7 @@ async fn main() {
|
|||
let client = Client::new(&keys);
|
||||
info!("My public key: {}", keys.public_key());
|
||||
|
||||
// TODO use NewRelay message for all relays
|
||||
match var("MOSTR_RELAY") {
|
||||
Ok(relay) => {
|
||||
or_print(client.add_relay(relay).await);
|
||||
|
@ -237,19 +239,26 @@ async fn main() {
|
|||
let mut queue: Option<(Url, Vec<Event>)> = None;
|
||||
|
||||
loop {
|
||||
// TODO invalid acknowledgement from bucket relay slows sending down
|
||||
match rx.recv_timeout(Duration::from_secs(INACTVITY_DELAY)) {
|
||||
Ok(AddTasks(url, mut events)) => {
|
||||
if 1 == 2 {
|
||||
client.connect_relay("").await;
|
||||
let result = rx.recv_timeout(Duration::from_secs(INACTVITY_DELAY));
|
||||
match result {
|
||||
Ok(MostrMessage::NewRelay(url)) => {
|
||||
if client.add_relay(&url).await.unwrap() {
|
||||
match client.connect_relay(&url).await {
|
||||
Ok(()) => info!("Connected to {url}"),
|
||||
Err(e) => warn!("Unable to connect to relay {url}: {e}")
|
||||
}
|
||||
} else {
|
||||
warn!("Relay {url} already added");
|
||||
}
|
||||
debug!("Queueing {:?}", &events);
|
||||
}
|
||||
Ok(MostrMessage::AddTasks(url, mut events)) => {
|
||||
trace!("Queueing {:?}", &events);
|
||||
if let Some((queue_url, mut queue_events)) = queue {
|
||||
if queue_url == url {
|
||||
queue_events.append(&mut events);
|
||||
queue = Some((queue_url, queue_events));
|
||||
} else {
|
||||
info!("Sending {} events due to relay change", queue_events.len());
|
||||
info!("Sending {} events to {url} due to relay change", queue_events.len());
|
||||
client.batch_event_to(vec![queue_url], queue_events, RelaySendOptions::new()).await;
|
||||
queue = None;
|
||||
}
|
||||
|
@ -259,19 +268,19 @@ async fn main() {
|
|||
queue = Some((url, events))
|
||||
}
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout) => if let Some((url, events)) = queue {
|
||||
info!("Sending {} events due to inactivity", events.len());
|
||||
Ok(MostrMessage::Flush) | Err(RecvTimeoutError::Timeout) => if let Some((url, events)) = queue {
|
||||
info!("Sending {} events to {url} due to {:?}", events.len(), result);
|
||||
client.batch_event_to(vec![url], events, RelaySendOptions::new()).await;
|
||||
queue = None;
|
||||
}
|
||||
arg => {
|
||||
debug!("Finalizing nostr communication thread because of {:?}", arg);
|
||||
break
|
||||
Err(err) => {
|
||||
debug!("Finalizing nostr communication thread because of {:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some((url, events)) = queue {
|
||||
info!("Sending {} events before exiting", events.len());
|
||||
info!("Sending {} events to {url} before exiting", events.len());
|
||||
client.batch_event_to(vec![url], events, RelaySendOptions::new()).await;
|
||||
}
|
||||
info!("Shutting down nostr communication thread");
|
||||
|
@ -289,18 +298,19 @@ async fn main() {
|
|||
|
||||
let mut lines = stdin().lines();
|
||||
loop {
|
||||
trace!("All Root Tasks:\n{}", relays.iter().map(|(url, tasks)|
|
||||
format!("{}: [{}]", url, tasks.children_of(None).map(|id| tasks.get_task_title(id)).join("; "))).join("\n"));
|
||||
println!();
|
||||
selected_relay.as_ref().and_then(|url| relays.get(url)).inspect(|tasks| {
|
||||
print!(
|
||||
"{}",
|
||||
format!(
|
||||
"{} {}{}) ",
|
||||
selected_relay.as_ref().map_or("local".to_string(), |url| url.to_string()),
|
||||
tasks.get_task_path(tasks.get_position()),
|
||||
tasks.get_prompt_suffix()
|
||||
).italic()
|
||||
);
|
||||
});
|
||||
let tasks = selected_relay.as_ref().and_then(|url| relays.get(url)).unwrap_or(&local_tasks);
|
||||
print!(
|
||||
"{} {}) ",
|
||||
selected_relay.as_ref().map_or("TEMP".to_string(), |url| url.to_string()).bright_black().italic(),
|
||||
format!(
|
||||
"{}{}",
|
||||
tasks.get_task_path(tasks.get_position()),
|
||||
tasks.get_prompt_suffix()
|
||||
).bold()
|
||||
);
|
||||
stdout().flush().unwrap();
|
||||
match lines.next() {
|
||||
Some(Ok(input)) => {
|
||||
|
@ -312,10 +322,13 @@ async fn main() {
|
|||
..
|
||||
} = notification
|
||||
{
|
||||
print_event(&event);
|
||||
debug!(
|
||||
"At {} found {} kind {} content \"{}\" tags {:?}",
|
||||
event.created_at, event.id, event.kind, event.content, event.tags.iter().map(|tag| tag.as_vec()).collect_vec()
|
||||
);
|
||||
match relays.get_mut(&relay_url) {
|
||||
Some(tasks) => tasks.add(*event),
|
||||
None => warn!("Event received from unknown relay {relay_url}: {:?}", event)
|
||||
None => warn!("Event received from unknown relay {relay_url}: {:?}", *event)
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
|
@ -387,7 +400,7 @@ async fn main() {
|
|||
tasks.move_up();
|
||||
}
|
||||
|
||||
Some('@') => {
|
||||
Some('@') | Some('&') => {
|
||||
tasks.undo();
|
||||
}
|
||||
|
||||
|
@ -462,19 +475,16 @@ async fn main() {
|
|||
None => tasks.clear_filter()
|
||||
}
|
||||
|
||||
Some('(') =>
|
||||
match arg {
|
||||
Some(arg) =>
|
||||
if !tasks.track_from(arg) {
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
println!("{}", tasks.times_tracked());
|
||||
Some('(') => {
|
||||
if let Some(arg) = arg {
|
||||
if !tasks.track_from(arg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
println!("{}", tasks.times_tracked());
|
||||
continue;
|
||||
}
|
||||
|
||||
Some(')') => {
|
||||
tasks.move_to(None);
|
||||
if let Some(arg) = arg {
|
||||
|
@ -541,22 +551,22 @@ async fn main() {
|
|||
_ =>
|
||||
if Regex::new("^wss?://").unwrap().is_match(&input.trim()) {
|
||||
tasks.move_to(None);
|
||||
let mut new_relay = relays.keys().find(|key| key.as_str().starts_with(&input)).cloned();
|
||||
if new_relay.is_none() {
|
||||
if let Some(url) = or_print(Url::parse(&input)) {
|
||||
warn!("Connecting to {url} while running not yet supported");
|
||||
//new_relay = Some(url.clone());
|
||||
//relays.insert(url.clone(), tasks_for_url(Some(url.clone())));
|
||||
//if client.add_relay(url).await.unwrap() {
|
||||
// relays.insert(url.clone(), tasks_for_url(Some(url.clone())));
|
||||
// client.connect().await;
|
||||
//}
|
||||
if let Some((url, tasks)) = relays.iter().find(|(key, _)| key.as_str().starts_with(&input)) {
|
||||
selected_relay = Some(url.clone());
|
||||
or_print(tasks.print_tasks());
|
||||
continue;
|
||||
}
|
||||
match Url::parse(&input) {
|
||||
Err(e) => warn!("Failed to parse url \"{input}\": {}", e),
|
||||
Ok(url) => match tx.send(MostrMessage::NewRelay(url.clone())) {
|
||||
Err(e) => error!("Nostr communication thread failure, cannot add relay \"{url}\": {e}"),
|
||||
Ok(_) => {
|
||||
info!("Connecting to {url}");
|
||||
selected_relay = Some(url.clone());
|
||||
relays.insert(url.clone(), tasks_for_url(Some(url)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if new_relay.is_some() {
|
||||
selected_relay = new_relay;
|
||||
}
|
||||
//or_print(tasks.print_tasks());
|
||||
continue;
|
||||
} else {
|
||||
tasks.filter_or_create(&input);
|
||||
|
@ -577,10 +587,3 @@ async fn main() {
|
|||
info!("Submitting pending updates...");
|
||||
or_print(sender.await);
|
||||
}
|
||||
|
||||
fn print_event(event: &Event) {
|
||||
debug!(
|
||||
"At {} found {} kind {} \"{}\" {:?}",
|
||||
event.created_at, event.id, event.kind, event.content, event.tags
|
||||
);
|
||||
}
|
||||
|
|
36
src/tasks.rs
36
src/tasks.rs
|
@ -28,6 +28,7 @@ pub(crate) struct Tasks {
|
|||
tasks: TaskMap,
|
||||
/// History of active tasks by PubKey
|
||||
history: HashMap<PublicKey, BTreeSet<Event>>,
|
||||
|
||||
/// The task properties currently visible
|
||||
properties: Vec<String>,
|
||||
/// The task properties sorted by
|
||||
|
@ -336,7 +337,7 @@ impl Tasks {
|
|||
self.position.and_then(|id| self.get_by_id(&id))
|
||||
}
|
||||
|
||||
pub(crate) fn children_of(&self, id: Option<EventId>) -> impl IntoIterator<Item=&EventId> + '_ {
|
||||
pub(crate) fn children_of(&self, id: Option<EventId>) -> impl Iterator<Item=&EventId> + '_ {
|
||||
self.tasks
|
||||
.values()
|
||||
.filter(move |t| t.parent_id() == id.as_ref())
|
||||
|
@ -349,9 +350,10 @@ impl Tasks {
|
|||
}
|
||||
let res: Vec<&Task> = self.resolve_tasks(self.view.iter());
|
||||
if res.len() > 0 {
|
||||
// Currently ignores filter when it matches nothing
|
||||
// Currently ignores filtered view when it matches nothing
|
||||
return res;
|
||||
}
|
||||
// TODO use ChildrenIterator
|
||||
self.resolve_tasks(self.children_of(self.position)).into_iter()
|
||||
.filter(|t| {
|
||||
// TODO apply filters in transit
|
||||
|
@ -686,7 +688,7 @@ impl Tasks {
|
|||
)
|
||||
}
|
||||
|
||||
fn get_task_title(&self, id: &EventId) -> String {
|
||||
pub(crate) fn get_task_title(&self, id: &EventId) -> String {
|
||||
self.tasks.get(id).map_or(id.to_string(), |t| t.get_title())
|
||||
}
|
||||
|
||||
|
@ -698,14 +700,14 @@ impl Tasks {
|
|||
} else if let Ok(date) = DateTime::parse_from_rfc3339(str) {
|
||||
self.track_at(Timestamp::from(date.to_utc().timestamp() as u64));
|
||||
} else {
|
||||
warn!("Cannot parse {str}");
|
||||
warn!("Cannot parse time from {str}");
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn track_at(&mut self, time: Timestamp) -> EventId {
|
||||
info!("{} from {}", self.position.map_or(String::from("Stopping time-tracking"), |id| format!("Tracking \"{}\"", self.get_task_title(&id))), time.to_human_datetime()); // TODO omit seconds
|
||||
info!("{} from {}", self.position.map_or(String::from("Stopping time-tracking"), |id| format!("Tracking \"{}\"", self.get_task_title(&id))), time.to_human_datetime());
|
||||
let pos = self.get_position();
|
||||
let tracking = build_tracking(pos);
|
||||
// TODO this can lead to funny deletions
|
||||
|
@ -723,6 +725,7 @@ impl Tasks {
|
|||
self.submit(tracking.custom_created_at(time))
|
||||
}
|
||||
|
||||
/// Sign and queue the event to the relay, returning its id
|
||||
fn submit(&mut self, builder: EventBuilder) -> EventId {
|
||||
let event = self.sender.submit(builder).unwrap();
|
||||
let id = event.id;
|
||||
|
@ -829,7 +832,7 @@ impl Tasks {
|
|||
self.sorting = vec;
|
||||
info!("Now sorting by {:?}", self.sorting);
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn add_sorting_property(&mut self, property: String) {
|
||||
// TODO reverse order if already present
|
||||
self.sorting.push_front(property);
|
||||
|
@ -843,9 +846,11 @@ pub trait PropertyCollection<T> {
|
|||
fn add_or_remove(&mut self, value: T);
|
||||
fn add_or_remove_at(&mut self, value: T, index: usize);
|
||||
}
|
||||
impl <T> PropertyCollection<T> for Vec<T>
|
||||
where T: Display, T: Eq, T: Clone {
|
||||
fn remove_at(&mut self, index: usize) {
|
||||
impl<T> PropertyCollection<T> for Vec<T>
|
||||
where
|
||||
T: Display + Eq + Clone,
|
||||
{
|
||||
fn remove_at(&mut self, index: usize) {
|
||||
let col = self.remove(index);
|
||||
info!("Removed property column \"{col}\"");
|
||||
}
|
||||
|
@ -1052,20 +1057,20 @@ mod tasks_test {
|
|||
tasks.make_task_and_enter("proc: tags", State::Procedure);
|
||||
let side = tasks.submit(build_task("side", vec![tasks.make_event_tag(&tasks.get_current_task().unwrap().event, MARKER_DEPENDS)]));
|
||||
assert_eq!(tasks.get_current_task().unwrap().children, HashSet::<EventId>::new());
|
||||
let subid = tasks.make_task("sub");
|
||||
assert_eq!(tasks.get_current_task().unwrap().children, HashSet::from([subid]));
|
||||
let sub = tasks.get_by_id(&subid).unwrap();
|
||||
let sub_id = tasks.make_task("sub");
|
||||
assert_eq!(tasks.get_current_task().unwrap().children, HashSet::from([sub_id]));
|
||||
assert_eq!(tasks.len(), 3);
|
||||
let sub = tasks.get_by_id(&sub_id).unwrap();
|
||||
assert_eq!(sub.get_dependendees(), Vec::<&EventId>::new());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tracking() {
|
||||
let mut tasks = stub_tasks();
|
||||
let zero = EventId::all_zeros();
|
||||
|
||||
//let task = tasks.make_task("task");
|
||||
tasks.track_at(Timestamp::from(0));
|
||||
assert_eq!(tasks.history.len(), 1);
|
||||
let zero = EventId::all_zeros();
|
||||
|
||||
tasks.move_to(Some(zero));
|
||||
let now: Timestamp = Timestamp::now() - 2u64;
|
||||
|
@ -1085,10 +1090,11 @@ mod tasks_test {
|
|||
fn test_timestamps() {
|
||||
let mut tasks = stub_tasks();
|
||||
let zero = EventId::all_zeros();
|
||||
|
||||
tasks.move_to(Some(zero));
|
||||
tasks.track_at(Timestamp::from(Timestamp::now().as_u64() + 100));
|
||||
assert_eq!(timestamps(tasks.history.values().nth(0).unwrap().into_iter(), &vec![&zero]).collect_vec().len(), 2)
|
||||
// TODO Does not show both future and current tracking properly, need to split by now
|
||||
// TODO Does not show both future and current tracking properly, need to split by current time
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue