Asked for in #24, alongside the ordering bug #27 fixed: with ~25 rules and only "move one place" buttons, a rule that ends up in the wrong place costs ten clicks to bring back. It was deliberately left out of that PR as an enhancement rather than the bug.
How it works
A grip handle on the left of each card arms the drag. The card is draggable only while its own handle is held, so the switch, the name, and the buttons still take a plain click.
The card being dragged fades; the card under the pointer draws a 3px line on the edge the rule would land on — top half means above, bottom half means below.
The up/down buttons stay exactly as they are. They are the keyboard path, and dragging is an addition rather than a replacement.
The drag carries a private application/x-ihasmail-sieve-rule type, so a rule cannot be dropped anywhere else and nothing else can be dropped on the list. Same idiom as the message-to-folder drag in MailboxTree.
The reorder itself is reorderRules() in lib/sieve.ts, next to upsertRule from #27, so ordering lives in one place and is unit-testable.
One real bug, found by running it
The guard against dropping a rule onto itself first read React state. dragstart and the first dragover can arrive in the same frame, before a re-render, so the guard read null and drew a drop line on the card being dragged. It now reads a ref, set synchronously in onDragStart. There is a test that fires the two events back to back, which fails against the state-based version.
Verified
Driven in a real browser against npm run dev:mock: handles present, draggable arming only the pressed card, the dragged card fading, the drop line on the correct edge (computed style confirmed as inset 0 -3px 0 0, bottom only), both directions reordering correctly, self-drop doing nothing, and every class and attribute cleared afterwards.
npm run typecheck, npm test — 194 web + 88 server, all passing. Five new component tests cover arming, both drop directions, the same-frame race, and a foreign drag type being ignored; four of them fail without the change.
Not included: touch dragging. HTML5 drag and drop is mouse-only, so on a phone the up/down buttons remain the way to reorder. Worth its own change if it matters.
Asked for in #24, alongside the ordering bug #27 fixed: with ~25 rules and only "move one place" buttons, a rule that ends up in the wrong place costs ten clicks to bring back. It was deliberately left out of that PR as an enhancement rather than the bug.
## How it works
- A grip handle on the left of each card arms the drag. The card is `draggable` only while its own handle is held, so the switch, the name, and the buttons still take a plain click.
- The card being dragged fades; the card under the pointer draws a 3px line on the edge the rule would land on — top half means above, bottom half means below.
- The up/down buttons stay exactly as they are. They are the keyboard path, and dragging is an addition rather than a replacement.
- The drag carries a private `application/x-ihasmail-sieve-rule` type, so a rule cannot be dropped anywhere else and nothing else can be dropped on the list. Same idiom as the message-to-folder drag in `MailboxTree`.
- The reorder itself is `reorderRules()` in `lib/sieve.ts`, next to `upsertRule` from #27, so ordering lives in one place and is unit-testable.
## One real bug, found by running it
The guard against dropping a rule onto itself first read React state. `dragstart` and the first `dragover` can arrive in the same frame, before a re-render, so the guard read `null` and drew a drop line on the card being dragged. It now reads a ref, set synchronously in `onDragStart`. There is a test that fires the two events back to back, which fails against the state-based version.
## Verified
Driven in a real browser against `npm run dev:mock`: handles present, `draggable` arming only the pressed card, the dragged card fading, the drop line on the correct edge (computed style confirmed as `inset 0 -3px 0 0`, bottom only), both directions reordering correctly, self-drop doing nothing, and every class and attribute cleared afterwards.
`npm run typecheck`, `npm test` — 194 web + 88 server, all passing. Five new component tests cover arming, both drop directions, the same-frame race, and a foreign drag type being ignored; four of them fail without the change.
Not included: touch dragging. HTML5 drag and drop is mouse-only, so on a phone the up/down buttons remain the way to reorder. Worth its own change if it matters.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
**Merged** 2026-08-25 as coffey-labs/ihasmail@9a2f355a4a16
<sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Asked for in #24, alongside the ordering bug #27 fixed: with ~25 rules and only "move one place" buttons, a rule that ends up in the wrong place costs ten clicks to bring back. It was deliberately left out of that PR as an enhancement rather than the bug.
How it works
draggableonly while its own handle is held, so the switch, the name, and the buttons still take a plain click.application/x-ihasmail-sieve-ruletype, so a rule cannot be dropped anywhere else and nothing else can be dropped on the list. Same idiom as the message-to-folder drag inMailboxTree.reorderRules()inlib/sieve.ts, next toupsertRulefrom #27, so ordering lives in one place and is unit-testable.One real bug, found by running it
The guard against dropping a rule onto itself first read React state.
dragstartand the firstdragovercan arrive in the same frame, before a re-render, so the guard readnulland drew a drop line on the card being dragged. It now reads a ref, set synchronously inonDragStart. There is a test that fires the two events back to back, which fails against the state-based version.Verified
Driven in a real browser against
npm run dev:mock: handles present,draggablearming only the pressed card, the dragged card fading, the drop line on the correct edge (computed style confirmed asinset 0 -3px 0 0, bottom only), both directions reordering correctly, self-drop doing nothing, and every class and attribute cleared afterwards.npm run typecheck,npm test— 194 web + 88 server, all passing. Five new component tests cover arming, both drop directions, the same-frame race, and a foreign drag type being ignored; four of them fail without the change.Not included: touch dragging. HTML5 drag and drop is mouse-only, so on a phone the up/down buttons remain the way to reorder. Worth its own change if it matters.
🤖 Generated with Claude Code
Merged 2026-08-25 as coffey-labs/ihasmail@9a2f355a4a
Rebuilt from: git history, session transcript.