Skip to content

Commit f5e8917

Browse files
feat: add undo button to page delete toast (#1163) (#1166)
Co-authored-by: Ona <no-reply@ona.com>
1 parent 3a3785e commit f5e8917

4 files changed

Lines changed: 203 additions & 8 deletions

File tree

.agents/quality.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Tracks code quality per domain. Updated by automations as a side effect of featu
1717
| Infrastructure | A | Sentry (client + server + edge), proxy with session refresh, health endpoint with tests (9 tests), PWA manifest, global error boundary, Supabase clients (browser + server + proxy). JetBrains Mono font correctly configured. Dark-only oklch theme tokens. Migration validation tests (33 tests). API route error-handling consistency tests (6 tests). |
1818
| Auth | A | Sign-in, sign-up, invite accept pages. OAuth sign-in with GitHub and Google fully functional. Auth guard in app layout with redirect. Error boundaries on all auth routes (5 error.tsx files, 22 tests). Loading state for invite page. Typography regression test (2 tests). Sign-in unit tests (10 tests): form validation, error handling, redirect logic, loading state. Sign-up unit tests (11 tests): form validation, error handling, redirect logic, loading state. Auth callback route tests (7 tests). Root page tests (4 tests). OAuth buttons unit tests (10 tests). E2E spec covers form rendering, redirect, and sign-in flow (8 tests). |
1919
| Workspaces | A | Workspace home, settings (name/slug/delete), workspace switcher with create dialog. Slug generation utility with unit tests (12 tests). Settings form unit tests (17 tests): validation, save, slug sanitization, delete confirmation flow, error handling. Create workspace dialog unit tests (5 tests). E2E specs cover workspace creation (3 tests), workspace settings (3 tests), and workspace limit enforcement (2 tests). Max 3 workspace limit enforced via DB trigger. |
20-
| Pages | A | Page view with title + editor, page tree with CRUD + drag-and-drop + nest/unnest + keyboard navigation (WAI-ARIA Treeview pattern with roving tabindex). Page menu with export/import markdown. Page icon picker with emoji support. Cover images, backlinks, version history, favorites, trash/soft-delete, page duplication, inline page link search. Keyboard shortcuts for duplicate (⌘D) and export (⌘⇧E). Tree logic extracted to `src/lib/page-tree.ts` with 46 unit tests covering build, reorder, nest, unnest, drop computation, visible item traversal, and parent lookup. Page tree actions hook tests (35 tests): create, create-database, duplicate (regular + database), delete with descendant cleanup, move up/down, nest/unnest, toggle favorite with optimistic updates and error rollback. Page tree keyboard shortcut tests (5 tests). Page icon design spec tests (1 test). Page visit error handling tests (2 tests). Persisted tree expansion tests (9 tests). 13 E2E specs: page CRUD (5), sidebar drag (2), sidebar keyboard nav (10), page icon (4), page cover (5), page duplicate (4), favorites (7), trash (7), version history (5), page link search (4), page shortcuts (4), backlinks (1), title advance (3) — 61 tests total. |
20+
| Pages | A | Page view with title + editor, page tree with CRUD + drag-and-drop + nest/unnest + keyboard navigation (WAI-ARIA Treeview pattern with roving tabindex). Page menu with export/import markdown. Page icon picker with emoji support. Cover images, backlinks, version history, favorites, trash/soft-delete with undo, page duplication, inline page link search. Keyboard shortcuts for duplicate (⌘D) and export (⌘⇧E). Tree logic extracted to `src/lib/page-tree.ts` with 46 unit tests covering build, reorder, nest, unnest, drop computation, visible item traversal, and parent lookup. Page tree actions hook tests (39 tests): create, create-database, duplicate (regular + database), delete with undo (toast action, restore RPC, navigate-back, error handling) and descendant cleanup, move up/down, nest/unnest, toggle favorite with optimistic updates and error rollback. Page tree keyboard shortcut tests (5 tests). Page icon design spec tests (1 test). Page visit error handling tests (2 tests). Persisted tree expansion tests (9 tests). 13 E2E specs: page CRUD (5), sidebar drag (2), sidebar keyboard nav (10), page icon (4), page cover (5), page duplicate (4), favorites (7), trash (9), version history (5), page link search (4), page shortcuts (4), backlinks (1), title advance (3) — 63 tests total. |
2121
| Editor | A | Full Lexical editor: slash commands, floating toolbar (with font family selector), floating link editor, drag-and-drop blocks, code highlighting, image upload, clipboard image paste, callouts, collapsible/toggle blocks, table support. Markdown import/export with shortcuts. Word count and reading time utility (18 tests). 18 unit test files (162 tests): theme mapping (9), markdown utils (8), design spec compliance (11), Node.contains safety (1), Lexical dispatch safety (1), collapsible toggle (12), callout node (5), image plugin (6), markdown shortcuts (12), emoji picker design spec (7), floating image toolbar (21), image node (19), word count (18), auto-link plugin (12), table serialization (6), save error handling (5), version save error handling (3), save debounce (6). 16 E2E specs (editor-auto-link, editor-auto-save, editor-callout-collapsible, editor-code-paste, editor-drag, editor-focus-mode, editor-font-family, editor-image-paste, editor-image-upload, editor-link, editor-list-indent, editor-markdown-shortcuts, editor-slash-commands, editor-table, editor-toolbar, editor-turn-into — 64 tests total). Auto-save with debounce. Sentry error capture on save failures and image uploads. |
2222
| Search | A | Full-text search via PostgreSQL tsvector + tsquery. API route with integration tests (14 tests) including transient network retry regression tests. Sidebar search component with unit tests (12 tests). Sentry error capture. E2E spec covers search flow (`e2e/search.spec.ts`, 5 tests). |
2323
| Import/Export | A | Markdown export (download .md) and import (parse .md, create page) via page menu and workspace home. Shared `useMarkdownImport` hook. Markdown utils with unit tests (8 tests). E2E specs: import-export flow plus edge cases (`e2e/import-export.spec.ts`, 7 tests), workspace home import (`e2e/workspace-home-import.spec.ts`, 3 tests) — 10 E2E tests total. |
@@ -33,16 +33,16 @@ Tracks code quality per domain. Updated by automations as a side effect of featu
3333

3434
| Category | Files | Tests |
3535
|---|---|---|
36-
| Unit/Integration (Vitest) | 144 | 1971 |
37-
| E2E (Playwright) | 86 | 425 |
38-
| **Total** | **230** | **2396** |
36+
| Unit/Integration (Vitest) | 144 | 1975 |
37+
| E2E (Playwright) | 86 | 427 |
38+
| **Total** | **230** | **2402** |
3939

4040
### Test files by domain
4141

4242
- **Auth**: `sign-in/page.test.tsx` (10 tests), `sign-in/error.test.ts` (4 tests), `sign-up/page.test.tsx` (11 tests), `sign-up/error.test.ts` (4 tests), `forgot-password/error.test.ts` (4 tests), `reset-password/error.test.ts` (4 tests), `invite/[token]/error.test.ts` (6 tests), `auth-typography.test.ts` (2 tests), `callback/route.test.ts` (7 tests), `page.test.tsx` (4 tests), `oauth-buttons.test.tsx` (10 tests), `e2e/auth.spec.ts` (8 tests)
4343
- **Workspaces**: `workspace.test.ts` (12 tests), `workspace-settings-form.test.tsx` (17 tests), `create-workspace-dialog.test.tsx` (5 tests), `e2e/workspace.spec.ts` (3 tests), `e2e/workspace-settings.spec.ts` (3 tests), `e2e/workspace-limit.spec.ts` (2 tests)
4444
- **Editor**: `theme.test.ts` (9), `markdown-utils.test.ts` (8), `design-spec-compliance.test.ts` (11), `node-contains-safety.test.ts` (1), `lexical-dispatch-safety.test.ts` (1), `callout-node.test.ts` (5), `collapsible-toggle.test.ts` (12), `image-plugin.test.ts` (6), `markdown-shortcuts.test.ts` (12), `emoji-picker-design-spec.test.ts` (7), `floating-image-toolbar.test.ts` (21), `image-node.test.ts` (19), `word-count.test.ts` (18), `auto-link-plugin.test.ts` (12), `table-serialization.test.ts` (6), `save-error-handling.test.ts` (5), `version-save-error-handling.test.ts` (3), `save-debounce.test.ts` (6), `e2e/editor-auto-link.spec.ts` (5), `e2e/editor-auto-save.spec.ts` (2), `e2e/editor-callout-collapsible.spec.ts` (3), `e2e/editor-code-paste.spec.ts` (2), `e2e/editor-drag.spec.ts` (4), `e2e/editor-focus-mode.spec.ts` (5), `e2e/editor-font-family.spec.ts` (5), `e2e/editor-image-upload.spec.ts` (2), `e2e/editor-image-paste.spec.ts` (2), `e2e/editor-link.spec.ts` (3), `e2e/editor-list-indent.spec.ts` (3), `e2e/editor-markdown-shortcuts.spec.ts` (4), `e2e/editor-slash-commands.spec.ts` (6), `e2e/editor-table.spec.ts` (8), `e2e/editor-toolbar.spec.ts` (4), `e2e/editor-turn-into.spec.ts` (6)
45-
- **Pages**: `page-tree.test.ts` (46 tests), `use-page-tree-actions.test.ts` (35 tests), `page-tree-shortcut.test.tsx` (5 tests), `page-icon-design-spec.test.ts` (1 test), `page-visit.test.ts` (2 tests), `use-persisted-expanded.test.ts` (9 tests), `e2e/page-crud.spec.ts` (5), `e2e/page-icon.spec.ts` (4), `e2e/sidebar-drag.spec.ts` (2), `e2e/sidebar-keyboard-nav.spec.ts` (10), `e2e/page-cover.spec.ts` (5), `e2e/page-duplicate.spec.ts` (4), `e2e/favorites.spec.ts` (7), `e2e/trash.spec.ts` (7), `e2e/version-history.spec.ts` (5), `e2e/page-link-search.spec.ts` (4), `e2e/page-shortcuts.spec.ts` (4), `e2e/backlinks.spec.ts` (1), `e2e/title-advance.spec.ts` (3)
45+
- **Pages**: `page-tree.test.ts` (46 tests), `use-page-tree-actions.test.ts` (39 tests), `page-tree-shortcut.test.tsx` (5 tests), `page-icon-design-spec.test.ts` (1 test), `page-visit.test.ts` (2 tests), `use-persisted-expanded.test.ts` (9 tests), `e2e/page-crud.spec.ts` (5), `e2e/page-icon.spec.ts` (4), `e2e/sidebar-drag.spec.ts` (2), `e2e/sidebar-keyboard-nav.spec.ts` (10), `e2e/page-cover.spec.ts` (5), `e2e/page-duplicate.spec.ts` (4), `e2e/favorites.spec.ts` (7), `e2e/trash.spec.ts` (9), `e2e/version-history.spec.ts` (5), `e2e/page-link-search.spec.ts` (4), `e2e/page-shortcuts.spec.ts` (4), `e2e/backlinks.spec.ts` (1), `e2e/title-advance.spec.ts` (3)
4646
- **Search**: `search/route.test.ts` (14 tests), `page-search.test.tsx` (12 tests), `e2e/search.spec.ts` (5 tests)
4747
- **Import/Export**: `e2e/import-export.spec.ts` (7 tests), `e2e/workspace-home-import.spec.ts` (3 tests)
4848
- **Database**: `database.test.ts` (55 tests), `column-helpers.test.ts` (65 tests), `database-duplicate.test.ts` (11 tests), `database-filters.test.ts` (54 tests), `formula.test.ts` (80 tests), `csv-export.test.ts` (38 tests), `property-type-picker.test.ts` (14 tests), `property-type-picker-completeness.test.ts` (5 tests), `table-view-value-format.test.ts` (33 tests), `table-defaults.test.ts` (27 tests), `table-cell.test.tsx` (40 tests), `database-width.test.ts` (3 tests), `database-view-client.test.tsx` (6 tests), `filter-bar.test.tsx` (20 tests), `filter-value-editor.test.tsx` (34 tests), `sort-menu.test.tsx` (19 tests), `rename-property-dialog.test.tsx` (14 tests), `row-properties-header.test.tsx` (17 tests), `view-tabs.test.tsx` (24 tests), `use-database-filters.test.ts` (18 tests), `use-database-properties.test.ts` (20 tests), `use-database-rows.test.ts` (23 tests), `use-database-views.test.ts` (24 tests), `checkbox.test.tsx` (8 tests), `computed.test.tsx` (15 tests), `date.test.tsx` (12 tests), `email.test.tsx` (8 tests), `files.test.tsx` (11 tests), `formula.test.tsx` (6 tests), `multi-select.test.tsx` (10 tests), `number.test.tsx` (14 tests), `person.test.tsx` (9 tests), `phone.test.tsx` (10 tests), `relation.test.tsx` (12 tests), `select-dropdown.test.tsx` (32 tests), `select.test.tsx` (11 tests), `status.test.tsx` (14 tests), `text.test.tsx` (10 tests), `url.test.tsx` (10 tests), `board-view-helpers.test.ts` (18 tests), `board-view.test.tsx` (13 tests), `calendar-keyboard.test.ts` (25 tests), `calendar-view-helpers.test.ts` (35 tests), `calendar-view.test.tsx` (15 tests), `database-empty-state.test.tsx` (14 tests), `gallery-view.test.tsx` (17 tests), `list-keyboard.test.ts` (14 tests), `list-view.test.tsx` (22 tests), `row-count-announcer.test.tsx` (7 tests), `row-count-status-bar.test.tsx` (5 tests), `formula-design-spec.test.ts` (2 tests), `person-skeleton-design-spec.test.ts` (1 test), `calendar-view-design-spec.test.ts` (1 test), `gallery-view-design-spec.test.ts` (3 tests), `filter-sort-toolbar-design-spec.test.ts` (2 tests), `e2e/database-crud.spec.ts` (11 tests), `e2e/database-duplicate.spec.ts` (2), `e2e/database-duplicate-row.spec.ts` (3), `e2e/database-add-property-types.spec.ts` (10), `e2e/database-board.spec.ts` (4), `e2e/database-board-keyboard.spec.ts` (6), `e2e/database-calendar.spec.ts` (4), `e2e/database-calendar-keyboard.spec.ts` (7), `e2e/database-column-reorder.spec.ts` (3), `e2e/database-column-resize.spec.ts` (4), `e2e/database-csv-export.spec.ts` (3), `e2e/database-error-recovery.spec.ts` (4), `e2e/database-files.spec.ts` (4), `e2e/database-filter-keyboard.spec.ts` (6), `e2e/database-filter-types.spec.ts` (4), `e2e/database-formula.spec.ts` (3), `e2e/database-gallery.spec.ts` (4), `e2e/database-gallery-keyboard.spec.ts` (5), `e2e/database-inline.spec.ts` (4), `e2e/database-list.spec.ts` (4), `e2e/database-list-keyboard.spec.ts` (6), `e2e/database-person.spec.ts` (5), `e2e/database-relation.spec.ts` (3), `e2e/database-row-page.spec.ts` (7), `e2e/database-search.spec.ts` (2), `e2e/database-select-options.spec.ts` (6), `e2e/database-table-editor-portal.spec.ts` (4), `e2e/database-table-keyboard.spec.ts` (6), `e2e/database-view-config.spec.ts` (4), `e2e/database-views.spec.ts` (10), `use-row-selection.test.ts` (10 tests), `e2e/database-bulk-select.spec.ts` (7)
@@ -165,5 +165,6 @@ Tracks code quality per domain. Updated by automations as a side effect of featu
165165
| 2026-05-18 | Title Enter/Tab focuses editor (#1137). Added `onAdvance` callback to `PageTitle`, wired in `PageViewClient` to focus Lexical editor on Enter/Tab. Added 1 new E2E spec: `e2e/title-advance.spec.ts` (3 tests). Added `WithAdvance` Storybook story and visual regression baseline. Test totals: 144 Vitest files (1953 tests), 85 E2E specs (415 tests). |
166166
| 2026-05-18 | Add aria-live region to editor save status indicator (#1138). Added sr-only `role="status"` span to editor save status div so screen readers announce terminal states (Saved, Save failed) without noisy Saving... announcements. No new test files. Test totals: 144 Vitest files (1962 tests), 85 E2E specs (415 tests). |
167167
| 2026-05-19 | Add keyboard navigation to sidebar page tree (#1150). Added WAI-ARIA Treeview keyboard navigation (ArrowUp/Down, ArrowLeft/Right expand/collapse, Enter, Home, End) with roving tabindex pattern. Added `getVisibleItems` and `findParentNode` to `page-tree.ts`. Updated `page-tree.test.ts` (37→46): 9 new tests for visible item traversal and parent lookup. Added 1 new E2E spec: `e2e/sidebar-keyboard-nav.spec.ts` (10 tests). Test totals: 144 Vitest files (1971 tests), 86 E2E specs (425 tests). |
168+
| 2026-05-21 | Add undo button to page delete toast (#1163). Updated `use-page-tree-actions.test.ts` (35→39): 4 new tests for undo toast action, restore RPC, navigate-back, and error handling. Updated `e2e/trash.spec.ts` (7→9): 2 new E2E tests for undo restore and undo navigate-back. Test totals: 144 Vitest files (1975 tests), 86 E2E specs (427 tests). |
168169

169170

e2e/trash.spec.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,76 @@ test.describe("Trash and soft-delete operations", () => {
272272
await expect(trashToggle).not.toBeVisible({ timeout: 10_000 });
273273
});
274274

275+
test("soft-delete toast shows Undo button that restores the page", async ({
276+
authenticatedPage: page,
277+
}) => {
278+
const sidebar = page.getByRole("complementary");
279+
280+
// Create a page to delete
281+
await createPage(page);
282+
await waitForTreeLoaded(page);
283+
284+
const treeItems = sidebar.locator('[role="treeitem"]');
285+
const countBefore = await treeItems.count();
286+
287+
// Soft-delete the page
288+
await softDeleteCurrentPage(page);
289+
290+
// The tree should have one fewer item after deletion
291+
if (countBefore === 1) {
292+
await expect(treeItems).toHaveCount(0, { timeout: 5_000 });
293+
} else {
294+
await expect(treeItems).toHaveCount(countBefore - 1, { timeout: 5_000 });
295+
}
296+
297+
// The toast should appear with an Undo button
298+
const undoBtn = page
299+
.locator('[data-sonner-toast]', { hasText: "Page moved to trash" })
300+
.getByRole("button", { name: "Undo" });
301+
await expect(undoBtn).toBeVisible({ timeout: 5_000 });
302+
303+
// Click Undo
304+
await undoBtn.click();
305+
306+
// The page should be restored in the tree
307+
await expect(treeItems).toHaveCount(countBefore, { timeout: 10_000 });
308+
});
309+
310+
test("undo navigates back to the restored page when user was on it", async ({
311+
authenticatedPage: page,
312+
}) => {
313+
// Create a page and capture its URL
314+
const pageUrl = await createPage(page);
315+
await waitForTreeLoaded(page);
316+
317+
// Soft-delete the page (user is currently viewing it)
318+
await softDeleteCurrentPage(page);
319+
320+
// User should be navigated away from the deleted page
321+
await page.waitForURL(
322+
(url) => url.pathname.split("/").filter(Boolean).length < 2,
323+
{ timeout: 10_000 },
324+
);
325+
326+
// Click Undo in the toast
327+
const undoBtn = page
328+
.locator('[data-sonner-toast]', { hasText: "Page moved to trash" })
329+
.getByRole("button", { name: "Undo" });
330+
await expect(undoBtn).toBeVisible({ timeout: 5_000 });
331+
await undoBtn.click();
332+
333+
// User should be navigated back to the restored page
334+
const expectedPageId = new URL(pageUrl).pathname.split("/").pop();
335+
await page.waitForURL(
336+
(url) => url.pathname.includes(expectedPageId!),
337+
{ timeout: 10_000 },
338+
);
339+
340+
// The editor should be visible on the restored page
341+
const editor = page.locator('[contenteditable="true"]');
342+
await expect(editor).toBeVisible({ timeout: 10_000 });
343+
});
344+
275345
test("soft-deleted page is not visible in the main page tree", async ({
276346
authenticatedPage: page,
277347
}) => {

0 commit comments

Comments
 (0)