Skip to content

fix: cleared input fields re-fill with stale draft text on re-render#435

Merged
PureWeen merged 1 commit intomainfrom
fix/input-draft-ghost-text
Mar 25, 2026
Merged

fix: cleared input fields re-fill with stale draft text on re-render#435
PureWeen merged 1 commit intomainfrom
fix/input-draft-ghost-text

Conversation

@PureWeen
Copy link
Copy Markdown
Owner

Problem

When a user clears text from a chat input field (backspace/select-all-delete), the old text immediately reappears on the next Blazor render cycle. Typing new text also gets overwritten by the stale draft.

Root Cause

The draft capture JS (line 1096) only saved inputs with truthy values:

if (el.id && el.value) result[el.id] = el.value;

When the user cleared an input, el.value was "" (falsy), so the empty value was never captured. But draftBySession in C# still held the old text from a previous capture. On the next render, restoreDraftsAndFocus re-applied it.

Fix

  1. JS: Always capture input values, even empty strings
  2. C#: Remove the session from draftBySession when captured value is empty

Two lines changed.

The draft capture JS only saved inputs with truthy values:
  if (el.id && el.value) result[el.id] = el.value;

When the user cleared an input, el.value was '' (falsy), so the
empty value was never captured. But draftBySession still held the
old text from a previous capture. On the next Blazor render cycle,
restoreDraftsAndFocus re-applied the stale draft — putting the old
text right back in the input field.

Fix:
1. JS: Always capture the input value (even empty strings)
2. C#: Remove the session from draftBySession when captured value
   is empty, so restoreDraftsAndFocus won't re-apply stale text

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen
Copy link
Copy Markdown
Owner Author

🔍 Squad Review — PR #435 Round 1

PR: fix: cleared input fields re-fill with stale draft text on re-render
Models: claude-opus-4.6 ×2, claude-sonnet-4.6, gemini-3-pro-preview, gpt-5.3-codex
Files changed: 1 (Dashboard.razor) | +3, -1
Tests: ✅ 2959 passing, 0 failing
CI: No checks reported on this branch


Root Cause & Fix

The diagnosis is accurate and the fix is correct. The bug was a classic JS falsy-value trap:

  • Old: if (el.id && el.value) — empty string is falsy, cleared inputs were silently skipped
  • draftBySession kept the old value → next render restored ghost text
  • Fix: always capture (if (el.id)), then remove from dictionary when value is ""

Both sides of the fix are necessary and work together correctly.


Findings

# Severity Consensus Finding
F1 🟡 5/5 Debounce timing gap is unchanged — if a user clears input and a render fires within 3s of the last capture, shouldCaptureDrafts = false and the fix doesn't run. The stale draft survives that single render cycle. Pre-existing limitation, not introduced by this PR.
F2 🟡 2/5 SaveDraftsAndCursor inconsistency — the session-switch capture path (line 3074) still does draftBySession[name] = item.Value unconditionally, storing "" for cleared fields. Functionally harmless (restore filters with !string.IsNullOrEmpty), but inconsistent with the fix's approach. Easy follow-up.
F3 🟢 3/5 el.value || '' is defensive-but-dead codeel.value is always a string (never null/undefined) on real input/textarea elements. The || '' fallback never fires. Harmless, slightly misleading.
F4 🟢 2/5 No test coverage — draft capture/restore is JS interop + render-cycle logic; hard to unit test. Pre-existing gap. Not blocking.

Debounce Gap Detail (F1)

The shouldCaptureDrafts condition:

var shouldCaptureDrafts = !_sessionSwitching 
    && completedSessions.Count == 0
    && (nowTicks - lastCapture) >= 3000;

Scenario where ghost text can still appear after this fix:

  1. Capture runs → draftBySession["s1"] = "hello" (t=0)
  2. User clears input (t=0.5s)
  3. Re-render fires (t<3s) → capture debounced → draftBySession["s1"] still has "hello"
  4. Restore applies "hello" to cleared input

The debounce exists for good performance reason (multi-agent event bursts). The window is ≤3s and self-corrects on the next capture tick. Acceptable to ship as-is.

SaveDraftsAndCursor Follow-up (F2)

Easy to fix in a follow-up — apply the same remove-if-empty logic:

// In SaveDraftsAndCursor, after draftBySession.Clear():
foreach (var item in state.Items ?? [])
{
    var name = item.Id.Replace("input-", "").Replace("-", " ");
    if (!string.IsNullOrEmpty(item.Value))  // add this guard
        draftBySession[name] = item.Value;
    // else: already cleared above, nothing to do
}

Test Coverage Assessment

No new tests added (this is JS interop + render-cycle code, hard to unit test at the unit level). The fix is simple enough that the logic is self-evident. A MauiDevFlow CDP integration test could cover the scenario but isn't required for merge.


Verdict: ✅ Approve

The core fix is correct and addresses the reported bug. F1 (debounce gap) is a pre-existing limitation and acceptable. F2 (SaveDraftsAndCursor inconsistency) is a clean-up that can be a fast follow-up — it won't cause regressions either way. F3 and F4 are cosmetic/minor.

Verdicts by model: ✅ opus-1 · ⚠️ opus-2 (wants F2 fixed first) · ✅ sonnet · ⚠️ gemini (wants a test) · ✅ codex
Consensus: 3 Approve, 2 Request Changes — the 2 concerns are minor and don't block the bug fix from shipping.

@PureWeen PureWeen merged commit a8c2654 into main Mar 25, 2026
@PureWeen PureWeen deleted the fix/input-draft-ghost-text branch March 25, 2026 19:34
PureWeen added a commit that referenced this pull request Mar 25, 2026
…435)

## Problem

When a user clears text from a chat input field
(backspace/select-all-delete), the old text immediately reappears on the
next Blazor render cycle. Typing new text also gets overwritten by the
stale draft.

## Root Cause

The draft capture JS (line 1096) only saved inputs with **truthy**
values:
```js
if (el.id && el.value) result[el.id] = el.value;
```

When the user cleared an input, `el.value` was `""` (falsy), so the
empty value was **never captured**. But `draftBySession` in C# still
held the old text from a previous capture. On the next render,
`restoreDraftsAndFocus` re-applied it.

## Fix

1. **JS**: Always capture input values, even empty strings
2. **C#**: Remove the session from `draftBySession` when captured value
is empty

Two lines changed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant