···66/// Color type
77///
88/// We store it as a u32, but its actually 00000000rrrrrrrrggggggggbbbbbbbb
99-#[derive(Clone, Copy, PartialEq, Eq, DeriveValueType)]
99+#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Copy, DeriveValueType)]
1010pub struct Color(u32);
11111212impl Color {
···1313pub use sea_orm::IntoActiveModel;
1414pub use sea_orm::QueryFilter;
1515pub use sea_orm::QueryOrder;
1616+pub use sea_orm::entity::compound::HasMany;
1717+pub use sea_orm::entity::compound::HasOne;
16181719/// Exporting this as a generic NanoId.
1820pub use migration::types::NanoId;
···2022/// and add additional functionality to it.
2123pub use migration::types::Priority as PriorityDTO;
22242525+pub use sea_orm::entity::prelude::Date;
2326pub use sea_orm::entity::prelude::DateTime;
2727+pub use sea_orm::entity::prelude::Time;
24282529/// Color type, exporting as DTO because I might
2630/// want to newtype wrap this, might not have to, depending
···11use std::cmp::Ordering;
2233-use autosurgeon::{Hydrate, Reconcile};
43use serde::{Deserialize, Serialize};
5465use crate::{
···156155/// Any function that takes a `NodeId` can `panic`, but this should
157156/// only happen with improper `NodeId` management within `tree`, and
158157/// should have nothing to do with library user's code.
159159-#[derive(Debug, Serialize, Deserialize, Reconcile, Hydrate)]
158158+#[derive(Debug, Serialize, Deserialize)]
159159+#[cfg_attr(
160160+ feature = "automerge",
161161+ derive(autosurgeon::Reconcile, autosurgeon::Hydrate)
162162+)]
160163pub struct Tree<T> {
161164 root: Option<NodeId>,
162165 pub(crate) nodes: Vec<Option<Node<T>>>,
+30-1
src/cli/mod.rs
···11use clap::{Parser, Subcommand};
22+use dto::NanoId;
2334use crate::config::{get_config_dir, get_data_dir};
45···2829 /// Manage `Zettel`s
2930 #[command(subcommand)]
3031 Zettel(ZettelSubcommand),
3232+3333+ #[command(subcommand)]
3434+ Todo(TodoSubcommand),
31353236 /// Spawn the `LSP`
3337 Lsp,
···5559 // default values if they arent present / aren't able to be
5660 // parsed properly
5761 // Import(ImportArgs),
6262+ Test,
5863}
59646065#[derive(Subcommand, Debug)]
6161-/// Subcommand to manage tars groups.
6666+/// Subcommand to manage Zettels.
6267pub enum ZettelSubcommand {
6368 /// Add a group.
6469 New {
···7176 /// Filter by tag
7277 #[arg(short = 't', long)]
7378 by_tag: String,
7979+ },
8080+}
8181+8282+#[derive(Subcommand, Debug)]
8383+/// Subcommand to manage To-Do functionality
8484+pub enum TodoSubcommand {
8585+ Group {
8686+ /// The name of this group
8787+ #[arg(short, long)]
8888+ name: String,
8989+9090+ /// If this group has a parent, provide the parent id.
9191+ #[arg(short, long)]
9292+ parent_id: Option<NanoId>,
9393+ },
9494+9595+ Task {
9696+ /// Name of the task
9797+ #[arg(short, long)]
9898+ name: String,
9999+100100+ /// Parent group of the task
101101+ #[arg(short, long)]
102102+ parent_id: NanoId,
74103 },
75104}
76105
+129-3
src/cli/process.rs
···44 io::Write,
55};
6677-use color_eyre::eyre::{Context, Result};
77+use color_eyre::eyre::{Context, Result, eyre};
88+use dto::{
99+ Date, DateTime, GroupActiveModel, GroupEntity, HasOne, IntoActiveModel, TagActiveModel,
1010+ TagEntity, TaskActiveModel, TaskEntity, Time, ZettelEntity,
1111+};
812use tower_lsp::{LspService, Server};
9131014use crate::{
1115 cli::{Commands, ZettelSubcommand},
1216 config::{Config, get_config_dir},
1317 lsp::Backend,
1414- types::{Kasten, Zettel},
1818+ types::{Group, Kasten, Priority, Tag, Task, Zettel},
1519};
16201721impl Commands {
2222+ #[expect(clippy::too_many_lines)]
1823 pub async fn process(self) -> Result<()> {
1924 match self {
2025 Self::Init { name } => {
···50555156 match zettel_sub_command {
5257 ZettelSubcommand::New { title } => {
5353- let zettel = Zettel::new(title, &mut kt).await?;
5858+ let zettel = Zettel::new(title, &mut kt, vec![]).await?;
5459 println!("Zettel Created! {zettel:#?}");
5560 }
5661 ZettelSubcommand::List { by_tag: _by_tag } => {}
···6873 let (service, socket) = LspService::new(|client| Backend::new(client, kt));
69747075 Server::new(stdin, stdout, socket).serve(service).await;
7676+ }
7777+7878+ Self::Todo(command) => {
7979+ let conf = Config::parse()?;
8080+ let mut kt = Kasten::instansiate(conf.fil_dir).await?;
8181+8282+ match command {
8383+ super::TodoSubcommand::Group { name, parent_id } => {
8484+ // lets create a tag for this first group first
8585+ let tag: Tag = TagActiveModel::builder()
8686+ .set_name(name.clone())
8787+ .insert(&kt.db)
8888+ .await?
8989+ .into();
9090+9191+ let tag_id = tag.id.clone();
9292+9393+ // then create the zettel for the group
9494+ let zettel = Zettel::new(name.clone(), &mut kt, vec![tag]).await?;
9595+9696+ // then insert that shi
9797+ let inserted = GroupActiveModel::builder()
9898+ .set_name(name)
9999+ .set_parent_group_id(parent_id)
100100+ .set_tag(
101101+ TagEntity::load()
102102+ .filter_by_nano_id(tag_id)
103103+ .one(&kt.db)
104104+ .await?
105105+ .expect("Tag must exist since we just created it")
106106+ .into_active_model(),
107107+ )
108108+ .set_zettel(
109109+ ZettelEntity::load()
110110+ .filter_by_nano_id(zettel.id)
111111+ .one(&kt.db)
112112+ .await?
113113+ .expect("Zettel must exist since we just created it")
114114+ .into_active_model(),
115115+ )
116116+ .set_priority(Priority::default())
117117+ .insert(&kt.db)
118118+ .await?;
119119+120120+ // group should also have the accompanying tag for it.
121121+ let group: Group = GroupEntity::load()
122122+ .with(TagEntity)
123123+ .with((ZettelEntity, TagEntity))
124124+ .filter_by_nano_id(inserted.nano_id)
125125+ .one(&kt.db)
126126+ .await?
127127+ .expect("We just inserted it")
128128+ .into();
129129+130130+ println!("created group {group:#?}");
131131+ }
132132+ super::TodoSubcommand::Task { name, parent_id } => {
133133+ // need to create the task
134134+ let parent = GroupEntity::load()
135135+ .with(TagEntity)
136136+ .filter_by_nano_id(parent_id)
137137+ .one(&kt.db)
138138+ .await
139139+ .with_context(|| "failed to communicate with db")?
140140+ .ok_or_else(|| eyre!("could not find the group"))?;
141141+142142+ let HasOne::Loaded(tag) = parent.tag else {
143143+ panic!("this has to be loaded since we just loaded it right above")
144144+ };
145145+146146+ let zettel =
147147+ Zettel::new(name.clone(), &mut kt, vec![(*tag).into()]).await?;
148148+149149+ let inserted = TaskActiveModel::builder()
150150+ .set_name(name)
151151+ .set_group_id(parent.nano_id.clone())
152152+ .set_priority(Priority::default())
153153+ .set_zettel(
154154+ ZettelEntity::load()
155155+ .filter_by_nano_id(zettel.id)
156156+ .one(&kt.db)
157157+ .await?
158158+ .expect("Zettel must exist since we just created it")
159159+ .into_active_model(),
160160+ )
161161+ .set_due(Some(DateTime::new(
162162+ Date::from_ymd_opt(2026, 1, 31).unwrap(),
163163+ Time::from_hms_opt(10, 10, 10).unwrap(),
164164+ )))
165165+ .insert(&kt.db)
166166+ .await?;
167167+168168+ let group = GroupEntity::load()
169169+ .with(TagEntity)
170170+ .with((ZettelEntity, TagEntity))
171171+ .filter_by_nano_id(parent.nano_id)
172172+ .one(&kt.db)
173173+ .await?
174174+ .expect("We just inserted it");
175175+176176+ let mut task = TaskEntity::load()
177177+ .with((ZettelEntity, TagEntity))
178178+ .filter_by_nano_id(inserted.nano_id)
179179+ .one(&kt.db)
180180+ .await?
181181+ .expect("We just inserted it");
182182+183183+ task.group = HasOne::Loaded(Box::new(group));
184184+185185+ println!("task: {task:#?}");
186186+187187+ let task: Task = task.into();
188188+189189+ println!("created task: {task:#?}");
190190+ }
191191+ }
192192+ }
193193+ Self::Test => {
194194+ let conf = Config::parse()?;
195195+ let kt = Kasten::instansiate(conf.fil_dir).await?;
196196+ println!("kt: {kt:#?}");
71197 }
72198 }
73199
···7272 let mut zettels: Vec<Zettel> = fetch_all().await?;
73737474 if zettels.is_empty() {
7575- let _ = Zettel::new("Welcome!", &mut *kh.write().await).await?;
7575+ let _ = Zettel::new("Welcome!", &mut *kh.write().await, vec![]).await?;
7676 zettels = fetch_all().await?;
7777 }
7878···236236 let mut kt = self.kh.write().await;
237237238238 // we create the zettel with the query as the
239239- let z = Zettel::new(self.search.query(), &mut kt)
239239+ let z = Zettel::new(self.search.query(), &mut kt, vec![])
240240 .await
241241 .with_context(|| "Failed to create a new Zettel!")?;
242242
+2-1
src/tui/mod.rs
···11/// The tui app
22mod app;
33pub use app::App as TuiApp;
44-pub use app::Region;
44+pub use app::Page;
55+pub use app::TodoRegion;
5667/// Tui components
78mod components;
···11use dto::{DateTime, GroupModelEx, NanoId};
2233-use crate::types::{Color, Priority, Zettel};
33+use crate::types::{Priority, Tag, Zettel, frontmatter};
4455/// A `Group` which contains tasks!
66-#[expect(dead_code)]
77-#[derive(Debug, Clone)]
66+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
87pub struct Group {
98 /// Should only be constructed from models.
109 _private: (),
11101211 pub id: NanoId,
1312 pub name: String,
1414- pub color: Color,
1513 pub priority: Priority,
1414+ pub parent_id: Option<NanoId>,
1615 pub created_at: DateTime,
1716 pub modified_at: DateTime,
1817 /// The `Zettel` that is related to this `Group`.
1918 /// Can store notes regarding this group in
2019 /// the `Zettel`
2120 pub zettel: Zettel,
2121+2222+ /// The `Tag` that is related to this `Group`
2323+ pub tag: Tag,
2424+}
2525+2626+impl Group {
2727+ pub fn created_at(&self) -> String {
2828+ self.created_at
2929+ .format(frontmatter::DATE_FMT_STR)
3030+ .to_string()
3131+ }
3232+ pub fn modified_at(&self) -> String {
3333+ self.modified_at
3434+ .format(frontmatter::DATE_FMT_STR)
3535+ .to_string()
3636+ }
2237}
23382439impl From<GroupModelEx> for Group {
···2742 _private: (),
2843 id: value.nano_id,
2944 name: value.name,
3030- color: value.color.into(),
3145 priority: value.priority.into(),
4646+ parent_id: value.parent_group_id,
3247 created_at: value.created_at,
3348 modified_at: value.modified_at,
3449 zettel: value
···3853 "When fetching a Group from the database, we expect to always have the Zettel loaded!!",
3954 )
4055 .into(),
5656+ tag: value
5757+ .tag
5858+ .into_option()
5959+ .expect(
6060+ "When fetching a Group from the database, we expect to always have the Tag loaded!!",
6161+ )
6262+ .into(),
4163 }
4264 }
4365}
+12-12
src/types/index.rs
src/types/kasten/index/mod.rs
···1818#[derive(Debug, Clone)]
1919pub struct Index {
2020 pub(super) zods: HashMap<ZettelId, ZettelOnDisk>,
2121- pub(super) outgoing_links: HashMap<ZettelId, Vec<Link>>,
2121+ pub outgoing_links: HashMap<ZettelId, Vec<Link>>,
2222 // pub(super) incoming_links: HashMap<ZettelId, Vec<Link>>,
2323}
2424···230230 Ok(())
231231 }
232232233233+ pub fn get_links(&self, zid: &ZettelId) -> &Vec<Link> {
234234+ self.outgoing_links
235235+ .get(zid)
236236+ .expect("Invariant broken. Any zid we look up exist inside this map")
237237+ }
238238+239239+ pub fn sync_with_db(&self, _db: &DatabaseConnection) {
240240+ todo!()
241241+ }
242242+233243 pub fn get_zod(&self, zid: &ZettelId) -> &ZettelOnDisk {
234244 self.zods.get(zid).expect("Invariant broken. Any zid we lookup must exist in the index, otherwise the db is corrupt or not sync'd.")
235245 }
236246237237- fn get_zod_mut(&mut self, zid: &ZettelId) -> &mut ZettelOnDisk {
247247+ pub fn get_zod_mut(&mut self, zid: &ZettelId) -> &mut ZettelOnDisk {
238248 self.zods.get_mut(zid).expect("Invariant broken. Any zid we lookup must exist in the index, otherwise the db is corrupt or not sync'd.")
239249 }
240250241241- pub fn get_links(&self, zid: &ZettelId) -> &Vec<Link> {
242242- self.outgoing_links
243243- .get(zid)
244244- .expect("Invariant broken. Any zid we look up exist inside this map")
245245- }
246246-247251 pub const fn zods(&self) -> &HashMap<ZettelId, ZettelOnDisk> {
248252 &self.zods
249249- }
250250-251251- pub fn sync_with_db(&self, _db: &DatabaseConnection) {
252252- todo!()
253253 }
254254}
+14-2
src/types/kasten.rs
src/types/kasten/mod.rs
···1111};
1212use tracing::debug;
13131414-use crate::types::{FrontMatter, Index, ZettelId, index::ZettelOnDisk};
1414+use crate::types::{FrontMatter, ZettelId};
1515+1616+mod index;
1717+pub use index::Index;
1818+pub use index::ZettelOnDisk;
1919+mod todo_tree;
2020+2121+pub use todo_tree::{TodoNode, TodoNodeKind, TodoTree};
15221616-#[derive(Debug, Clone)]
2323+#[derive(Debug)]
1724pub struct Kasten {
1825 /// Private field so it can only be instantiated from a `Path`
1926 _private: (),
···2128 pub root: PathBuf,
22292330 pub index: Index,
3131+3232+ pub todo_tree: TodoTree,
24332534 pub db: DatabaseConnection,
2635}
···5463 // run da migrations every time we connect, just in case
5564 Migrator::up(&conn, None).await?;
56656666+ let todo_tree = TodoTree::construct(&conn).await?;
6767+5768 Ok(Self {
5869 _private: (),
5970 db: conn,
6071 root,
6172 index,
7373+ todo_tree,
6274 })
6375 }
6476
···1515pub use group::Group;
16161717mod task;
1818-#[expect(unused_imports)]
1918pub use task::Task;
20192120mod link;
···2423mod filaments;
2524pub use filaments::Filaments;
26252727-mod index;
2828-pub use index::Index;
2929-3026mod kasten;
2727+pub use kasten::Index;
3128pub use kasten::Kasten;
3229pub use kasten::KastenHandle;
3030+pub use kasten::TodoNode;
3131+pub use kasten::TodoNodeKind;
3232+pub use kasten::TodoTree;
33333434mod frontmatter;
3535pub use frontmatter::FrontMatter;
+19-4
src/types/priority.rs
···11+use std::fmt::Display;
22+13use dto::PriorityDTO;
2435/// An Enum for the various `Priority` levels
46/// for `Task`s and `Group`s
55-#[expect(dead_code)]
66-#[derive(Debug, Clone)]
77-pub struct Priority(PriorityDTO);
77+#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
88+pub struct Priority {
99+ field1: PriorityDTO,
1010+}
811912impl From<PriorityDTO> for Priority {
1013 fn from(value: PriorityDTO) -> Self {
1111- Self(value)
1414+ Self { field1: value }
1515+ }
1616+}
1717+1818+impl From<Priority> for PriorityDTO {
1919+ fn from(value: Priority) -> Self {
2020+ value.field1
2121+ }
2222+}
2323+2424+impl Display for Priority {
2525+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2626+ write!(f, "{}", self.field1)
1227 }
1328}
+1-1
src/types/tag.rs
···4455/// Represents a `Tag` in a `ZettelKasten` note taking method.
66/// Easy way to link multiple notes under one simple word.
77-#[derive(Debug, Clone)]
77+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
88pub struct Tag {
99 /// Should only be constructed from models.
1010 _private: (),
···1919/// A `Zettel` is a note about a single idea.
2020/// It can have many `Tag`s, just meaning it can fall under many
2121/// categories.
2222-#[derive(Debug, Clone)]
2222+2323+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
2324pub struct Zettel {
2425 /// Should only be constructed from models.
2526 _private: (),
···4142 .map(Into::into))
4243 }
43444444- pub async fn new(title: impl Into<String>, kt: &mut Kasten) -> Result<Self> {
4545+ pub async fn new(title: impl Into<String>, kt: &mut Kasten, tags: Vec<Tag>) -> Result<Self> {
4546 // fn new(title: impl Into<String>) -> Result<Self> {
4647 let title = title.into();
4748···6061 format!("Failed to create file at local file path: {local_file_path}")
6162 })?;
62636363- let inserted = ZettelActiveModel::builder()
6464- .set_title(title.clone())
6565- .set_nano_id(nano_id)
6666- .insert(&kt.db)
6767- .await?;
6464+ let inserted = {
6565+ let mut am = ZettelActiveModel::builder()
6666+ .set_title(title.clone())
6767+ .set_nano_id(nano_id);
6868+6969+ for tag in tags {
7070+ let tag = TagEntity::load()
7171+ .filter_by_nano_id(tag.id)
7272+ .one(&kt.db)
7373+ .await?
7474+ .expect("Invariant broken, tag must exist");
7575+ am = am.add_tag(tag);
7676+ }
7777+7878+ am.insert(&kt.db).await?
7979+ };
68806981 // need to load tags...
7082 let zettel = ZettelEntity::load()