max / goingson
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
25 files changed,
+1299 insertions,
-26 deletions
| @@ -2224,6 +2224,7 @@ | |||
| 2224 | 2224 | "async-trait", | |
| 2225 | 2225 | "chrono", | |
| 2226 | 2226 | "chrono-tz", | |
| 2227 | + | "painhours", | |
| 2227 | 2228 | "serde", | |
| 2228 | 2229 | "serde_json", | |
| 2229 | 2230 | "sha2 0.11.0", | |
| @@ -4074,6 +4075,14 @@ | |||
| 4074 | 4075 | "thiserror 2.0.18", | |
| 4075 | 4076 | ] | |
| 4076 | 4077 | ||
| 4078 | + | [[package]] | |
| 4079 | + | name = "painhours" | |
| 4080 | + | version = "0.1.0" | |
| 4081 | + | dependencies = [ | |
| 4082 | + | "chrono", | |
| 4083 | + | "serde", | |
| 4084 | + | ] | |
| 4085 | + | ||
| 4077 | 4086 | [[package]] | |
| 4078 | 4087 | name = "palette" | |
| 4079 | 4088 | version = "0.7.6" |
| @@ -112,6 +112,9 @@ | |||
| 112 | 112 | # Tag standard | |
| 113 | 113 | tagtree = { path = "../../MNW/shared/tagtree" } | |
| 114 | 114 | ||
| 115 | + | # Shared urgency model (also used by MNW/wam), so problems and tickets rank alike | |
| 116 | + | painhours = { path = "../../MNW/shared/painhours" } | |
| 117 | + | ||
| 115 | 118 | # MCP bridge (go-mcp) | |
| 116 | 119 | kberg = { path = "../../MNW/shared/kberg", default-features = false, features = ["server"] } | |
| 117 | 120 |
| @@ -18,6 +18,7 @@ | |||
| 18 | 18 | strum_macros = { workspace = true } | |
| 19 | 19 | sqlx = { workspace = true, features = ["sqlite", "uuid"], optional = true } | |
| 20 | 20 | tagtree = { workspace = true } | |
| 21 | + | painhours = { workspace = true } | |
| 21 | 22 | sha2 = { workspace = true } | |
| 22 | 23 | ||
| 23 | 24 | [lints] |
| @@ -61,3 +61,4 @@ | |||
| 61 | 61 | 058 8c800b52c85af7f107d7706f4d5c3dfcc3a44f5877a606da23bb3e1df0fa4812f34f2fc78999b16bcab9a6491ff78512 | |
| 62 | 62 | 059 32140c4ee5bb75b9d6530a0b7c60cef872acc5fa50bb969eb9368036131d4f84931cddbcf6f8b41038fb0d5fc175af7b | |
| 63 | 63 | 060 e5b833cff6c768710b0daf88fc9cf087938278ec158c99c0789242878832e802ae00e82bef2cc45efe21644de8d9fa57 | |
| 64 | + | 061 8a37aa99045c49df8e54d36227702e68f570abb870114da14b31760cd0a1433cdd8f0d234808ec0d0da22672bc7fcb11 |
| @@ -55,6 +55,12 @@ | |||
| 55 | 55 | let weekly_reviews = state.weekly_reviews.list_all(user_id).await?; | |
| 56 | 56 | let monthly_goals = state.monthly_reviews.list_all_goals(user_id).await?; | |
| 57 | 57 | let monthly_reflections = state.monthly_reviews.list_all_reflections(user_id).await?; | |
| 58 | + | // Unfiltered: a backup captures settled problems too, since dismissals and | |
| 59 | + | // promotions are the part that cannot be re-pulled. | |
| 60 | + | let problems = state | |
| 61 | + | .problems | |
| 62 | + | .list(user_id, &goingson_core::ProblemFilter::default()) | |
| 63 | + | .await?; | |
| 58 | 64 | Ok(FullExport::new( | |
| 59 | 65 | projects, | |
| 60 | 66 | tasks, | |
| @@ -70,6 +76,7 @@ | |||
| 70 | 76 | weekly_reviews, | |
| 71 | 77 | monthly_goals, | |
| 72 | 78 | monthly_reflections, | |
| 79 | + | problems, | |
| 73 | 80 | )) | |
| 74 | 81 | } | |
| 75 | 82 |
| @@ -3,16 +3,17 @@ | |||
| 3 | 3 | use goingson_core::{ | |
| 4 | 4 | AttachmentRepository, BackupSettingsRepository, ContactRepository, DailyNoteRepository, | |
| 5 | 5 | EmailAccountRepository, EmailRepository, EventRepository, MilestoneRepository, | |
| 6 | - | MonthlyReviewRepository, ProjectRepository, SavedViewRepository, SearchRepository, | |
| 7 | - | StatsRepository, SyncAccountRepository, TaskRepository, WeeklyReviewRepository, | |
| 6 | + | MonthlyReviewRepository, ProblemRepository, ProjectRepository, SavedViewRepository, | |
| 7 | + | SearchRepository, StatsRepository, SyncAccountRepository, TaskRepository, | |
| 8 | + | WeeklyReviewRepository, | |
| 8 | 9 | }; | |
| 9 | 10 | use goingson_db_sqlite::{ | |
| 10 | 11 | SqliteAttachmentRepository, SqliteBackupSettingsRepository, SqliteContactRepository, | |
| 11 | 12 | SqliteDailyNoteRepository, SqliteEmailAccountRepository, SqliteEmailRepository, | |
| 12 | 13 | SqliteEventRepository, SqliteMilestoneRepository, SqliteMonthlyReviewRepository, | |
| 13 | - | SqliteProjectRepository, SqliteSavedViewRepository, SqliteSearchRepository, | |
| 14 | - | SqliteStatsRepository, SqliteSyncAccountRepository, SqliteTaskRepository, | |
| 15 | - | SqliteWeeklyReviewRepository, | |
| 14 | + | SqliteProblemRepository, SqliteProjectRepository, SqliteSavedViewRepository, | |
| 15 | + | SqliteSearchRepository, SqliteStatsRepository, SqliteSyncAccountRepository, | |
| 16 | + | SqliteTaskRepository, SqliteWeeklyReviewRepository, | |
| 16 | 17 | }; | |
| 17 | 18 | use sqlx::SqlitePool; | |
| 18 | 19 | use std::path::PathBuf; | |
| @@ -43,6 +44,7 @@ | |||
| 43 | 44 | pub stats: Arc<dyn StatsRepository>, | |
| 44 | 45 | pub search: Arc<dyn SearchRepository>, | |
| 45 | 46 | pub milestones: Arc<dyn MilestoneRepository>, | |
| 47 | + | pub problems: Arc<dyn ProblemRepository>, | |
| 46 | 48 | pub saved_views: Arc<dyn SavedViewRepository>, | |
| 47 | 49 | pub weekly_reviews: Arc<dyn WeeklyReviewRepository>, | |
| 48 | 50 | pub monthly_reviews: Arc<dyn MonthlyReviewRepository>, | |
| @@ -143,6 +145,7 @@ | |||
| 143 | 145 | let stats = Arc::new(SqliteStatsRepository::new(pool.clone())); | |
| 144 | 146 | let search = Arc::new(SqliteSearchRepository::new(pool.clone())); | |
| 145 | 147 | let milestones = Arc::new(SqliteMilestoneRepository::new(pool.clone())); | |
| 148 | + | let problems = Arc::new(SqliteProblemRepository::new(pool.clone())); | |
| 146 | 149 | let saved_views = Arc::new(SqliteSavedViewRepository::new(pool.clone())); | |
| 147 | 150 | let weekly_reviews = Arc::new(SqliteWeeklyReviewRepository::new(pool.clone())); | |
| 148 | 151 | let monthly_reviews = Arc::new(SqliteMonthlyReviewRepository::new(pool.clone())); | |
| @@ -195,6 +198,7 @@ | |||
| 195 | 198 | stats, | |
| 196 | 199 | search, | |
| 197 | 200 | milestones, | |
| 201 | + | problems, | |
| 198 | 202 | saved_views, | |
| 199 | 203 | weekly_reviews, | |
| 200 | 204 | monthly_reviews, |
| @@ -55,6 +55,9 @@ | |||
| 55 | 55 | ||
| 56 | 56 | let state = AppState { | |
| 57 | 57 | pool: pool.clone(), | |
| 58 | + | problems: Arc::new(goingson_db_sqlite::SqliteProblemRepository::new( | |
| 59 | + | pool.clone(), | |
| 60 | + | )), | |
| 58 | 61 | projects: Arc::new(SqliteProjectRepository::new(pool.clone())), | |
| 59 | 62 | tasks: Arc::new(SqliteTaskRepository::new(pool.clone())), | |
| 60 | 63 | events: Arc::new(SqliteEventRepository::new(pool.clone())), |
| @@ -8,8 +8,8 @@ | |||
| 8 | 8 | ||
| 9 | 9 | use crate::Contact; | |
| 10 | 10 | use crate::models::{ | |
| 11 | - | Attachment, DailyNote, Email, Event, Milestone, MonthlyGoal, MonthlyReflection, Project, | |
| 12 | - | SavedView, SyncAccount, Task, TimeSession, WeeklyReview, | |
| 11 | + | Attachment, DailyNote, Email, Event, Milestone, MonthlyGoal, MonthlyReflection, Problem, | |
| 12 | + | Project, SavedView, SyncAccount, Task, TimeSession, WeeklyReview, | |
| 13 | 13 | }; | |
| 14 | 14 | ||
| 15 | 15 | /// Result of a restore operation. | |
| @@ -49,6 +49,10 @@ | |||
| 49 | 49 | pub monthly_goals_restored: usize, | |
| 50 | 50 | /// Number of monthly reflections restored. | |
| 51 | 51 | pub monthly_reflections_restored: usize, | |
| 52 | + | /// Number of problems restored. The mirrored rows re-pull on their own, but | |
| 53 | + | /// the triage state on them (dismissed, promoted, and the task backlink) | |
| 54 | + | /// does not, so problems are backed up rather than excluded. | |
| 55 | + | pub problems_restored: usize, | |
| 52 | 56 | } | |
| 53 | 57 | ||
| 54 | 58 | /// Pre-parsed backup data for restoration. | |
| @@ -67,6 +71,7 @@ | |||
| 67 | 71 | pub weekly_reviews: Vec<WeeklyReview>, | |
| 68 | 72 | pub monthly_goals: Vec<MonthlyGoal>, | |
| 69 | 73 | pub monthly_reflections: Vec<MonthlyReflection>, | |
| 74 | + | pub problems: Vec<Problem>, | |
| 70 | 75 | } | |
| 71 | 76 | ||
| 72 | 77 | #[cfg(test)] | |
| @@ -99,6 +104,7 @@ | |||
| 99 | 104 | weekly_reviews: vec![], | |
| 100 | 105 | monthly_goals: vec![], | |
| 101 | 106 | monthly_reflections: vec![], | |
| 107 | + | problems: vec![], | |
| 102 | 108 | }; | |
| 103 | 109 | assert!(input.projects.is_empty()); | |
| 104 | 110 | assert!(input.tasks.is_empty()); |