react-os-shell 0.2.43 → 0.2.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/{Browser-5ZCLRIRU.js → Browser-LSJORULM.js} +3 -3
  2. package/dist/{Browser-5ZCLRIRU.js.map → Browser-LSJORULM.js.map} +1 -1
  3. package/dist/{Calculator-OHL2AZ52.js → Calculator-ZKEH53OV.js} +4 -4
  4. package/dist/{Calculator-OHL2AZ52.js.map → Calculator-ZKEH53OV.js.map} +1 -1
  5. package/dist/{Calendar-6AHL3UJY.js → Calendar-PKRZ5MBU.js} +154 -55
  6. package/dist/Calendar-PKRZ5MBU.js.map +1 -0
  7. package/dist/{CurrencyConverter-XZVZ7XOF.js → CurrencyConverter-5N44NZ6Z.js} +56 -50
  8. package/dist/CurrencyConverter-5N44NZ6Z.js.map +1 -0
  9. package/dist/{Documents-IPVZ47JX.js → Documents-CQVIIFZV.js} +3 -3
  10. package/dist/{Documents-IPVZ47JX.js.map → Documents-CQVIIFZV.js.map} +1 -1
  11. package/dist/{Email-Z7FDKAFD.js → Email-CR6XS2AD.js} +5 -5
  12. package/dist/{Email-Z7FDKAFD.js.map → Email-CR6XS2AD.js.map} +1 -1
  13. package/dist/Files-ITIKVHIE.js +8 -0
  14. package/dist/{Files-KEHRZ2UY.js.map → Files-ITIKVHIE.js.map} +1 -1
  15. package/dist/{GeminiChat-ITU46EH4.js → GeminiChat-XTEBZIVK.js} +3 -3
  16. package/dist/{GeminiChat-ITU46EH4.js.map → GeminiChat-XTEBZIVK.js.map} +1 -1
  17. package/dist/{Minesweeper-NGN4O6C4.js → Minesweeper-QGUPDVRS.js} +3 -3
  18. package/dist/{Minesweeper-NGN4O6C4.js.map → Minesweeper-QGUPDVRS.js.map} +1 -1
  19. package/dist/{Notepad-W3YYZ3GS.js → Notepad-74CQPZCV.js} +3 -3
  20. package/dist/{Notepad-W3YYZ3GS.js.map → Notepad-74CQPZCV.js.map} +1 -1
  21. package/dist/PomodoroTimer-FHSOLF3O.js +627 -0
  22. package/dist/PomodoroTimer-FHSOLF3O.js.map +1 -0
  23. package/dist/Preview-4MBQI66Q.js +7 -0
  24. package/dist/{Preview-4354N46U.js.map → Preview-4MBQI66Q.js.map} +1 -1
  25. package/dist/{Spreadsheet-FCFII6DW.js → Spreadsheet-MKXPPSKV.js} +3 -3
  26. package/dist/{Spreadsheet-FCFII6DW.js.map → Spreadsheet-MKXPPSKV.js.map} +1 -1
  27. package/dist/TodoList-7JZ2SLDI.js +494 -0
  28. package/dist/TodoList-7JZ2SLDI.js.map +1 -0
  29. package/dist/Weather-YXSCSPQT.js +424 -0
  30. package/dist/Weather-YXSCSPQT.js.map +1 -0
  31. package/dist/WorldClock-XHM7WAUV.js +196 -0
  32. package/dist/WorldClock-XHM7WAUV.js.map +1 -0
  33. package/dist/apps/index.d.ts +10 -11
  34. package/dist/apps/index.js +23 -21
  35. package/dist/apps/index.js.map +1 -1
  36. package/dist/chunk-25L4DIKH.js +90 -0
  37. package/dist/chunk-25L4DIKH.js.map +1 -0
  38. package/dist/{chunk-62MVMTBT.js → chunk-5VXRBUEH.js} +20 -3
  39. package/dist/chunk-5VXRBUEH.js.map +1 -0
  40. package/dist/chunk-7CCHEEYC.js +94 -0
  41. package/dist/chunk-7CCHEEYC.js.map +1 -0
  42. package/dist/{chunk-T2NQXP2J.js → chunk-7M3BBAHQ.js} +10 -4
  43. package/dist/chunk-7M3BBAHQ.js.map +1 -0
  44. package/dist/{chunk-IY7JJVHR.js → chunk-DUUANLLE.js} +3 -3
  45. package/dist/{chunk-IY7JJVHR.js.map → chunk-DUUANLLE.js.map} +1 -1
  46. package/dist/chunk-MK3HLUO4.js +380 -0
  47. package/dist/chunk-MK3HLUO4.js.map +1 -0
  48. package/dist/{chunk-2SRU4BYH.js → chunk-MTLVXT2C.js} +4 -4
  49. package/dist/{chunk-2SRU4BYH.js.map → chunk-MTLVXT2C.js.map} +1 -1
  50. package/dist/{chunk-46LICZUM.js → chunk-MVWEL34Y.js} +3 -2
  51. package/dist/chunk-MVWEL34Y.js.map +1 -0
  52. package/dist/{chunk-TVOBLSSV.js → chunk-UK2AA3J6.js} +3 -3
  53. package/dist/{chunk-TVOBLSSV.js.map → chunk-UK2AA3J6.js.map} +1 -1
  54. package/dist/index.d.ts +16 -1
  55. package/dist/index.js +4089 -11
  56. package/dist/index.js.map +1 -1
  57. package/dist/styles.css +6 -4
  58. package/package.json +1 -1
  59. package/dist/Calendar-6AHL3UJY.js.map +0 -1
  60. package/dist/CurrencyConverter-XZVZ7XOF.js.map +0 -1
  61. package/dist/Files-KEHRZ2UY.js +0 -8
  62. package/dist/PomodoroTimer-T2J5NDJR.js +0 -196
  63. package/dist/PomodoroTimer-T2J5NDJR.js.map +0 -1
  64. package/dist/Preview-4354N46U.js +0 -7
  65. package/dist/Weather-XTADR7Z3.js +0 -266
  66. package/dist/Weather-XTADR7Z3.js.map +0 -1
  67. package/dist/WorldClock-OFK2EA2H.js +0 -126
  68. package/dist/WorldClock-OFK2EA2H.js.map +0 -1
  69. package/dist/chunk-46LICZUM.js.map +0 -1
  70. package/dist/chunk-62MVMTBT.js.map +0 -1
  71. package/dist/chunk-7KZWBIDL.js +0 -4144
  72. package/dist/chunk-7KZWBIDL.js.map +0 -1
  73. package/dist/chunk-T2NQXP2J.js.map +0 -1
@@ -0,0 +1,494 @@
1
+ import { isDemoMode, getDemoTasks } from './chunk-5VXRBUEH.js';
2
+ import { useTodoTasks } from './chunk-25L4DIKH.js';
3
+ import { useGoogleAuth, getGoogleAccessToken } from './chunk-MVWEL34Y.js';
4
+ import './chunk-36VM54SC.js';
5
+ import { toast_default } from './chunk-WIJ45SYD.js';
6
+ import { confirm } from './chunk-PLGHQ7QW.js';
7
+ import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
8
+ import { jsxs, jsx } from 'react/jsx-runtime';
9
+
10
+ // src/apps/_googleTasks.ts
11
+ var TASKS_API = "https://tasks.googleapis.com/tasks/v1";
12
+ var DEFAULT_LIST = "@default";
13
+ function dueToIso(date) {
14
+ if (!date) return void 0;
15
+ return `${date}T12:00:00.000Z`;
16
+ }
17
+ function dueFromIso(iso) {
18
+ if (!iso) return void 0;
19
+ return iso.slice(0, 10);
20
+ }
21
+ function mapFromGoogle(g, listId, ts) {
22
+ return {
23
+ id: `gtask-${g.id}`,
24
+ name: g.title || "",
25
+ done: g.status === "completed",
26
+ dueDate: dueFromIso(g.due),
27
+ notes: g.notes,
28
+ gtaskId: g.id,
29
+ gtaskListId: listId,
30
+ syncedAt: ts,
31
+ createdAt: g.updated || ts,
32
+ updatedAt: g.updated || ts
33
+ };
34
+ }
35
+ function mapToGoogle(task) {
36
+ return {
37
+ title: task.name,
38
+ notes: task.notes,
39
+ status: task.done ? "completed" : "needsAction",
40
+ due: dueToIso(task.dueDate)
41
+ };
42
+ }
43
+ async function fetchGoogleTasks(token, listId = DEFAULT_LIST) {
44
+ const res = await fetch(`${TASKS_API}/lists/${listId}/tasks?showCompleted=true&showHidden=false&maxResults=200`, {
45
+ headers: { Authorization: `Bearer ${token}` }
46
+ });
47
+ if (!res.ok) throw new Error(`Google Tasks fetch failed: ${res.status}`);
48
+ const data = await res.json();
49
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
50
+ return (data.items || []).filter((g) => !g.deleted && g.id).map((g) => mapFromGoogle(g, listId, ts));
51
+ }
52
+ async function pushGoogleTask(token, task) {
53
+ const listId = task.gtaskListId || DEFAULT_LIST;
54
+ const payload = mapToGoogle(task);
55
+ const isUpdate = !!task.gtaskId;
56
+ const url = isUpdate ? `${TASKS_API}/lists/${listId}/tasks/${task.gtaskId}` : `${TASKS_API}/lists/${listId}/tasks`;
57
+ const res = await fetch(url, {
58
+ method: isUpdate ? "PATCH" : "POST",
59
+ headers: {
60
+ Authorization: `Bearer ${token}`,
61
+ "Content-Type": "application/json"
62
+ },
63
+ body: JSON.stringify(payload)
64
+ });
65
+ if (!res.ok) throw new Error(`Google Tasks push failed: ${res.status}`);
66
+ const g = await res.json();
67
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
68
+ return {
69
+ ...task,
70
+ gtaskId: g.id,
71
+ gtaskListId: listId,
72
+ syncedAt: ts
73
+ };
74
+ }
75
+ async function deleteGoogleTask(token, gtaskId, listId = DEFAULT_LIST) {
76
+ const res = await fetch(`${TASKS_API}/lists/${listId}/tasks/${gtaskId}`, {
77
+ method: "DELETE",
78
+ headers: { Authorization: `Bearer ${token}` }
79
+ });
80
+ if (!res.ok && res.status !== 404) throw new Error(`Google Tasks delete failed: ${res.status}`);
81
+ }
82
+ var FILTERS = [
83
+ { id: "today", label: "Today" },
84
+ { id: "upcoming", label: "Upcoming" },
85
+ { id: "all", label: "All" },
86
+ { id: "done", label: "Done" }
87
+ ];
88
+ function todayStr() {
89
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
90
+ }
91
+ function fmtDueLabel(due) {
92
+ const d = /* @__PURE__ */ new Date(due + "T00:00:00");
93
+ const today = /* @__PURE__ */ new Date();
94
+ today.setHours(0, 0, 0, 0);
95
+ const diffDays = Math.round((d.getTime() - today.getTime()) / (24 * 3600 * 1e3));
96
+ if (diffDays === 0) return "Today";
97
+ if (diffDays === 1) return "Tomorrow";
98
+ if (diffDays === -1) return "Yesterday";
99
+ if (diffDays > 0 && diffDays < 7) return d.toLocaleDateString(void 0, { weekday: "short" });
100
+ return d.toLocaleDateString(void 0, { month: "short", day: "numeric" });
101
+ }
102
+ function isOverdue(t) {
103
+ return !t.done && !!t.dueDate && t.dueDate < todayStr();
104
+ }
105
+ function TodoList() {
106
+ const { tasks, addTask, updateTask, removeTask, toggleDone, setAllTasks } = useTodoTasks();
107
+ const google = useGoogleAuth();
108
+ const [filter, setFilter] = useState("today");
109
+ const [editingId, setEditingId] = useState(null);
110
+ const [adding, setAdding] = useState(false);
111
+ const [syncing, setSyncing] = useState(false);
112
+ const [lastSync, setLastSync] = useState(null);
113
+ const demoMode = isDemoMode();
114
+ const seededRef = useRef(false);
115
+ useEffect(() => {
116
+ if (seededRef.current) return;
117
+ seededRef.current = true;
118
+ if (demoMode && !google.isConnected && tasks.length === 0) {
119
+ setAllTasks(getDemoTasks());
120
+ }
121
+ }, []);
122
+ const syncFromGoogle = useCallback(async () => {
123
+ const token = getGoogleAccessToken();
124
+ if (!token) {
125
+ toast_default.info("Connect Google to sync tasks.");
126
+ return;
127
+ }
128
+ setSyncing(true);
129
+ try {
130
+ const remote = await fetchGoogleTasks(token);
131
+ const remoteByGid = new Map(remote.map((t) => [t.gtaskId, t]));
132
+ const merged = [];
133
+ for (const local of tasks) {
134
+ if (!local.gtaskId) {
135
+ merged.push(local);
136
+ continue;
137
+ }
138
+ const r = remoteByGid.get(local.gtaskId);
139
+ if (!r) {
140
+ continue;
141
+ }
142
+ merged.push({
143
+ ...local,
144
+ name: r.name,
145
+ done: r.done,
146
+ dueDate: r.dueDate,
147
+ notes: r.notes,
148
+ syncedAt: r.syncedAt,
149
+ updatedAt: r.updatedAt
150
+ });
151
+ remoteByGid.delete(local.gtaskId);
152
+ }
153
+ for (const r of remoteByGid.values()) merged.push(r);
154
+ setAllTasks(merged);
155
+ setLastSync((/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
156
+ toast_default.info(`Synced ${remote.length} tasks from Google`);
157
+ } catch (e) {
158
+ toast_default.info(`Sync failed: ${e?.message ?? "unknown error"}`);
159
+ } finally {
160
+ setSyncing(false);
161
+ }
162
+ }, [tasks, setAllTasks]);
163
+ useEffect(() => {
164
+ if (google.isConnected) syncFromGoogle();
165
+ }, [google.isConnected]);
166
+ const pushIfConnected = useCallback(async (task) => {
167
+ const token = getGoogleAccessToken();
168
+ if (!token || !google.isConnected) return;
169
+ try {
170
+ const synced = await pushGoogleTask(token, task);
171
+ if (synced.gtaskId !== task.gtaskId || synced.gtaskListId !== task.gtaskListId) {
172
+ updateTask(task.id, { gtaskId: synced.gtaskId, gtaskListId: synced.gtaskListId, syncedAt: synced.syncedAt });
173
+ } else {
174
+ updateTask(task.id, { syncedAt: synced.syncedAt });
175
+ }
176
+ } catch (e) {
177
+ toast_default.info(`Couldn't push to Google: ${e?.message ?? "unknown"}`);
178
+ }
179
+ }, [google.isConnected, updateTask]);
180
+ const today = todayStr();
181
+ const visible = useMemo(() => {
182
+ const sorted = [...tasks].sort((a, b) => {
183
+ if (a.done !== b.done) return a.done ? 1 : -1;
184
+ const aOver = isOverdue(a), bOver = isOverdue(b);
185
+ if (aOver !== bOver) return aOver ? -1 : 1;
186
+ if (a.dueDate && b.dueDate && a.dueDate !== b.dueDate) return a.dueDate < b.dueDate ? -1 : 1;
187
+ if (!!a.dueDate !== !!b.dueDate) return a.dueDate ? -1 : 1;
188
+ return a.createdAt.localeCompare(b.createdAt);
189
+ });
190
+ if (filter === "today") return sorted.filter((t) => !t.done && (t.dueDate === today || isOverdue(t)));
191
+ if (filter === "upcoming") return sorted.filter((t) => !t.done && t.dueDate && t.dueDate > today);
192
+ if (filter === "done") return sorted.filter((t) => t.done);
193
+ return sorted;
194
+ }, [tasks, filter, today]);
195
+ const counts = useMemo(() => ({
196
+ today: tasks.filter((t) => !t.done && (t.dueDate === today || isOverdue(t))).length,
197
+ upcoming: tasks.filter((t) => !t.done && t.dueDate && t.dueDate > today).length,
198
+ all: tasks.filter((t) => !t.done).length,
199
+ done: tasks.filter((t) => t.done).length
200
+ }), [tasks, today]);
201
+ const handleAdd = (input) => {
202
+ const id = addTask(input);
203
+ setAdding(false);
204
+ const fresh = { ...input, id, done: false, createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
205
+ pushIfConnected(fresh);
206
+ };
207
+ const handleToggle = (id) => {
208
+ const t = tasks.find((x) => x.id === id);
209
+ if (!t) return;
210
+ toggleDone(id);
211
+ pushIfConnected({ ...t, done: !t.done });
212
+ };
213
+ const handleEdit = (id, patch) => {
214
+ updateTask(id, patch);
215
+ const t = tasks.find((x) => x.id === id);
216
+ if (t) pushIfConnected({ ...t, ...patch });
217
+ };
218
+ const handleDelete = async (id) => {
219
+ const t = tasks.find((x) => x.id === id);
220
+ if (!t) return;
221
+ const ok = await confirm({
222
+ title: "Delete this task?",
223
+ message: `\u201C${t.name}\u201D will be removed from the Todo List, the Pomodoro widget, and Google Tasks (if synced). This can't be undone.`,
224
+ confirmLabel: "Delete",
225
+ variant: "danger"
226
+ });
227
+ if (!ok) return;
228
+ removeTask(id);
229
+ setEditingId(null);
230
+ if (t.gtaskId) {
231
+ const token = getGoogleAccessToken();
232
+ if (token) {
233
+ try {
234
+ await deleteGoogleTask(token, t.gtaskId, t.gtaskListId);
235
+ } catch (e) {
236
+ toast_default.info(`Couldn't delete on Google: ${e?.message ?? "unknown"}`);
237
+ }
238
+ }
239
+ }
240
+ };
241
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
242
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 px-4 py-2 border-b border-gray-200 shrink-0", children: [
243
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: FILTERS.map((f) => {
244
+ const count = counts[f.id];
245
+ const active = filter === f.id;
246
+ return /* @__PURE__ */ jsxs(
247
+ "button",
248
+ {
249
+ onClick: () => setFilter(f.id),
250
+ className: `px-2.5 py-1 text-xs font-medium rounded-md transition-colors flex items-center gap-1.5 ${active ? "bg-blue-600 text-white" : "text-gray-600 hover:bg-gray-100"}`,
251
+ children: [
252
+ /* @__PURE__ */ jsx("span", { children: f.label }),
253
+ /* @__PURE__ */ jsx("span", { className: `text-[10px] tabular-nums ${active ? "text-white/80" : "text-gray-400"}`, children: count })
254
+ ]
255
+ },
256
+ f.id
257
+ );
258
+ }) }),
259
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
260
+ google.isConnected ? /* @__PURE__ */ jsxs(
261
+ "button",
262
+ {
263
+ onClick: syncFromGoogle,
264
+ disabled: syncing,
265
+ title: lastSync ? `Last synced ${lastSync}` : "Sync with Google Tasks",
266
+ className: "flex items-center gap-1.5 px-2 py-1 text-[11px] rounded-md border border-gray-200 text-gray-600 hover:bg-gray-50 disabled:opacity-50",
267
+ children: [
268
+ /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-green-500" }),
269
+ /* @__PURE__ */ jsx("span", { children: "Google" }),
270
+ /* @__PURE__ */ jsx("svg", { className: `h-3 w-3 ${syncing ? "animate-spin" : ""}`, fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) })
271
+ ]
272
+ }
273
+ ) : /* @__PURE__ */ jsx(
274
+ "button",
275
+ {
276
+ onClick: () => window.dispatchEvent(new Event("open-google-connect")),
277
+ className: "flex items-center gap-1.5 px-2 py-1 text-[11px] rounded-md border border-gray-200 text-gray-500 hover:bg-gray-50",
278
+ children: "Connect Google Tasks"
279
+ }
280
+ ),
281
+ /* @__PURE__ */ jsxs(
282
+ "button",
283
+ {
284
+ onClick: () => setAdding((a) => !a),
285
+ className: "flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700",
286
+ children: [
287
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", d: "M12 5v14M5 12h14" }) }),
288
+ /* @__PURE__ */ jsx("span", { children: "Add" })
289
+ ]
290
+ }
291
+ )
292
+ ] })
293
+ ] }),
294
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto", children: [
295
+ adding && /* @__PURE__ */ jsx(AddTaskRow, { onSubmit: handleAdd, onCancel: () => setAdding(false) }),
296
+ visible.length === 0 && !adding && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center h-full text-center px-6 text-gray-400", children: [
297
+ /* @__PURE__ */ jsxs("svg", { className: "h-10 w-10 mb-2 text-gray-300", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, children: [
298
+ /* @__PURE__ */ jsx("rect", { x: "4", y: "4", width: "16", height: "16", rx: "2" }),
299
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 12l2 2 4-4" })
300
+ ] }),
301
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: filter === "done" ? "No completed tasks yet." : "Nothing here. Add a task to get started." })
302
+ ] }),
303
+ visible.map((task) => /* @__PURE__ */ jsx(
304
+ TaskRow,
305
+ {
306
+ task,
307
+ editing: editingId === task.id,
308
+ onToggle: () => handleToggle(task.id),
309
+ onClick: () => setEditingId(editingId === task.id ? null : task.id),
310
+ onSave: (patch) => {
311
+ handleEdit(task.id, patch);
312
+ setEditingId(null);
313
+ },
314
+ onDelete: () => handleDelete(task.id),
315
+ onCancelEdit: () => setEditingId(null)
316
+ },
317
+ task.id
318
+ ))
319
+ ] })
320
+ ] });
321
+ }
322
+ function TaskRow({ task, editing, onToggle, onClick, onSave, onDelete, onCancelEdit }) {
323
+ if (editing) {
324
+ return /* @__PURE__ */ jsx(EditDrawer, { task, onSave, onCancel: onCancelEdit, onDelete });
325
+ }
326
+ const dueLabel = task.dueDate ? fmtDueLabel(task.dueDate) : null;
327
+ const overdue = isOverdue(task);
328
+ const pomos = task.estimated || task.completed ? `${task.completed ?? 0}/${task.estimated ?? "?"}` : null;
329
+ return /* @__PURE__ */ jsxs(
330
+ "div",
331
+ {
332
+ onClick,
333
+ className: "flex items-center gap-3 px-4 py-2.5 border-b border-gray-200 cursor-pointer hover:bg-gray-50 transition-colors",
334
+ children: [
335
+ /* @__PURE__ */ jsx(
336
+ "button",
337
+ {
338
+ onClick: (e) => {
339
+ e.stopPropagation();
340
+ onToggle();
341
+ },
342
+ className: "shrink-0",
343
+ children: task.done ? /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-blue-600", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx("path", { d: "M10 0a10 10 0 100 20 10 10 0 000-20zm-1 14.5l-4.5-4.5 1.4-1.4 3.1 3.1 6.1-6.1 1.4 1.4z" }) }) : /* @__PURE__ */ jsx("svg", { className: "h-5 w-5 text-gray-300 hover:text-gray-500", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }) })
344
+ }
345
+ ),
346
+ /* @__PURE__ */ jsx("span", { className: `flex-1 text-sm truncate ${task.done ? "line-through text-gray-400" : "text-gray-800"}`, children: task.name || /* @__PURE__ */ jsx("span", { className: "italic text-gray-400", children: "(untitled)" }) }),
347
+ pomos && /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-[11px] tabular-nums text-gray-500", title: "Pomodoros completed / estimated", children: [
348
+ "\u{1F345} ",
349
+ pomos
350
+ ] }),
351
+ dueLabel && /* @__PURE__ */ jsx("span", { className: `shrink-0 text-[11px] font-medium px-1.5 py-0.5 rounded ${overdue ? "bg-red-100 text-red-700" : task.dueDate === todayStr() ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-600"}`, children: dueLabel }),
352
+ task.gtaskId && /* @__PURE__ */ jsx("span", { title: "Synced with Google Tasks", className: "h-1.5 w-1.5 rounded-full bg-green-500 shrink-0" })
353
+ ]
354
+ }
355
+ );
356
+ }
357
+ function EditDrawer({ task, onSave, onCancel, onDelete }) {
358
+ const [name, setName] = useState(task.name);
359
+ const [dueDate, setDueDate] = useState(task.dueDate || "");
360
+ const [estimated, setEstimated] = useState(task.estimated ?? 0);
361
+ const [notes, setNotes] = useState(task.notes || "");
362
+ const submit = () => onSave({
363
+ name: name.trim(),
364
+ dueDate: dueDate || void 0,
365
+ estimated: estimated > 0 ? estimated : void 0,
366
+ notes: notes.trim() || void 0
367
+ });
368
+ return /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 border-b border-gray-200 bg-gray-50", children: [
369
+ /* @__PURE__ */ jsx(
370
+ "input",
371
+ {
372
+ autoFocus: true,
373
+ value: name,
374
+ onChange: (e) => setName(e.target.value),
375
+ onKeyDown: (e) => {
376
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) submit();
377
+ if (e.key === "Escape") onCancel();
378
+ },
379
+ placeholder: "Task name",
380
+ className: "w-full text-sm font-medium bg-white border border-gray-200 rounded px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
381
+ }
382
+ ),
383
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2 mt-2", children: [
384
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col text-[10px] font-semibold text-gray-500 uppercase tracking-wide", children: [
385
+ "Due",
386
+ /* @__PURE__ */ jsx(
387
+ "input",
388
+ {
389
+ type: "date",
390
+ value: dueDate,
391
+ onChange: (e) => setDueDate(e.target.value),
392
+ className: "mt-0.5 text-sm bg-white border border-gray-200 rounded px-2 py-1 text-gray-800 focus:outline-none focus:ring-1 focus:ring-blue-500"
393
+ }
394
+ )
395
+ ] }),
396
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col text-[10px] font-semibold text-gray-500 uppercase tracking-wide", children: [
397
+ "Est. pomos",
398
+ /* @__PURE__ */ jsx(
399
+ "input",
400
+ {
401
+ type: "number",
402
+ min: 0,
403
+ max: 20,
404
+ value: estimated,
405
+ onChange: (e) => setEstimated(Math.max(0, Math.min(20, parseInt(e.target.value, 10) || 0))),
406
+ className: "mt-0.5 text-sm bg-white border border-gray-200 rounded px-2 py-1 text-gray-800 focus:outline-none focus:ring-1 focus:ring-blue-500"
407
+ }
408
+ )
409
+ ] })
410
+ ] }),
411
+ /* @__PURE__ */ jsx(
412
+ "textarea",
413
+ {
414
+ value: notes,
415
+ onChange: (e) => setNotes(e.target.value),
416
+ placeholder: "Notes (optional)",
417
+ rows: 2,
418
+ className: "mt-2 w-full text-xs bg-white border border-gray-200 rounded px-2 py-1.5 text-gray-700 focus:outline-none focus:ring-1 focus:ring-blue-500 resize-none"
419
+ }
420
+ ),
421
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mt-2", children: [
422
+ /* @__PURE__ */ jsx("button", { onClick: onDelete, className: "text-xs text-red-600 hover:text-red-700", children: "Delete" }),
423
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
424
+ /* @__PURE__ */ jsx("button", { onClick: onCancel, className: "px-3 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded", children: "Cancel" }),
425
+ /* @__PURE__ */ jsx("button", { onClick: submit, className: "px-3 py-1 text-xs font-semibold bg-blue-600 text-white rounded hover:bg-blue-700", children: "Save" })
426
+ ] })
427
+ ] })
428
+ ] });
429
+ }
430
+ function AddTaskRow({ onSubmit, onCancel }) {
431
+ const [name, setName] = useState("");
432
+ const [dueDate, setDueDate] = useState("");
433
+ const [estimated, setEstimated] = useState(0);
434
+ const submit = () => {
435
+ const trimmed = name.trim();
436
+ if (!trimmed) return;
437
+ onSubmit({
438
+ name: trimmed,
439
+ dueDate: dueDate || void 0,
440
+ estimated: estimated > 0 ? estimated : void 0
441
+ });
442
+ };
443
+ return /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 border-b border-gray-200 bg-blue-50/40", children: [
444
+ /* @__PURE__ */ jsx(
445
+ "input",
446
+ {
447
+ autoFocus: true,
448
+ value: name,
449
+ onChange: (e) => setName(e.target.value),
450
+ onKeyDown: (e) => {
451
+ if (e.key === "Enter") submit();
452
+ if (e.key === "Escape") onCancel();
453
+ },
454
+ placeholder: "What do you need to do?",
455
+ className: "w-full text-sm font-medium bg-white border border-gray-200 rounded px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500"
456
+ }
457
+ ),
458
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
459
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-[10px] font-semibold text-gray-500 uppercase tracking-wide", children: [
460
+ "Due",
461
+ /* @__PURE__ */ jsx(
462
+ "input",
463
+ {
464
+ type: "date",
465
+ value: dueDate,
466
+ onChange: (e) => setDueDate(e.target.value),
467
+ className: "text-xs bg-white border border-gray-200 rounded px-1.5 py-0.5 text-gray-800 focus:outline-none focus:ring-1 focus:ring-blue-500"
468
+ }
469
+ )
470
+ ] }),
471
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-[10px] font-semibold text-gray-500 uppercase tracking-wide", children: [
472
+ "Est. pomos",
473
+ /* @__PURE__ */ jsx(
474
+ "input",
475
+ {
476
+ type: "number",
477
+ min: 0,
478
+ max: 20,
479
+ value: estimated,
480
+ onChange: (e) => setEstimated(Math.max(0, Math.min(20, parseInt(e.target.value, 10) || 0))),
481
+ className: "w-12 text-xs bg-white border border-gray-200 rounded px-1.5 py-0.5 text-right text-gray-800 focus:outline-none focus:ring-1 focus:ring-blue-500"
482
+ }
483
+ )
484
+ ] }),
485
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
486
+ /* @__PURE__ */ jsx("button", { onClick: onCancel, className: "px-3 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded", children: "Cancel" }),
487
+ /* @__PURE__ */ jsx("button", { onClick: submit, className: "px-3 py-1 text-xs font-semibold bg-blue-600 text-white rounded hover:bg-blue-700", children: "Add" })
488
+ ] })
489
+ ] });
490
+ }
491
+
492
+ export { TodoList as default };
493
+ //# sourceMappingURL=TodoList-7JZ2SLDI.js.map
494
+ //# sourceMappingURL=TodoList-7JZ2SLDI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/apps/_googleTasks.ts","../src/apps/TodoList.tsx"],"names":[],"mappings":";;;;;;;;;;AAgBA,IAAM,SAAA,GAAY,uCAAA;AAClB,IAAM,YAAA,GAAe,UAAA;AAmBrB,SAAS,SAAS,IAAA,EAA8C;AAC9D,EAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,EAAA,OAAO,GAAG,IAAI,CAAA,cAAA,CAAA;AAChB;AAGA,SAAS,WAAW,GAAA,EAA6C;AAC/D,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,OAAO,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AACxB;AAEA,SAAS,aAAA,CAAc,CAAA,EAAsB,MAAA,EAAgB,EAAA,EAAsB;AACjF,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,CAAA,MAAA,EAAS,CAAA,CAAE,EAAE,CAAA,CAAA;AAAA,IACjB,IAAA,EAAM,EAAE,KAAA,IAAS,EAAA;AAAA,IACjB,IAAA,EAAM,EAAE,MAAA,KAAW,WAAA;AAAA,IACnB,OAAA,EAAS,UAAA,CAAW,CAAA,CAAE,GAAG,CAAA;AAAA,IACzB,OAAO,CAAA,CAAE,KAAA;AAAA,IACT,SAAS,CAAA,CAAE,EAAA;AAAA,IACX,WAAA,EAAa,MAAA;AAAA,IACb,QAAA,EAAU,EAAA;AAAA,IACV,SAAA,EAAW,EAAE,OAAA,IAAW,EAAA;AAAA,IACxB,SAAA,EAAW,EAAE,OAAA,IAAW;AAAA,GAC1B;AACF;AAEA,SAAS,YAAY,IAAA,EAAmC;AACtD,EAAA,OAAO;AAAA,IACL,OAAO,IAAA,CAAK,IAAA;AAAA,IACZ,OAAO,IAAA,CAAK,KAAA;AAAA,IACZ,MAAA,EAAQ,IAAA,CAAK,IAAA,GAAO,WAAA,GAAc,aAAA;AAAA,IAClC,GAAA,EAAK,QAAA,CAAS,IAAA,CAAK,OAAO;AAAA,GAC5B;AACF;AAEA,eAAsB,gBAAA,CAAiB,KAAA,EAAe,MAAA,GAAiB,YAAA,EAAmC;AACxG,EAAA,MAAM,MAAM,MAAM,KAAA,CAAM,GAAG,SAAS,CAAA,OAAA,EAAU,MAAM,CAAA,yDAAA,CAAA,EAA6D;AAAA,IAC/G,OAAA,EAAS,EAAE,aAAA,EAAe,CAAA,OAAA,EAAU,KAAK,CAAA,CAAA;AAAG,GAC7C,CAAA;AACD,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,2BAAA,EAA8B,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AACvE,EAAA,MAAM,IAAA,GAAgC,MAAM,GAAA,CAAI,IAAA,EAAK;AACrD,EAAA,MAAM,EAAA,GAAA,iBAAK,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAClC,EAAA,OAAA,CAAQ,KAAK,KAAA,IAAS,IAAI,MAAA,CAAO,CAAA,CAAA,KAAK,CAAC,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,EAAE,EAAE,GAAA,CAAI,CAAA,CAAA,KAAK,cAAc,CAAA,EAAG,MAAA,EAAQ,EAAE,CAAC,CAAA;AACjG;AAIA,eAAsB,cAAA,CAAe,OAAe,IAAA,EAAmC;AACrF,EAAA,MAAM,MAAA,GAAS,KAAK,WAAA,IAAe,YAAA;AACnC,EAAA,MAAM,OAAA,GAAU,YAAY,IAAI,CAAA;AAChC,EAAA,MAAM,QAAA,GAAW,CAAC,CAAC,IAAA,CAAK,OAAA;AACxB,EAAA,MAAM,GAAA,GAAM,QAAA,GACR,CAAA,EAAG,SAAS,CAAA,OAAA,EAAU,MAAM,CAAA,OAAA,EAAU,IAAA,CAAK,OAAO,CAAA,CAAA,GAClD,CAAA,EAAG,SAAS,UAAU,MAAM,CAAA,MAAA,CAAA;AAChC,EAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,IAC3B,MAAA,EAAQ,WAAW,OAAA,GAAU,MAAA;AAAA,IAC7B,OAAA,EAAS;AAAA,MACP,aAAA,EAAe,UAAU,KAAK,CAAA,CAAA;AAAA,MAC9B,cAAA,EAAgB;AAAA,KAClB;AAAA,IACA,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,GAC7B,CAAA;AACD,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,0BAAA,EAA6B,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AACtE,EAAA,MAAM,CAAA,GAAuB,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5C,EAAA,MAAM,EAAA,GAAA,iBAAK,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAClC,EAAA,OAAO;AAAA,IACL,GAAG,IAAA;AAAA,IACH,SAAS,CAAA,CAAE,EAAA;AAAA,IACX,WAAA,EAAa,MAAA;AAAA,IACb,QAAA,EAAU;AAAA,GACZ;AACF;AAEA,eAAsB,gBAAA,CAAiB,KAAA,EAAe,OAAA,EAAiB,MAAA,GAAiB,YAAA,EAA6B;AACnH,EAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,OAAA,EAAU,MAAM,CAAA,OAAA,EAAU,OAAO,CAAA,CAAA,EAAI;AAAA,IACvE,MAAA,EAAQ,QAAA;AAAA,IACR,OAAA,EAAS,EAAE,aAAA,EAAe,CAAA,OAAA,EAAU,KAAK,CAAA,CAAA;AAAG,GAC7C,CAAA;AAED,EAAA,IAAI,CAAC,GAAA,CAAI,EAAA,IAAM,GAAA,CAAI,MAAA,KAAW,GAAA,EAAK,MAAM,IAAI,KAAA,CAAM,CAAA,4BAAA,EAA+B,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAChG;ACzGA,IAAM,OAAA,GAA2C;AAAA,EAC/C,EAAE,EAAA,EAAI,OAAA,EAAY,KAAA,EAAO,OAAA,EAAQ;AAAA,EACjC,EAAE,EAAA,EAAI,UAAA,EAAY,KAAA,EAAO,UAAA,EAAW;AAAA,EACpC,EAAE,EAAA,EAAI,KAAA,EAAY,KAAA,EAAO,KAAA,EAAM;AAAA,EAC/B,EAAE,EAAA,EAAI,MAAA,EAAY,KAAA,EAAO,MAAA;AAC3B,CAAA;AAEA,SAAS,QAAA,GAAmB;AAC1B,EAAA,OAAA,qBAAW,IAAA,EAAK,EAAE,aAAY,CAAE,KAAA,CAAM,GAAG,EAAE,CAAA;AAC7C;AAEA,SAAS,YAAY,GAAA,EAAqB;AACxC,EAAA,MAAM,CAAA,mBAAI,IAAI,IAAA,CAAK,GAAA,GAAM,WAAW,CAAA;AACpC,EAAA,MAAM,KAAA,uBAAY,IAAA,EAAK;AAAG,EAAA,KAAA,CAAM,QAAA,CAAS,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AACnD,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAA,CAAO,CAAA,CAAE,OAAA,EAAQ,GAAI,KAAA,CAAM,OAAA,EAAQ,KAAM,EAAA,GAAK,IAAA,GAAO,GAAA,CAAK,CAAA;AAChF,EAAA,IAAI,QAAA,KAAa,GAAG,OAAO,OAAA;AAC3B,EAAA,IAAI,QAAA,KAAa,GAAG,OAAO,UAAA;AAC3B,EAAA,IAAI,QAAA,KAAa,IAAI,OAAO,WAAA;AAC5B,EAAA,IAAI,QAAA,GAAW,CAAA,IAAK,QAAA,GAAW,CAAA,EAAG,OAAO,CAAA,CAAE,kBAAA,CAAmB,MAAA,EAAW,EAAE,OAAA,EAAS,OAAA,EAAS,CAAA;AAC7F,EAAA,OAAO,CAAA,CAAE,mBAAmB,MAAA,EAAW,EAAE,OAAO,OAAA,EAAS,GAAA,EAAK,WAAW,CAAA;AAC3E;AAEA,SAAS,UAAU,CAAA,EAAsB;AACvC,EAAA,OAAO,CAAC,EAAE,IAAA,IAAQ,CAAC,CAAC,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,OAAA,GAAU,QAAA,EAAS;AACxD;AAEe,SAAR,QAAA,GAA4B;AACjC,EAAA,MAAM,EAAE,OAAO,OAAA,EAAS,UAAA,EAAY,YAAY,UAAA,EAAY,WAAA,KAAgB,YAAA,EAAa;AACzF,EAAA,MAAM,SAAS,aAAA,EAAc;AAC7B,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAiB,OAAO,CAAA;AACpD,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAwB,IAAI,CAAA;AAC9D,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,KAAK,CAAA;AAC1C,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAwB,IAAI,CAAA;AAC5D,EAAA,MAAM,WAAW,UAAA,EAAW;AAM5B,EAAA,MAAM,SAAA,GAAY,OAAO,KAAK,CAAA;AAC9B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,UAAU,OAAA,EAAS;AACvB,IAAA,SAAA,CAAU,OAAA,GAAU,IAAA;AACpB,IAAA,IAAI,YAAY,CAAC,MAAA,CAAO,WAAA,IAAe,KAAA,CAAM,WAAW,CAAA,EAAG;AACzD,MAAA,WAAA,CAAY,cAAc,CAAA;AAAA,IAC5B;AAAA,EAEF,CAAA,EAAG,EAAE,CAAA;AAGL,EAAA,MAAM,cAAA,GAAiB,YAAY,YAAY;AAC7C,IAAA,MAAM,QAAQ,oBAAA,EAAqB;AACnC,IAAA,IAAI,CAAC,KAAA,EAAO;AAAE,MAAA,aAAA,CAAM,KAAK,+BAA+B,CAAA;AAAG,MAAA;AAAA,IAAQ;AACnE,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,gBAAA,CAAiB,KAAK,CAAA;AAI3C,MAAA,MAAM,WAAA,GAAc,IAAI,GAAA,CAAI,MAAA,CAAO,GAAA,CAAI,CAAA,CAAA,KAAK,CAAC,CAAA,CAAE,OAAA,EAAU,CAAC,CAAC,CAAC,CAAA;AAC5D,MAAA,MAAM,SAAqB,EAAC;AAC5B,MAAA,KAAA,MAAW,SAAS,KAAA,EAAO;AACzB,QAAA,IAAI,CAAC,MAAM,OAAA,EAAS;AAAE,UAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAG,UAAA;AAAA,QAAU;AACpD,QAAA,MAAM,CAAA,GAAI,WAAA,CAAY,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA;AACvC,QAAA,IAAI,CAAC,CAAA,EAAG;AAEN,UAAA;AAAA,QACF;AAEA,QAAA,MAAA,CAAO,IAAA,CAAK;AAAA,UACV,GAAG,KAAA;AAAA,UACH,MAAM,CAAA,CAAE,IAAA;AAAA,UACR,MAAM,CAAA,CAAE,IAAA;AAAA,UACR,SAAS,CAAA,CAAE,OAAA;AAAA,UACX,OAAO,CAAA,CAAE,KAAA;AAAA,UACT,UAAU,CAAA,CAAE,QAAA;AAAA,UACZ,WAAW,CAAA,CAAE;AAAA,SACd,CAAA;AACD,QAAA,WAAA,CAAY,MAAA,CAAO,MAAM,OAAO,CAAA;AAAA,MAClC;AAEA,MAAA,KAAA,MAAW,KAAK,WAAA,CAAY,MAAA,EAAO,EAAG,MAAA,CAAO,KAAK,CAAC,CAAA;AACnD,MAAA,WAAA,CAAY,MAAM,CAAA;AAClB,MAAA,WAAA,CAAA,iBAAY,IAAI,IAAA,EAAK,EAAE,kBAAA,CAAmB,EAAC,EAAG,EAAE,IAAA,EAAM,SAAA,EAAW,MAAA,EAAQ,SAAA,EAAW,CAAC,CAAA;AACrF,MAAA,aAAA,CAAM,IAAA,CAAK,CAAA,OAAA,EAAU,MAAA,CAAO,MAAM,CAAA,kBAAA,CAAoB,CAAA;AAAA,IACxD,SAAS,CAAA,EAAQ;AACf,MAAA,aAAA,CAAM,IAAA,CAAK,CAAA,aAAA,EAAgB,CAAA,EAAG,OAAA,IAAW,eAAe,CAAA,CAAE,CAAA;AAAA,IAC5D,CAAA,SAAE;AACA,MAAA,UAAA,CAAW,KAAK,CAAA;AAAA,IAClB;AAAA,EACF,CAAA,EAAG,CAAC,KAAA,EAAO,WAAW,CAAC,CAAA;AAGvB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,MAAA,CAAO,aAAa,cAAA,EAAe;AAAA,EAEzC,CAAA,EAAG,CAAC,MAAA,CAAO,WAAW,CAAC,CAAA;AAGvB,EAAA,MAAM,eAAA,GAAkB,WAAA,CAAY,OAAO,IAAA,KAAmB;AAC5D,IAAA,MAAM,QAAQ,oBAAA,EAAqB;AACnC,IAAA,IAAI,CAAC,KAAA,IAAS,CAAC,MAAA,CAAO,WAAA,EAAa;AACnC,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,cAAA,CAAe,KAAA,EAAO,IAAI,CAAA;AAC/C,MAAA,IAAI,OAAO,OAAA,KAAY,IAAA,CAAK,WAAW,MAAA,CAAO,WAAA,KAAgB,KAAK,WAAA,EAAa;AAC9E,QAAA,UAAA,CAAW,IAAA,CAAK,EAAA,EAAI,EAAE,OAAA,EAAS,MAAA,CAAO,OAAA,EAAS,WAAA,EAAa,MAAA,CAAO,WAAA,EAAa,QAAA,EAAU,MAAA,CAAO,QAAA,EAAU,CAAA;AAAA,MAC7G,CAAA,MAAO;AACL,QAAA,UAAA,CAAW,KAAK,EAAA,EAAI,EAAE,QAAA,EAAU,MAAA,CAAO,UAAU,CAAA;AAAA,MACnD;AAAA,IACF,SAAS,CAAA,EAAQ;AACf,MAAA,aAAA,CAAM,IAAA,CAAK,CAAA,yBAAA,EAA4B,CAAA,EAAG,OAAA,IAAW,SAAS,CAAA,CAAE,CAAA;AAAA,IAClE;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,CAAO,WAAA,EAAa,UAAU,CAAC,CAAA;AAGnC,EAAA,MAAM,QAAQ,QAAA,EAAS;AACvB,EAAA,MAAM,OAAA,GAAU,QAAQ,MAAM;AAC5B,IAAA,MAAM,MAAA,GAAS,CAAC,GAAG,KAAK,EAAE,IAAA,CAAK,CAAC,GAAG,CAAA,KAAM;AAEvC,MAAA,IAAI,EAAE,IAAA,KAAS,CAAA,CAAE,MAAM,OAAO,CAAA,CAAE,OAAO,CAAA,GAAI,EAAA;AAE3C,MAAA,MAAM,QAAQ,SAAA,CAAU,CAAC,CAAA,EAAG,KAAA,GAAQ,UAAU,CAAC,CAAA;AAC/C,MAAA,IAAI,KAAA,KAAU,KAAA,EAAO,OAAO,KAAA,GAAQ,EAAA,GAAK,CAAA;AACzC,MAAA,IAAI,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,OAAA,KAAY,CAAA,CAAE,OAAA,EAAS,OAAO,CAAA,CAAE,OAAA,GAAU,CAAA,CAAE,UAAU,EAAA,GAAK,CAAA;AAC3F,MAAA,IAAI,CAAC,CAAC,CAAA,CAAE,OAAA,KAAY,CAAC,CAAC,CAAA,CAAE,OAAA,EAAS,OAAO,CAAA,CAAE,OAAA,GAAU,EAAA,GAAK,CAAA;AACzD,MAAA,OAAO,CAAA,CAAE,SAAA,CAAU,aAAA,CAAc,CAAA,CAAE,SAAS,CAAA;AAAA,IAC9C,CAAC,CAAA;AACD,IAAA,IAAI,MAAA,KAAW,OAAA,EAAS,OAAO,MAAA,CAAO,OAAO,CAAA,CAAA,KAAK,CAAC,CAAA,CAAE,IAAA,KAAS,CAAA,CAAE,OAAA,KAAY,KAAA,IAAS,SAAA,CAAU,CAAC,CAAA,CAAE,CAAA;AAClG,IAAA,IAAI,MAAA,KAAW,UAAA,EAAY,OAAO,MAAA,CAAO,MAAA,CAAO,CAAA,CAAA,KAAK,CAAC,CAAA,CAAE,IAAA,IAAQ,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,UAAU,KAAK,CAAA;AAC9F,IAAA,IAAI,WAAW,MAAA,EAAQ,OAAO,OAAO,MAAA,CAAO,CAAA,CAAA,KAAK,EAAE,IAAI,CAAA;AACvD,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,EAAG,CAAC,KAAA,EAAO,MAAA,EAAQ,KAAK,CAAC,CAAA;AAEzB,EAAA,MAAM,MAAA,GAAS,QAAQ,OAAO;AAAA,IAC5B,KAAA,EAAO,KAAA,CAAM,MAAA,CAAO,CAAA,CAAA,KAAK,CAAC,CAAA,CAAE,IAAA,KAAS,CAAA,CAAE,OAAA,KAAY,KAAA,IAAS,SAAA,CAAU,CAAC,EAAE,CAAA,CAAE,MAAA;AAAA,IAC3E,QAAA,EAAU,KAAA,CAAM,MAAA,CAAO,CAAA,CAAA,KAAK,CAAC,CAAA,CAAE,IAAA,IAAQ,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,OAAA,GAAU,KAAK,CAAA,CAAE,MAAA;AAAA,IACvE,KAAK,KAAA,CAAM,MAAA,CAAO,OAAK,CAAC,CAAA,CAAE,IAAI,CAAA,CAAE,MAAA;AAAA,IAChC,MAAM,KAAA,CAAM,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,IAAI,CAAA,CAAE;AAAA,GAClC,CAAA,EAAI,CAAC,KAAA,EAAO,KAAK,CAAC,CAAA;AAGlB,EAAA,MAAM,SAAA,GAAY,CAAC,KAAA,KAAkE;AACnF,IAAA,MAAM,EAAA,GAAK,QAAQ,KAAK,CAAA;AACxB,IAAA,SAAA,CAAU,KAAK,CAAA;AACf,IAAA,MAAM,QAAQ,EAAE,GAAG,OAAO,EAAA,EAAI,IAAA,EAAM,OAAO,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,aAAY,EAAG,SAAA,EAAA,qBAAe,IAAA,EAAK,EAAE,aAAY,EAAE;AACpH,IAAA,eAAA,CAAgB,KAAK,CAAA;AAAA,EACvB,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,EAAA,KAAe;AACnC,IAAA,MAAM,IAAI,KAAA,CAAM,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,EAAE,CAAA;AACrC,IAAA,IAAI,CAAC,CAAA,EAAG;AACR,IAAA,UAAA,CAAW,EAAE,CAAA;AACb,IAAA,eAAA,CAAgB,EAAE,GAAG,CAAA,EAAG,MAAM,CAAC,CAAA,CAAE,MAAM,CAAA;AAAA,EACzC,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,CAAC,EAAA,EAAY,KAAA,KAA6B;AAC3D,IAAA,UAAA,CAAW,IAAI,KAAK,CAAA;AACpB,IAAA,MAAM,IAAI,KAAA,CAAM,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,EAAE,CAAA;AACrC,IAAA,IAAI,GAAG,eAAA,CAAgB,EAAE,GAAG,CAAA,EAAG,GAAG,OAAO,CAAA;AAAA,EAC3C,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,OAAO,EAAA,KAAe;AACzC,IAAA,MAAM,IAAI,KAAA,CAAM,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,EAAE,CAAA;AACrC,IAAA,IAAI,CAAC,CAAA,EAAG;AACR,IAAA,MAAM,EAAA,GAAK,MAAM,OAAA,CAAQ;AAAA,MACvB,KAAA,EAAO,mBAAA;AAAA,MACP,OAAA,EAAS,CAAA,MAAA,EAAI,CAAA,CAAE,IAAI,CAAA,mHAAA,CAAA;AAAA,MACnB,YAAA,EAAc,QAAA;AAAA,MACd,OAAA,EAAS;AAAA,KACV,CAAA;AACD,IAAA,IAAI,CAAC,EAAA,EAAI;AACT,IAAA,UAAA,CAAW,EAAE,CAAA;AACb,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,IAAI,EAAE,OAAA,EAAS;AACb,MAAA,MAAM,QAAQ,oBAAA,EAAqB;AACnC,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,IAAI;AAAE,UAAA,MAAM,gBAAA,CAAiB,KAAA,EAAO,CAAA,CAAE,OAAA,EAAS,EAAE,WAAW,CAAA;AAAA,QAAG,SACxD,CAAA,EAAQ;AAAE,UAAA,aAAA,CAAM,IAAA,CAAK,CAAA,2BAAA,EAA8B,CAAA,EAAG,OAAA,IAAW,SAAS,CAAA,CAAE,CAAA;AAAA,QAAG;AAAA,MACxF;AAAA,IACF;AAAA,EACF,CAAA;AAEA,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,sBAAA,EAEb,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,qFAAA,EACb,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAA,EACZ,QAAA,EAAA,OAAA,CAAQ,IAAI,CAAA,CAAA,KAAK;AAChB,QAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,CAAA,CAAE,EAAE,CAAA;AACzB,QAAA,MAAM,MAAA,GAAS,WAAW,CAAA,CAAE,EAAA;AAC5B,QAAA,uBACE,IAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YAAkB,OAAA,EAAS,MAAM,SAAA,CAAU,CAAA,CAAE,EAAE,CAAA;AAAA,YAC9C,SAAA,EAAW,CAAA,uFAAA,EAA0F,MAAA,GAAS,wBAAA,GAA2B,iCAAiC,CAAA,CAAA;AAAA,YAC1K,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,MAAA,EAAA,EAAM,YAAE,KAAA,EAAM,CAAA;AAAA,8BACf,GAAA,CAAC,UAAK,SAAA,EAAW,CAAA,yBAAA,EAA4B,SAAS,eAAA,GAAkB,eAAe,IAAK,QAAA,EAAA,KAAA,EAAM;AAAA;AAAA,WAAA;AAAA,UAHvF,CAAA,CAAE;AAAA,SAIf;AAAA,MAEJ,CAAC,CAAA,EACH,CAAA;AAAA,sBACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAA,EACZ,QAAA,EAAA;AAAA,QAAA,MAAA,CAAO,WAAA,mBACN,IAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YAAO,OAAA,EAAS,cAAA;AAAA,YAAgB,QAAA,EAAU,OAAA;AAAA,YACzC,KAAA,EAAO,QAAA,GAAW,CAAA,YAAA,EAAe,QAAQ,CAAA,CAAA,GAAK,wBAAA;AAAA,YAC9C,SAAA,EAAU,sIAAA;AAAA,YACV,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,WAAU,uCAAA,EAAwC,CAAA;AAAA,8BACxD,GAAA,CAAC,UAAK,QAAA,EAAA,QAAA,EAAM,CAAA;AAAA,8BACZ,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,CAAA,QAAA,EAAW,OAAA,GAAU,iBAAiB,EAAE,CAAA,CAAA,EAAI,IAAA,EAAK,MAAA,EAAO,MAAA,EAAO,cAAA,EAAe,aAAa,CAAA,EAAG,OAAA,EAAQ,WAAA,EACpH,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAc,SAAQ,cAAA,EAAe,OAAA,EAAQ,CAAA,EAAE,6GAAA,EAA8G,CAAA,EACrK;AAAA;AAAA;AAAA,SACF,mBAEA,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YAAO,SAAS,MAAM,MAAA,CAAO,cAAc,IAAI,KAAA,CAAM,qBAAqB,CAAC,CAAA;AAAA,YAC1E,SAAA,EAAU,kHAAA;AAAA,YAAmH,QAAA,EAAA;AAAA;AAAA,SAE/H;AAAA,wBAEF,IAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YAAO,OAAA,EAAS,MAAM,SAAA,CAAU,CAAA,CAAA,KAAK,CAAC,CAAC,CAAA;AAAA,YACtC,SAAA,EAAU,6GAAA;AAAA,YACV,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,SAAI,SAAA,EAAU,aAAA,EAAc,OAAA,EAAQ,WAAA,EAAY,MAAK,MAAA,EAAO,MAAA,EAAO,cAAA,EAAe,WAAA,EAAa,KAC9F,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,eAAc,OAAA,EAAQ,CAAA,EAAE,oBAAmB,CAAA,EACnD,CAAA;AAAA,8BACA,GAAA,CAAC,UAAK,QAAA,EAAA,KAAA,EAAG;AAAA;AAAA;AAAA;AACX,OAAA,EACF;AAAA,KAAA,EACF,CAAA;AAAA,oBAGA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wBAAA,EACZ,QAAA,EAAA;AAAA,MAAA,MAAA,oBACC,GAAA,CAAC,cAAW,QAAA,EAAU,SAAA,EAAW,UAAU,MAAM,SAAA,CAAU,KAAK,CAAA,EAAG,CAAA;AAAA,MAEpE,OAAA,CAAQ,WAAW,CAAA,IAAK,CAAC,0BACxB,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,iFAAA,EACb,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,8BAAA,EAA+B,OAAA,EAAQ,WAAA,EAAY,MAAK,MAAA,EAAO,MAAA,EAAO,cAAA,EAAe,WAAA,EAAa,GAAA,EAC/G,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,GAAA,EAAI,CAAA,EAAE,GAAA,EAAI,OAAM,IAAA,EAAK,MAAA,EAAO,IAAA,EAAK,EAAA,EAAG,GAAA,EAAI,CAAA;AAAA,8BAC/C,MAAA,EAAA,EAAK,aAAA,EAAc,SAAQ,cAAA,EAAe,OAAA,EAAQ,GAAE,eAAA,EAAgB;AAAA,SAAA,EACvE,CAAA;AAAA,4BACC,GAAA,EAAA,EAAE,SAAA,EAAU,WAAW,QAAA,EAAA,MAAA,KAAW,MAAA,GAAS,4BAA4B,0CAAA,EAA2C;AAAA,OAAA,EACrH,CAAA;AAAA,MAED,OAAA,CAAQ,IAAI,CAAA,IAAA,qBACX,GAAA;AAAA,QAAC,OAAA;AAAA,QAAA;AAAA,UAEC,IAAA;AAAA,UACA,OAAA,EAAS,cAAc,IAAA,CAAK,EAAA;AAAA,UAC5B,QAAA,EAAU,MAAM,YAAA,CAAa,IAAA,CAAK,EAAE,CAAA;AAAA,UACpC,OAAA,EAAS,MAAM,YAAA,CAAa,SAAA,KAAc,KAAK,EAAA,GAAK,IAAA,GAAO,KAAK,EAAE,CAAA;AAAA,UAClE,MAAA,EAAQ,CAAC,KAAA,KAAU;AAAE,YAAA,UAAA,CAAW,IAAA,CAAK,IAAI,KAAK,CAAA;AAAG,YAAA,YAAA,CAAa,IAAI,CAAA;AAAA,UAAG,CAAA;AAAA,UACrE,QAAA,EAAU,MAAM,YAAA,CAAa,IAAA,CAAK,EAAE,CAAA;AAAA,UACpC,YAAA,EAAc,MAAM,YAAA,CAAa,IAAI;AAAA,SAAA;AAAA,QAPhC,IAAA,CAAK;AAAA,OASb;AAAA,KAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEJ;AAMA,SAAS,OAAA,CAAQ,EAAE,IAAA,EAAM,OAAA,EAAS,UAAU,OAAA,EAAS,MAAA,EAAQ,QAAA,EAAU,YAAA,EAAa,EAQjF;AACD,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,2BAAQ,UAAA,EAAA,EAAW,IAAA,EAAY,MAAA,EAAgB,QAAA,EAAU,cAAc,QAAA,EAAoB,CAAA;AAAA,EAC7F;AACA,EAAA,MAAM,WAAW,IAAA,CAAK,OAAA,GAAU,WAAA,CAAY,IAAA,CAAK,OAAO,CAAA,GAAI,IAAA;AAC5D,EAAA,MAAM,OAAA,GAAU,UAAU,IAAI,CAAA;AAC9B,EAAA,MAAM,KAAA,GAAS,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,SAAA,GAClC,CAAA,EAAG,IAAA,CAAK,SAAA,IAAa,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,SAAA,IAAa,GAAG,CAAA,CAAA,GAC/C,IAAA;AACJ,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,OAAA;AAAA,MACA,SAAA,EAAU,gHAAA;AAAA,MACV,QAAA,EAAA;AAAA,wBAAA,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YAAO,OAAA,EAAS,CAAC,CAAA,KAAM;AAAE,cAAA,CAAA,CAAE,eAAA,EAAgB;AAAG,cAAA,QAAA,EAAS;AAAA,YAAG,CAAA;AAAA,YACzD,SAAA,EAAU,UAAA;AAAA,YACT,eAAK,IAAA,mBACJ,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAwB,IAAA,EAAK,cAAA,EAAe,OAAA,EAAQ,WAAA,EACjE,8BAAC,MAAA,EAAA,EAAK,CAAA,EAAE,wFAAA,EAAyF,CAAA,EACnG,oBAEA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2CAAA,EAA4C,MAAK,MAAA,EAAO,MAAA,EAAO,cAAA,EAAe,WAAA,EAAa,GAAG,OAAA,EAAQ,WAAA,EACnH,QAAA,kBAAA,GAAA,CAAC,QAAA,EAAA,EAAO,IAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,CAAA,EAAE,MAAK,CAAA,EACjC;AAAA;AAAA,SAEJ;AAAA,4BACC,MAAA,EAAA,EAAK,SAAA,EAAW,CAAA,wBAAA,EAA2B,IAAA,CAAK,OAAO,4BAAA,GAA+B,eAAe,CAAA,CAAA,EACnG,QAAA,EAAA,IAAA,CAAK,wBAAQ,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,sBAAA,EAAuB,wBAAU,CAAA,EACjE,CAAA;AAAA,QACC,yBACC,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iDAAA,EAAkD,OAAM,iCAAA,EAAkC,QAAA,EAAA;AAAA,UAAA,YAAA;AAAA,UACpG;AAAA,SAAA,EACN,CAAA;AAAA,QAED,QAAA,oBACC,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,0DAA0D,OAAA,GAAU,yBAAA,GAA4B,IAAA,CAAK,OAAA,KAAY,QAAA,EAAS,GAAI,2BAAA,GAA8B,2BAA2B,IACrM,QAAA,EAAA,QAAA,EACH,CAAA;AAAA,QAED,KAAK,OAAA,oBACJ,GAAA,CAAC,UAAK,KAAA,EAAM,0BAAA,EAA2B,WAAU,gDAAA,EAAiD;AAAA;AAAA;AAAA,GAEtG;AAEJ;AAEA,SAAS,WAAW,EAAE,IAAA,EAAM,MAAA,EAAQ,QAAA,EAAU,UAAS,EAKpD;AACD,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,QAAA,CAAS,KAAK,IAAI,CAAA;AAC1C,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,IAAI,QAAA,CAAS,IAAA,CAAK,WAAW,EAAE,CAAA;AACzD,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,IAAI,QAAA,CAAS,IAAA,CAAK,aAAa,CAAC,CAAA;AAC9D,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,IAAI,QAAA,CAAS,IAAA,CAAK,SAAS,EAAE,CAAA;AAEnD,EAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO;AAAA,IAC1B,IAAA,EAAM,KAAK,IAAA,EAAK;AAAA,IAChB,SAAS,OAAA,IAAW,MAAA;AAAA,IACpB,SAAA,EAAW,SAAA,GAAY,CAAA,GAAI,SAAA,GAAY,MAAA;AAAA,IACvC,KAAA,EAAO,KAAA,CAAM,IAAA,EAAK,IAAK;AAAA,GACxB,CAAA;AAED,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,+CAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QAAM,SAAA,EAAS,IAAA;AAAA,QAAC,KAAA,EAAO,IAAA;AAAA,QAAM,QAAA,EAAU,CAAA,CAAA,KAAK,OAAA,CAAQ,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,QACjE,WAAW,CAAA,CAAA,KAAK;AAAE,UAAA,IAAI,EAAE,GAAA,KAAQ,OAAA,KAAY,EAAE,OAAA,IAAW,CAAA,CAAE,UAAU,MAAA,EAAO;AAAG,UAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU,QAAA,EAAS;AAAA,QAAG,CAAA;AAAA,QACnH,WAAA,EAAY,WAAA;AAAA,QACZ,SAAA,EAAU;AAAA;AAAA,KAAqI;AAAA,oBACjJ,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6BAAA,EACb,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,OAAA,EAAA,EAAM,WAAU,+EAAA,EAAgF,QAAA,EAAA;AAAA,QAAA,KAAA;AAAA,wBAE/F,GAAA;AAAA,UAAC,OAAA;AAAA,UAAA;AAAA,YAAM,IAAA,EAAK,MAAA;AAAA,YAAO,KAAA,EAAO,OAAA;AAAA,YAAS,QAAA,EAAU,CAAA,CAAA,KAAK,UAAA,CAAW,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,YACzE,SAAA,EAAU;AAAA;AAAA;AAAqI,OAAA,EACnJ,CAAA;AAAA,sBACA,IAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,+EAAA,EAAgF,QAAA,EAAA;AAAA,QAAA,YAAA;AAAA,wBAE/F,GAAA;AAAA,UAAC,OAAA;AAAA,UAAA;AAAA,YAAM,IAAA,EAAK,QAAA;AAAA,YAAS,GAAA,EAAK,CAAA;AAAA,YAAG,GAAA,EAAK,EAAA;AAAA,YAAI,KAAA,EAAO,SAAA;AAAA,YAC3C,UAAU,CAAA,CAAA,KAAK,YAAA,CAAa,IAAA,CAAK,GAAA,CAAI,GAAG,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,QAAA,CAAS,EAAE,MAAA,CAAO,KAAA,EAAO,EAAE,CAAA,IAAK,CAAC,CAAC,CAAC,CAAA;AAAA,YACxF,SAAA,EAAU;AAAA;AAAA;AAAqI,OAAA,EACnJ;AAAA,KAAA,EACF,CAAA;AAAA,oBACA,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QAAS,KAAA,EAAO,KAAA;AAAA,QAAO,QAAA,EAAU,CAAA,CAAA,KAAK,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,QAC5D,WAAA,EAAY,kBAAA;AAAA,QACZ,IAAA,EAAM,CAAA;AAAA,QACN,SAAA,EAAU;AAAA;AAAA,KAAwJ;AAAA,oBACpK,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wCAAA,EACb,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,QAAA,EAAA,EAAO,OAAA,EAAS,QAAA,EAAU,SAAA,EAAU,2CAA0C,QAAA,EAAA,QAAA,EAE/E,CAAA;AAAA,sBACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,YAAA,EACb,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,QAAA,EAAA,EAAO,OAAA,EAAS,QAAA,EAAU,SAAA,EAAU,6DAA4D,QAAA,EAAA,QAAA,EAAM,CAAA;AAAA,4BACtG,QAAA,EAAA,EAAO,OAAA,EAAS,MAAA,EAAQ,SAAA,EAAU,oFAAmF,QAAA,EAAA,MAAA,EAAI;AAAA,OAAA,EAC5H;AAAA,KAAA,EACF;AAAA,GAAA,EACF,CAAA;AAEJ;AAEA,SAAS,UAAA,CAAW,EAAE,QAAA,EAAU,QAAA,EAAS,EAGtC;AACD,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,EAAE,CAAA;AACnC,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,EAAE,CAAA;AACzC,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,CAAC,CAAA;AAE5C,EAAA,MAAM,SAAS,MAAM;AACnB,IAAA,MAAM,OAAA,GAAU,KAAK,IAAA,EAAK;AAC1B,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,QAAA,CAAS;AAAA,MACP,IAAA,EAAM,OAAA;AAAA,MACN,SAAS,OAAA,IAAW,MAAA;AAAA,MACpB,SAAA,EAAW,SAAA,GAAY,CAAA,GAAI,SAAA,GAAY;AAAA,KACxC,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,kDAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QAAM,SAAA,EAAS,IAAA;AAAA,QAAC,KAAA,EAAO,IAAA;AAAA,QAAM,QAAA,EAAU,CAAA,CAAA,KAAK,OAAA,CAAQ,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,QACjE,WAAW,CAAA,CAAA,KAAK;AAAE,UAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,OAAA,EAAS,MAAA,EAAO;AAAG,UAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU,QAAA,EAAS;AAAA,QAAG,CAAA;AAAA,QACvF,WAAA,EAAY,yBAAA;AAAA,QACZ,SAAA,EAAU;AAAA;AAAA,KAAqI;AAAA,oBACjJ,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,8BAAA,EACb,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,OAAA,EAAA,EAAM,WAAU,2FAAA,EAA4F,QAAA,EAAA;AAAA,QAAA,KAAA;AAAA,wBAE3G,GAAA;AAAA,UAAC,OAAA;AAAA,UAAA;AAAA,YAAM,IAAA,EAAK,MAAA;AAAA,YAAO,KAAA,EAAO,OAAA;AAAA,YAAS,QAAA,EAAU,CAAA,CAAA,KAAK,UAAA,CAAW,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,YACzE,SAAA,EAAU;AAAA;AAAA;AAAkI,OAAA,EAChJ,CAAA;AAAA,sBACA,IAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,2FAAA,EAA4F,QAAA,EAAA;AAAA,QAAA,YAAA;AAAA,wBAE3G,GAAA;AAAA,UAAC,OAAA;AAAA,UAAA;AAAA,YAAM,IAAA,EAAK,QAAA;AAAA,YAAS,GAAA,EAAK,CAAA;AAAA,YAAG,GAAA,EAAK,EAAA;AAAA,YAAI,KAAA,EAAO,SAAA;AAAA,YAC3C,UAAU,CAAA,CAAA,KAAK,YAAA,CAAa,IAAA,CAAK,GAAA,CAAI,GAAG,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,QAAA,CAAS,EAAE,MAAA,CAAO,KAAA,EAAO,EAAE,CAAA,IAAK,CAAC,CAAC,CAAC,CAAA;AAAA,YACxF,SAAA,EAAU;AAAA;AAAA;AAAkJ,OAAA,EAChK,CAAA;AAAA,sBACA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,QAAA,EAAS,CAAA;AAAA,0BACvB,QAAA,EAAA,EAAO,OAAA,EAAS,QAAA,EAAU,SAAA,EAAU,6DAA4D,QAAA,EAAA,QAAA,EAAM,CAAA;AAAA,0BACtG,QAAA,EAAA,EAAO,OAAA,EAAS,MAAA,EAAQ,SAAA,EAAU,oFAAmF,QAAA,EAAA,KAAA,EAAG;AAAA,KAAA,EAC3H;AAAA,GAAA,EACF,CAAA;AAEJ","file":"TodoList-7JZ2SLDI.js","sourcesContent":["/**\n * Google Tasks REST helpers — used by the Todo List app to two-way sync\n * with `tasks.googleapis.com`. Mirrors the lightweight pattern used by\n * Calendar.tsx (no SDK, just `fetch` + bearer token).\n *\n * • `fetchGoogleTasks(token)` — pull all tasks from the user's\n * `@default` list and map them to local `TodoTask` shape.\n * • `pushGoogleTask(token, task)` — POST (new) or PATCH (existing).\n * • `deleteGoogleTask(token, gtaskId, listId?)` — DELETE.\n *\n * Conflict policy is last-write-wins on `updated` timestamps, owned by\n * the caller (TodoList.tsx). These helpers just talk HTTP.\n */\n\nimport type { TodoTask } from './_todoTypes';\n\nconst TASKS_API = 'https://tasks.googleapis.com/tasks/v1';\nconst DEFAULT_LIST = '@default';\n\ninterface GoogleTaskPayload {\n id?: string;\n title?: string;\n notes?: string;\n status?: 'needsAction' | 'completed';\n due?: string; // RFC 3339 timestamp — Google stores due as a date-only field but in datetime form\n updated?: string;\n completed?: string;\n deleted?: boolean;\n}\n\ninterface GoogleTasksListResponse {\n items?: GoogleTaskPayload[];\n}\n\n/** Convert YYYY-MM-DD → RFC 3339 timestamp at noon UTC (Google's\n * expected representation for date-only `due`). */\nfunction dueToIso(date: string | undefined): string | undefined {\n if (!date) return undefined;\n return `${date}T12:00:00.000Z`;\n}\n\n/** Convert RFC 3339 → YYYY-MM-DD. */\nfunction dueFromIso(iso: string | undefined): string | undefined {\n if (!iso) return undefined;\n return iso.slice(0, 10);\n}\n\nfunction mapFromGoogle(g: GoogleTaskPayload, listId: string, ts: string): TodoTask {\n return {\n id: `gtask-${g.id}`,\n name: g.title || '',\n done: g.status === 'completed',\n dueDate: dueFromIso(g.due),\n notes: g.notes,\n gtaskId: g.id,\n gtaskListId: listId,\n syncedAt: ts,\n createdAt: g.updated || ts,\n updatedAt: g.updated || ts,\n };\n}\n\nfunction mapToGoogle(task: TodoTask): GoogleTaskPayload {\n return {\n title: task.name,\n notes: task.notes,\n status: task.done ? 'completed' : 'needsAction',\n due: dueToIso(task.dueDate),\n };\n}\n\nexport async function fetchGoogleTasks(token: string, listId: string = DEFAULT_LIST): Promise<TodoTask[]> {\n const res = await fetch(`${TASKS_API}/lists/${listId}/tasks?showCompleted=true&showHidden=false&maxResults=200`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) throw new Error(`Google Tasks fetch failed: ${res.status}`);\n const data: GoogleTasksListResponse = await res.json();\n const ts = new Date().toISOString();\n return (data.items || []).filter(g => !g.deleted && g.id).map(g => mapFromGoogle(g, listId, ts));\n}\n\n/** POST a new task or PATCH an existing one (decided by `task.gtaskId`).\n * Returns the merged TodoTask with `gtaskId` / `gtaskListId` / `syncedAt` populated. */\nexport async function pushGoogleTask(token: string, task: TodoTask): Promise<TodoTask> {\n const listId = task.gtaskListId || DEFAULT_LIST;\n const payload = mapToGoogle(task);\n const isUpdate = !!task.gtaskId;\n const url = isUpdate\n ? `${TASKS_API}/lists/${listId}/tasks/${task.gtaskId}`\n : `${TASKS_API}/lists/${listId}/tasks`;\n const res = await fetch(url, {\n method: isUpdate ? 'PATCH' : 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n });\n if (!res.ok) throw new Error(`Google Tasks push failed: ${res.status}`);\n const g: GoogleTaskPayload = await res.json();\n const ts = new Date().toISOString();\n return {\n ...task,\n gtaskId: g.id,\n gtaskListId: listId,\n syncedAt: ts,\n };\n}\n\nexport async function deleteGoogleTask(token: string, gtaskId: string, listId: string = DEFAULT_LIST): Promise<void> {\n const res = await fetch(`${TASKS_API}/lists/${listId}/tasks/${gtaskId}`, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${token}` },\n });\n // 404 means \"already gone\" — treat as success.\n if (!res.ok && res.status !== 404) throw new Error(`Google Tasks delete failed: ${res.status}`);\n}\n","import { useEffect, useMemo, useState, useCallback, useRef } from 'react';\nimport { useTodoTasks } from './_todoStore';\nimport type { TodoTask } from './_todoTypes';\nimport useGoogleAuth, { getGoogleAccessToken } from '../hooks/useGoogleAuth';\nimport { fetchGoogleTasks, pushGoogleTask, deleteGoogleTask } from './_googleTasks';\nimport { isDemoMode, getDemoTasks } from './google-demo-fixtures';\nimport toast from '../shell/toast';\nimport { confirm } from '../shell/ConfirmDialog';\n\ntype Filter = 'today' | 'upcoming' | 'all' | 'done';\n\nconst FILTERS: { id: Filter; label: string }[] = [\n { id: 'today', label: 'Today' },\n { id: 'upcoming', label: 'Upcoming' },\n { id: 'all', label: 'All' },\n { id: 'done', label: 'Done' },\n];\n\nfunction todayStr(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nfunction fmtDueLabel(due: string): string {\n const d = new Date(due + 'T00:00:00');\n const today = new Date(); today.setHours(0, 0, 0, 0);\n const diffDays = Math.round((d.getTime() - today.getTime()) / (24 * 3600 * 1000));\n if (diffDays === 0) return 'Today';\n if (diffDays === 1) return 'Tomorrow';\n if (diffDays === -1) return 'Yesterday';\n if (diffDays > 0 && diffDays < 7) return d.toLocaleDateString(undefined, { weekday: 'short' });\n return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n}\n\nfunction isOverdue(t: TodoTask): boolean {\n return !t.done && !!t.dueDate && t.dueDate < todayStr();\n}\n\nexport default function TodoList() {\n const { tasks, addTask, updateTask, removeTask, toggleDone, setAllTasks } = useTodoTasks();\n const google = useGoogleAuth();\n const [filter, setFilter] = useState<Filter>('today');\n const [editingId, setEditingId] = useState<string | null>(null);\n const [adding, setAdding] = useState(false);\n const [syncing, setSyncing] = useState(false);\n const [lastSync, setLastSync] = useState<string | null>(null);\n const demoMode = isDemoMode();\n\n // ── Demo-mode seed ──\n // First mount in demo mode and the user has no tasks yet → seed with\n // the canned `getDemoTasks()` list so the UI isn't empty when there's\n // no Google connection.\n const seededRef = useRef(false);\n useEffect(() => {\n if (seededRef.current) return;\n seededRef.current = true;\n if (demoMode && !google.isConnected && tasks.length === 0) {\n setAllTasks(getDemoTasks());\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // ── Google Tasks pull (on mount + manual sync button) ──\n const syncFromGoogle = useCallback(async () => {\n const token = getGoogleAccessToken();\n if (!token) { toast.info('Connect Google to sync tasks.'); return; }\n setSyncing(true);\n try {\n const remote = await fetchGoogleTasks(token);\n // Merge: keep local-only tasks (no gtaskId), replace synced ones\n // by gtaskId with the remote version (last-write-wins on\n // updatedAt vs Google's `updated`).\n const remoteByGid = new Map(remote.map(t => [t.gtaskId!, t]));\n const merged: TodoTask[] = [];\n for (const local of tasks) {\n if (!local.gtaskId) { merged.push(local); continue; }\n const r = remoteByGid.get(local.gtaskId);\n if (!r) {\n // Existed remotely before — Google deleted it. Drop locally.\n continue;\n }\n // Keep local id stable; absorb fresh fields.\n merged.push({\n ...local,\n name: r.name,\n done: r.done,\n dueDate: r.dueDate,\n notes: r.notes,\n syncedAt: r.syncedAt,\n updatedAt: r.updatedAt,\n });\n remoteByGid.delete(local.gtaskId);\n }\n // Anything left in the map is new from Google — append.\n for (const r of remoteByGid.values()) merged.push(r);\n setAllTasks(merged);\n setLastSync(new Date().toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }));\n toast.info(`Synced ${remote.length} tasks from Google`);\n } catch (e: any) {\n toast.info(`Sync failed: ${e?.message ?? 'unknown error'}`);\n } finally {\n setSyncing(false);\n }\n }, [tasks, setAllTasks]);\n\n // Auto-pull on mount when connected.\n useEffect(() => {\n if (google.isConnected) syncFromGoogle();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [google.isConnected]);\n\n // ── Push local changes to Google (fire-and-forget) ──\n const pushIfConnected = useCallback(async (task: TodoTask) => {\n const token = getGoogleAccessToken();\n if (!token || !google.isConnected) return;\n try {\n const synced = await pushGoogleTask(token, task);\n if (synced.gtaskId !== task.gtaskId || synced.gtaskListId !== task.gtaskListId) {\n updateTask(task.id, { gtaskId: synced.gtaskId, gtaskListId: synced.gtaskListId, syncedAt: synced.syncedAt });\n } else {\n updateTask(task.id, { syncedAt: synced.syncedAt });\n }\n } catch (e: any) {\n toast.info(`Couldn't push to Google: ${e?.message ?? 'unknown'}`);\n }\n }, [google.isConnected, updateTask]);\n\n // ── Filtered view ──\n const today = todayStr();\n const visible = useMemo(() => {\n const sorted = [...tasks].sort((a, b) => {\n // Done tasks always sink to the bottom.\n if (a.done !== b.done) return a.done ? 1 : -1;\n // Overdue first, then today, then by due date, then by createdAt.\n const aOver = isOverdue(a), bOver = isOverdue(b);\n if (aOver !== bOver) return aOver ? -1 : 1;\n if (a.dueDate && b.dueDate && a.dueDate !== b.dueDate) return a.dueDate < b.dueDate ? -1 : 1;\n if (!!a.dueDate !== !!b.dueDate) return a.dueDate ? -1 : 1;\n return a.createdAt.localeCompare(b.createdAt);\n });\n if (filter === 'today') return sorted.filter(t => !t.done && (t.dueDate === today || isOverdue(t)));\n if (filter === 'upcoming') return sorted.filter(t => !t.done && t.dueDate && t.dueDate > today);\n if (filter === 'done') return sorted.filter(t => t.done);\n return sorted;\n }, [tasks, filter, today]);\n\n const counts = useMemo(() => ({\n today: tasks.filter(t => !t.done && (t.dueDate === today || isOverdue(t))).length,\n upcoming: tasks.filter(t => !t.done && t.dueDate && t.dueDate > today).length,\n all: tasks.filter(t => !t.done).length,\n done: tasks.filter(t => t.done).length,\n }), [tasks, today]);\n\n // ── Action handlers ──\n const handleAdd = (input: { name: string; dueDate?: string; estimated?: number }) => {\n const id = addTask(input);\n setAdding(false);\n const fresh = { ...input, id, done: false, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() } as TodoTask;\n pushIfConnected(fresh);\n };\n\n const handleToggle = (id: string) => {\n const t = tasks.find(x => x.id === id);\n if (!t) return;\n toggleDone(id);\n pushIfConnected({ ...t, done: !t.done });\n };\n\n const handleEdit = (id: string, patch: Partial<TodoTask>) => {\n updateTask(id, patch);\n const t = tasks.find(x => x.id === id);\n if (t) pushIfConnected({ ...t, ...patch });\n };\n\n const handleDelete = async (id: string) => {\n const t = tasks.find(x => x.id === id);\n if (!t) return;\n const ok = await confirm({\n title: 'Delete this task?',\n message: `“${t.name}” will be removed from the Todo List, the Pomodoro widget, and Google Tasks (if synced). This can't be undone.`,\n confirmLabel: 'Delete',\n variant: 'danger',\n });\n if (!ok) return;\n removeTask(id);\n setEditingId(null);\n if (t.gtaskId) {\n const token = getGoogleAccessToken();\n if (token) {\n try { await deleteGoogleTask(token, t.gtaskId, t.gtaskListId); }\n catch (e: any) { toast.info(`Couldn't delete on Google: ${e?.message ?? 'unknown'}`); }\n }\n }\n };\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Header — filter pills + sync chip + add button */}\n <div className=\"flex items-center justify-between gap-3 px-4 py-2 border-b border-gray-200 shrink-0\">\n <div className=\"flex items-center gap-1\">\n {FILTERS.map(f => {\n const count = counts[f.id];\n const active = filter === f.id;\n return (\n <button key={f.id} onClick={() => setFilter(f.id)}\n className={`px-2.5 py-1 text-xs font-medium rounded-md transition-colors flex items-center gap-1.5 ${active ? 'bg-blue-600 text-white' : 'text-gray-600 hover:bg-gray-100'}`}>\n <span>{f.label}</span>\n <span className={`text-[10px] tabular-nums ${active ? 'text-white/80' : 'text-gray-400'}`}>{count}</span>\n </button>\n );\n })}\n </div>\n <div className=\"flex items-center gap-2\">\n {google.isConnected ? (\n <button onClick={syncFromGoogle} disabled={syncing}\n title={lastSync ? `Last synced ${lastSync}` : 'Sync with Google Tasks'}\n className=\"flex items-center gap-1.5 px-2 py-1 text-[11px] rounded-md border border-gray-200 text-gray-600 hover:bg-gray-50 disabled:opacity-50\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-green-500\" />\n <span>Google</span>\n <svg className={`h-3 w-3 ${syncing ? 'animate-spin' : ''}`} fill=\"none\" stroke=\"currentColor\" strokeWidth={2} viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15\" />\n </svg>\n </button>\n ) : (\n <button onClick={() => window.dispatchEvent(new Event('open-google-connect'))}\n className=\"flex items-center gap-1.5 px-2 py-1 text-[11px] rounded-md border border-gray-200 text-gray-500 hover:bg-gray-50\">\n Connect Google Tasks\n </button>\n )}\n <button onClick={() => setAdding(a => !a)}\n className=\"flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700\">\n <svg className=\"h-3.5 w-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <path strokeLinecap=\"round\" d=\"M12 5v14M5 12h14\" />\n </svg>\n <span>Add</span>\n </button>\n </div>\n </div>\n\n {/* List */}\n <div className=\"flex-1 overflow-y-auto\">\n {adding && (\n <AddTaskRow onSubmit={handleAdd} onCancel={() => setAdding(false)} />\n )}\n {visible.length === 0 && !adding && (\n <div className=\"flex flex-col items-center justify-center h-full text-center px-6 text-gray-400\">\n <svg className=\"h-10 w-10 mb-2 text-gray-300\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={1.5}>\n <rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"2\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9 12l2 2 4-4\" />\n </svg>\n <p className=\"text-sm\">{filter === 'done' ? 'No completed tasks yet.' : 'Nothing here. Add a task to get started.'}</p>\n </div>\n )}\n {visible.map(task => (\n <TaskRow\n key={task.id}\n task={task}\n editing={editingId === task.id}\n onToggle={() => handleToggle(task.id)}\n onClick={() => setEditingId(editingId === task.id ? null : task.id)}\n onSave={(patch) => { handleEdit(task.id, patch); setEditingId(null); }}\n onDelete={() => handleDelete(task.id)}\n onCancelEdit={() => setEditingId(null)}\n />\n ))}\n </div>\n </div>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────\n// Row + add form\n// ─────────────────────────────────────────────────────────────────────\n\nfunction TaskRow({ task, editing, onToggle, onClick, onSave, onDelete, onCancelEdit }: {\n task: TodoTask;\n editing: boolean;\n onToggle: () => void;\n onClick: () => void;\n onSave: (patch: Partial<TodoTask>) => void;\n onDelete: () => void;\n onCancelEdit: () => void;\n}) {\n if (editing) {\n return <EditDrawer task={task} onSave={onSave} onCancel={onCancelEdit} onDelete={onDelete} />;\n }\n const dueLabel = task.dueDate ? fmtDueLabel(task.dueDate) : null;\n const overdue = isOverdue(task);\n const pomos = (task.estimated || task.completed)\n ? `${task.completed ?? 0}/${task.estimated ?? '?'}`\n : null;\n return (\n <div\n onClick={onClick}\n className=\"flex items-center gap-3 px-4 py-2.5 border-b border-gray-200 cursor-pointer hover:bg-gray-50 transition-colors\">\n <button onClick={(e) => { e.stopPropagation(); onToggle(); }}\n className=\"shrink-0\">\n {task.done ? (\n <svg className=\"h-5 w-5 text-blue-600\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M10 0a10 10 0 100 20 10 10 0 000-20zm-1 14.5l-4.5-4.5 1.4-1.4 3.1 3.1 6.1-6.1 1.4 1.4z\" />\n </svg>\n ) : (\n <svg className=\"h-5 w-5 text-gray-300 hover:text-gray-500\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2} viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n </svg>\n )}\n </button>\n <span className={`flex-1 text-sm truncate ${task.done ? 'line-through text-gray-400' : 'text-gray-800'}`}>\n {task.name || <span className=\"italic text-gray-400\">(untitled)</span>}\n </span>\n {pomos && (\n <span className=\"shrink-0 text-[11px] tabular-nums text-gray-500\" title=\"Pomodoros completed / estimated\">\n 🍅 {pomos}\n </span>\n )}\n {dueLabel && (\n <span className={`shrink-0 text-[11px] font-medium px-1.5 py-0.5 rounded ${overdue ? 'bg-red-100 text-red-700' : task.dueDate === todayStr() ? 'bg-blue-100 text-blue-700' : 'bg-gray-100 text-gray-600'}`}>\n {dueLabel}\n </span>\n )}\n {task.gtaskId && (\n <span title=\"Synced with Google Tasks\" className=\"h-1.5 w-1.5 rounded-full bg-green-500 shrink-0\" />\n )}\n </div>\n );\n}\n\nfunction EditDrawer({ task, onSave, onCancel, onDelete }: {\n task: TodoTask;\n onSave: (patch: Partial<TodoTask>) => void;\n onCancel: () => void;\n onDelete: () => void;\n}) {\n const [name, setName] = useState(task.name);\n const [dueDate, setDueDate] = useState(task.dueDate || '');\n const [estimated, setEstimated] = useState(task.estimated ?? 0);\n const [notes, setNotes] = useState(task.notes || '');\n\n const submit = () => onSave({\n name: name.trim(),\n dueDate: dueDate || undefined,\n estimated: estimated > 0 ? estimated : undefined,\n notes: notes.trim() || undefined,\n });\n\n return (\n <div className=\"px-4 py-3 border-b border-gray-200 bg-gray-50\">\n <input autoFocus value={name} onChange={e => setName(e.target.value)}\n onKeyDown={e => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) submit(); if (e.key === 'Escape') onCancel(); }}\n placeholder=\"Task name\"\n className=\"w-full text-sm font-medium bg-white border border-gray-200 rounded px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500\" />\n <div className=\"grid grid-cols-2 gap-2 mt-2\">\n <label className=\"flex flex-col text-[10px] font-semibold text-gray-500 uppercase tracking-wide\">\n Due\n <input type=\"date\" value={dueDate} onChange={e => setDueDate(e.target.value)}\n className=\"mt-0.5 text-sm bg-white border border-gray-200 rounded px-2 py-1 text-gray-800 focus:outline-none focus:ring-1 focus:ring-blue-500\" />\n </label>\n <label className=\"flex flex-col text-[10px] font-semibold text-gray-500 uppercase tracking-wide\">\n Est. pomos\n <input type=\"number\" min={0} max={20} value={estimated}\n onChange={e => setEstimated(Math.max(0, Math.min(20, parseInt(e.target.value, 10) || 0)))}\n className=\"mt-0.5 text-sm bg-white border border-gray-200 rounded px-2 py-1 text-gray-800 focus:outline-none focus:ring-1 focus:ring-blue-500\" />\n </label>\n </div>\n <textarea value={notes} onChange={e => setNotes(e.target.value)}\n placeholder=\"Notes (optional)\"\n rows={2}\n className=\"mt-2 w-full text-xs bg-white border border-gray-200 rounded px-2 py-1.5 text-gray-700 focus:outline-none focus:ring-1 focus:ring-blue-500 resize-none\" />\n <div className=\"flex items-center justify-between mt-2\">\n <button onClick={onDelete} className=\"text-xs text-red-600 hover:text-red-700\">\n Delete\n </button>\n <div className=\"flex gap-2\">\n <button onClick={onCancel} className=\"px-3 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded\">Cancel</button>\n <button onClick={submit} className=\"px-3 py-1 text-xs font-semibold bg-blue-600 text-white rounded hover:bg-blue-700\">Save</button>\n </div>\n </div>\n </div>\n );\n}\n\nfunction AddTaskRow({ onSubmit, onCancel }: {\n onSubmit: (input: { name: string; dueDate?: string; estimated?: number }) => void;\n onCancel: () => void;\n}) {\n const [name, setName] = useState('');\n const [dueDate, setDueDate] = useState('');\n const [estimated, setEstimated] = useState(0);\n\n const submit = () => {\n const trimmed = name.trim();\n if (!trimmed) return;\n onSubmit({\n name: trimmed,\n dueDate: dueDate || undefined,\n estimated: estimated > 0 ? estimated : undefined,\n });\n };\n\n return (\n <div className=\"px-4 py-3 border-b border-gray-200 bg-blue-50/40\">\n <input autoFocus value={name} onChange={e => setName(e.target.value)}\n onKeyDown={e => { if (e.key === 'Enter') submit(); if (e.key === 'Escape') onCancel(); }}\n placeholder=\"What do you need to do?\"\n className=\"w-full text-sm font-medium bg-white border border-gray-200 rounded px-2 py-1.5 focus:outline-none focus:ring-1 focus:ring-blue-500\" />\n <div className=\"flex items-center gap-2 mt-2\">\n <label className=\"flex items-center gap-1.5 text-[10px] font-semibold text-gray-500 uppercase tracking-wide\">\n Due\n <input type=\"date\" value={dueDate} onChange={e => setDueDate(e.target.value)}\n className=\"text-xs bg-white border border-gray-200 rounded px-1.5 py-0.5 text-gray-800 focus:outline-none focus:ring-1 focus:ring-blue-500\" />\n </label>\n <label className=\"flex items-center gap-1.5 text-[10px] font-semibold text-gray-500 uppercase tracking-wide\">\n Est. pomos\n <input type=\"number\" min={0} max={20} value={estimated}\n onChange={e => setEstimated(Math.max(0, Math.min(20, parseInt(e.target.value, 10) || 0)))}\n className=\"w-12 text-xs bg-white border border-gray-200 rounded px-1.5 py-0.5 text-right text-gray-800 focus:outline-none focus:ring-1 focus:ring-blue-500\" />\n </label>\n <div className=\"flex-1\" />\n <button onClick={onCancel} className=\"px-3 py-1 text-xs text-gray-600 hover:bg-gray-100 rounded\">Cancel</button>\n <button onClick={submit} className=\"px-3 py-1 text-xs font-semibold bg-blue-600 text-white rounded hover:bg-blue-700\">Add</button>\n </div>\n </div>\n );\n}\n"]}