From 4d6031d007a4aed9e766e691b87e1f82ac1e103d Mon Sep 17 00:00:00 2001 From: Marcus Rehbock Date: Fri, 7 Aug 2026 19:10:59 -0700 Subject: [PATCH] Quadrant home screen: Next/Waiting/Someday/Projects grid replaces bottom tabs Co-Authored-By: Claude Fable 5 --- app/src/app/(tabs)/_layout.tsx | 36 ------ app/src/app/(tabs)/index.tsx | 5 - app/src/app/(tabs)/next.tsx | 5 - app/src/app/(tabs)/someday.tsx | 5 - app/src/app/(tabs)/waiting.tsx | 5 - app/src/app/_layout.tsx | 16 ++- app/src/app/index.tsx | 157 ++++++++++++++++++++++++++ app/src/app/list/[status].tsx | 32 ++++++ app/src/app/{(tabs) => }/projects.tsx | 6 +- 9 files changed, 206 insertions(+), 61 deletions(-) delete mode 100644 app/src/app/(tabs)/_layout.tsx delete mode 100644 app/src/app/(tabs)/index.tsx delete mode 100644 app/src/app/(tabs)/next.tsx delete mode 100644 app/src/app/(tabs)/someday.tsx delete mode 100644 app/src/app/(tabs)/waiting.tsx create mode 100644 app/src/app/index.tsx create mode 100644 app/src/app/list/[status].tsx rename app/src/app/{(tabs) => }/projects.tsx (95%) diff --git a/app/src/app/(tabs)/_layout.tsx b/app/src/app/(tabs)/_layout.tsx deleted file mode 100644 index 07d99ea..0000000 --- a/app/src/app/(tabs)/_layout.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Link, Tabs } from "expo-router"; -import { Text } from "react-native"; -import { C } from "../../lib/theme"; - -function icon(glyph: string) { - return ({ focused }: { focused: boolean }) => ( - {glyph} - ); -} - -export default function TabsLayout() { - return ( - ( - - ⚙️ - - ), - }} - > - - - - - - - ); -} diff --git a/app/src/app/(tabs)/index.tsx b/app/src/app/(tabs)/index.tsx deleted file mode 100644 index 3249c37..0000000 --- a/app/src/app/(tabs)/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import TaskListScreen from "../../components/TaskListScreen"; - -export default function Inbox() { - return ; -} diff --git a/app/src/app/(tabs)/next.tsx b/app/src/app/(tabs)/next.tsx deleted file mode 100644 index 2b333b7..0000000 --- a/app/src/app/(tabs)/next.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import TaskListScreen from "../../components/TaskListScreen"; - -export default function Next() { - return ; -} diff --git a/app/src/app/(tabs)/someday.tsx b/app/src/app/(tabs)/someday.tsx deleted file mode 100644 index a35ace5..0000000 --- a/app/src/app/(tabs)/someday.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import TaskListScreen from "../../components/TaskListScreen"; - -export default function Someday() { - return ; -} diff --git a/app/src/app/(tabs)/waiting.tsx b/app/src/app/(tabs)/waiting.tsx deleted file mode 100644 index fb720c6..0000000 --- a/app/src/app/(tabs)/waiting.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import TaskListScreen from "../../components/TaskListScreen"; - -export default function Waiting() { - return ; -} diff --git a/app/src/app/_layout.tsx b/app/src/app/_layout.tsx index 0a56e52..57cac87 100644 --- a/app/src/app/_layout.tsx +++ b/app/src/app/_layout.tsx @@ -1,4 +1,4 @@ -import { Stack } from "expo-router"; +import { Link, Stack } from "expo-router"; import { StatusBar } from "expo-status-bar"; import { C } from "../lib/theme"; @@ -14,7 +14,19 @@ export default function RootLayout() { contentStyle: { backgroundColor: C.bg }, }} > - + ( + + ⚙️ + + ), + }} + /> + + diff --git a/app/src/app/index.tsx b/app/src/app/index.tsx new file mode 100644 index 0000000..a1092a4 --- /dev/null +++ b/app/src/app/index.tsx @@ -0,0 +1,157 @@ +import { useFocusEffect, useRouter } from "expo-router"; +import { useCallback, useState } from "react"; +import { Pressable, StyleSheet, Text, TextInput, View } from "react-native"; +import { api } from "../lib/api"; +import { C } from "../lib/theme"; +import type { Project, Task } from "../lib/types"; + +const Q = { + next: { title: "Next up", color: "#3DBF77", bg: "#15301F", border: "#1E4D33" }, + waiting: { title: "Waiting for", color: "#E0A93E", bg: "#33270F", border: "#544019" }, + someday: { title: "Someday", color: "#9D8FE8", bg: "#221E38", border: "#38315C" }, + projects: { title: "Projects", color: "#5B9DE8", bg: "#152538", border: "#1F3D5C" }, +}; + +function Quadrant({ + kind, + count, + preview, + onPress, +}: { + kind: keyof typeof Q; + count: number; + preview: string[]; + onPress: () => void; +}) { + const q = Q[kind]; + return ( + + + {q.title} + {count} + + {preview.map((p, i) => ( + + {p} + + ))} + + ); +} + +export default function Home() { + const router = useRouter(); + const [tasks, setTasks] = useState([]); + const [projects, setProjects] = useState([]); + const [draft, setDraft] = useState(""); + const [error, setError] = useState(null); + + const load = useCallback(async () => { + try { + const [ts, ps] = await Promise.all([api.tasks.list(), api.projects.list()]); + setTasks(ts); + setProjects(ps.filter((p) => p.status === "active")); + setError(null); + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + } + }, []); + + useFocusEffect( + useCallback(() => { + load(); + }, [load]), + ); + + const capture = async () => { + const title = draft.trim(); + if (!title) return; + setDraft(""); + try { + await api.tasks.create({ title }); + } finally { + load(); + } + }; + + const byStatus = (st: string) => tasks.filter((t) => t.status === st); + const inboxCount = byStatus("inbox").length; + const titles = (st: string) => byStatus(st).slice(0, 3).map((t) => t.title); + + return ( + + + router.push("/list/inbox")}> + 📥 Inbox + {inboxCount} + + {error && {error}} + + + router.push("/list/next")} /> + router.push("/list/waiting")} /> + + + router.push("/list/someday")} /> + p.name)} + onPress={() => router.push("/projects")} + /> + + + + ); +} + +const s = StyleSheet.create({ + screen: { flex: 1, backgroundColor: C.bg, padding: 12 }, + capture: { + padding: 14, + borderRadius: 12, + backgroundColor: C.surface, + color: C.text, + fontSize: 16, + borderWidth: 1, + borderColor: C.border, + }, + inboxPill: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginTop: 10, + paddingVertical: 12, + paddingHorizontal: 16, + borderRadius: 12, + backgroundColor: C.surface, + borderWidth: 1, + borderColor: C.border, + }, + inboxText: { color: C.text, fontSize: 16, fontWeight: "600" }, + grid: { flex: 1, marginTop: 10, gap: 10 }, + gridRow: { flex: 1, flexDirection: "row", gap: 10 }, + quad: { + flex: 1, + borderRadius: 16, + borderWidth: 1, + padding: 14, + }, + quadHead: { flexDirection: "row", justifyContent: "space-between", marginBottom: 8 }, + quadTitle: { fontSize: 17, fontWeight: "700" }, + quadCount: { fontSize: 17, fontWeight: "700" }, + quadItem: { color: C.muted, fontSize: 13, marginBottom: 4 }, + error: { color: C.danger, marginTop: 8 }, +}); diff --git a/app/src/app/list/[status].tsx b/app/src/app/list/[status].tsx new file mode 100644 index 0000000..602b996 --- /dev/null +++ b/app/src/app/list/[status].tsx @@ -0,0 +1,32 @@ +import { Stack, useLocalSearchParams } from "expo-router"; +import TaskListScreen from "../../components/TaskListScreen"; +import type { TaskStatus } from "../../lib/types"; + +const TITLES: Record = { + inbox: "Inbox", + next: "Next up", + waiting: "Waiting for", + scheduled: "Scheduled", + someday: "Someday", + done: "Done", +}; + +const HINTS: Record = { + inbox: "Inbox zero. Capture from the home screen.", + next: "No next actions. Clarify your inbox.", + waiting: "Not waiting on anyone.", + scheduled: "Nothing scheduled.", + someday: "No someday/maybe items.", + done: "Nothing done yet.", +}; + +export default function ListByStatus() { + const { status } = useLocalSearchParams<{ status: string }>(); + const st = (status in TITLES ? status : "inbox") as TaskStatus; + return ( + <> + + + + ); +} diff --git a/app/src/app/(tabs)/projects.tsx b/app/src/app/projects.tsx similarity index 95% rename from app/src/app/(tabs)/projects.tsx rename to app/src/app/projects.tsx index caf7d9d..95c0874 100644 --- a/app/src/app/(tabs)/projects.tsx +++ b/app/src/app/projects.tsx @@ -1,9 +1,9 @@ import { useFocusEffect, useRouter } from "expo-router"; import { useCallback, useState } from "react"; import { FlatList, Pressable, StyleSheet, Text, TextInput, View } from "react-native"; -import { api } from "../../lib/api"; -import { C } from "../../lib/theme"; -import type { Project } from "../../lib/types"; +import { api } from "../lib/api"; +import { C } from "../lib/theme"; +import type { Project } from "../lib/types"; export default function Projects() { const router = useRouter();