···1616### Removed
1717- 9 orphaned context-menu builder helpers (`buildDocsTextItems`, `buildDocsLinkItems`, `buildDocsImageItems`, `buildDocsTableItems`, `buildSheetsCellItems`, `buildSheetsColumnHeaderItems`, `buildSheetsRowHeaderItems`, `buildSheetsContextItems`, plus the `SheetsContextTarget` type): returned menu arrays with 45 no-op `() => {}` handlers and were only referenced by tests — never wired into a real editor. (#109)
18181919+### Fixed
2020+- E2EE key-loss warning no longer pops a "Your document is safely encrypted" modal on every document for users who are signed in with Tailscale and have their key already synced — the shield icon in the topbar still shows the status and can be clicked to view the explainer. At-risk (unsynced) and anonymous users still see the warning — one-time per doc — since the data-loss risk is real for them. (#684)
2121+1922## [0.49.0] — 2026-04-16
20232124### Added
+11-2
src/lib/key-warning.ts
···155155156156function renderModal(docId: string, level: KeyLossRiskLevel, opts: MountKeyWarningOptions): void {
157157 if (!isBrowser()) return;
158158- if (activeModal) return; // dedupe
158158+ // Dedupe, but tolerate the case where the overlay was removed from the DOM
159159+ // out-of-band (e.g. another component wiped the body, or a test harness).
160160+ if (activeModal && !activeModal.isConnected) activeModal = null;
161161+ if (activeModal) return;
159162160163 const copy = copyForLevel(level);
161164···309312 * Show the key-loss warning for the current document, if the user hasn't seen it yet.
310313 *
311314 * Safe to call more than once per page; subsequent calls are no-ops unless `force: true`.
315315+ *
316316+ * #684 — skip the auto-mount entirely for "safe" users (authed + synced). The
317317+ * shield icon in the topbar already communicates status for them, and popping a
318318+ * modal on every doc is pure noise. Users can still re-open the modal manually
319319+ * via the shield icon (which passes `force: true`).
312320 */
313321export function mountKeyWarning(opts: MountKeyWarningOptions): void {
314322 if (!isBrowser()) return;
315323 if (!opts.docId) return;
324324+ const level = classifyKeyLossRisk({ user: opts.user, hasServerSyncedKey: opts.hasServerSyncedKey });
325325+ if (!opts.force && level === 'safe') return;
316326 if (!opts.force && hasSeenKeyWarning(opts.docId)) return;
317317- const level = classifyKeyLossRisk({ user: opts.user, hasServerSyncedKey: opts.hasServerSyncedKey });
318327 renderModal(opts.docId, level, opts);
319328}
320329
+36
tests/key-warning.test.ts
···2828 hasSeenKeyWarning,
2929 markKeyWarningSeen,
3030 resetKeyWarningSeen,
3131+ mountKeyWarning,
3132} = await import('../src/lib/key-warning.js');
32333334const authedUser: TailscaleUser = { login: 'scott@example.com', name: 'Scott', profilePic: null };
···8485 expect(hasSeenKeyWarning('')).toBe(false);
8586 });
8687});
8888+8989+// #684 — the "safe" variant should not pop a modal at all (it was noise on every doc).
9090+describe('mountKeyWarning — noise suppression (#684)', () => {
9191+ beforeEach(() => {
9292+ localStorage.clear();
9393+ // Clear any lingering overlay from a prior test
9494+ document.querySelectorAll('.key-warning-overlay').forEach(n => n.remove());
9595+ });
9696+9797+ const authedUser: TailscaleUser = { login: 'scott@example.com', name: 'Scott', profilePic: null };
9898+9999+ it('does NOT show the modal for "safe" users (authed + synced)', () => {
100100+ mountKeyWarning({ docId: 'doc-safe', user: authedUser, hasServerSyncedKey: true });
101101+ expect(document.querySelector('.key-warning-overlay')).toBeNull();
102102+ });
103103+104104+ it('still shows the modal for anonymous users (genuine risk)', () => {
105105+ mountKeyWarning({ docId: 'doc-anon', user: null, hasServerSyncedKey: false });
106106+ expect(document.querySelector('.key-warning-overlay')).not.toBeNull();
107107+ });
108108+109109+ it('still shows the modal for at-risk users (authed, unsynced)', () => {
110110+ mountKeyWarning({ docId: 'doc-at-risk', user: authedUser, hasServerSyncedKey: false });
111111+ expect(document.querySelector('.key-warning-overlay')).not.toBeNull();
112112+ });
113113+114114+ it('does not show the modal twice for the same doc (per-doc dismissal still works)', () => {
115115+ mountKeyWarning({ docId: 'doc-anon', user: null, hasServerSyncedKey: false });
116116+ const first = document.querySelector('.key-warning-overlay');
117117+ first?.remove();
118118+ markKeyWarningSeen('doc-anon');
119119+ mountKeyWarning({ docId: 'doc-anon', user: null, hasServerSyncedKey: false });
120120+ expect(document.querySelector('.key-warning-overlay')).toBeNull();
121121+ });
122122+});