Todoist-style: priorities P1-P4, estimates, FAB add form, Today view, natural quick add, drag-and-drop with haptics
All checks were successful
Build & Release APK / build (push) Successful in 1m53s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 19:46:11 -07:00
parent 035a114e58
commit 0487bc7e4e
17 changed files with 752 additions and 123 deletions

View File

@@ -78,7 +78,7 @@ function now() {
// Fields the API accepts — everything else (timestamps, completed_at) is local
// bookkeeping until the server's authoritative row arrives via pull().
const TASK_FIELDS = ["title", "notes", "status", "project_id", "context", "waiting_for", "due_date", "defer_date", "sort_order"] as const;
const TASK_FIELDS = ["title", "notes", "status", "project_id", "context", "waiting_for", "due_date", "defer_date", "priority", "estimate_min", "sort_order"] as const;
const PROJECT_FIELDS = ["name", "status", "notes", "sort_order"] as const;
function pickFields<T>(data: Partial<T>, fields: readonly (keyof T & string)[]): Partial<T> {
@@ -111,6 +111,8 @@ export function createTask(data: Partial<Task> & { title: string }): Task {
waiting_for: data.waiting_for ?? null,
due_date: data.due_date ?? null,
defer_date: data.defer_date ?? null,
priority: data.priority ?? 4,
estimate_min: data.estimate_min ?? null,
completed_at: null,
sort_order: data.sort_order ?? 0,
created_at: now(),
@@ -237,10 +239,30 @@ async function pull() {
// ---- selectors ----
function byPriorityDue(a: Task, b: Task): number {
return (
a.priority - b.priority ||
(a.due_date ?? "9999").localeCompare(b.due_date ?? "9999") ||
a.sort_order - b.sort_order ||
b.created_at.localeCompare(a.created_at)
);
}
export function tasksByStatus(s: State, status: Task["status"]): Task[] {
return s.tasks
.filter((t) => t.status === status)
.sort((a, b) => a.sort_order - b.sort_order || b.created_at.localeCompare(a.created_at));
return s.tasks.filter((t) => t.status === status).sort(byPriorityDue);
}
/** Overdue + due-today + deferred-arriving-today, across all open lists. */
export function todayTasks(s: State): { overdue: Task[]; today: Task[] } {
const now = new Date();
const todayIso = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
const open = s.tasks.filter((t) => t.status !== "done" && t.status !== "trashed");
return {
overdue: open.filter((t) => t.due_date && t.due_date < todayIso).sort(byPriorityDue),
today: open
.filter((t) => t.due_date === todayIso || (t.defer_date === todayIso && !t.due_date))
.sort(byPriorityDue),
};
}
export function activeProjects(s: State): Project[] {