The transport fix stops the truncation that caused #76, but the save path
had no answer for a baseline that arrives incomplete. It is neither
unknown nor empty, so every existing guard passes it through: it parses
into a shorter rule list that looks exactly like a script with fewer
rules, and saving writes that back over the real one.
Check the script against the shape the generator emits instead. Every
rule comment parses, every enabled rule has an if and a closed body under
it, every block ends with a blank line. Structural rather than a
re-serialize-and-compare, so a script written by an older version whose
serializer differed is still editable.
The rule editor reports a short script as unreadable rather than showing
the rules that happened to parse, since a list that looks complete over a
script that is not is the most dangerous thing it could offer.
A cut at the end of a complete rule block is still a valid shorter script
and cannot be told apart from one; that residual is the proxy's to cover.
Twenty-five rules and two buttons that move one place at a time meant a
rule pushed to the wrong end cost ten clicks to bring back. It can now
be dragged.
A grip on the left of each card arms the drag, so the switch, the name
and the buttons still take a plain click, and the up and down buttons
stay for the keyboard. The card being dragged fades; the one under the
pointer draws a line on the edge the rule would land on, top half or
bottom.
The guard against dropping a rule onto itself reads a ref rather than
state: dragstart and the first dragover can arrive in the same frame,
and a stale read there drew a drop line on the card being dragged. Found
by driving the real thing in a browser, and covered by a test that fires
the two events back to back.
Renaming or editing a Sieve rule moved it to the bottom of the list, and
in Sieve the order is the order the rules run in, so mail started being
filed by a different rule than before. Putting it back took a click per
place moved.
Two things did it. saveAndApply always appended the rule it was given —
right for a rule created from a message, wrong for one being edited. And
the "Also apply to existing messages" tick defaulted to on wherever it
was offered, so every edit in Settings went down that path, including a
plain rename.
The rule now keeps its seat: a shared upsertRule replaces by id in place
and only appends what is genuinely new. The tick defaults to on only in
"Filter messages like this…", where applying it is the point, and the
toast no longer calls an edited rule "created".
Fixes#24