fix(composer): do not wipe in-progress text on draft-autosave (LC-338) #354

Merged
nrupard merged 1 commit from fix/lc-338-composer-draft-wipe into main 2026-06-18 18:38:32 +02:00
Owner

Problem

Typing a message in a room and pausing (or clicking off the input) wipes the in-progress text from the composer; a page refresh restores it. Reported on staging and latest main.

Root cause

The composer textarea auto-saves drafts: server/templates/room/composer.html:217-263 puts hx-put="/room/{id}/draft" + hx-trigger="keyup[...] changed delay:1000ms" on <textarea name="body">, which lives inside the #composer form. That draft-save request bubbles up to the form's hx-on::after-request handler. The sibling hx-on::config-request handler guards if (event.detail.elt !== this) return; (and documents skipping the draft-autosave PUT), but after-request had no such guard. So ~1s after the user stops typing, the draft-save returns 204, the send-completion path runs, ta.value === sentBody is true, and ta.value = '' wipes the text. The draft is persisted server-side (LC-239), which is why a refresh restores it.

No server-driven update touches the composer (send is 204 / hx-swap="none"; WS fragments swap only #messages, #typing, and the sidebar draft badge). The defect is purely the missing client-side guard.

Fix

Add the same if (event.detail.elt !== this) return; guard to hx-on::after-request, scoping the entire send-completion body to the form's own send. Form send has elt === #composer; the bubbled draft-save has elt === <textarea> !== this and is skipped. Bonus: a draft-save failure no longer incorrectly raises the composer send-error banner. Draft persistence is unchanged.

Test

  • cargo check, just test, just test-saas pass (template-only change; no server logic touched).
  • No JS test harness in this repo. Manual: type + pause >2s -> text stays; click off -> stays; reload -> restored from draft; send via Enter and button -> clears + posts; forced send failure -> error banner still shows.

Closes LC-338.

## Problem Typing a message in a room and pausing (or clicking off the input) wipes the in-progress text from the composer; a page refresh restores it. Reported on staging and latest main. ## Root cause The composer textarea auto-saves drafts: `server/templates/room/composer.html:217-263` puts `hx-put="/room/{id}/draft"` + `hx-trigger="keyup[...] changed delay:1000ms"` on `<textarea name="body">`, which lives inside the `#composer` form. That draft-save request bubbles up to the form's `hx-on::after-request` handler. The sibling `hx-on::config-request` handler guards `if (event.detail.elt !== this) return;` (and documents skipping the draft-autosave PUT), but `after-request` had no such guard. So ~1s after the user stops typing, the draft-save returns 204, the send-completion path runs, `ta.value === sentBody` is true, and `ta.value = ''` wipes the text. The draft is persisted server-side (LC-239), which is why a refresh restores it. No server-driven update touches the composer (send is 204 / `hx-swap="none"`; WS fragments swap only `#messages`, `#typing`, and the sidebar draft badge). The defect is purely the missing client-side guard. ## Fix Add the same `if (event.detail.elt !== this) return;` guard to `hx-on::after-request`, scoping the entire send-completion body to the form's own send. Form send has `elt === #composer`; the bubbled draft-save has `elt === <textarea> !== this` and is skipped. Bonus: a draft-save failure no longer incorrectly raises the composer send-error banner. Draft persistence is unchanged. ## Test - `cargo check`, `just test`, `just test-saas` pass (template-only change; no server logic touched). - No JS test harness in this repo. Manual: type + pause >2s -> text stays; click off -> stays; reload -> restored from draft; send via Enter and button -> clears + posts; forced send failure -> error banner still shows. Closes LC-338.
fix(composer): do not wipe in-progress text on draft-autosave (LC-338)
All checks were successful
check-secrets / TruffleHog (push) Successful in 3s
check-secrets / Nosey parker (push) Successful in 4s
check-secrets / Kingfisher (push) Successful in 4s
check-secrets / Nosey parker (pull_request) Successful in 4s
check-secrets / Kingfisher (pull_request) Successful in 5s
check-secrets / TruffleHog (pull_request) Successful in 6s
Check / clippy + fmt + tests (pull_request) Successful in 2m40s
Create release / Create release from merged PR (pull_request) Has been skipped
8b9277c7de
The composer textarea auto-saves drafts via hx-put .../draft on a 1s keyup debounce. That request bubbles up to the #composer form's hx-on::after-request handler, which had no child-request guard, so when the draft-save returned 204 it ran the send-completion path and `ta.value = ''` wiped the in-progress text ~1s after the user stopped typing. The draft is persisted server-side (LC-239), so a page refresh restored it - matching the report.

Add the same `if (event.detail.elt !== this) return;` guard the sibling hx-on::config-request handler already uses, so the send-completion body (textarea clear, error banner, optimistic-echo flip, re-focus, attach/quote/broadcast resets) runs only for the form's own send, never for the bubbled draft-autosave PUT. A draft-save failure also no longer incorrectly raises the composer send-error banner.

#LC-338

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch fix/lc-338-composer-draft-wipe 2026-06-18 18:38:32 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!354
No description provided.