Skip to main content

max / goingson

Adopt lint block, pin stable toolchain, cargo fmt, fix clippy Wire the shared clippy::pedantic block across all members, pin channel=stable, normalize with cargo fmt, and reach green under -D warnings: #[must_use] on builders, write! over format!-push, let-else, by-value Copy sort keys, Path-based ext checks. Scoped #[allow]s (with reasons) for Tauri command-handler args, map_err callbacks, ToString builders, and exact float compares in tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-24 15:28 UTC
Commit: fb5dc3bee94ce0efe9f59c6a00d22f92ebf88ad4
Parent: 78b5a09
248 files changed, +2047 insertions, -2201 deletions
M Cargo.toml +32
@@ -118,3 +118,35 @@ kberg = { path = "../../MNW/shared/kberg", default-features = false, features =
118 118 # Internal crates
119 119 goingson-core = { path = "crates/core" }
120 120 goingson-db-sqlite = { path = "crates/db-sqlite" }
121 +
122 + [workspace.lints.rust]
123 + unused = "warn"
124 + unreachable_pub = "warn"
125 +
126 + [workspace.lints.clippy]
127 + pedantic = { level = "warn", priority = -1 }
128 + # Allow-list tuned from a measured breakdown across server/multithreaded/pter
129 + # (2026-07-22). These are the high-churn / low-signal pedantic lints; everything
130 + # else in `pedantic` stays a warning. Keep this block identical across repos.
131 + module_name_repetitions = "allow"
132 + # Doc lints. No docs-completeness push is underway.
133 + missing_errors_doc = "allow"
134 + missing_panics_doc = "allow"
135 + doc_markdown = "allow"
136 + # Numeric casts. Endemic and mostly intentional in size and byte math.
137 + cast_possible_truncation = "allow"
138 + cast_sign_loss = "allow"
139 + cast_precision_loss = "allow"
140 + cast_possible_wrap = "allow"
141 + cast_lossless = "allow"
142 + # Subjective structure and style nags. High churn, low signal.
143 + must_use_candidate = "allow"
144 + too_many_lines = "allow"
145 + struct_excessive_bools = "allow"
146 + similar_names = "allow"
147 + items_after_statements = "allow"
148 + single_match_else = "allow"
149 + # Frequent false-positives in TUI and router-heavy code.
150 + match_same_arms = "allow"
151 + unnecessary_wraps = "allow"
152 + type_complexity = "allow"
@@ -19,3 +19,6 @@ strum_macros = { workspace = true }
19 19 sqlx = { workspace = true, features = ["sqlite", "uuid"], optional = true }
20 20 tagtree = { workspace = true }
21 21 sha2 = { workspace = true }
22 +
23 + [lints]
24 + workspace = true
@@ -3,7 +3,7 @@
3 3 //! This module centralizes magic numbers and configuration values
4 4 //! to improve maintainability and documentation.
5 5
6 - // ============ Time Constants ============
6 + // Time Constants
7 7
8 8 /// Hours in a day.
9 9 pub const HOURS_PER_DAY: f64 = 24.0;
@@ -14,7 +14,7 @@ pub const DAYS_PER_WEEK: i64 = 7;
14 14 /// Approximate days in a month (for relative date calculations).
15 15 pub const APPROXIMATE_DAYS_PER_MONTH: i64 = 30;
16 16
17 - // ============ Parser Defaults ============
17 + // Parser Defaults
18 18
19 19 /// Default hour for parsed dates (9:00 AM).
20 20 pub const DEFAULT_PARSE_HOUR: u32 = 9;
@@ -22,7 +22,7 @@ pub const DEFAULT_PARSE_HOUR: u32 = 9;
22 22 /// Default minute for parsed dates.
23 23 pub const DEFAULT_PARSE_MINUTE: u32 = 0;
24 24
25 - // ============ Urgency Thresholds ============
25 + // Urgency Thresholds
26 26
27 27 /// Urgency score threshold for "high" classification (on a 0–10 scale).
28 28 /// Scores are computed by `calculate_urgency()` from priority, due date
@@ -34,21 +34,21 @@ pub const URGENCY_HIGH_THRESHOLD: f64 = 8.0;
34 34 /// Tasks between 5.0 and 8.0 get "urgency-medium"; below 5.0 gets "urgency-low".
35 35 pub const URGENCY_MEDIUM_THRESHOLD: f64 = 5.0;
36 36
37 - // ============ Display Thresholds ============
37 + // Display Thresholds
38 38
39 39 /// Number of days for short-form due date display (e.g., "+3d" instead of "Mar 15").
40 40 /// Used by `TaskResponse::from()` and `GoingsOn.utils.formatDue()` in the frontend
41 41 /// to decide between relative ("today", "+3d") and absolute ("Mar 15") formats.
42 42 pub const DAYS_THRESHOLD_SHORT_FORMAT: i64 = 7;
43 43
44 - // ============ Preview Lengths ============
44 + // Preview Lengths
45 45
46 46 /// Maximum length for email body preview snippets in the email list view.
47 47 /// Truncates the plain-text body to this many characters for the compact
48 48 /// thread listing, with an ellipsis appended if truncated.
49 49 pub const EMAIL_BODY_PREVIEW_LENGTH: usize = 100;
50 50
51 - // ============ Validation Limits ============
51 + // Validation Limits
52 52
53 53 /// Maximum length for project names.
54 54 pub const MAX_PROJECT_NAME_LENGTH: usize = 255;
@@ -8,7 +8,7 @@ use crate::id_types::{ContactEmailId, ContactId, ContactPhoneId, CustomFieldId,
8 8 use chrono::{DateTime, NaiveDate, Utc};
9 9 use serde::{Deserialize, Serialize};
10 10
11 - // ============ Main Entity ============
11 + // Main Entity
12 12
13 13 /// A contact (person) with optional sub-collections.
14 14 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -75,11 +75,11 @@ impl Contact {
75 75 }
76 76 }
77 77
78 - // ============ Sub-collection Entities ============
78 + // Sub-collection Entities
79 79
80 80 /// A flattened (name, email) pair for compose autocomplete.
81 81 ///
82 - /// Returned by `list_email_directory` — a single JOIN that skips the per-contact
82 + /// Returned by `list_email_directory`, a single JOIN that skips the per-contact
83 83 /// sub-collection hydration the compose screen does not need.
84 84 #[derive(Debug, Clone, Serialize, Deserialize)]
85 85 #[serde(rename_all = "camelCase")]
@@ -137,7 +137,7 @@ pub struct ContactCustomField {
137 137 pub url: Option<String>,
138 138 }
139 139
140 - // ============ DTOs ============
140 + // DTOs
141 141
142 142 /// Data for creating a new contact.
143 143 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -198,7 +198,7 @@ pub struct NewContactCustomField {
198 198 pub url: Option<String>,
199 199 }
200 200
201 - // ============ Activity Feed ============
201 + // Activity Feed
202 202
203 203 /// Which kind of entity an activity item represents.
204 204 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -124,7 +124,7 @@ fn parse_weekday(
124 124 default_time: NaiveTime,
125 125 ) -> Option<NaiveDateTime> {
126 126 let is_next = tokens.first() == Some(&"next");
127 - let name_idx = if is_next { 1 } else { 0 };
127 + let name_idx = usize::from(is_next);
128 128 let target = WEEKDAYS
129 129 .iter()
130 130 .position(|d| Some(d) == tokens.get(name_idx))? as i64;
@@ -18,10 +18,10 @@ use chrono_tz::Tz;
18 18 /// rather than panicking (mirrors `tz::local_civil_to_utc` in the app crate).
19 19 pub fn civil_midnight_utc(date: NaiveDate, tz: Tz) -> DateTime<Utc> {
20 20 let naive = date.and_hms_opt(0, 0, 0).expect("midnight is always valid");
21 - tz.from_local_datetime(&naive)
22 - .earliest()
23 - .map(|dt| dt.with_timezone(&Utc))
24 - .unwrap_or_else(|| DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc))
21 + tz.from_local_datetime(&naive).earliest().map_or_else(
22 + || DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc),
23 + |dt| dt.with_timezone(&Utc),
24 + )
25 25 }
26 26
27 27 /// Formats a date relative to now, bidirectional: "2d ago", "today", "tomorrow", "+3d", "Mar 15".
@@ -38,7 +38,7 @@ pub fn format_relative_date(dt: DateTime<Utc>, now: DateTime<Utc>) -> String {
38 38 } else if diff_days == 1 {
39 39 "tomorrow".to_string()
40 40 } else if diff_days < 7 {
41 - format!("+{}d", diff_days)
41 + format!("+{diff_days}d")
42 42 } else {
43 43 dt.format("%b %d").to_string()
44 44 }
@@ -54,7 +54,7 @@ pub fn format_relative_future(dt: DateTime<Utc>, now: DateTime<Utc>) -> String {
54 54 } else if diff_days == 1 {
55 55 "tomorrow".to_string()
56 56 } else if diff_days < 7 {
57 - format!("+{}d", diff_days)
57 + format!("+{diff_days}d")
58 58 } else {
59 59 dt.format("%b %d").to_string()
60 60 }
@@ -69,11 +69,11 @@ pub fn format_elapsed_time(dt: DateTime<Utc>, now: DateTime<Utc>) -> String {
69 69 if hours < 1 {
70 70 "Just now".to_string()
71 71 } else if hours < 24 {
72 - format!("{}h ago", hours)
72 + format!("{hours}h ago")
73 73 } else {
74 74 let days = diff.num_days();
75 75 if days < 7 {
76 - format!("{}d ago", days)
76 + format!("{days}d ago")
77 77 } else {
78 78 dt.with_timezone(&Local).format("%b %d").to_string()
79 79 }
@@ -91,7 +91,7 @@ mod tests {
91 91 Utc.from_utc_datetime(&date.and_time(time))
92 92 }
93 93
94 - // ---- format_relative_date ----
94 + // format_relative_date
95 95
96 96 #[test]
97 97 fn relative_date_today() {
@@ -149,7 +149,7 @@ mod tests {
149 149 assert_eq!(format_relative_date(dt, now), "Apr 22");
150 150 }
151 151
152 - // ---- format_relative_future ----
152 + // format_relative_future
153 153
154 154 #[test]
155 155 fn relative_future_today() {
@@ -179,7 +179,7 @@ mod tests {
179 179 assert_eq!(format_relative_future(dt, now), "Jun 01");
180 180 }
181 181
182 - // ---- format_elapsed_time ----
182 + // format_elapsed_time
183 183
184 184 #[test]
185 185 fn elapsed_just_now() {
@@ -80,7 +80,7 @@ mod tests {
80 80 TimelineItem {
81 81 id: Uuid::new_v4(),
82 82 item_type: "event".to_string(),
83 - title: format!("Event at {}:{:02}", hour, minute),
83 + title: format!("Event at {hour}:{minute:02}"),
84 84 start_time: start,
85 85 end_time: Some(start + Duration::minutes(duration_mins as i64)),
86 86 duration: Some(duration_mins),
@@ -1,6 +1,6 @@
1 1 //! Reply/forward compose-prefill construction.
2 2 //!
3 - //! Building a reply or forward is domain logic — recipient assembly with
3 + //! Building a reply or forward is domain logic, recipient assembly with
4 4 //! own-address exclusion, `Re:`/`Fwd:` subject prefixing, and body quoting.
5 5 //! It lives here in `core` (not the JS frontend) so the rules are uniform,
6 6 //! unit-tested, and driven by the authoritative account list rather than a
@@ -45,7 +45,7 @@ fn prefix_once(subject: &str, prefix: &str) -> String {
45 45 if already {
46 46 subject.to_string()
47 47 } else {
48 - format!("{} {}", prefix, subject)
48 + format!("{prefix} {subject}")
49 49 }
50 50 }
51 51
@@ -89,10 +89,10 @@ pub fn reply_recipients(from: &str, to: &str, own_addresses: &[String], reply_al
89 89 pub fn quoted_reply_body(from: &str, date: &str, body: &str) -> String {
90 90 let quoted: String = body
91 91 .split('\n')
92 - .map(|l| format!("> {}", l))
92 + .map(|l| format!("> {l}"))
93 93 .collect::<Vec<_>>()
94 94 .join("\n");
95 - format!("\n\nOn {}, {} wrote:\n>\n{}", date, from, quoted)
95 + format!("\n\nOn {date}, {from} wrote:\n>\n{quoted}")
96 96 }
97 97
98 98 /// Build the body for a forwarded message: a header block followed by the
@@ -100,12 +100,11 @@ pub fn quoted_reply_body(from: &str, date: &str, body: &str) -> String {
100 100 pub fn forward_body(from: &str, date: &str, subject: &str, to: &str, body: &str) -> String {
101 101 format!(
102 102 "\n\n---------- Forwarded message ----------\n\
103 - From: {}\n\
104 - Date: {}\n\
105 - Subject: {}\n\
106 - To: {}\n\n\
107 - {}",
108 - from, date, subject, to, body
103 + From: {from}\n\
104 + Date: {date}\n\
105 + Subject: {subject}\n\
106 + To: {to}\n\n\
107 + {body}"
109 108 )
110 109 }
111 110
@@ -138,7 +138,7 @@ pub async fn process_fetched_emails(
138 138 });
139 139 }
140 140
141 - // Batch insert — single transaction, no post-insert SELECTs
141 + // Batch insert, single transaction, no post-insert SELECTs
142 142 result.emails_saved = email_repo
143 143 .create_with_tracking_batch(user_id, new_emails)
144 144 .await?;
@@ -65,6 +65,10 @@ impl CoreError {
65 65 }
66 66
67 67 /// Creates a not-found error with resource type and identifier.
68 + #[allow(
69 + clippy::needless_pass_by_value,
70 + reason = "generic `impl ToString` builder contract: callers pass Copy id newtypes, &str literals, and owned String by value at ~90 sites; a borrow would force `&` everywhere for no gain"
71 + )]
68 72 pub fn not_found(resource: &'static str, id: impl ToString) -> Self {
69 73 CoreError::NotFound {
70 74 resource,
@@ -180,7 +180,7 @@ mod tests {
180 180 let uuid = Uuid::new_v4();
181 181 let task_id = TaskId::from(uuid);
182 182 let project_id = ProjectId::from(uuid);
183 - // Same inner value but different types — can't accidentally swap them.
183 + // Same inner value but different types, can't accidentally swap them.
184 184 assert_eq!(*task_id, *project_id);
185 185 }
186 186
@@ -89,7 +89,7 @@ pub fn mime_from_extension(filename: &str) -> &'static str {
89 89 /// Format a byte count as a human-readable string.
90 90 pub fn format_file_size(bytes: i64) -> String {
91 91 if bytes < 1024 {
92 - format!("{} B", bytes)
92 + format!("{bytes} B")
93 93 } else if bytes < 1024 * 1024 {
94 94 format!("{:.1} KB", bytes as f64 / 1024.0)
95 95 } else if bytes < 1024 * 1024 * 1024 {
@@ -126,9 +126,9 @@ mod tests {
126 126 assert_eq!(format_file_size(512), "512 B");
127 127 assert_eq!(format_file_size(1024), "1.0 KB");
128 128 assert_eq!(format_file_size(1536), "1.5 KB");
129 - assert_eq!(format_file_size(1048576), "1.0 MB");
130 - assert_eq!(format_file_size(1572864), "1.5 MB");
131 - assert_eq!(format_file_size(1073741824), "1.0 GB");
129 + assert_eq!(format_file_size(1_048_576), "1.0 MB");
130 + assert_eq!(format_file_size(1_572_864), "1.5 MB");
131 + assert_eq!(format_file_size(1_073_741_824), "1.0 GB");
132 132 }
133 133
134 134 #[test]
@@ -4,7 +4,7 @@ use chrono::{DateTime, Utc};
4 4 use serde::{Deserialize, Serialize};
5 5 use uuid::Uuid;
6 6
7 - // ============ Backup Settings ============
7 + // Backup Settings
8 8
9 9 /// User's backup configuration.
10 10 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -11,7 +11,7 @@ use crate::id_types::{EmailAccountId, EmailId, ProjectId};
11 11 use chrono::{DateTime, Utc};
12 12 use serde::{Deserialize, Serialize};
13 13
14 - // ============ Email ============
14 + // Email
15 15
16 16 /// An email message synced from IMAP or sent via SMTP.
17 17 ///
@@ -39,7 +39,7 @@ pub struct Email {
39 39 /// body can be re-fetched on demand via the provider id.
40 40 pub body_truncated: bool,
41 41 /// Provider email id for JMAP accounts, used to re-fetch a truncated body
42 - /// (internal — never serialized to the frontend).
42 + /// (internal, never serialized to the frontend).
43 43 #[serde(skip_serializing)]
44 44 pub jmap_id: Option<String>,
45 45 /// Whether the email has been read.
@@ -100,9 +100,9 @@ impl Email {
100 100 if hours < 1 {
101 101 "Just now".to_string()
102 102 } else if hours < 24 {
103 - format!("{}h ago", hours)
103 + format!("{hours}h ago")
104 104 } else if days < DAYS_THRESHOLD_SHORT_FORMAT {
105 - format!("{}d ago", days)
105 + format!("{days}d ago")
106 106 } else {
107 107 self.received_at.format("%b %d").to_string()
108 108 }
@@ -143,9 +143,7 @@ impl Email {
143 143
144 144 /// Returns true if the email is currently snoozed (snoozed_until is in the future).
145 145 pub fn is_snoozed(&self) -> bool {
146 - self.snoozed_until
147 - .map(|until| until > Utc::now())
148 - .unwrap_or(false)
146 + self.snoozed_until.is_some_and(|until| until > Utc::now())
149 147 }
150 148
151 149 /// Returns true if the email is waiting for a reply.
@@ -158,8 +156,7 @@ impl Email {
158 156 self.waiting_for_response
159 157 && self
160 158 .expected_response_date
161 - .map(|date| date < Utc::now())
162 - .unwrap_or(false)
159 + .is_some_and(|date| date < Utc::now())
163 160 }
164 161 }
165 162
@@ -177,7 +174,7 @@ pub struct EmailThread {
177 174 pub has_unread: bool,
178 175 }
179 176
180 - // ============ Email DTOs ============
177 + // Email DTOs
181 178
182 179 #[cfg(test)]
183 180 mod tests {
@@ -352,7 +349,7 @@ mod tests {
352 349 let mut email = make_email();
353 350 email.received_at = Utc::now() - Duration::days(30);
354 351 let formatted = email.received_formatted();
355 - // Should be like "Jan 26" — not "30d ago"
352 + // Should be like "Jan 26", not "30d ago"
356 353 assert!(!formatted.contains("ago"));
357 354 }
358 355 }
@@ -7,7 +7,7 @@ use strum_macros::EnumString;
7 7
8 8 use super::shared::DbValue;
9 9
10 - // ============ Email Account ============
10 + // Email Account
11 11
12 12 /// Authentication method for email accounts.
13 13 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, EnumString)]
@@ -11,7 +11,7 @@ use crate::id_types::{ContactId, EventId, ProjectId, TaskId, UserId};
11 11 use chrono::{DateTime, Duration, TimeZone, Timelike, Utc};
12 12 use serde::{Deserialize, Serialize};
13 13
14 - // ============ Event ============
14 + // Event
15 15
16 16 /// A calendar event with optional time-blocking link to a task.
17 17 ///
@@ -178,11 +178,11 @@ impl Event {
178 178 /// *exclusive* local-midnight boundary after the last covered day:
179 179 /// - no end given → the day after the start (a single-day span);
180 180 /// - an end already at local midnight is treated as already-exclusive and kept
181 - /// as-is (so re-snapping a stored all-day span is idempotent — it does not grow
181 + /// as-is (so re-snapping a stored all-day span is idempotent, it does not grow
182 182 /// by a day on every edit);
183 183 /// - a mid-day end covers the whole of its day, so it rounds up to the next midnight.
184 184 ///
185 - /// Both bounds are returned as UTC instants — the shape [`Event::is_all_day_in`]
185 + /// Both bounds are returned as UTC instants, the shape [`Event::is_all_day_in`]
186 186 /// detects, so a snapped event round-trips as all-day.
187 187 ///
188 188 /// On a DST spring-forward gap the civil midnight doesn't exist; it falls back to
@@ -215,13 +215,13 @@ pub fn snap_all_day_span<Tz: TimeZone>(
215 215 /// Convert a calendar date's local midnight in `tz` to the corresponding UTC instant.
216 216 fn local_midnight_utc<Tz: TimeZone>(date: chrono::NaiveDate, tz: &Tz) -> DateTime<Utc> {
217 217 let naive = date.and_hms_opt(0, 0, 0).expect("midnight is always valid");
218 - tz.from_local_datetime(&naive)
219 - .earliest()
220 - .map(|dt| dt.with_timezone(&Utc))
221 - .unwrap_or_else(|| DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc))
218 + tz.from_local_datetime(&naive).earliest().map_or_else(
219 + || DateTime::<Utc>::from_naive_utc_and_offset(naive, Utc),
220 + |dt| dt.with_timezone(&Utc),
221 + )
222 222 }
223 223
224 - // ============ Event DTOs ============
224 + // Event DTOs
225 225
226 226 /// Data for creating a new event.
227 227 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -342,60 +342,70 @@ impl NewEventBuilder {
342 342 }
343 343
344 344 /// Sets the user ID.
345 + #[must_use]
345 346 pub fn user_id(mut self, user_id: UserId) -> Self {
346 347 self.user_id = Some(user_id);
347 348 self
348 349 }
349 350
350 351 /// Sets the project ID.
352 + #[must_use]
351 353 pub fn project_id(mut self, project_id: ProjectId) -> Self {
352 354 self.project_id = Some(project_id);
353 355 self
354 356 }
355 357
356 358 /// Sets the contact ID.
359 + #[must_use]
357 360 pub fn contact_id(mut self, contact_id: ContactId) -> Self {
358 361 self.contact_id = Some(contact_id);
359 362 self
360 363 }
361 364
362 365 /// Sets the description.
366 + #[must_use]
363 367 pub fn description(mut self, description: impl Into<String>) -> Self {
364 368 self.description = description.into();
365 369 self
366 370 }
367 371
368 372 /// Sets the end time.
373 + #[must_use]
369 374 pub fn end_time(mut self, end_time: DateTime<Utc>) -> Self {
370 375 self.end_time = Some(end_time);
371 376 self
372 377 }
373 378
374 379 /// Sets the location.
380 + #[must_use]
375 381 pub fn location(mut self, location: impl Into<String>) -> Self {
376 382 self.location = Some(location.into());
377 383 self
378 384 }
379 385
380 386 /// Sets the linked task ID (for time-blocking).
387 + #[must_use]
381 388 pub fn linked_task_id(mut self, task_id: TaskId) -> Self {
382 389 self.linked_task_id = Some(task_id);
383 390 self
384 391 }
385 392
386 393 /// Sets the recurrence pattern.
394 + #[must_use]
387 395 pub fn recurrence(mut self, recurrence: Recurrence) -> Self {
388 396 self.recurrence = recurrence;
389 397 self
390 398 }
391 399
392 400 /// Sets the rich recurrence rule.
401 + #[must_use]
393 402 pub fn recurrence_rule(mut self, rule: RecurrenceRule) -> Self {
394 403 self.recurrence_rule = Some(rule);
395 404 self
396 405 }
397 406
398 407 /// Sets the block type.
408 + #[must_use]
399 409 pub fn block_type(mut self, block_type: BlockType) -> Self {
400 410 self.block_type = Some(block_type);
401 411 self
@@ -521,7 +531,7 @@ mod is_all_day_tests {
521 531 #[test]
522 532 fn snap_keeps_midnight_end_exclusive_no_growth() {
523 533 // Editing an existing single-day all-day event (stored 00:00..next-00:00)
524 - // must not grow it by a day — the midnight end is already exclusive.
534 + // must not grow it by a day, the midnight end is already exclusive.
525 535 let start = Utc.with_ymd_and_hms(2026, 7, 4, 0, 0, 0).unwrap();
526 536 let end = Utc.with_ymd_and_hms(2026, 7, 5, 0, 0, 0).unwrap();
527 537 let (s, e) = snap_all_day_span(start, Some(end), &Utc);
@@ -6,7 +6,7 @@ use chrono::{DateTime, Utc};
6 6 use serde::{Deserialize, Serialize};
7 7 use strum_macros::EnumString;
8 8
9 - // ============ Milestones ============
9 + // Milestones
10 10
11 11 /// Lifecycle status of a milestone.
12 12 // `ascii_case_insensitive` so the lowercase `db_value()` form ("open"/"completed")
@@ -4,7 +4,7 @@ use crate::id_types::{MonthlyGoalId, MonthlyReflectionId, UserId};
4 4 use chrono::{DateTime, Utc};
5 5 use serde::{Deserialize, Serialize};
6 6
7 - // ============ Monthly Goal ============
7 + // Monthly Goal
8 8
9 9 /// Status of a monthly goal.
10 10 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -38,7 +38,7 @@ impl std::str::FromStr for MonthlyGoalStatus {
38 38 }
39 39 }
40 40
41 - /// A monthly goal — free-text item set at the start of each month.
41 + /// A monthly goal, free-text item set at the start of each month.
42 42 #[derive(Debug, Clone, Serialize, Deserialize)]
43 43 #[serde(rename_all = "camelCase")]
44 44 pub struct MonthlyGoal {
@@ -54,7 +54,7 @@ pub struct MonthlyGoal {
54 54 pub updated_at: DateTime<Utc>,
55 55 }
56 56
57 - // ============ Monthly Reflection ============
57 + // Monthly Reflection
58 58
59 59 /// A monthly reflection capturing highlights and areas for improvement.
60 60 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -11,7 +11,7 @@ use chrono::{DateTime, Utc};
11 11 use serde::{Deserialize, Serialize};
12 12 use strum_macros::EnumString;
13 13
14 - // ============ Project Types ============
14 + // Project Types
15 15
16 16 /// Classification of project types.
17 17 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, EnumString)]
@@ -165,7 +165,7 @@ pub struct Project {
165 165 pub group_id: Option<String>,
166 166 }
167 167
168 - // ============ Project DTOs ============
168 + // Project DTOs
169 169
170 170 /// Data for creating a new project.
171 171 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -11,7 +11,7 @@ use strum_macros::EnumString;
11 11
12 12 use super::shared::{DbValue, ParseableEnum, SortDirection};
13 13
14 - // ============ Saved Views ============
14 + // Saved Views
15 15
16 16 /// Type of view for saved filters.
17 17 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, EnumString)]
@@ -113,7 +113,7 @@ pub struct SavedView {
113 113 /// Unique view ID.
114 114 pub id: SavedViewId,
115 115 /// User who owns this view. Not serialized into API responses; `default` lets a
116 - /// backup (which omits it) still deserialize -- restore re-homes the row to the
116 + /// backup (which omits it) still deserialize, restore re-homes the row to the
117 117 /// target user, so the placeholder is never persisted.
118 118 #[serde(default, skip_serializing)]
119 119 pub user_id: UserId,
@@ -137,7 +137,7 @@ pub struct SavedView {
137 137 pub updated_at: DateTime<Utc>,
138 138 }
139 139
140 - // ============ Saved View DTOs ============
140 + // Saved View DTOs
141 141
142 142 /// Data for creating a new saved view.
143 143 #[derive(Debug, Clone, Serialize, Deserialize)]