diffsplain 0.1.0
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.
- package/README.md +64 -0
- package/dist/assets/geist-latin-BGnTDqni.woff2 +0 -0
- package/dist/assets/geist-mono-latin-pyAoGB9p.woff2 +0 -0
- package/dist/assets/index-CLtrIbvS.css +2 -0
- package/dist/assets/index-d0QMfM_f.js +10 -0
- package/dist/demo-diff-data.json +264 -0
- package/dist/favicon.svg +6 -0
- package/dist/index.html +19 -0
- package/package.json +62 -0
- package/scripts/build-diff-data.mjs +1003 -0
- package/scripts/clean-built-live-data.mjs +9 -0
- package/scripts/cli-args.mjs +293 -0
- package/scripts/generate-summaries.mjs +735 -0
- package/scripts/present.mjs +228 -0
- package/scripts/serve-built.mjs +135 -0
- package/scripts/summary-path.mjs +35 -0
- package/scripts/write-todo-demo.mjs +60 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "5d2d2bb7580d",
|
|
3
|
+
"generatedAt": "2026-07-29T06:30:00.000Z",
|
|
4
|
+
"repo": {
|
|
5
|
+
"name": "todo-list-demo",
|
|
6
|
+
"root": "/demo/todo-list",
|
|
7
|
+
"base": "main",
|
|
8
|
+
"head": "feature/todo-filters",
|
|
9
|
+
"branch": "feature/todo-filters",
|
|
10
|
+
"baseBranch": "main",
|
|
11
|
+
"target": {
|
|
12
|
+
"kind": "pull-request"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"change": {
|
|
16
|
+
"title": "Add filters, due dates, and saved todos",
|
|
17
|
+
"number": 42,
|
|
18
|
+
"summary": "Adds the basic pieces of a useful todo list: persistent tasks, completion controls, filters, due dates, and focused tests.",
|
|
19
|
+
"why": "The first version lost tasks on refresh and treated every item the same. This change makes the small app useful across sessions and keeps finished work out of the way.",
|
|
20
|
+
"highlights": [
|
|
21
|
+
"Persists todos in local storage and restores them on load.",
|
|
22
|
+
"Adds active, completed, and all-task filters.",
|
|
23
|
+
"Adds due dates, priorities, completion styles, and reducer tests."
|
|
24
|
+
],
|
|
25
|
+
"risks": [
|
|
26
|
+
"Stored todos from older versions need safe defaults for new fields.",
|
|
27
|
+
"Date-only values must not shift when the browser applies a time zone."
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"notes": {
|
|
31
|
+
"reviewFingerprint": "5d2d2bb7580d9040d31c2f29c55688264f6d4724d1ba921a86d4ba11513382d8",
|
|
32
|
+
"generatedFor": "5d2d2bb7580d9040d31c2f29c55688264f6d4724d1ba921a86d4ba11513382d8",
|
|
33
|
+
"fresh": true,
|
|
34
|
+
"complete": true
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
{
|
|
38
|
+
"path": "src/components/TodoItem.tsx",
|
|
39
|
+
"status": "modified",
|
|
40
|
+
"additions": 12,
|
|
41
|
+
"deletions": 3,
|
|
42
|
+
"patch": "diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx\nindex e86c1ab..d3b508a 100644\n--- a/src/components/TodoItem.tsx\n+++ b/src/components/TodoItem.tsx\n@@ -8,8 +8,17 @@ type Props = {\n export function TodoItem({ todo, onToggle }: Props) {\n return (\n- <li className=\"todo-item\">\n- <button onClick={() => onToggle(todo.id)}>Done</button>\n- <span>{todo.title}</span>\n+ <li className={todo.completed ? \"todo-item is-complete\" : \"todo-item\"}>\n+ <label className=\"todo-check\">\n+ <input\n+ type=\"checkbox\"\n+ checked={todo.completed}\n+ onChange={() => onToggle(todo.id)}\n+ />\n+ <span>{todo.title}</span>\n+ </label>\n+ {todo.dueDate ? (\n+ <time dateTime={todo.dueDate}>{formatDueDate(todo.dueDate)}</time>\n+ ) : null}\n </li>\n );\n }",
|
|
43
|
+
"summary": {
|
|
44
|
+
"title": "Make completion a clear state",
|
|
45
|
+
"what": "Replaces the generic Done button with a checked control and shows an optional due date beside each todo.",
|
|
46
|
+
"why": "A todo should expose its current completion state without making the reader infer it from a button label.",
|
|
47
|
+
"details": [
|
|
48
|
+
"The checked state now comes from todo.completed.",
|
|
49
|
+
"The due date uses a semantic time element."
|
|
50
|
+
],
|
|
51
|
+
"risks": [
|
|
52
|
+
"formatDueDate must treat the stored value as a local date, not UTC."
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"isBinary": false,
|
|
56
|
+
"isTruncated": false,
|
|
57
|
+
"totalDiffLines": 25,
|
|
58
|
+
"snippet": "diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx\nindex e86c1ab..d3b508a 100644\n--- a/src/components/TodoItem.tsx\n+++ b/src/components/TodoItem.tsx\n@@ -8,8 +8,17 @@ type Props = {\n export function TodoItem({ todo, onToggle }: Props) {\n return (\n- <li className=\"todo-item\">\n- <button onClick={() => onToggle(todo.id)}>Done</button>\n- <span>{todo.title}</span>\n+ <li className={todo.completed ? \"todo-item is-complete\" : \"todo-item\"}>\n+ <label className=\"todo-check\">\n+ <input\n+ type=\"checkbox\"\n+ checked={todo.completed}\n+ onChange={() => onToggle(todo.id)}\n+ />\n+ <span>{todo.title}</span>\n+ </label>\n+ {todo.dueDate ? (\n+ <time dateTime={todo.dueDate}>{formatDueDate(todo.dueDate)}</time>\n+ ) : null}\n </li>\n );\n }"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"path": "src/components/TodoList.tsx",
|
|
62
|
+
"status": "modified",
|
|
63
|
+
"additions": 13,
|
|
64
|
+
"deletions": 6,
|
|
65
|
+
"patch": "diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx\nindex 2620e53..498407f 100644\n--- a/src/components/TodoList.tsx\n+++ b/src/components/TodoList.tsx\n@@ -4,13 +4,20 @@ import { TodoItem } from \"./TodoItem\";\n export function TodoList({ todos, filter, onToggle }: Props) {\n- if (!todos.length) {\n- return <p>No todos yet.</p>;\n- }\n+ const visibleTodos = todos.filter((todo) => {\n+ if (filter === \"active\") return !todo.completed;\n+ if (filter === \"completed\") return todo.completed;\n+ return true;\n+ });\n \n return (\n- <ul className=\"todo-list\">\n- {todos.map((todo) => (\n+ <section aria-live=\"polite\">\n+ {!visibleTodos.length ? (\n+ <p className=\"empty-todos\">No todos match this filter.</p>\n+ ) : null}\n+ <ul className=\"todo-list\">\n+ {visibleTodos.map((todo) => (\n <TodoItem key={todo.id} todo={todo} onToggle={onToggle} />\n ))}\n- </ul>\n+ </ul>\n+ </section>\n );\n }",
|
|
66
|
+
"summary": {
|
|
67
|
+
"title": "Filter the visible todo list",
|
|
68
|
+
"what": "Derives the shown todos from the selected filter and gives an empty filter a useful message.",
|
|
69
|
+
"why": "Completed items should be easy to hide without changing the stored list.",
|
|
70
|
+
"details": [
|
|
71
|
+
"All, active, and completed views share one source array.",
|
|
72
|
+
"The list announces filter results without moving focus."
|
|
73
|
+
],
|
|
74
|
+
"risks": [
|
|
75
|
+
"The filter names must stay aligned with the FilterBar values."
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
"isBinary": false,
|
|
79
|
+
"isTruncated": false,
|
|
80
|
+
"totalDiffLines": 31,
|
|
81
|
+
"snippet": "diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx\nindex 2620e53..498407f 100644\n--- a/src/components/TodoList.tsx\n+++ b/src/components/TodoList.tsx\n@@ -4,13 +4,20 @@ import { TodoItem } from \"./TodoItem\";\n export function TodoList({ todos, filter, onToggle }: Props) {\n- if (!todos.length) {\n- return <p>No todos yet.</p>;\n- }\n+ const visibleTodos = todos.filter((todo) => {\n+ if (filter === \"active\") return !todo.completed;\n+ if (filter === \"completed\") return todo.completed;\n+ return true;\n+ });\n \n return (\n- <ul className=\"todo-list\">\n- {todos.map((todo) => (\n+ <section aria-live=\"polite\">\n+ {!visibleTodos.length ? (\n+ <p className=\"empty-todos\">No todos match this filter.</p>\n+ ) : null}\n+ <ul className=\"todo-list\">\n+ {visibleTodos.map((todo) => (\n <TodoItem key={todo.id} todo={todo} onToggle={onToggle} />\n ))}\n- </ul>\n+ </ul>\n+ </section>\n );\n }"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"path": "src/components/AddTodoForm.tsx",
|
|
85
|
+
"status": "modified",
|
|
86
|
+
"additions": 13,
|
|
87
|
+
"deletions": 3,
|
|
88
|
+
"patch": "diff --git a/src/components/AddTodoForm.tsx b/src/components/AddTodoForm.tsx\nindex 2c5e70f..06a9971 100644\n--- a/src/components/AddTodoForm.tsx\n+++ b/src/components/AddTodoForm.tsx\n@@ -9,15 +9,25 @@ export function AddTodoForm({ onAdd }: Props) {\n const [title, setTitle] = useState(\"\");\n+ const [dueDate, setDueDate] = useState(\"\");\n \n function submit(event: FormEvent) {\n event.preventDefault();\n- onAdd(title);\n+ const cleanTitle = title.trim();\n+ if (!cleanTitle) return;\n+ onAdd({ title: cleanTitle, dueDate: dueDate || undefined });\n setTitle(\"\");\n+ setDueDate(\"\");\n }\n \n return (\n <form onSubmit={submit}>\n- <input value={title} onChange={(event) => setTitle(event.target.value)} />\n- <button>Add</button>\n+ <input\n+ value={title}\n+ onChange={(event) => setTitle(event.target.value)}\n+ placeholder=\"What needs doing?\"\n+ aria-label=\"Todo title\"\n+ />\n+ <input type=\"date\" value={dueDate} onChange={(event) => setDueDate(event.target.value)} />\n+ <button disabled={!title.trim()}>Add todo</button>\n </form>\n );\n }",
|
|
89
|
+
"summary": {
|
|
90
|
+
"title": "Reject blank todos at the form",
|
|
91
|
+
"what": "Trims the title, disables empty submission, and lets a new todo carry an optional due date.",
|
|
92
|
+
"why": "Blank rows create cleanup work and make the list feel broken.",
|
|
93
|
+
"details": [
|
|
94
|
+
"Both fields reset after a successful add.",
|
|
95
|
+
"The title input has a clear accessible name."
|
|
96
|
+
],
|
|
97
|
+
"risks": [
|
|
98
|
+
"The date input needs a visible label in the final form layout."
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
"isBinary": false,
|
|
102
|
+
"isTruncated": false,
|
|
103
|
+
"totalDiffLines": 33,
|
|
104
|
+
"snippet": "diff --git a/src/components/AddTodoForm.tsx b/src/components/AddTodoForm.tsx\nindex 2c5e70f..06a9971 100644\n--- a/src/components/AddTodoForm.tsx\n+++ b/src/components/AddTodoForm.tsx\n@@ -9,15 +9,25 @@ export function AddTodoForm({ onAdd }: Props) {\n const [title, setTitle] = useState(\"\");\n+ const [dueDate, setDueDate] = useState(\"\");\n \n function submit(event: FormEvent) {\n event.preventDefault();\n- onAdd(title);\n+ const cleanTitle = title.trim();\n+ if (!cleanTitle) return;\n+ onAdd({ title: cleanTitle, dueDate: dueDate || undefined });\n setTitle(\"\");\n+ setDueDate(\"\");\n }\n \n return (\n <form onSubmit={submit}>\n- <input value={title} onChange={(event) => setTitle(event.target.value)} />\n- <button>Add</button>\n+ <input\n+ value={title}\n+ onChange={(event) => setTitle(event.target.value)}\n+ placeholder=\"What needs doing?\"\n+ aria-label=\"Todo title\"\n+ />\n+ <input type=\"date\" value={dueDate} onChange={(event) => setDueDate(event.target.value)} />\n+ <button disabled={!title.trim()}>Add todo</button>\n </form>\n );\n }"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"path": "src/hooks/useTodos.ts",
|
|
108
|
+
"status": "modified",
|
|
109
|
+
"additions": 19,
|
|
110
|
+
"deletions": 4,
|
|
111
|
+
"patch": "diff --git a/src/hooks/useTodos.ts b/src/hooks/useTodos.ts\nindex 6bbf15c..f786fb1 100644\n--- a/src/hooks/useTodos.ts\n+++ b/src/hooks/useTodos.ts\n@@ -1,9 +1,24 @@\n-import { useState } from \"react\";\n+import { useEffect, useReducer } from \"react\";\n+import { todoReducer } from \"../lib/todoStore\";\n \n export function useTodos() {\n- const [todos, setTodos] = useState<Todo[]>([]);\n+ const [todos, dispatch] = useReducer(\n+ todoReducer,\n+ [],\n+ () => {\n+ const saved = localStorage.getItem(\"todos\");\n+ return saved ? JSON.parse(saved) : [];\n+ },\n+ );\n \n- const addTodo = (title: string) => setTodos([...todos, createTodo(title)]);\n+ useEffect(() => {\n+ localStorage.setItem(\"todos\", JSON.stringify(todos));\n+ }, [todos]);\n \n- return { todos, addTodo };\n+ return {\n+ todos,\n+ addTodo: (input: NewTodo) => dispatch({ type: \"added\", input }),\n+ toggleTodo: (id: string) => dispatch({ type: \"toggled\", id }),\n+ removeTodo: (id: string) => dispatch({ type: \"removed\", id }),\n+ };\n }",
|
|
112
|
+
"summary": {
|
|
113
|
+
"title": "Keep todos after refresh",
|
|
114
|
+
"what": "Moves todo updates into a reducer, restores saved items on first load, and saves each later change.",
|
|
115
|
+
"why": "A useful todo list cannot discard its contents whenever the page reloads.",
|
|
116
|
+
"details": [
|
|
117
|
+
"The lazy reducer initializer reads storage only once.",
|
|
118
|
+
"All writes flow through named reducer actions."
|
|
119
|
+
],
|
|
120
|
+
"risks": [
|
|
121
|
+
"Malformed stored JSON currently stops initialization and needs a fallback."
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
"isBinary": false,
|
|
125
|
+
"isTruncated": false,
|
|
126
|
+
"totalDiffLines": 33,
|
|
127
|
+
"snippet": "diff --git a/src/hooks/useTodos.ts b/src/hooks/useTodos.ts\nindex 6bbf15c..f786fb1 100644\n--- a/src/hooks/useTodos.ts\n+++ b/src/hooks/useTodos.ts\n@@ -1,9 +1,24 @@\n-import { useState } from \"react\";\n+import { useEffect, useReducer } from \"react\";\n+import { todoReducer } from \"../lib/todoStore\";\n \n export function useTodos() {\n- const [todos, setTodos] = useState<Todo[]>([]);\n+ const [todos, dispatch] = useReducer(\n+ todoReducer,\n+ [],\n+ () => {\n+ const saved = localStorage.getItem(\"todos\");\n+ return saved ? JSON.parse(saved) : [];\n+ },\n+ );\n \n- const addTodo = (title: string) => setTodos([...todos, createTodo(title)]);\n+ useEffect(() => {\n+ localStorage.setItem(\"todos\", JSON.stringify(todos));\n+ }, [todos]);\n \n- return { todos, addTodo };\n+ return {\n+ todos,\n+ addTodo: (input: NewTodo) => dispatch({ type: \"added\", input }),\n+ toggleTodo: (id: string) => dispatch({ type: \"toggled\", id }),\n+ removeTodo: (id: string) => dispatch({ type: \"removed\", id }),\n+ };\n }"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"path": "src/lib/todoStore.ts",
|
|
131
|
+
"status": "added",
|
|
132
|
+
"additions": 30,
|
|
133
|
+
"deletions": 0,
|
|
134
|
+
"patch": "diff --git a/src/lib/todoStore.ts b/src/lib/todoStore.ts\nnew file mode 100644\nindex 0000000..9f7006c\n--- /dev/null\n+++ b/src/lib/todoStore.ts\n@@ -0,0 +1,30 @@\n+import type { NewTodo, Todo, TodoAction } from \"../types/todo\";\n+\n+export function todoReducer(todos: Todo[], action: TodoAction): Todo[] {\n+ if (action.type === \"added\") {\n+ return [\n+ ...todos,\n+ {\n+ id: crypto.randomUUID(),\n+ title: action.input.title,\n+ completed: false,\n+ dueDate: action.input.dueDate,\n+ priority: action.input.priority ?? \"normal\",\n+ },\n+ ];\n+ }\n+\n+ if (action.type === \"toggled\") {\n+ return todos.map((todo) =>\n+ todo.id === action.id\n+ ? { ...todo, completed: !todo.completed }\n+ : todo,\n+ );\n+ }\n+\n+ if (action.type === \"removed\") {\n+ return todos.filter((todo) => todo.id !== action.id);\n+ }\n+\n+ return todos;\n+}",
|
|
135
|
+
"summary": {
|
|
136
|
+
"title": "Put todo updates in one reducer",
|
|
137
|
+
"what": "Adds pure add, toggle, and remove transitions for the todo collection.",
|
|
138
|
+
"why": "Central state transitions are easier to test and keep storage writes separate from list logic.",
|
|
139
|
+
"details": [
|
|
140
|
+
"New todos receive an id, default completion state, and normal priority.",
|
|
141
|
+
"Every action returns a new array."
|
|
142
|
+
],
|
|
143
|
+
"risks": [
|
|
144
|
+
"crypto.randomUUID needs a fallback in older browsers and some test runners."
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
"isBinary": false,
|
|
148
|
+
"isTruncated": false,
|
|
149
|
+
"totalDiffLines": 36,
|
|
150
|
+
"snippet": "diff --git a/src/lib/todoStore.ts b/src/lib/todoStore.ts\nnew file mode 100644\nindex 0000000..9f7006c\n--- /dev/null\n+++ b/src/lib/todoStore.ts\n@@ -0,0 +1,30 @@\n+import type { NewTodo, Todo, TodoAction } from \"../types/todo\";\n+\n+export function todoReducer(todos: Todo[], action: TodoAction): Todo[] {\n+ if (action.type === \"added\") {\n+ return [\n+ ...todos,\n+ {\n+ id: crypto.randomUUID(),\n+ title: action.input.title,\n+ completed: false,\n+ dueDate: action.input.dueDate,\n+ priority: action.input.priority ?? \"normal\",\n+ },\n+ ];\n+ }\n+\n+ if (action.type === \"toggled\") {\n+ return todos.map((todo) =>\n+ todo.id === action.id\n+ ? { ...todo, completed: !todo.completed }\n+ : todo,\n+ );\n+ }\n+\n+ if (action.type === \"removed\") {\n+ return todos.filter((todo) => todo.id !== action.id);\n+ }\n+\n+ return todos;\n+}"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"path": "src/types/todo.ts",
|
|
154
|
+
"status": "modified",
|
|
155
|
+
"additions": 14,
|
|
156
|
+
"deletions": 1,
|
|
157
|
+
"patch": "diff --git a/src/types/todo.ts b/src/types/todo.ts\nindex 25e13c0..a4066ed 100644\n--- a/src/types/todo.ts\n+++ b/src/types/todo.ts\n@@ -1,5 +1,18 @@\n export type Todo = {\n id: string;\n title: string;\n- done: boolean;\n+ completed: boolean;\n+ dueDate?: string;\n+ priority: \"low\" | \"normal\" | \"high\";\n };\n+\n+export type NewTodo = Pick<Todo, \"title\" | \"dueDate\"> & {\n+ priority?: Todo[\"priority\"];\n+};\n+\n+export type TodoFilter = \"all\" | \"active\" | \"completed\";\n+\n+export type TodoAction =\n+ | { type: \"added\"; input: NewTodo }\n+ | { type: \"toggled\"; id: string }\n+ | { type: \"removed\"; id: string };",
|
|
158
|
+
"summary": {
|
|
159
|
+
"title": "Describe richer todo state",
|
|
160
|
+
"what": "Renames the completion field and adds due dates, priorities, filters, and reducer action types.",
|
|
161
|
+
"why": "The UI and reducer need one shared contract for the new behavior.",
|
|
162
|
+
"details": [
|
|
163
|
+
"NewTodo permits a missing priority so the reducer can supply a default.",
|
|
164
|
+
"Filter values form a closed string union."
|
|
165
|
+
],
|
|
166
|
+
"risks": [
|
|
167
|
+
"Existing saved items still use done and need a small migration."
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
"isBinary": false,
|
|
171
|
+
"isTruncated": false,
|
|
172
|
+
"totalDiffLines": 24,
|
|
173
|
+
"snippet": "diff --git a/src/types/todo.ts b/src/types/todo.ts\nindex 25e13c0..a4066ed 100644\n--- a/src/types/todo.ts\n+++ b/src/types/todo.ts\n@@ -1,5 +1,18 @@\n export type Todo = {\n id: string;\n title: string;\n- done: boolean;\n+ completed: boolean;\n+ dueDate?: string;\n+ priority: \"low\" | \"normal\" | \"high\";\n };\n+\n+export type NewTodo = Pick<Todo, \"title\" | \"dueDate\"> & {\n+ priority?: Todo[\"priority\"];\n+};\n+\n+export type TodoFilter = \"all\" | \"active\" | \"completed\";\n+\n+export type TodoAction =\n+ | { type: \"added\"; input: NewTodo }\n+ | { type: \"toggled\"; id: string }\n+ | { type: \"removed\"; id: string };"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"path": "src/styles/todos.css",
|
|
177
|
+
"status": "modified",
|
|
178
|
+
"additions": 24,
|
|
179
|
+
"deletions": 2,
|
|
180
|
+
"patch": "diff --git a/src/styles/todos.css b/src/styles/todos.css\nindex 11e4c3a..ccdf474 100644\n--- a/src/styles/todos.css\n+++ b/src/styles/todos.css\n@@ -12,7 +12,29 @@\n .todo-item {\n align-items: center;\n- border-bottom: 1px solid #ddd;\n+ border-bottom: 1px solid #d8d1c4;\n display: flex;\n+ gap: 16px;\n justify-content: space-between;\n- padding: 8px;\n+ padding: 14px 0;\n+}\n+\n+.todo-check {\n+ align-items: center;\n+ display: flex;\n+ gap: 10px;\n+}\n+\n+.todo-item time {\n+ color: #73766d;\n+ font-size: 12px;\n+}\n+\n+.todo-item.is-complete span {\n+ color: #8b8d86;\n+ text-decoration: line-through;\n+}\n+\n+.empty-todos {\n+ color: #73766d;\n+ padding: 32px 0;\n }",
|
|
181
|
+
"summary": {
|
|
182
|
+
"title": "Show completed work quietly",
|
|
183
|
+
"what": "Adds spacing, due-date text, a muted completed state, and a calm empty message.",
|
|
184
|
+
"why": "Completion needs to remain visible without competing with active work.",
|
|
185
|
+
"details": [
|
|
186
|
+
"The row keeps its simple ruled-list structure.",
|
|
187
|
+
"The completed state changes both color and decoration."
|
|
188
|
+
],
|
|
189
|
+
"risks": []
|
|
190
|
+
},
|
|
191
|
+
"isBinary": false,
|
|
192
|
+
"isTruncated": false,
|
|
193
|
+
"totalDiffLines": 36,
|
|
194
|
+
"snippet": "diff --git a/src/styles/todos.css b/src/styles/todos.css\nindex 11e4c3a..ccdf474 100644\n--- a/src/styles/todos.css\n+++ b/src/styles/todos.css\n@@ -12,7 +12,29 @@\n .todo-item {\n align-items: center;\n- border-bottom: 1px solid #ddd;\n+ border-bottom: 1px solid #d8d1c4;\n display: flex;\n+ gap: 16px;\n justify-content: space-between;\n- padding: 8px;\n+ padding: 14px 0;\n+}\n+\n+.todo-check {\n+ align-items: center;\n+ display: flex;\n+ gap: 10px;\n+}\n+\n+.todo-item time {\n+ color: #73766d;\n+ font-size: 12px;\n+}\n+\n+.todo-item.is-complete span {\n+ color: #8b8d86;\n+ text-decoration: line-through;\n+}\n+\n+.empty-todos {\n+ color: #73766d;\n+ padding: 32px 0;\n }"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"path": "src/components/FilterBar.tsx",
|
|
198
|
+
"status": "added",
|
|
199
|
+
"additions": 21,
|
|
200
|
+
"deletions": 0,
|
|
201
|
+
"patch": "diff --git a/src/components/FilterBar.tsx b/src/components/FilterBar.tsx\nnew file mode 100644\nindex 0000000..b3372f1\n--- /dev/null\n+++ b/src/components/FilterBar.tsx\n@@ -0,0 +1,21 @@\n+import type { TodoFilter } from \"../types/todo\";\n+\n+const filters: TodoFilter[] = [\"all\", \"active\", \"completed\"];\n+\n+export function FilterBar({ value, onChange }: Props) {\n+ return (\n+ <div className=\"filter-bar\" aria-label=\"Filter todos\">\n+ {filters.map((filter) => (\n+ <button\n+ key={filter}\n+ type=\"button\"\n+ className={value === filter ? \"is-active\" : \"\"}\n+ aria-pressed={value === filter}\n+ onClick={() => onChange(filter)}\n+ >\n+ {filter[0].toUpperCase() + filter.slice(1)}\n+ </button>\n+ ))}\n+ </div>\n+ );\n+}",
|
|
202
|
+
"summary": {
|
|
203
|
+
"title": "Add direct list filters",
|
|
204
|
+
"what": "Adds three pressed-state controls for all, active, and completed todos.",
|
|
205
|
+
"why": "People need a quick way to focus on unfinished work or review what they completed.",
|
|
206
|
+
"details": [
|
|
207
|
+
"aria-pressed exposes the current filter to assistive technology.",
|
|
208
|
+
"The values come from the shared TodoFilter type."
|
|
209
|
+
],
|
|
210
|
+
"risks": [
|
|
211
|
+
"The Props type must be added or imported before this component compiles."
|
|
212
|
+
]
|
|
213
|
+
},
|
|
214
|
+
"isBinary": false,
|
|
215
|
+
"isTruncated": false,
|
|
216
|
+
"totalDiffLines": 27,
|
|
217
|
+
"snippet": "diff --git a/src/components/FilterBar.tsx b/src/components/FilterBar.tsx\nnew file mode 100644\nindex 0000000..b3372f1\n--- /dev/null\n+++ b/src/components/FilterBar.tsx\n@@ -0,0 +1,21 @@\n+import type { TodoFilter } from \"../types/todo\";\n+\n+const filters: TodoFilter[] = [\"all\", \"active\", \"completed\"];\n+\n+export function FilterBar({ value, onChange }: Props) {\n+ return (\n+ <div className=\"filter-bar\" aria-label=\"Filter todos\">\n+ {filters.map((filter) => (\n+ <button\n+ key={filter}\n+ type=\"button\"\n+ className={value === filter ? \"is-active\" : \"\"}\n+ aria-pressed={value === filter}\n+ onClick={() => onChange(filter)}\n+ >\n+ {filter[0].toUpperCase() + filter.slice(1)}\n+ </button>\n+ ))}\n+ </div>\n+ );\n+}"
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"path": "src/lib/todoStore.test.ts",
|
|
221
|
+
"status": "added",
|
|
222
|
+
"additions": 32,
|
|
223
|
+
"deletions": 0,
|
|
224
|
+
"patch": "diff --git a/src/lib/todoStore.test.ts b/src/lib/todoStore.test.ts\nnew file mode 100644\nindex 0000000..b20af2c\n--- /dev/null\n+++ b/src/lib/todoStore.test.ts\n@@ -0,0 +1,32 @@\n+import { describe, expect, it, vi } from \"vitest\";\n+import { todoReducer } from \"./todoStore\";\n+\n+describe(\"todoReducer\", () => {\n+ it(\"adds a todo with safe defaults\", () => {\n+ vi.stubGlobal(\"crypto\", { randomUUID: () => \"todo-1\" });\n+\n+ const todos = todoReducer([], {\n+ type: \"added\",\n+ input: { title: \"Buy oat milk\" },\n+ });\n+\n+ expect(todos).toEqual([\n+ {\n+ id: \"todo-1\",\n+ title: \"Buy oat milk\",\n+ completed: false,\n+ dueDate: undefined,\n+ priority: \"normal\",\n+ },\n+ ]);\n+ });\n+\n+ it(\"toggles only the chosen todo\", () => {\n+ const first = makeTodo({ id: \"one\", completed: false });\n+ const second = makeTodo({ id: \"two\", completed: false });\n+\n+ expect(\n+ todoReducer([first, second], { type: \"toggled\", id: \"two\" }),\n+ ).toEqual([first, { ...second, completed: true }]);\n+ });\n+});",
|
|
225
|
+
"summary": {
|
|
226
|
+
"title": "Lock down reducer behavior",
|
|
227
|
+
"what": "Tests new-todo defaults and confirms toggling leaves unrelated items unchanged.",
|
|
228
|
+
"why": "The reducer now owns every todo transition and needs fast tests around its core rules.",
|
|
229
|
+
"details": [
|
|
230
|
+
"The id source is stubbed for a stable add assertion.",
|
|
231
|
+
"The toggle test checks both the changed and unchanged entries."
|
|
232
|
+
],
|
|
233
|
+
"risks": [
|
|
234
|
+
"The global crypto stub should be restored after each test."
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
"isBinary": false,
|
|
238
|
+
"isTruncated": false,
|
|
239
|
+
"totalDiffLines": 38,
|
|
240
|
+
"snippet": "diff --git a/src/lib/todoStore.test.ts b/src/lib/todoStore.test.ts\nnew file mode 100644\nindex 0000000..b20af2c\n--- /dev/null\n+++ b/src/lib/todoStore.test.ts\n@@ -0,0 +1,32 @@\n+import { describe, expect, it, vi } from \"vitest\";\n+import { todoReducer } from \"./todoStore\";\n+\n+describe(\"todoReducer\", () => {\n+ it(\"adds a todo with safe defaults\", () => {\n+ vi.stubGlobal(\"crypto\", { randomUUID: () => \"todo-1\" });\n+\n+ const todos = todoReducer([], {\n+ type: \"added\",\n+ input: { title: \"Buy oat milk\" },\n+ });\n+\n+ expect(todos).toEqual([\n+ {\n+ id: \"todo-1\",\n+ title: \"Buy oat milk\",\n+ completed: false,\n+ dueDate: undefined,\n+ priority: \"normal\",\n+ },\n+ ]);\n+ });\n+\n+ it(\"toggles only the chosen todo\", () => {\n+ const first = makeTodo({ id: \"one\", completed: false });\n+ const second = makeTodo({ id: \"two\", completed: false });\n+\n+ expect(\n+ todoReducer([first, second], { type: \"toggled\", id: \"two\" }),\n+ ).toEqual([first, { ...second, completed: true }]);\n+ });\n+});"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"path": "README.md",
|
|
244
|
+
"status": "modified",
|
|
245
|
+
"additions": 12,
|
|
246
|
+
"deletions": 2,
|
|
247
|
+
"patch": "diff --git a/README.md b/README.md\nindex f2f695a..d5512c1 100644\n--- a/README.md\n+++ b/README.md\n@@ -8,4 +8,14 @@ npm run dev\n \n-Add a todo, mark it done, or remove it.\n+Add a todo, give it a due date, and mark it complete.\n \n-Todos reset when the page reloads.\n+Todos are saved in local storage and return after a reload.\n+\n+## Filters\n+\n+Use the list controls to show:\n+\n+- All todos\n+- Active todos\n+- Completed todos\n+\n+The app keeps filter state in memory and never removes hidden todos.",
|
|
248
|
+
"summary": {
|
|
249
|
+
"title": "Document the useful todo flow",
|
|
250
|
+
"what": "Updates the quick guide for due dates, saved todos, and the three list filters.",
|
|
251
|
+
"why": "The README should match the behavior a new contributor sees after starting the app.",
|
|
252
|
+
"details": [
|
|
253
|
+
"It states that filtering hides rather than deletes todos.",
|
|
254
|
+
"It explains that persistence uses local storage."
|
|
255
|
+
],
|
|
256
|
+
"risks": []
|
|
257
|
+
},
|
|
258
|
+
"isBinary": false,
|
|
259
|
+
"isTruncated": false,
|
|
260
|
+
"totalDiffLines": 21,
|
|
261
|
+
"snippet": "diff --git a/README.md b/README.md\nindex f2f695a..d5512c1 100644\n--- a/README.md\n+++ b/README.md\n@@ -8,4 +8,14 @@ npm run dev\n \n-Add a todo, mark it done, or remove it.\n+Add a todo, give it a due date, and mark it complete.\n \n-Todos reset when the page reloads.\n+Todos are saved in local storage and return after a reload.\n+\n+## Filters\n+\n+Use the list controls to show:\n+\n+- All todos\n+- Active todos\n+- Completed todos\n+\n+The app keeps filter state in memory and never removes hidden todos."
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
}
|
package/dist/favicon.svg
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M22 19.2727C22 20.779 20.779 22 19.2727 22H14.7273C13.221 22 12 20.779 12 19.2727V12H19.2727C20.779 12 22 13.221 22 14.7273V19.2727Z" fill="#68C4FF"/>
|
|
3
|
+
<path d="M20 2C21.1046 2 22 2.89543 22 4V7C22 8.10457 21.1046 9 20 9H17C15.8954 9 15 8.10457 15 7V4C15 2.89543 15.8954 2 17 2H20Z" fill="#0C79D8"/>
|
|
4
|
+
<path d="M7 15C8.10457 15 9 15.8954 9 17V20C9 21.1046 8.10457 22 7 22H4C2.89543 22 2 21.1046 2 20V17C2 15.8954 2.89543 15 4 15H7Z" fill="#0C79D8"/>
|
|
5
|
+
<path d="M12 12H4.72727C3.22104 12 2 10.779 2 9.27273V4.72727C2 3.22104 3.22104 2 4.72727 2H9.27273C10.779 2 12 3.22104 12 4.72727V12Z" fill="#2E9EFF"/>
|
|
6
|
+
</svg>
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta
|
|
7
|
+
name="description"
|
|
8
|
+
content="Clear code diffs with concise notes from your coding agent."
|
|
9
|
+
/>
|
|
10
|
+
<meta name="theme-color" content="#e9e2d5" />
|
|
11
|
+
<link rel="icon" href="./favicon.svg" type="image/svg+xml" />
|
|
12
|
+
<title>Diffsplain</title>
|
|
13
|
+
<script type="module" crossorigin src="./assets/index-d0QMfM_f.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CLtrIbvS.css">
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<div id="root"></div>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "diffsplain",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Review Git diffs one file at a time with Codex notes beside each patch.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"codex",
|
|
7
|
+
"diff",
|
|
8
|
+
"git",
|
|
9
|
+
"github",
|
|
10
|
+
"pull-request",
|
|
11
|
+
"review"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/itsjling/diff-presenter.git"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/itsjling/diff-presenter#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/itsjling/diff-presenter/issues"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"diffsplain": "scripts/present.mjs"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"scripts"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22.13.0"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"dev": "vite --port 3000",
|
|
33
|
+
"docs:dev": "cd docs && blume dev",
|
|
34
|
+
"docs:build": "cd docs && blume build",
|
|
35
|
+
"docs:check": "cd docs && blume check && blume validate",
|
|
36
|
+
"docs:preview": "cd docs && blume preview",
|
|
37
|
+
"present": "node scripts/present.mjs",
|
|
38
|
+
"summarize": "node scripts/generate-summaries.mjs",
|
|
39
|
+
"snapshot": "node scripts/build-diff-data.mjs",
|
|
40
|
+
"watch:diff": "node scripts/build-diff-data.mjs --watch",
|
|
41
|
+
"build": "vite build && node scripts/clean-built-live-data.mjs",
|
|
42
|
+
"start": "node scripts/serve-built.mjs",
|
|
43
|
+
"prepack": "npm run build",
|
|
44
|
+
"test": "npm run build && node --test tests/*.test.mjs",
|
|
45
|
+
"lint": "tsc --noEmit && eslint ."
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@tailwindcss/postcss": "4.2.1",
|
|
49
|
+
"@types/node": "22.19.19",
|
|
50
|
+
"@types/react": "19.2.14",
|
|
51
|
+
"@types/react-dom": "19.2.3",
|
|
52
|
+
"@vitejs/plugin-react": "6.0.2",
|
|
53
|
+
"blume": "^1.2.0",
|
|
54
|
+
"eslint": "10.8.0",
|
|
55
|
+
"react": "19.2.6",
|
|
56
|
+
"react-dom": "19.2.6",
|
|
57
|
+
"tailwindcss": "4.2.1",
|
|
58
|
+
"typescript": "5.9.3",
|
|
59
|
+
"vite": "8.1.5"
|
|
60
|
+
},
|
|
61
|
+
"type": "module"
|
|
62
|
+
}
|