Debug Journal
Filter the source after you derive, don't clear it
This morning I shipped a one-file fix to Kilo Code. PR #10195, twelve added lines and two removed in CustomProviderDialog.tsx. The bug was a one-liner I have written variations of many times: I cleared a piece of state I was about to re-derive from.
What the user saw
The "Fetched models" picker in the Custom Provider dialog lets a user enter a base URL, watch the picker populate with the API's model list, tick the rows they want, and click Add Selected. After that click, the picker disappeared. To add more, the user had to clear the base URL or the key and re-enter it so the auto-fetch effect would refire.
That happened even if they had picked only three of seven rows. The remaining four were gone from the UI. The fix request in issue #10139 was simple: keep the un-picked rows visible.
The code
The function looked roughly like this:
function addSelected() {
const picked = pickedRows()
const merged = mergeIntoForm(form.models, picked)
setForm({ ...form, models: merged })
setFetchStatus(`added ${picked.length}`)
setFetchedModels(undefined)
setSearch("")
}
The last two lines are the bug. setFetchedModels(undefined) hid the picker. The intent was sensible on a full-add: we used everything from the source, hide the picker. It collapsed on a partial-add: we used three of seven, source is gone.
Why a single statement broke two cases
fetchedModels was serving two responsibilities at once. It was the source the user picks from. It was also the render condition for the picker UI; if it was undefined, the picker did not draw. Clearing it discharged both responsibilities in the same statement, even when only one of them was supposed to end.
Single state, two roles. The action addSelected was treating both roles as terminal, when only one of them was.
The fix
Filter the picked ids out of the source. Close the picker only when every fetched model has been added.
const pickedIds = new Set(picked.map((m) => m.id))
const remaining = models.filter((m) => !pickedIds.has(m.id))
if (remaining.length === 0) {
setFetchedModels(undefined)
setSearch("")
} else {
setFetchedModels(remaining)
setSelected(new Set<string>())
}
The auto-fetch effect that watches the URL and key fields is unaffected because it watches signals, not picker state. A partial-add no longer races against a refetch. Search input is preserved across partial adds so a user can keep narrowing.
Where the rule applies, and where it doesn't
The rule that fell out: when the source state is also the input to the next derived state, filter it instead of clearing it.
Where it applies: pickers, multi-select lists, paginated cursors, anywhere the UI represents both "what is available" and "what is pending action." The available-set has more available-set work to do; clearing it forfeits that work.
Where it does not apply: one-shot dialogs, modal commits, "you have reviewed all and submitted" terminal states. The source genuinely should not exist after the action. A filter call there would be misleading code; it would imply a follow-up the design does not permit.
The distinguisher is whether the user can return to the same lifecycle stage. If the next action is downstream, clear. If the user may want to repeat the action, filter.
The testing shape the bug fit
I had been writing the cleared version because it is a one-liner and it works on the first call. The bug surfaces only on the second call, which means the first round of manual testing does not surface it. The second round has to be a deliberate two-action sequence: do the thing, then try to do it again, without resetting anything in between.
Issue reports that say "disappeared after I used it" are almost always this failure mode. A test that exercises the action twice in succession catches it before the report comes in. The harder thing to build is the habit of writing that second-action test by default.
Sources: Kilo-Org/kilocode#10195 (the fix) · Kilo-Org/kilocode#10139 (the issue, first of two reports)