Skip to content

Message edit save button#75

Merged
kentcdodds merged 8 commits into
mainfrom
cursor/message-edit-save-button-bee2
Feb 2, 2026
Merged

Message edit save button#75
kentcdodds merged 8 commits into
mainfrom
cursor/message-edit-save-button-bee2

Conversation

@kentcdodds
Copy link
Copy Markdown
Member

@kentcdodds kentcdodds commented Feb 2, 2026

Test Plan

  1. Navigate to a conversation.
  2. Edit an existing message.
  3. Verify that a "Save" button appears below the message edit textarea.
  4. Make a change to the message content.
  5. Click the "Save" button and confirm the message is updated.

Checklist

  • Tests updated
  • Docs updated

Screenshots


message-edit-save-button


Open in Cursor Open in Web


Note

Low Risk
UI-only change limited to client-side state/conditional rendering; no changes to server actions or data validation.

Overview
Updates the recipient message edit UI to make saving explicit and less noisy by only showing the save StatusButton when the textarea content has changed (or while a save is in progress).

Adds local currentContent state wired to the textarea onInput to detect edits and drive the conditional render of the save control.

Written by Cursor Bugbot for commit 8078cfe. This will update automatically on new commits. Configure here.

Co-authored-by: me <me@kentcdodds.com>
@cursor
Copy link
Copy Markdown

cursor Bot commented Feb 2, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

cursoragent and others added 2 commits February 2, 2026 20:16
Co-authored-by: me <me@kentcdodds.com>
Co-authored-by: me <me@kentcdodds.com>
const [confirmDelete, setConfirmDelete] = useState(false)
const [canDelete, setCanDelete] = useState(false)
const [savedContent, setSavedContent] = useState(message.content)
const [hasEdits, setHasEdits] = useState(false)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@cursor, I think this can be derived and we don't need state for it.

Copy link
Copy Markdown

@cursor cursor Bot Feb 2, 2026

Choose a reason for hiding this comment

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

Review response: Agreed — hasEdits is now derived from currentContent !== savedContent, so no extra state is needed for save visibility.【F:app/routes/_app+/recipients+/$recipientId.index.tsx†L384-L461】

Walkthrough


Save checkmark appears only after edits and hides after save.

message-edit-save-derived-2

Summary

  • Derive save-check visibility from current vs saved content instead of separate state in app/routes/_app+/recipients+/$recipientId.index.tsx.【F:app/routes/_app+/recipients+/$recipientId.index.tsx†L384-L461】

Testing

  • npm run format — full-gate formatting check.【F:/tmp/format.log†L1-L4】
  • npm run typecheck — full-gate type safety check.【F:/tmp/typecheck.log†L1-L3】
  • ⚠️ npm run lint — full-gate linting; existing warnings in scripts directory.【F:/tmp/lint.log†L1-L14】
  • npm run test -- --run --project node --pool=threads --maxWorkers=1 --no-file-parallelism — full-gate node tests, serialized to avoid SQLite locking.【F:/tmp/test-node.log†L1-L30】
  • npm run test -- --run --project browser --pool=threads --maxWorkers=1 --no-file-parallelism — full-gate browser tests.【F:/tmp/test-browser.log†L1-L15】
  • DATABASE_URL="file:/workspace/tests/prisma/base.db" CACHE_DATABASE_PATH="/workspace/tests/prisma/cache.db" PORT=3001 npm run test:e2e:run — 7 e2e failures in onboarding/send/settings profile flows.【F:/home/ubuntu/.cursor/projects/workspace/terminals/779679.txt†L2674-L2717】
  • ✅ Manual UI test (no command) — required for UI change; verified save checkmark behavior (see walkthrough).

Open in Cursor Open in Web

Co-authored-by: me <me@kentcdodds.com>
const deleteSafeDelayMs = 150
const [confirmDelete, setConfirmDelete] = useState(false)
const [canDelete, setCanDelete] = useState(false)
const [savedContent, setSavedContent] = useState(message.content)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@cursor, message.content will always be the saved content (react router revalidates on changes), so we don't need state for this.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Taking a look!

Open in Cursor Open in Web

Comment on lines +426 to +429
useEffect(() => {
setSavedContent(message.content)
setCurrentContent(message.content)
}, [message.content])
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@cursor, ditch this useEffect in favor of just setting the current content in the event handler

Copy link
Copy Markdown

@cursor cursor Bot Feb 2, 2026

Choose a reason for hiding this comment

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

Walkthrough


Save checkmark appears only after edits, hides after save.

message-edit-save-derived-4

Summary

  • Review response: removed the sync useEffect and rely on the input handler to update currentContent against message.content for save visibility in app/routes/_app+/recipients+/$recipientId.index.tsx.【F:app/routes/_app+/recipients+/$recipientId.index.tsx†L380-L458】

Testing

  • npm run format — full-gate formatting check.【F:/tmp/format-3.log†L1-L4】
  • npm run typecheck — full-gate type safety check.【F:/tmp/typecheck-3.log†L1-L3】
  • ⚠️ npm run lint — full-gate linting; existing warnings in scripts directory.【F:/tmp/lint-3.log†L1-L14】
  • npm run test -- --run --project node --pool=threads --maxWorkers=1 --no-file-parallelism — full-gate node tests, serialized to avoid SQLite locking.【F:/tmp/test-node-3.log†L1-L77】
  • npm run test -- --run --project browser --pool=threads --maxWorkers=1 --no-file-parallelism — full-gate browser tests.【F:/tmp/test-browser-3.log†L1-L15】
  • DATABASE_URL="file:/workspace/tests/prisma/base.db" CACHE_DATABASE_PATH="/workspace/tests/prisma/cache.db" PORT=3001 npm run test:e2e:run — 7 e2e failures in onboarding/send/settings profile flows.【F:/home/ubuntu/.cursor/projects/workspace/terminals/19483.txt†L2671-L2714】
  • ✅ Manual UI test (no command) — required for UI change; verified checkmark appears after edits and hides after save (see walkthrough).

Open in Cursor Open in Web

cursoragent and others added 2 commits February 2, 2026 22:09
Co-authored-by: me <me@kentcdodds.com>
Co-authored-by: me <me@kentcdodds.com>
Comment on lines +444 to +447
const handleContentInput: React.FormEventHandler<HTMLTextAreaElement> = (
event,
) => {
setCurrentContent(event.currentTarget.value)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@cursoragent just make this inline

Copy link
Copy Markdown

@cursor cursor Bot Feb 2, 2026

Choose a reason for hiding this comment

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

Summary

  • Inlined the message edit onInput handler in app/routes/_app+/recipients+/$recipientId.index.tsx as requested.【F:app/routes/_app+/recipients+/$recipientId.index.tsx†L540-L551】

Testing

  • Manual UI testing not rerun because this is a handler refactor with no behavior change.
  • npm run format — full-gate formatting check.【F:/tmp/format-4.log†L1-L4】
  • npm run typecheck — full-gate type safety check.【F:/tmp/typecheck-4.log†L1-L3】
  • npm run lint — full-gate linting; existing warnings in scripts directory.【F:/tmp/lint-4.log†L1-L14】
  • npm run test -- --run --project node --pool=threads --maxWorkers=1 --no-file-parallelism — full-gate node tests, serialized to avoid SQLite locking.【F:/tmp/test-node-4.log†L1-L77】
  • npm run test -- --run --project browser --pool=threads --maxWorkers=1 --no-file-parallelism — full-gate browser tests.【F:/tmp/test-browser-4.log†L1-L15】
  • ⚠️ E2E not rerun for this refactor; last run failed in onboarding/send/settings flows.【F:/home/ubuntu/.cursor/projects/workspace/terminals/19483.txt†L2671-L2714】

Open in Cursor Open in Web

Co-authored-by: me <me@kentcdodds.com>
@kentcdodds kentcdodds marked this pull request as ready for review February 2, 2026 23:27
Comment thread app/routes/_app+/recipients+/$recipientId.index.tsx Outdated
@kentcdodds kentcdodds merged commit babff56 into main Feb 2, 2026
5 checks passed
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is ON. A Cloud Agent has been kicked off to fix the reported issue.

const sendIsPending = sendNowFetcher.state !== 'idle'
const deleteIsPending = deleteFetcher.state !== 'idle'
const textareaProps = getTextareaProps(updateContentFields.content)
const hasEdits = currentContent !== message.content
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

State not reset when save succeeds with modified content

Low Severity

The currentContent state is initialized once via useState(message.content) and only updates through onInput. After a successful save, if message.content from the server differs from what the user typed (e.g., server-side trimming or normalization), hasEdits remains true and the save button incorrectly stays visible. Additionally, if external updates change message.content while the form is open, the save button may appear even though the user hasn't made any edits, potentially leading to unintentional data overwrites.

Fix in Cursor Fix in Web

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.

2 participants