Rename due_date to date across codebase

The kebab menu and docs referred to a task "due date" but the field was
just a date; this change renames due_date/group_by_due_date and related
identifiers to date/group_by_date across Rust, TypeScript, Svelte, CLI,
docs and tests to keep terminology consistent. Updates include
API/command names, storage/models, repository methods, UI text, JS
variables and builder/parse functions so code, tests and documentation
all use "date" semantics.
Preserve old "group_by_due_date" field name

Add serde alias to ListMetadata.group_by_date so older .listdata.json
files that used the previous field name (group_by_due_date) can still be
deserialized correctly. This fixes serialization/deserialization issues
encountered at /home/trztn/Documents/Onyx and
/var/home/trztn/Nextcloud/Onyx.
the frontmatter due should be date... I don't want due anywhere

- Renamed `TaskFrontmatter.due` → `TaskFrontmatter.date`; YAML key on disk is now `date:` instead of `due:`
- Added `#[serde(alias = "due")]` so existing task files with `due:` frontmatter still deserialize correctly
- Updated google_tasks.rs to write `date:` instead of `due:` in generated YAML
- Renamed CLI `--due` flag to `--date`; updated function signature and display string "Due:" → "Date:"
Remove serde aliases and rename due→date

Drop backwards-compat aliases and update frontmatter/metadata to use the
canonical "date"/"group_by_date" fields. The aliases for
group_by_due_date and due were removed to avoid maintaining
backward-compatibility and to reflect the current schema. Updated
storage types to rename the fields and adjusted serialization attributes
so YAML/JSON frontmatter and .listdata.json files now use group_by_date
and date consistently.
This commit is contained in:
Tristan Michael 2026-04-14 07:19:27 -07:00
parent a0c183df82
commit 9ed84690ac
14 changed files with 88 additions and 88 deletions

View file

@ -56,7 +56,7 @@ The GUI uses Svelte 5 runes mode (`$state`, `$derived`, `$effect`, `$props()`).
- **Task animations**: Grid-rows `0fr`/`1fr` trick for smooth collapse/expand. Module-level `animateInIds` Set coordinates expand-in after toggle.
- **Inline editing**: Click task to edit, auto-save on blur. `debouncedSave` snapshots task before timer to prevent stale-reference errors on component destroy.
- **Kebab menus**: Tasks and lists use kebab menus with custom `ConfirmDialog` component (not native `confirm()`). "Move to..." is inline in the menu (not a submenu) to avoid overflow.
- **Main panel header**: Hamburger + window controls in top bar; list name (large, bold) + kebab below divider (matching task detail layout). Kebab has Rename, Group by due date, Delete completed, Delete list.
- **Main panel header**: Hamburger + window controls in top bar; list name (large, bold) + kebab below divider (matching task detail layout). Kebab has Rename, Group by date, Delete completed, Delete list.
- **New task**: FAB button opens bottom toast sheet (outside sliding container for fixed positioning).
### Development phase
@ -81,10 +81,10 @@ Pre-alpha. No users, no released builds, no data to migrate. Breaking changes to
- Workspace switcher drop-up with add/remove
- Per-workspace theme system (System default, Light, Dark, Nord, Dracula, Solarized Dark) via CSS `data-theme` attribute
- Completed tasks section with animated show/hide
- Due date picker/editor (DateTimePicker in new task + task detail); `has_time: bool` field tracks whether time is set
- Date picker/editor (DateTimePicker in new task + task detail); `has_time: bool` field tracks whether time is set
- Move task between lists (inline list in kebab menu, no submenu)
- List rename (inline input in main panel header via kebab)
- Group-by-due-date toggle per list (main panel kebab; persists flag but display sorting not yet implemented)
- Group-by-date toggle per list (main panel kebab; persists flag but display sorting not yet implemented)
- Delete completed tasks (main panel kebab + subtask kebab, with confirmation dialogs)
- Keyboard shortcuts (Escape priority chain: settings → detail → list menu → drawer → menus)
- Setup screen with 2-step mode selection (Local Folder vs WebDAV Server), window dragging, "Open Existing Folder" option, remote folder browsing

View file

@ -488,7 +488,7 @@ fn rename_list(
}
#[tauri::command]
fn set_group_by_due_date(
fn set_group_by_date(
list_id: String,
enabled: bool,
state: State<'_, Mutex<AppState>>,
@ -498,12 +498,12 @@ fn set_group_by_due_date(
mute_watcher(&mut s);
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
repo_mut(&mut s)?
.set_group_by_due_date(id, enabled)
.set_group_by_date(id, enabled)
.map_err(|e| e.to_string())
}
#[tauri::command]
fn get_group_by_due_date(
fn get_group_by_date(
list_id: String,
state: State<'_, Mutex<AppState>>,
) -> Result<bool, String> {
@ -511,7 +511,7 @@ fn get_group_by_due_date(
ensure_repo(&mut s)?;
let id = Uuid::parse_str(&list_id).map_err(|e| e.to_string())?;
repo_ref(&s)?
.get_group_by_due_date(id)
.get_group_by_date(id)
.map_err(|e| e.to_string())
}
@ -924,8 +924,8 @@ pub fn run() {
reorder_task,
move_task,
rename_list,
set_group_by_due_date,
get_group_by_due_date,
set_group_by_date,
get_group_by_date,
set_webdav_config,
set_workspace_theme,
set_sync_interval,

View file

@ -9,21 +9,21 @@
let title = $state("");
let description = $state("");
let dueDate = $state<string | null>(null);
let dueDateHasTime = $state(false);
let date = $state<string | null>(null);
let dateHasTime = $state(false);
let inputEl = $state<HTMLInputElement | null>(null);
let showDatePicker = $state(false);
async function handleSubmit() {
if (!title.trim()) return;
const created = await app.createTask(title.trim(), description.trim() || undefined);
if (dueDate && created) {
await app.updateTask({ ...created, due_date: dueDate, has_time: dueDateHasTime });
if (date && created) {
await app.updateTask({ ...created, date: date, has_time: dateHasTime });
}
title = "";
description = "";
dueDate = null;
dueDateHasTime = false;
date = null;
dateHasTime = false;
newTaskState.open = false;
}
@ -31,14 +31,14 @@
newTaskState.open = false;
title = "";
description = "";
dueDate = null;
dueDateHasTime = false;
date = null;
dateHasTime = false;
showDatePicker = false;
}
function handleDateChange(iso: string | null, hasTime: boolean = false) {
dueDate = iso;
dueDateHasTime = hasTime;
date = iso;
dateHasTime = hasTime;
}
function formatDateChip(iso: string): string {
@ -47,7 +47,7 @@
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const day = dayNames[d.getDay()];
const pad = (n: number) => String(n).padStart(2, "0");
const timePart = dueDateHasTime ? `, ${pad(d.getHours())}:${pad(d.getMinutes())}` : "";
const timePart = dateHasTime ? `, ${pad(d.getHours())}:${pad(d.getMinutes())}` : "";
if (d.toDateString() === today.toDateString()) return `Today${timePart}`;
return `${day}, ${pad(d.getDate())}/${pad(d.getMonth() + 1)}${timePart}`;
}
@ -102,12 +102,12 @@
<svg class="h-5 w-5 shrink-0 opacity-40" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd" />
</svg>
{#if dueDate}
{#if date}
<div class="flex items-center gap-1.5 rounded-full border border-border-light bg-black/5 px-3 py-1 text-sm dark:border-border-dark dark:bg-white/10">
<button type="button" onclick={() => (showDatePicker = true)} class="hover:opacity-70">
{formatDateChip(dueDate)}
{formatDateChip(date)}
</button>
<button type="button" onclick={() => (dueDate = null)} class="opacity-40 hover:opacity-80">
<button type="button" onclick={() => (date = null)} class="opacity-40 hover:opacity-80">
<svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
</svg>

View file

@ -48,7 +48,7 @@
}
function handleDateChange(iso: string | null, hasTime: boolean = false) {
app.updateTask({ ...task, due_date: iso, has_time: hasTime });
app.updateTask({ ...task, date: iso, has_time: hasTime });
}
async function handleToggle() {
@ -237,10 +237,10 @@
<svg class="h-5 w-5 shrink-0 opacity-40" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd" />
</svg>
{#if task.due_date}
{#if task.date}
<div class="flex items-center gap-1.5 rounded-full border border-border-light bg-black/5 px-3 py-1 text-sm dark:border-border-dark dark:bg-white/10">
<button onclick={() => (showDatePicker = true)} class="hover:opacity-70">
{formatDateChip(task.due_date)}
{formatDateChip(task.date)}
</button>
<button onclick={() => handleDateChange(null)} class="opacity-40 hover:opacity-80">
<svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">
@ -388,7 +388,7 @@
<!-- Date picker overlay -->
{#if showDatePicker}
<DateTimePicker
value={task.due_date}
value={task.date}
has_time={task.has_time}
onchange={handleDateChange}
onclose={() => (showDatePicker = false)}

View file

@ -150,14 +150,14 @@
{#if task.description}
<p class="mt-0.5 text-xs opacity-40 line-clamp-1">{task.description}</p>
{/if}
{#if task.due_date && dateChipStyle !== "hidden"}
{#if task.date && dateChipStyle !== "hidden"}
{#if dateChipStyle === "overdue"}
<span class="mt-1 inline-block rounded-full border border-danger px-2 py-0.5 text-xs text-danger opacity-80">
{formatDate(task.due_date)}
{formatDate(task.date)}
</span>
{:else}
<span class="mt-1 inline-block rounded-full border border-border-light px-2 py-0.5 text-xs opacity-50 dark:border-border-dark">
{formatDate(task.due_date)}
{formatDate(task.date)}
</span>
{/if}
{/if}

View file

@ -55,7 +55,7 @@ let completedTasks = $derived(tasks.filter((t) => t.status === "completed" && !t
type TaskGroup = { label: string; tasks: Task[]; date: Date | null };
let groupedPendingTasks = $derived.by((): TaskGroup[] | null => {
if (!activeList?.group_by_due_date) return null;
if (!activeList?.group_by_date) return null;
const now = new Date();
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const tomorrowStart = new Date(todayStart);
@ -68,10 +68,10 @@ let groupedPendingTasks = $derived.by((): TaskGroup[] | null => {
const noDate: Task[] = [];
for (const task of pendingTasks) {
if (!task.due_date) {
if (!task.date) {
noDate.push(task);
} else {
const d = new Date(task.due_date);
const d = new Date(task.date);
const dayStart = new Date(d.getFullYear(), d.getMonth(), d.getDate());
if (dayStart < todayStart) overdue.push(task);
else if (dayStart.getTime() === todayStart.getTime()) today.push(task);
@ -86,7 +86,7 @@ let groupedPendingTasks = $derived.by((): TaskGroup[] | null => {
const taskOrderIndex = new Map(pendingTasks.map((t, i) => [t.id, i]));
const sortByDue = (a: Task, b: Task) => {
const dateDiff = new Date(a.due_date!).getTime() - new Date(b.due_date!).getTime();
const dateDiff = new Date(a.date!).getTime() - new Date(b.date!).getTime();
if (dateDiff !== 0) return dateDiff;
return (taskOrderIndex.get(a.id) ?? 0) - (taskOrderIndex.get(b.id) ?? 0);
};
@ -395,11 +395,11 @@ async function renameList(listId: string, newName: string) {
}
}
async function setGroupByDueDate(listId: string, enabled: boolean) {
async function setGroupByDate(listId: string, enabled: boolean) {
try {
await invoke("set_group_by_due_date", { listId, enabled });
await invoke("set_group_by_date", { listId, enabled });
lists = lists.map((l) =>
l.id === listId ? { ...l, group_by_due_date: enabled } : l,
l.id === listId ? { ...l, group_by_date: enabled } : l,
);
if (listId === activeListId) await loadTasks();
} catch (e) {
@ -656,7 +656,7 @@ export const app = {
deleteTask,
moveTask,
renameList,
setGroupByDueDate,
setGroupByDate,
triggerSync,
startAutoSync,
stopAutoSync,

View file

@ -3,7 +3,7 @@ export interface Task {
title: string;
description: string;
status: "backlog" | "completed";
due_date: string | null;
date: string | null;
has_time: boolean;
version: number;
parent_id: string | null;
@ -15,7 +15,7 @@ export interface TaskList {
tasks: Task[];
created_at: string;
updated_at: string;
group_by_due_date: boolean;
group_by_date: boolean;
}
export type WorkspaceMode = "local" | "webdav";

View file

@ -12,10 +12,10 @@ pub fn enable(list_name: String, workspace: Option<String>) -> Result<()> {
.find(|l| l.title == list_name)
.ok_or_else(|| anyhow::anyhow!("List '{}' not found", list_name))?;
repo.set_group_by_due_date(list.id, true)
repo.set_group_by_date(list.id, true)
.context("Failed to enable grouping")?;
output::success(&format!("Enabled group-by-due-date for list \"{}\"", list_name));
output::success(&format!("Enabled group-by-date for list \"{}\"", list_name));
Ok(())
}
@ -30,10 +30,10 @@ pub fn disable(list_name: String, workspace: Option<String>) -> Result<()> {
.find(|l| l.title == list_name)
.ok_or_else(|| anyhow::anyhow!("List '{}' not found", list_name))?;
repo.set_group_by_due_date(list.id, false)
repo.set_group_by_date(list.id, false)
.context("Failed to disable grouping")?;
output::success(&format!("Disabled group-by-due-date for list \"{}\"", list_name));
output::success(&format!("Disabled group-by-date for list \"{}\"", list_name));
Ok(())
}

View file

@ -11,7 +11,7 @@ fn print_tasks(tasks: &[Task]) {
}
for task in tasks {
let checkbox = if task.status == TaskStatus::Completed { "[✓]".green() } else { "[ ]".normal() };
let due_str = task.due_date.map(|d| format!(" (due: {})", d.format("%Y-%m-%d")).yellow().to_string()).unwrap_or_default();
let due_str = task.date.map(|d| format!(" ({})", d.format("%Y-%m-%d")).yellow().to_string()).unwrap_or_default();
output::item(&format!("{} {}{} {}", checkbox, task.title, due_str, task.id.to_string().dimmed()));
}
}

View file

@ -5,7 +5,7 @@ use uuid::Uuid;
use crate::output;
use crate::commands::get_repository;
pub fn add(title: String, list_name: Option<String>, due_str: Option<String>, workspace: Option<String>) -> Result<()> {
pub fn add(title: String, list_name: Option<String>, date_str: Option<String>, workspace: Option<String>) -> Result<()> {
let (mut repo, _workspace_name) = get_repository(workspace)?;
// Get lists
@ -29,18 +29,18 @@ pub fn add(title: String, list_name: Option<String>, due_str: Option<String>, wo
// Create task
let mut task = Task::new(title.clone());
// Parse due date if provided
if let Some(due_str) = due_str {
let due_date = parse_due_date(&due_str)?;
task.due_date = Some(due_date);
// Parse date if provided
if let Some(due_str) = date_str {
let date = parse_date(&due_str)?;
task.date = Some(date);
}
// Save task
repo.create_task(list.id, task.clone())
.context("Failed to create task")?;
let due_info = if let Some(due) = task.due_date {
format!("\n Due: {}", due.format("%Y-%m-%d"))
let due_info = if let Some(due) = task.date {
format!("\n Date: {}", due.format("%Y-%m-%d"))
} else {
String::new()
};
@ -204,7 +204,7 @@ pub fn edit(task_id_str: String, workspace: Option<String>) -> Result<()> {
Ok(())
}
fn parse_due_date(s: &str) -> Result<DateTime<Utc>> {
fn parse_date(s: &str) -> Result<DateTime<Utc>> {
// Try parsing as date only (YYYY-MM-DD)
if let Ok(naive_date) = chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") {
let naive_datetime = naive_date.and_hms_opt(0, 0, 0)

View file

@ -39,9 +39,9 @@ enum Commands {
/// List to add task to
#[arg(short, long)]
list: Option<String>,
/// Due date (ISO 8601 format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS)
/// Date (ISO 8601 format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS)
#[arg(short, long)]
due: Option<String>,
date: Option<String>,
/// Workspace to use
#[arg(short, long)]
workspace: Option<String>,
@ -74,7 +74,7 @@ enum Commands {
workspace: Option<String>,
},
/// Toggle group-by-due-date for a list
/// Toggle group-by-date for a list
#[command(subcommand)]
Group(GroupCommands),
@ -176,7 +176,7 @@ enum ListCommands {
#[derive(Subcommand)]
enum GroupCommands {
/// Enable group-by-due-date for a list
/// Enable group-by-date for a list
Enable {
/// Name of the list
#[arg(short, long)]
@ -186,7 +186,7 @@ enum GroupCommands {
workspace: Option<String>,
},
/// Disable group-by-due-date for a list
/// Disable group-by-date for a list
Disable {
/// Name of the list
#[arg(short, long)]
@ -235,8 +235,8 @@ fn main() -> Result<()> {
list::delete(name, workspace)?;
}
},
Commands::Add { title, list, due, workspace } => {
task::add(title, list, due, workspace)?;
Commands::Add { title, list, date, workspace } => {
task::add(title, list, date, workspace)?;
}
Commands::Complete { task_id, workspace } => {
task::complete(task_id, workspace)?;

View file

@ -16,7 +16,7 @@ pub struct Task {
pub description: String,
pub status: TaskStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub due_date: Option<DateTime<Utc>>,
pub date: Option<DateTime<Utc>>,
#[serde(default)]
pub has_time: bool,
pub version: u64,
@ -31,7 +31,7 @@ impl Task {
title,
description: String::new(),
status: TaskStatus::Backlog,
due_date: None,
date: None,
has_time: false,
version: 0,
parent_id: None,
@ -43,8 +43,8 @@ impl Task {
self
}
pub fn with_due_date(mut self, due_date: DateTime<Utc>) -> Self {
self.due_date = Some(due_date);
pub fn with_date(mut self, date: DateTime<Utc>) -> Self {
self.date = Some(date);
self
}
@ -69,7 +69,7 @@ pub struct TaskList {
pub tasks: Vec<Task>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub group_by_due_date: bool,
pub group_by_date: bool,
}
impl TaskList {
@ -81,7 +81,7 @@ impl TaskList {
tasks: Vec::new(),
created_at: now,
updated_at: now,
group_by_due_date: false,
group_by_date: false,
}
}
@ -152,7 +152,7 @@ mod tests {
assert_eq!(task.title, "My Task");
assert_eq!(task.description, "");
assert_eq!(task.status, TaskStatus::Backlog);
assert!(task.due_date.is_none());
assert!(task.date.is_none());
assert!(!task.has_time);
assert_eq!(task.version, 0);
assert!(task.parent_id.is_none());
@ -197,12 +197,12 @@ mod tests {
let dt = Utc::now();
let task = Task::new("Chained".to_string())
.with_description("Desc".to_string())
.with_due_date(dt)
.with_date(dt)
.with_parent(parent_id);
assert_eq!(task.title, "Chained");
assert_eq!(task.description, "Desc");
assert_eq!(task.due_date, Some(dt));
assert_eq!(task.date, Some(dt));
assert_eq!(task.parent_id, Some(parent_id));
}
@ -231,7 +231,7 @@ mod tests {
fn test_task_serde_skips_none_fields() {
let task = Task::new("Minimal".to_string());
let json = serde_json::to_string(&task).unwrap();
assert!(!json.contains("due_date"));
assert!(!json.contains("\"date\""));
assert!(!json.contains("parent_id"));
}

View file

@ -118,17 +118,17 @@ impl TaskRepository {
}
// Grouping preference
pub fn set_group_by_due_date(&mut self, list_id: Uuid, enabled: bool) -> Result<()> {
pub fn set_group_by_date(&mut self, list_id: Uuid, enabled: bool) -> Result<()> {
let mut metadata = self.storage.read_list_metadata(list_id)?;
metadata.group_by_due_date = enabled;
metadata.group_by_date = enabled;
metadata.updated_at = chrono::Utc::now();
self.storage.write_list_metadata(&metadata)?;
Ok(())
}
pub fn get_group_by_due_date(&self, list_id: Uuid) -> Result<bool> {
pub fn get_group_by_date(&self, list_id: Uuid) -> Result<bool> {
let metadata = self.storage.read_list_metadata(list_id)?;
Ok(metadata.group_by_due_date)
Ok(metadata.group_by_date)
}
}
@ -214,19 +214,19 @@ mod tests {
}
#[test]
fn test_group_by_due_date() {
fn test_group_by_date() {
let temp_dir = TempDir::new().unwrap();
let mut repo = TaskRepository::init(temp_dir.path().to_path_buf()).unwrap();
let list = repo.create_list("Test List".to_string()).unwrap();
assert!(!repo.get_group_by_due_date(list.id).unwrap());
assert!(!repo.get_group_by_date(list.id).unwrap());
repo.set_group_by_due_date(list.id, true).unwrap();
assert!(repo.get_group_by_due_date(list.id).unwrap());
repo.set_group_by_date(list.id, true).unwrap();
assert!(repo.get_group_by_date(list.id).unwrap());
repo.set_group_by_due_date(list.id, false).unwrap();
assert!(!repo.get_group_by_due_date(list.id).unwrap());
repo.set_group_by_date(list.id, false).unwrap();
assert!(!repo.get_group_by_date(list.id).unwrap());
}
// --- Error path tests ---

View file

@ -62,7 +62,7 @@ pub struct ListMetadata {
pub id: Uuid,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub group_by_due_date: bool,
pub group_by_date: bool,
pub task_order: Vec<Uuid>,
}
@ -73,7 +73,7 @@ impl ListMetadata {
id,
created_at: now,
updated_at: now,
group_by_due_date: false,
group_by_date: false,
task_order: Vec::new(),
}
}
@ -88,7 +88,7 @@ pub struct TaskFrontmatter {
pub id: Uuid,
pub status: TaskStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub due: Option<DateTime<Utc>>,
pub date: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "is_false")]
pub has_time: bool,
#[serde(default = "default_version")]
@ -360,7 +360,7 @@ impl Storage for FileSystemStorage {
title,
description,
status: frontmatter.status,
due_date: frontmatter.due,
date: frontmatter.date,
has_time: frontmatter.has_time,
version: frontmatter.version,
parent_id: frontmatter.parent,
@ -456,7 +456,7 @@ impl Storage for FileSystemStorage {
title,
description,
status: frontmatter.status,
due_date: frontmatter.due,
date: frontmatter.date,
has_time: frontmatter.has_time,
version: frontmatter.version,
parent_id: frontmatter.parent,
@ -547,7 +547,7 @@ impl Storage for FileSystemStorage {
tasks: Vec::new(),
created_at: list_metadata.created_at,
updated_at: list_metadata.updated_at,
group_by_due_date: list_metadata.group_by_due_date,
group_by_date: list_metadata.group_by_date,
};
Ok(task_list)
@ -582,7 +582,7 @@ impl Storage for FileSystemStorage {
tasks,
created_at: list_metadata.created_at,
updated_at: list_metadata.updated_at,
group_by_due_date: list_metadata.group_by_due_date,
group_by_date: list_metadata.group_by_date,
};
lists.push(task_list);
@ -761,7 +761,7 @@ mod tests {
let content = "---\nid: 550e8400-e29b-41d4-a716-446655440000\nstatus: backlog\ndue: 2026-06-15T12:00:00Z\nversion: 2\nparent: 660e8400-e29b-41d4-a716-446655440001\n---\n\nNotes";
let (fm, _) = storage.parse_markdown_with_frontmatter(content).unwrap();
assert!(fm.due.is_some());
assert!(fm.date.is_some());
assert!(fm.parent.is_some());
}