···12121313### Changed
1414- Replaced 12 blocking `prompt()`/`confirm()` call sites with the new modal helpers: trash permanent-delete and empty-trash, version-panel name-and-restore, calendar event delete, sheets sheet-delete, landing folder delete, landing identity (Tailscale signed-in, change display name), docs toolbar (insert link, insert image, add comment), and the Forge Note button (#681).
1515+- Eliminated remaining 11 blocking `prompt()`/`confirm()` call sites: doclist tag editor, docs comment-reply, docs link-preview edit, docs slash-commands (image, link, footnote), docs command-palette footnote, slides add-image, slides thumbnail right-click (now a proper Duplicate/Delete context menu — previously a `confirm("...? Cancel to duplicate")` with inverted semantics), forms link-to-sheet, sheets column and row resize. All now go through `modalPrompt`/`modalConfirm` or `createContextMenu`. (#110)
1616+- Consolidated three drifted `showToast` implementations: the canonical queue-based helper in `src/landing-toast.ts` now serves as the single source of truth. `src/docs/export-import.ts` and `src/sheets/import-export.ts` now re-export from it instead of each maintaining their own basic version. Also replaced 6 blocking `alert()` calls — version restore failure, diagram toolbar errors (text-only mode, palette validation), sheets chart validation, sheets pivot row-field validation — with error-styled toasts. (#110)
1717+1818+### Fixed
1919+- `insertFootnote` TipTap command previously called `prompt()` internally when no content was supplied, which froze the main thread. The command now requires the `content` argument; the slash-command handler and command palette gather footnote text via the async modal helper before invoking it. (#110)
15201621### Removed
1722- 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)
+19-5
src/diagrams/main.ts
···477477 if (visible) renderLayersPanel();
478478});
479479480480-btnAddLayer.addEventListener('click', () => {
481481- const name = prompt('Layer name:');
480480+btnAddLayer.addEventListener('click', async () => {
481481+ const { modalPrompt } = await import('../lib/modal-dialog.js');
482482+ const name = await modalPrompt({
483483+ title: 'New layer',
484484+ message: 'Name this layer:',
485485+ placeholder: 'e.g. Annotations',
486486+ okLabel: 'Create',
487487+ });
482488 if (!name || !name.trim()) return;
483489 layerState = addLayer(layerState, name.trim());
484490 syncToYjs();
485491 renderLayersPanel();
486492});
487493488488-layersList.addEventListener('click', (e) => {
494494+layersList.addEventListener('click', async (e) => {
489495 const target = e.target as HTMLElement;
490496 const action = target.dataset.action;
491497 const item = target.closest('.layer-item') as HTMLElement | null;
···504510 syncToYjs();
505511 renderLayersPanel();
506512 break;
507507- case 'remove':
508508- if (confirm('Delete this layer? Shapes will move to Default.')) {
513513+ case 'remove': {
514514+ const { modalConfirm } = await import('../lib/modal-dialog.js');
515515+ const ok = await modalConfirm({
516516+ title: 'Delete layer?',
517517+ message: 'Shapes in this layer will move to Default.',
518518+ okLabel: 'Delete',
519519+ destructive: true,
520520+ });
521521+ if (ok) {
509522 layerState = removeLayer(layerState, layerId);
510523 syncToYjs();
511524 render();
512525 renderLayersPanel();
513526 }
514527 break;
528528+ }
515529 case 'select':
516530 // Assign selected shapes to this layer
517531 if (selectedShapeIds.size > 0) {
+4-2
src/diagrams/toolbar-wiring.ts
···361361 const text = await file.text();
362362 const { shapes, skippedCount } = parseSvgToShapes(text);
363363 if (shapes.length === 0) {
364364- alert('No supported shapes found in SVG. Only rect, circle, ellipse, line, polygon, and text elements are imported.');
364364+ const { showToast } = await import('../landing-toast.js');
365365+ showToast('No supported shapes found in SVG. Only rect, circle, ellipse, line, polygon, and text are imported.', 5000, true);
365366 return;
366367 }
367368 deps.pushHistory();
···377378 }
378379 } catch (err) {
379380 console.error('SVG import failed:', err);
380380- alert('Failed to import SVG file.');
381381+ const { showToast } = await import('../landing-toast.js');
382382+ showToast('Failed to import SVG file.', 4000, true);
381383 }
382384 });
383385
···4949 URL.revokeObjectURL(url);
5050}
51515252-export function showToast(message: string, duration = 3000): void {
5353- const existing = document.querySelector('.toast-notification');
5454- if (existing) existing.remove();
5555- const toast = document.createElement('div');
5656- toast.className = 'toast-notification';
5757- toast.textContent = message;
5858- document.body.appendChild(toast);
5959- toast.offsetHeight;
6060- toast.classList.add('toast-visible');
6161- setTimeout(() => { toast.classList.remove('toast-visible'); setTimeout(() => toast.remove(), 300); }, duration);
6262-}
5252+// Re-exported from the canonical helper in landing-toast.ts so all editors
5353+// share a single implementation (queue, error styling, undo actions).
5454+export { showToast } from '../landing-toast.js';
63556456// ── Export ───────────────────────────────────────────────────
6557
+2-1
src/sheets/pivot-ui.ts
···77import { colToLetter, cellId } from './formulas.js';
88import { computePivot, formatAggregateValue } from './pivot-table.js';
99import type { PivotConfig, AggregateFunction } from './pivot-table.js';
1010+import { showToast } from '../landing-toast.js';
10111112// ── Types ───────────────────────────────────────────────────
1213···8182 const title = (overlay.querySelector('#pivot-title') as HTMLInputElement).value.trim();
82838384 if (rowFields.length === 0) {
8484- alert('Select at least one row field.');
8585+ showToast('Select at least one row field.', 4000, true);
8586 return;
8687 }
8788