diff --git a/README.md b/README.md index 04e51ec..d2af762 100644 --- a/README.md +++ b/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 \ No newline at end of file +- 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. diff --git a/src/helpers.rs b/src/helpers.rs index 9d250b5..31fa707 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -7,6 +7,7 @@ pub fn some_non_empty(str: &str) -> Option { if str.is_empty() { None } else { Some(str.to_string()) } } +// TODO as macro so that log comes from appropriate module pub fn or_print(result: Result) -> Option { match result { Ok(value) => Some(value), diff --git a/src/main.rs b/src/main.rs index bee9463..ce483f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -400,7 +400,7 @@ async fn main() { tasks.move_up(); } - Some('@') => { + Some('@') | Some('&') => { tasks.undo(); } diff --git a/src/tasks.rs b/src/tasks.rs index a3f3385..b871d16 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -28,6 +28,7 @@ pub(crate) struct Tasks { tasks: TaskMap, /// History of active tasks by PubKey history: HashMap>, + /// The task properties currently visible properties: Vec, /// The task properties sorted by @@ -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 @@ -705,7 +707,7 @@ impl Tasks { } 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 { fn add_or_remove(&mut self, value: T); fn add_or_remove_at(&mut self, value: T, index: usize); } -impl PropertyCollection for Vec -where T: Display, T: Eq, T: Clone { - fn remove_at(&mut self, index: usize) { +impl PropertyCollection for Vec +where + T: Display + Eq + Clone, +{ + fn remove_at(&mut self, index: usize) { let col = self.remove(index); info!("Removed property column \"{col}\""); }