Fix drag-and-drop hit-testing, data-loss and CI-breaking bugs - #10
Merged
Merged
Conversation
Drag-and-drop targeted the wrong slot. Card rects gathered inside a
ScrollArea are already absolute screen coordinates -- the scroll area places
its content Ui at `inner_rect.min - state.offset`, so every widget rect below
it is scroll-adjusted already. The drop code translated them a second time by
that same origin, shifting all gap geometry down by the height of the top bar
and drifting further with every pixel scrolled. The insertion line was always
painted away from the gap it claimed to mark, and once the shift exceeded half
a row's pitch (i.e. as soon as the grid was scrolled) cards landed in the
wrong position. Drop the bogus translation.
Corrupt data files no longer destroy the library. `storage::load` returned
`Option`, so a snippets.json that merely failed to parse looked exactly like a
first launch: the app seeded defaults and the next edit overwrote the user's
data. Loaders now return `Load::{Loaded, Missing, Corrupt}`; an unreadable file
is preserved as `<name>.corrupt`, defaults are loaded, and the banner says
where the original went. A zero-byte file still counts as absent.
Saves are now atomic (temp file -> sync_all -> rename), so a crash, power loss
or full disk mid-write can't leave a truncated snippets.json behind.
Also fixed:
- Reorder resolved the dragged card by an index captured at drag start. If the
library changed mid-drag that moved the wrong card, or panicked on an
out-of-bounds `Vec::remove`. It now resolves the stable snippet id at drop
time and no-ops if the snippet is gone.
- A drag left live when the filtered grid became empty wedged `self.drag` as
`Some` forever, blocking all further drags. The empty-grid path now cancels it.
- Enter pressed anywhere (search box, editor modal) submitted the top-bar
"new category" field. It now requires the Enter to belong to that field.
- A successful config write (e.g. switching themes) cleared the banner
reporting an unsaved snippet library. Each save now retires only its own
error, and the banner is dismissible.
- Blank or "All" categories from a hand-edited snippets.json produced a badge
that no filter entry could select; they now map to "Uncategorized".
- Two `empty_line_after_doc_comment` lints in theme.rs failed CI, which runs
clippy with `-D warnings`.
- Category comparison used `eq_ignore_ascii_case` in app.rs and `to_lowercase`
in storage.rs; both now share `storage::same_category`.
- `partial_cmp(..).unwrap()` on card-center distances could panic on NaN; use
`total_cmp`.
Test suite grows from 12 to 28: the scroll-area coordinate space, reordering
(including filtered views and a snippet that vanishes mid-drag), save-error
scoping, atomic writes, corrupt-file recovery and category normalization.
Tests that touch save paths now use a temp directory via a `test_app` helper
instead of writing snippets.json into the repository root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C64gVpQ6io6vxNNQ65ZmMZ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Drag-and-drop targeted the wrong slot. Card rects gathered inside a
ScrollArea are already absolute screen coordinates -- the scroll area places
its content Ui at
inner_rect.min - state.offset, so every widget rect belowit is scroll-adjusted already. The drop code translated them a second time by
that same origin, shifting all gap geometry down by the height of the top bar
and drifting further with every pixel scrolled. The insertion line was always
painted away from the gap it claimed to mark, and once the shift exceeded half
a row's pitch (i.e. as soon as the grid was scrolled) cards landed in the
wrong position. Drop the bogus translation.
Corrupt data files no longer destroy the library.
storage::loadreturnedOption, so a snippets.json that merely failed to parse looked exactly like afirst launch: the app seeded defaults and the next edit overwrote the user's
data. Loaders now return
Load::{Loaded, Missing, Corrupt}; an unreadable fileis preserved as
<name>.corrupt, defaults are loaded, and the banner sayswhere the original went. A zero-byte file still counts as absent.
Saves are now atomic (temp file -> sync_all -> rename), so a crash, power loss
or full disk mid-write can't leave a truncated snippets.json behind.
Also fixed:
library changed mid-drag that moved the wrong card, or panicked on an
out-of-bounds
Vec::remove. It now resolves the stable snippet id at droptime and no-ops if the snippet is gone.
self.dragasSomeforever, blocking all further drags. The empty-grid path now cancels it."new category" field. It now requires the Enter to belong to that field.
reporting an unsaved snippet library. Each save now retires only its own
error, and the banner is dismissible.
that no filter entry could select; they now map to "Uncategorized".
empty_line_after_doc_commentlints in theme.rs failed CI, which runsclippy with
-D warnings.eq_ignore_ascii_casein app.rs andto_lowercasein storage.rs; both now share
storage::same_category.partial_cmp(..).unwrap()on card-center distances could panic on NaN; usetotal_cmp.Test suite grows from 12 to 28: the scroll-area coordinate space, reordering
(including filtered views and a snippet that vanishes mid-drag), save-error
scoping, atomic writes, corrupt-file recovery and category normalization.
Tests that touch save paths now use a temp directory via a
test_apphelperinstead of writing snippets.json into the repository root.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01C64gVpQ6io6vxNNQ65ZmMZ