Bell: lock-expiry notifications + retention horizon + feed cache (#402) #403

Merged
Cordy merged 6 commits from lock-notify into main 2026-09-03 00:00:04 +00:00
Owner

Implements #402 as ratified (mockup approved).

Lock-expiry notification — derived, not stored: filelock.LapsedFor surfaces manual locks the admin cap has expired (they stay recorded since #394), and the feed turns them into lock-expired items timestamped at the lapse moment. Bell renders them with a new open-shackle padlock in the lock tint; meta reads "Your lock expired (enforced by admin setting)" ×4 locales. Cap off or unlock → the item vanishes with the state, as a view should.

RetentionnotifyRetentionDays (0/absent = 30, max 365, no "forever"), new row in Basic settings whose first card is now titled Name & Notifications (all four locales). It drives three prunes, all lazy on existing writes, no sweeper: feed horizon (all kinds), lapsed-lock records dropped once lapsed longer than the horizon (locks.json bounded), and notify-seen.json watermarks older than the horizon dropped on save (lossless — such a watermark equals none; the file is bounded by users active within the window). Basic settings page description updated accordingly.

Scale guard — per-user in-memory feed cache (45 s TTL): derivation (1+P backend LISTs) runs at most once per user per interval however many tabs and devices poll. Watermark filters run per request on the cached items, so read-all/clear stay instant with no invalidation needed.

Tests: lapse detection (per-owner, lapse moment, no-cap = none), retention prune keeps live locks and drops long-lapsed ones (+ mutation check on the lapse comparison), watermark prune keeps fresh and drops stale, NotifyRetention default/explicit/validation bounds.

Closes #402.

Implements #402 as ratified (mockup approved). **Lock-expiry notification** — derived, not stored: `filelock.LapsedFor` surfaces manual locks the admin cap has expired (they stay recorded since #394), and the feed turns them into `lock-expired` items timestamped at the lapse moment. Bell renders them with a new open-shackle padlock in the lock tint; meta reads "Your lock expired (enforced by admin setting)" ×4 locales. Cap off or unlock → the item vanishes with the state, as a view should. **Retention** — `notifyRetentionDays` (0/absent = 30, max 365, no "forever"), new row in Basic settings whose first card is now titled **Name & Notifications** (all four locales). It drives three prunes, all lazy on existing writes, no sweeper: feed horizon (all kinds), lapsed-lock records dropped once lapsed longer than the horizon (`locks.json` bounded), and `notify-seen.json` watermarks older than the horizon dropped on save (lossless — such a watermark equals none; the file is bounded by users active within the window). Basic settings page description updated accordingly. **Scale guard** — per-user in-memory feed cache (45 s TTL): derivation (1+P backend LISTs) runs at most once per user per interval however many tabs and devices poll. Watermark filters run per request on the cached items, so read-all/clear stay instant with no invalidation needed. **Tests**: lapse detection (per-owner, lapse moment, no-cap = none), retention prune keeps live locks and drops long-lapsed ones (+ mutation check on the lapse comparison), watermark prune keeps fresh and drops stale, NotifyRetention default/explicit/validation bounds. Closes #402.
Cordy merged commit c5e86e9e48 into main 2026-09-03 00:00:04 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#403
No description provided.