Skip to main content

max / goingson

A context is its own record: the state that frames days Ruled 2026-08-18. Some things occupy your day and others frame it. An occupancy consumes hours and competes for them; a context consumes none, and things happen inside it rather than beside it. Being on leave does not clash with a meeting, it changes what having a meeting means. The code kept making the distinction geometrically. clip_to_day derived is_all_day from whether a span happened to fill a calendar day, so a conference running Wed 14:00 to Fri 17:00 was an occupancy on Wednesday, a context on Thursday, and an occupancy again on Friday. Nothing about it changed but which midnights it covered. This is the record, the migration and the storage. A context is authored as a span and read per day, both ends inclusive, so "off until the 17th" means the 17th is off. Migration 067 converts, as ruled on 8ee5c4fe. A multi-day event becomes a context, where multi-day means it covers a whole calendar day -- narrower than "ends on a later date", so an overnight flight stays an occupancy and a lone all-day marker stays an event. vacation_days becomes spans, and a run crossing two weekly reviews becomes one context rather than two, because nobody recording a nine-day holiday meant two holidays. The conversion guesses, so it is reversible: the event is hidden rather than deleted, the context names it, and deleting the context puts the event back with its times intact. Nothing is reconstructed by hand. vacation_days is not dropped. Dropping it is sync-visible, and the version gate that makes a mixed-version sync safe lives in the SyncKit SDK and does not exist yet, so the column stays as a legacy mirror new code does not write. Contexts are personal rather than group-scoped: sharing "I am on leave" with a project group is a different feature with its own consent question, and adding the column later is one ALTER while taking sharing back is not.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-18 22:27 UTC
Signed with PGP, not checked
Commit: da17043ad27447a31edbe890b486fd901dc062b4
Parent: ee127ba
25 files changed, +1424 insertions, -48 deletions
M Cargo.lock +8 -8
@@ -8496,14 +8496,6 @@
8496 8496 "winnow 1.0.4",
8497 8497 ]
8498 8498
8499 - [[patch.unused]]
8500 - name = "makeover-immediate"
8501 - version = "0.30.0"
8502 -
8503 - [[patch.unused]]
8504 - name = "ops-status"
8505 - version = "0.1.0"
8506 -
8507 8499 [[patch.unused]]
8508 8500 name = "quasi-axum"
8509 8501 version = "0.29.1"
@@ -8519,3 +8511,11 @@
8519 8511 [[patch.unused]]
8520 8512 name = "quasi-store"
8521 8513 version = "0.1.0"
8514 +
8515 + [[patch.unused]]
8516 + name = "ops-status"
8517 + version = "0.1.0"
8518 +
8519 + [[patch.unused]]
8520 + name = "makeover-immediate"
8521 + version = "0.30.0"
@@ -67,3 +67,4 @@
67 67 064 00f60ab309a763fe5b417fd0fddae1b3f4c83a3e99812fa8110cfb34de0e7f908b34dd405e74e09b75085047c0a344b5
68 68 065 734a797c90c0447e413fb4fde8a19d26a52752adbea385ca30f2b2369a281bc4275e57d088a8fe73c6d5adef816a195e
69 69 066 ddea0b76234ce4d2aa4482b01ac304efb64624c854aa916ca7ad6a43bc978bce5a8f890378880c5b0d2469c8f753d50d
70 + 067 6ac04d5280e01472180139ba6a5ae83be242dbcae8ca5b1adf90c15ad01507f734047262fd987bc8eb50c238f96621d8
@@ -73,6 +73,7 @@
73 73 let contacts = state.contacts.list_all_for_backup(user_id)?;
74 74 let time_sessions = state.tasks.list_all_time_sessions(user_id)?;
75 75 let milestones = state.milestones.list_all(user_id)?;
76 + let contexts = state.contexts.list_all(user_id)?;
76 77 let daily_notes = state.daily_notes.list_all(user_id)?;
77 78 let attachments = state.attachments.list_all(user_id)?;
78 79 let sync_accounts = state.sync_accounts.list_all(user_id)?;
@@ -94,6 +95,7 @@
94 95 contacts,
95 96 time_sessions,
96 97 milestones,
98 + contexts,
97 99 daily_notes,
98 100 attachments,
99 101 sync_accounts,
@@ -153,6 +155,7 @@
153 155 state.tasks.list_all_time_sessions(user_id),
154 156 )?;
155 157 stream_one(sink, "milestones", state.milestones.list_all(user_id))?;
158 + stream_one(sink, "contexts", state.contexts.list_all(user_id))?;
156 159 stream_one(sink, "dailyNotes", state.daily_notes.list_all(user_id))?;
157 160 stream_one(sink, "attachments", state.attachments.list_all(user_id))?;
158 161 stream_one(sink, "syncAccounts", state.sync_accounts.list_all(user_id))?;
@@ -1,20 +1,20 @@
1 1 //! Application state holding database pools and repository handles.
2 2
3 3 use goingson_core::{
4 - AttachmentRepository, BackupSettingsRepository, ContactRepository, DailyNoteRepository,
5 - EmailAccountRepository, EmailRepository, EventRepository, MilestoneRepository,
6 - MonthlyReviewRepository, ProblemRepository, ProjectRepository, SavedViewRepository,
7 - SearchRepository, StatsRepository, SyncAccountRepository, TaskRepository,
4 + AttachmentRepository, BackupSettingsRepository, ContactRepository, ContextRepository,
5 + DailyNoteRepository, EmailAccountRepository, EmailRepository, EventRepository,
6 + MilestoneRepository, MonthlyReviewRepository, ProblemRepository, ProjectRepository,
7 + SavedViewRepository, SearchRepository, StatsRepository, SyncAccountRepository, TaskRepository,
8 8 WeeklyReviewRepository,
9 9 };
10 10 use goingson_db_sqlite::Db;
11 11 use goingson_db_sqlite::{
12 12 SqliteAttachmentRepository, SqliteBackupSettingsRepository, SqliteContactRepository,
13 - SqliteDailyNoteRepository, SqliteEmailAccountRepository, SqliteEmailRepository,
14 - SqliteEventRepository, SqliteMilestoneRepository, SqliteMonthlyReviewRepository,
15 - SqliteProblemRepository, SqliteProjectRepository, SqliteSavedViewRepository,
16 - SqliteSearchRepository, SqliteStatsRepository, SqliteSyncAccountRepository,
17 - SqliteTaskRepository, SqliteWeeklyReviewRepository,
13 + SqliteContextRepository, SqliteDailyNoteRepository, SqliteEmailAccountRepository,
14 + SqliteEmailRepository, SqliteEventRepository, SqliteMilestoneRepository,
15 + SqliteMonthlyReviewRepository, SqliteProblemRepository, SqliteProjectRepository,
16 + SqliteSavedViewRepository, SqliteSearchRepository, SqliteStatsRepository,
17 + SqliteSyncAccountRepository, SqliteTaskRepository, SqliteWeeklyReviewRepository,
18 18 };
19 19 use rusqlite::params;
20 20 use std::path::PathBuf;
@@ -40,6 +40,7 @@
40 40 pub emails: Arc<dyn EmailRepository>,
41 41 pub email_accounts: Arc<dyn EmailAccountRepository>,
42 42 pub contacts: Arc<dyn ContactRepository>,
43 + pub contexts: Arc<dyn ContextRepository>,
43 44 pub daily_notes: Arc<dyn DailyNoteRepository>,
44 45 pub attachments: Arc<dyn AttachmentRepository>,
45 46 pub stats: Arc<dyn StatsRepository>,
@@ -153,6 +154,7 @@
153 154 let emails = Arc::new(SqliteEmailRepository::new(db.clone()));
154 155 let email_accounts = Arc::new(SqliteEmailAccountRepository::new(db.clone()));
155 156 let contacts = Arc::new(SqliteContactRepository::new(db.clone()));
157 + let contexts = Arc::new(SqliteContextRepository::new(db.clone()));
156 158 let daily_notes = Arc::new(SqliteDailyNoteRepository::new(db.clone()));
157 159 let attachments = Arc::new(SqliteAttachmentRepository::new(db.clone()));
158 160 let stats = Arc::new(SqliteStatsRepository::new(db.clone()));
@@ -216,6 +218,7 @@
216 218 emails,
217 219 email_accounts,
218 220 contacts,
221 + contexts,
219 222 daily_notes,
220 223 attachments,
221 224 stats,
@@ -60,6 +60,7 @@
60 60 emails: Arc::new(SqliteEmailRepository::new(db.clone())),
61 61 email_accounts: Arc::new(SqliteEmailAccountRepository::new(db.clone())),
62 62 contacts: Arc::new(SqliteContactRepository::new(db.clone())),
63 + contexts: Arc::new(goingson_db_sqlite::SqliteContextRepository::new(db.clone())),
63 64 daily_notes: Arc::new(SqliteDailyNoteRepository::new(db.clone())),
64 65 attachments: Arc::new(SqliteAttachmentRepository::new(db.clone())),
65 66 stats: Arc::new(SqliteStatsRepository::new(db.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, Problem,
12 - Project, SavedView, SyncAccount, Task, TaskDependency, TimeSession, WeeklyReview,
11 + Attachment, Context, DailyNote, Email, Event, Milestone, MonthlyGoal, MonthlyReflection,
12 + Problem, Project, SavedView, SyncAccount, Task, TaskDependency, TimeSession, WeeklyReview,
13 13 };
14 14
15 15 /// Result of a restore operation.
@@ -35,6 +35,8 @@
35 35 pub time_sessions_restored: usize,
36 36 /// Number of milestones restored.
37 37 pub milestones_restored: usize,
38 + /// Number of contexts restored.
39 + pub contexts_restored: usize,
38 40 /// Number of daily notes restored.
39 41 pub daily_notes_restored: usize,
40 42 /// Number of attachment records restored.
@@ -70,6 +72,7 @@
70 72 pub contacts: Vec<Contact>,
71 73 pub time_sessions: Vec<TimeSession>,
72 74 pub milestones: Vec<Milestone>,
75 + pub contexts: Vec<Context>,
73 76 pub daily_notes: Vec<DailyNote>,
74 77 pub attachments: Vec<Attachment>,
75 78 pub sync_accounts: Vec<SyncAccount>,
@@ -104,6 +107,7 @@
104 107 contacts: vec![],
105 108 time_sessions: vec![],
106 109 milestones: vec![],
110 + contexts: vec![],
107 111 daily_notes: vec![],
108 112 attachments: vec![],
109 113 sync_accounts: vec![],
@@ -89,6 +89,7 @@
89 89 ContactId,
90 90 MilestoneId,
91 91 DailyNoteId,
92 + ContextId,
92 93 WeeklyReviewId,
93 94 MonthlyGoalId,
94 95 MonthlyReflectionId,