A rule files mail into a folder by path, because that is what Sieve needs. Rename the folder and the path becomes a lie — the rule keeps matching and quietly stops filing. Delete the folder and the rule is aimed at nothing.
Renaming or moving a folder now rewrites the rules that file into it; deleting one takes its rules with it. Both are reported in a toast, since rules live on the server and are invisible from the folder list.
How it hangs together
One hook, both cases. Before a mailbox changes, the folder and everything beneath it are noted with the paths they have at that moment — renaming a parent rewrites every child's path, and rules naming those children go stale just the same. Afterwards, whatever still exists is retargeted and whatever has gone takes its rules with it.
Rules record the destination twice: mailboxId (set by the rule editor and by "filter messages like this") and mailbox, the path. The id is the reliable half and is preferred; the path is the fallback for older rules, or ones written by hand in the Scripts tab — and a rule matched by path has its id filled in on the way past, so the next rename needs no guessing.
Only the script the visual editor manages is touched. A hand-written script is left alone.
The reconciliation is awaited rather than fired and forgotten: a folder operation should not report success while the rules still disagree with it. It never throws — the folder change has already happened, and failing to tidy the rules must not make it look otherwise; that case reports its own error.
One judgement call worth your eye
Deleting a folder removes the whole rule, as asked. Worth knowing what that means for a rule like from: boss → fileinto Work, markread, stop: deleting Work drops the mark-as-read and the stop as well. The alternative — remove just the fileinto and delete the rule only if nothing is left — is a two-line change if you prefer it. I implemented what you asked for; say the word and I will switch it.
Verified
Unit: 10 new cases over the pure logic — rename by id, rename by path only, a child whose parent moved, a no-op move returning the same array, other rules untouched, other actions preserved, delete by id, delete of a subtree, delete matched by path, and no-match leaving the list alone. npm run typecheck, npm test — 211 web + 88 server, all passing.
End to end, driving the real app against the mock and then reading the stored script from the server rather than trusting the DOM:
created a rule filing into Newsletters → renamed the folder to Reading → the rule reads move to Reading
deleted the folder → the stored ihasmail script contains no rules at all
One thing I could not pin down
In my scripted harness, a full page reload immediately after the folder delete sometimes leaves the app stuck at boot — a spinner, no JS errors, and the server answering /api/session in 7ms. I could not attribute it to this change: awaiting the save rather than firing it made no difference, and a baseline run with the feature disabled hung rather than giving a clean comparison. The rules themselves are correct on the server in every run. Flagging it rather than leaving it unsaid; it may well be my automation navigating faster than a person can.
A rule files mail into a folder **by path**, because that is what Sieve needs. Rename the folder and the path becomes a lie — the rule keeps matching and quietly stops filing. Delete the folder and the rule is aimed at nothing.
Renaming or moving a folder now rewrites the rules that file into it; deleting one takes its rules with it. Both are reported in a toast, since rules live on the server and are invisible from the folder list.
## How it hangs together
One hook, both cases. Before a mailbox changes, the folder **and everything beneath it** are noted with the paths they have at that moment — renaming a parent rewrites every child's path, and rules naming those children go stale just the same. Afterwards, whatever still exists is retargeted and whatever has gone takes its rules with it.
Rules record the destination twice: `mailboxId` (set by the rule editor and by "filter messages like this") and `mailbox`, the path. The id is the reliable half and is preferred; the path is the fallback for older rules, or ones written by hand in the Scripts tab — and a rule matched by path has its id filled in on the way past, so the next rename needs no guessing.
Only the script the visual editor manages is touched. A hand-written script is left alone.
The reconciliation is awaited rather than fired and forgotten: a folder operation should not report success while the rules still disagree with it. It never throws — the folder change has already happened, and failing to tidy the rules must not make it look otherwise; that case reports its own error.
## One judgement call worth your eye
**Deleting a folder removes the whole rule**, as asked. Worth knowing what that means for a rule like `from: boss → fileinto Work, markread, stop`: deleting *Work* drops the mark-as-read and the stop as well. The alternative — remove just the `fileinto` and delete the rule only if nothing is left — is a two-line change if you prefer it. I implemented what you asked for; say the word and I will switch it.
## Verified
Unit: 10 new cases over the pure logic — rename by id, rename by path only, a child whose parent moved, a no-op move returning the same array, other rules untouched, other actions preserved, delete by id, delete of a subtree, delete matched by path, and no-match leaving the list alone. `npm run typecheck`, `npm test` — 211 web + 88 server, all passing.
End to end, driving the real app against the mock and then reading the stored script **from the server** rather than trusting the DOM:
- created a rule filing into `Newsletters` → renamed the folder to `Reading` → the rule reads `move to Reading`
- deleted the folder → the stored `ihasmail` script contains no rules at all
## One thing I could not pin down
In my scripted harness, a full page reload **immediately** after the folder delete sometimes leaves the app stuck at boot — a spinner, no JS errors, and the server answering `/api/session` in 7ms. I could not attribute it to this change: awaiting the save rather than firing it made no difference, and a baseline run with the feature disabled hung rather than giving a clean comparison. The rules themselves are correct on the server in every run. Flagging it rather than leaving it unsaid; it may well be my automation navigating faster than a person can.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
**Merged** 2026-08-25 as coffey-labs/ihasmail@b7f4b4e49612
<sub>Rebuilt from: git history, session transcript.</sub>
Switched to dropping only the filing action, per your call — 3292524.
detachFolders() now removes just the fileinto aimed at the deleted folder. A rule that also marked read, flagged or stopped keeps doing those things. A rule left with no actions at all is still removed, since it has nothing to do, and a rule filing into two folders keeps the one that still exists.
Verified in the running app with two rules aimed at the same folder:
rule
actions before
after deleting the folder
Only files
fileinto Newsletters
removed
Files and marks read
fileinto Newsletters, markread
kept, markread only
The script stored on the server agrees — the surviving rule reads "actions":[{"type":"markread"}].
The toast distinguishes the two outcomes: "1 filter rule no longer files there · 1 filter rule removed, having nothing left to do: “Only files”".
Tests updated: 12 over this module now, covering the filing-only rule, the rule with other actions, a subtree delete where one rule survives and one does not, a rule with a second fileinto pointing elsewhere, and path-only matching. 213 web + 88 server passing.
Switched to dropping only the filing action, per your call — 3292524.
`detachFolders()` now removes just the `fileinto` aimed at the deleted folder. A rule that also marked read, flagged or stopped keeps doing those things. A rule left with no actions at all is still removed, since it has nothing to do, and a rule filing into two folders keeps the one that still exists.
Verified in the running app with two rules aimed at the same folder:
| rule | actions before | after deleting the folder |
| --- | --- | --- |
| Only files | `fileinto Newsletters` | removed |
| Files and marks read | `fileinto Newsletters`, `markread` | kept, `markread` only |
The script stored on the server agrees — the surviving rule reads `"actions":[{"type":"markread"}]`.
The toast distinguishes the two outcomes: *"1 filter rule no longer files there · 1 filter rule removed, having nothing left to do: “Only files”"*.
Tests updated: 12 over this module now, covering the filing-only rule, the rule with other actions, a subtree delete where one rule survives and one does not, a rule with a second `fileinto` pointing elsewhere, and path-only matching. 213 web + 88 server passing.
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.
A rule files mail into a folder by path, because that is what Sieve needs. Rename the folder and the path becomes a lie — the rule keeps matching and quietly stops filing. Delete the folder and the rule is aimed at nothing.
Renaming or moving a folder now rewrites the rules that file into it; deleting one takes its rules with it. Both are reported in a toast, since rules live on the server and are invisible from the folder list.
How it hangs together
One hook, both cases. Before a mailbox changes, the folder and everything beneath it are noted with the paths they have at that moment — renaming a parent rewrites every child's path, and rules naming those children go stale just the same. Afterwards, whatever still exists is retargeted and whatever has gone takes its rules with it.
Rules record the destination twice:
mailboxId(set by the rule editor and by "filter messages like this") andmailbox, the path. The id is the reliable half and is preferred; the path is the fallback for older rules, or ones written by hand in the Scripts tab — and a rule matched by path has its id filled in on the way past, so the next rename needs no guessing.Only the script the visual editor manages is touched. A hand-written script is left alone.
The reconciliation is awaited rather than fired and forgotten: a folder operation should not report success while the rules still disagree with it. It never throws — the folder change has already happened, and failing to tidy the rules must not make it look otherwise; that case reports its own error.
One judgement call worth your eye
Deleting a folder removes the whole rule, as asked. Worth knowing what that means for a rule like
from: boss → fileinto Work, markread, stop: deleting Work drops the mark-as-read and the stop as well. The alternative — remove just thefileintoand delete the rule only if nothing is left — is a two-line change if you prefer it. I implemented what you asked for; say the word and I will switch it.Verified
Unit: 10 new cases over the pure logic — rename by id, rename by path only, a child whose parent moved, a no-op move returning the same array, other rules untouched, other actions preserved, delete by id, delete of a subtree, delete matched by path, and no-match leaving the list alone.
npm run typecheck,npm test— 211 web + 88 server, all passing.End to end, driving the real app against the mock and then reading the stored script from the server rather than trusting the DOM:
Newsletters→ renamed the folder toReading→ the rule readsmove to Readingihasmailscript contains no rules at allOne thing I could not pin down
In my scripted harness, a full page reload immediately after the folder delete sometimes leaves the app stuck at boot — a spinner, no JS errors, and the server answering
/api/sessionin 7ms. I could not attribute it to this change: awaiting the save rather than firing it made no difference, and a baseline run with the feature disabled hung rather than giving a clean comparison. The rules themselves are correct on the server in every run. Flagging it rather than leaving it unsaid; it may well be my automation navigating faster than a person can.🤖 Generated with Claude Code
Merged 2026-08-25 as coffey-labs/ihasmail@b7f4b4e496
Rebuilt from: git history, session transcript.
Switched to dropping only the filing action, per your call — 3292524.
detachFolders()now removes just thefileintoaimed at the deleted folder. A rule that also marked read, flagged or stopped keeps doing those things. A rule left with no actions at all is still removed, since it has nothing to do, and a rule filing into two folders keeps the one that still exists.Verified in the running app with two rules aimed at the same folder:
fileinto Newslettersfileinto Newsletters,markreadmarkreadonlyThe script stored on the server agrees — the surviving rule reads
"actions":[{"type":"markread"}].The toast distinguishes the two outcomes: "1 filter rule no longer files there · 1 filter rule removed, having nothing left to do: “Only files”".
Tests updated: 12 over this module now, covering the filing-only rule, the rule with other actions, a subtree delete where one rule survives and one does not, a rule with a second
fileintopointing elsewhere, and path-only matching. 213 web + 88 server passing.