the kebab menu calls the date on tasks a due date, but it's not a due date... it's just a date. can we make sure the codebase, documentation and everything is consistent about this?

- Renamed `due_date` field on Task struct to `date` (Rust, TypeScript, all usages)
- Renamed `group_by_due_date` field on TaskList/ListMetadata to `group_by_date`
- Renamed `set_group_by_due_date`/`get_group_by_due_date` methods to `set_group_by_date`/`get_group_by_date` in repository, Tauri commands, and JS store
- Renamed `with_due_date()` builder method to `with_date()`
- Renamed `parse_due_date` CLI function to `parse_date`
- Updated UI text "Group by due date" → "Group by date" in TasksScreen.svelte kebab menu
- Renamed JS variables `dueDate`/`dueDateHasTime` → `date`/`dateHasTime` in NewTaskInput.svelte
- Updated all test names and assertions across models.rs and repository.rs
- Updated CLAUDE.md documentation to use "date" terminology consistently
Close kebab menu when toggling subtasks

When toggling the "show subtasks" option from the main panel kebab menu,
the menu remained open which could obscure UI and lead to unexpected
interactions. Ensure that opening/closing the subtasks list also closes
the kebab (showListMenu = false) so the menu is dismissed when the user
chooses to view subtasks.
can we animate opening and closing of the kebab menus? Also, lets move the "NO DATE" section when selecting Group By Date to the top of the list before OVERDUE

- app.css: added CSS @starting-style + display transition on .dropdown-menu for open/close scale+fade animation
- app.svelte.ts: moved "No Date" group to the top (before "Overdue") in groupedPendingTasks
This commit is contained in:
Tristan Michael 2026-04-14 07:19:27 -07:00
parent 6a4b79801b
commit afedac7d32
7 changed files with 36 additions and 24 deletions

View file

@ -248,7 +248,7 @@ body {
border-color: rgba(0, 0, 0, 0.5);
}
/* ── Dropdown/kebab menu shadow ──────────────────────────────────── */
/* ── Dropdown/kebab menu shadow + open/close animation ───────────── */
.menu-shadow {
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.12);
@ -257,6 +257,18 @@ body {
box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.3);
}
.dropdown-menu {
transition: opacity 120ms ease, transform 120ms ease, display 120ms ease allow-discrete;
transform-origin: top right;
opacity: 1;
transform: scale(1);
@starting-style {
opacity: 0;
transform: scale(0.92);
}
}
/* ── Borderless mode: square corners on all popups/overlays ─────── */
.decorations-none .rounded-sm,

View file

@ -139,8 +139,8 @@
<!-- Date picker overlay -->
{#if showDatePicker}
<DateTimePicker
value={dueDate}
has_time={dueDateHasTime}
value={date}
has_time={dateHasTime}
onchange={handleDateChange}
onclose={() => (showDatePicker = false)}
/>

View file

@ -118,12 +118,12 @@
renamingListId = null;
}
async function handleToggleGroupByDueDate() {
async function handleToggleGroupByDate() {
showListMenu = false;
if (!app.activeListId) return;
var list = app.lists.find(l => l.id === app.activeListId);
if (!list) return;
await app.setGroupByDueDate(app.activeListId, !list.group_by_due_date);
await app.setGroupByDate(app.activeListId, !list.group_by_date);
}
function handleKeydown(e: KeyboardEvent) {
@ -188,16 +188,16 @@
const targetGroup = app.groupedPendingTasks?.find((g) => g.label === group);
const task = app.pendingTasks.find((t) => t.id === taskId);
if (task && targetGroup !== undefined) {
let newDueDate: string | null = null;
let newDate: string | null = null;
if (targetGroup.date !== null) {
const target = new Date(targetGroup.date);
if (task.has_time && task.due_date) {
const existing = new Date(task.due_date);
if (task.has_time && task.date) {
const existing = new Date(task.date);
target.setHours(existing.getHours(), existing.getMinutes(), existing.getSeconds(), 0);
}
newDueDate = target.toISOString();
newDate = target.toISOString();
}
await app.updateTask({ ...task, due_date: newDueDate, has_time: newDueDate ? task.has_time : false });
await app.updateTask({ ...task, date: newDate, has_time: newDate ? task.has_time : false });
}
}
@ -554,21 +554,21 @@
</button>
{/if}
<button
onclick={handleToggleGroupByDueDate}
onclick={handleToggleGroupByDate}
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-black/5 dark:hover:bg-white/10"
>
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd" />
</svg>
Group by due date
{#if app.activeList?.group_by_due_date}
Group by date
{#if app.activeList?.group_by_date}
<svg class="ml-auto h-4 w-4 text-primary" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" />
</svg>
{/if}
</button>
<button
onclick={() => (showSubtasks = !showSubtasks)}
onclick={() => { showSubtasks = !showSubtasks; showListMenu = false; }}
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-black/5 dark:hover:bg-white/10"
>
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">

View file

@ -95,6 +95,7 @@ let groupedPendingTasks = $derived.by((): TaskGroup[] | null => {
tomorrow.sort(sortByDue);
const groups: TaskGroup[] = [];
if (noDate.length) groups.push({ label: "No Date", tasks: noDate, date: null });
if (overdue.length) groups.push({ label: "Overdue", tasks: overdue, date: null });
if (today.length) groups.push({ label: "Today", tasks: today, date: todayStart });
if (tomorrow.length) groups.push({ label: "Tomorrow", tasks: tomorrow, date: tomorrowStart });
@ -108,7 +109,6 @@ let groupedPendingTasks = $derived.by((): TaskGroup[] | null => {
groups.push({ label: date.toLocaleDateString(undefined, opts), tasks, date });
}
if (noDate.length) groups.push({ label: "No Date", tasks: noDate, date: null });
return groups;
});

View file

@ -310,8 +310,8 @@ pub async fn sync_google_tasks(
TaskStatus::Backlog
};
// Google Tasks due dates are date-only (time is always T00:00:00Z).
let due_date = gt_task.due.as_deref()
// Google Tasks dates are date-only (time is always T00:00:00Z).
let date = gt_task.due.as_deref()
.and_then(|s| s.parse::<DateTime<Utc>>().ok());
let parent_id = gt_task.parent.as_deref().map(gt_id_to_uuid);
@ -321,7 +321,7 @@ pub async fn sync_google_tasks(
title: gt_task.title.clone(),
description: gt_task.notes.clone(),
status,
due_date,
date,
has_time: false,
version: 1,
parent_id,
@ -441,7 +441,7 @@ fn render_task_markdown(task: &Task) -> String {
TaskStatus::Completed => "completed",
};
let mut yaml = format!("id: {}\nstatus: {}\nversion: 1\n", task.id, status_str);
if let Some(due) = task.due_date {
if let Some(due) = task.date {
yaml.push_str(&format!("due: {}\n", due.to_rfc3339()));
}
if let Some(parent) = task.parent_id {

View file

@ -166,10 +166,10 @@ mod tests {
}
#[test]
fn test_task_with_due_date() {
fn test_task_with_date() {
let dt = Utc::now();
let task = Task::new("T".to_string()).with_due_date(dt);
assert_eq!(task.due_date, Some(dt));
let task = Task::new("T".to_string()).with_date(dt);
assert_eq!(task.date, Some(dt));
}
#[test]
@ -242,7 +242,7 @@ mod tests {
let list = TaskList::new("My List".to_string());
assert_eq!(list.title, "My List");
assert!(list.tasks.is_empty());
assert!(!list.group_by_due_date);
assert!(!list.group_by_date);
assert!(list.created_at <= Utc::now());
assert!(list.updated_at <= Utc::now());
}

View file

@ -102,7 +102,7 @@ impl From<&Task> for TaskFrontmatter {
Self {
id: task.id,
status: task.status,
due: task.due_date,
due: task.date,
has_time: task.has_time,
version: task.version,
parent: task.parent_id,