Skip to content

feat(todo): edit in place for todo titles#88

Open
demo3-labworksdev wants to merge 2 commits intomainfrom
feat/edit-in-place
Open

feat(todo): edit in place for todo titles#88
demo3-labworksdev wants to merge 2 commits intomainfrom
feat/edit-in-place

Conversation

@demo3-labworksdev
Copy link
Copy Markdown

Summary

  • Adds inline editing of todo titles (click to edit, Enter/blur to save, Escape to cancel)
  • New PATCH /edit/<id> endpoint with JSON response
  • Visual indicator: dashed underline, pointer cursor, hover highlight
  • Graceful 404 handling: user-friendly alert when editing a deleted todo, row removed on dismiss
  • 9 new unit tests with 100% coverage on app.py

Known Issues

  • No authentication or authorization on mutation endpoints (add, toggle, edit, delete) — to be addressed in a future issue

Test Plan

  • Click a todo title — input appears with dashed border
  • Edit and press Enter — title updates in place
  • Edit and click away (blur) — title saves
  • Press Escape while editing — original title restored
  • Delete a todo in another tab, attempt to edit it in this tab — alert appears, row is removed on dismiss
  • Run task test — all unit tests pass

Closes #30

🤖 Generated with Claude Code

demo3-labworksdev and others added 2 commits April 7, 2026 11:27
- Click a todo title to enter edit mode
- Save on Enter or blur, cancel on Escape
- Visual indicator (dashed underline + pointer cursor + hover highlight)
- PATCH /edit/<id> endpoint returns JSON
- Disables input during save; shows red error indicator on failure
- 8 new unit tests, 100% coverage on app.py

Known: authorization not yet implemented across all mutation endpoints
(toggle, delete, add, edit) - to be addressed in a future issue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Return 404 when editing a non-existent todo (rowcount check)
- Show alert explaining the todo was deleted if a 404 is returned
- Remove the stale row from the page after user acknowledges
- Add test for editing a non-existent todo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@ai-coding-guardrails ai-coding-guardrails bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got 2 comments for you to consider

Reviewed with 🤟 by Zenable

Comment thread src/templates/index.html
Comment on lines +105 to +112
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
input.removeEventListener('blur', save);
save();
} else if (e.key === 'Escape') {
input.removeEventListener('blur', save);
cell.textContent = originalText;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After pressing Enter or Escape, the blur event listener is removed via removeEventListener, but this only works if the exact same function reference is passed. Here save is a named function defined inside the click handler, so the reference is consistent — however, after pressing Escape, cell.textContent = originalText destroys the input (removing it from the DOM), which fires blur before the removeEventListener call on line 110 can take effect (since blur fires synchronously on DOM removal in some browsers). This means save() can be called on Escape, sending an unwanted PATCH request.

Consider setting a flag or removing the blur listener before modifying the DOM:

Suggested change
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
input.removeEventListener('blur', save);
save();
} else if (e.key === 'Escape') {
input.removeEventListener('blur', save);
cell.textContent = originalText;
}
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
input.removeEventListener('blur', save);
save();
} else if (e.key === 'Escape') {
input.removeEventListener('blur', save);
cell.textContent = originalText;
}
});

The fix is to call input.removeEventListener('blur', save) before cell.textContent = originalText on the Escape branch — which is already the order shown, but note that in Firefox and some environments blur fires synchronously when the element is removed. A safer approach is to use a boolean guard:

Suggested change
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
input.removeEventListener('blur', save);
save();
} else if (e.key === 'Escape') {
input.removeEventListener('blur', save);
cell.textContent = originalText;
}
var saving = false;
function save() {
if (saving) return;
saving = true;
var newTitle = input.value.trim();
if (!newTitle || newTitle === originalText) {
cell.textContent = originalText;
return;
}
Why did I show this?

Category: bug
Comment Quality: high

Based on general best practices

Tools used:

  1. list_changed_files, {'pattern': {'type': 'string', 'value': '**/*.py'}}
  2. list_changed_files, {'pattern': '**/*.py'}
  3. get_file_lines, {'file_path': 'src/todo_app/app.py', 'start_line': 1, 'end_line': 200}
  4. get_file_lines, {'file_path': 'tests/test_app.py', 'start_line': 1, 'end_line': 200}

Comment thread src/todo_app/app.py
Comment on lines +73 to +75
cursor = conn.execute("UPDATE todos SET title = ? WHERE id = ?", (title, todo_id))
conn.commit()
conn.close()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The database is committed before checking cursor.rowcount. While the UPDATE of a non-existent row is a no-op and won't corrupt data, the commit is unnecessary in that case. More importantly, if the commit somehow raises an exception after the execute, the connection is never closed (no try/finally). This pattern exists in other routes too, but the 404 path here makes the ordering more visible — the 404 response is returned after an already-committed (empty) transaction.

Consider checking cursor.rowcount before committing:

Suggested change
cursor = conn.execute("UPDATE todos SET title = ? WHERE id = ?", (title, todo_id))
conn.commit()
conn.close()
cursor = conn.execute("UPDATE todos SET title = ? WHERE id = ?", (title, todo_id))
if cursor.rowcount == 0:
conn.close()
return jsonify({"error": "Todo not found"}), 404
conn.commit()
conn.close()
Why did I show this?

Category: mistake
Comment Quality: high

Based on general best practices

Tools used:

  1. get_file_lines, {'file_path': 'src/todo_app/app.py', 'start_line': '1', 'end_line': '50'}
  2. get_file_lines, {'file_path': 'src/todo_app/app.py', 'start_line': '51', 'end_line': '92'}
  3. list_changed_files, {'pattern': ''}
  4. get_file_lines, {'file_path': 'tests/test_app.py', 'start_line': '1', 'end_line': '100'}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Edit In Place

1 participant