Skip to content

Fix drag-and-drop hit-testing, data-loss and CI-breaking bugs - #10

Merged
quantdale merged 1 commit into
mainfrom
claude/bug-fixes-issues-dkteg9
Jul 25, 2026
Merged

quantdale merged 1 commit into
mainfrom
claude/bug-fixes-issues-dkteg9

Conversation

@quantdale

Copy link
Copy Markdown
Owner

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

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
@quantdale
quantdale merged commit 4b1646c into main Jul 25, 2026
4 checks passed
@quantdale
quantdale deleted the claude/bug-fixes-issues-dkteg9 branch July 25, 2026 13:31
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.

2 participants