You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Found while fixing #1593, deliberately not grown into that PR: the same class of bug, on the choice list rather than the command list, and a live data-loss path on master.
The shape
ChoiceList.svelte:62-66 filters the rendered list to entries that can be keyed:
The comment above it (:58-61) says this is a "Render-time view only. Nothing here rewrites the tree." That is not true, because renderable is also what seeds the drop zone (:152) and what the persist path commits:
is a complete, working choice whose id was written as a JSON number by a bad hand-edit, a script, or a merge. It is invisible in the settings list, so the user cannot miss it - and one reorder deletes it, with no prompt and no undo. That undoes exactly what dedupeChoicesById (choiceUtils.ts:218-221) goes out of its way to preserve.
The second half of the comment is wrong for the same reason: "It cannot be hiding a choice - only a Multi's choices value can" is false, because :64 drops id-less entries that are otherwise whole choices.
Failing that, reconcile the persist back onto the raw array (the setFolderChildrenById by-id idiom, choiceService.ts:589-635) so a reorder can only reorder, never delete.
Either way ChoiceView.malformed.test.ts needs the missing ratchet: reorder a list containing a hole and an id-less choice, and assert the persisted array is byte-identical apart from the reorder.
Found while fixing #1593, deliberately not grown into that PR: the same class of bug, on the choice list rather than the command list, and a live data-loss path on
master.The shape
ChoiceList.svelte:62-66filters the rendered list to entries that can be keyed:The comment above it (
:58-61) says this is a "Render-time view only. Nothing here rewrites the tree." That is not true, becauserenderableis also what seeds the drop zone (:152) and what the persist path commits::152use:dndzone={baseDndOptions({items: renderable, ...})}:110choices = next(fromstripShadow(e.detail.items)):114actions.onReorderChoices(choices):133moveChoicedoes the same fromstripShadow(renderable)ChoiceView.svelte:328-331->save()->:101-103saveChoices(snapshot(choices))So the first drag or ArrowDown persists the filtered list, and every entry the filter dropped is gone from
data.json.Why it matters
For a
nullhole this is harmless - it carries nothing. But the filter also drops any entry whoseidis not a non-empty string, and{ "name": "Daily note", "type": "Template", "templatePath": "Templates/Daily.md", "format": {...}, "id": 12 }is a complete, working choice whose id was written as a JSON number by a bad hand-edit, a script, or a merge. It is invisible in the settings list, so the user cannot miss it - and one reorder deletes it, with no prompt and no undo. That undoes exactly what
dedupeChoicesById(choiceUtils.ts:218-221) goes out of its way to preserve.The second half of the comment is wrong for the same reason: "It cannot be hiding a choice - only a Multi's
choicesvalue can" is false, because:64drops id-less entries that are otherwise whole choices.Suggested shape
Two options, in order of preference:
commandshas no read-accessor guard, so a malformed one is unrecoverable through the UI #1593 shipped for commands: repair rather than hide. A choice with a missing/non-string/duplicate id gets a fresh uuid at the seam and stays visible, so the user can see and delete it.dedupeChoicesByIdalready does the duplicate half at load; this is the missing/non-string half.setFolderChildrenByIdby-id idiom,choiceService.ts:589-635) so a reorder can only reorder, never delete.Either way
ChoiceView.malformed.test.tsneeds the missing ratchet: reorder a list containing a hole and an id-less choice, and assert the persisted array is byte-identical apart from the reorder.Related
commandshas no read-accessor guard, so a malformed one is unrecoverable through the UI #1593 - the same bug formacro.commands, and where this was foundeach_key_duplicatecrash