+
-
+
`;
document.body.appendChild(panel);
panel.querySelector('#test-close-btn').addEventListener('click', close);
diff --git a/public/js/test/state-functions.test.js b/public/js/test/state-functions.test.js
new file mode 100644
index 0000000..02a424e
--- /dev/null
+++ b/public/js/test/state-functions.test.js
@@ -0,0 +1,114 @@
+import { describe, it, expect } from './runner.js';
+import { state, parseTags, sortedCards, getChildren, getGroupCards, getRecent, markRecent, getFavorites, toggleFavorite, isFavorite } from '../modules/state.js';
+
+describe('State: parseTags', () => {
+ it('splits comma-separated tags', () => {
+ expect(parseTags({ tags: 'alpha,beta,gamma' })).toEqual(['alpha', 'beta', 'gamma']);
+ });
+
+ it('handles empty tags', () => {
+ expect(parseTags({ tags: '' })).toEqual([]);
+ expect(parseTags({ tags: null })).toEqual([]);
+ expect(parseTags({})).toEqual([]);
+ });
+
+ it('handles single tag', () => {
+ expect(parseTags({ tags: 'solo' })).toEqual(['solo']);
+ });
+
+ it('handles hierarchical tags', () => {
+ expect(parseTags({ tags: 'parent/child,other' })).toEqual(['parent/child', 'other']);
+ });
+});
+
+describe('State: getChildren / getGroupCards', () => {
+ it('getChildren returns child cards', () => {
+ const parent = state.cards.find(c => {
+ return state.cards.some(ch => ch.parent_id == c.id);
+ });
+ if (!parent) return; // no groups
+ const children = getChildren(parent.id);
+ expect(children.length).toBeGreaterThan(0);
+ for (const ch of children) expect(ch.parent_id).toBe(parent.id);
+ });
+
+ it('getChildren returns empty for non-parent', () => {
+ const orphan = state.cards.find(c => !c.parent_id && !state.cards.some(ch => ch.parent_id == c.id));
+ if (!orphan) return;
+ expect(getChildren(orphan.id).length).toBe(0);
+ });
+
+ it('getGroupCards returns parent + children', () => {
+ const parent = state.cards.find(c => state.cards.some(ch => ch.parent_id == c.id));
+ if (!parent) return;
+ const group = getGroupCards(parent);
+ expect(group.length).toBeGreaterThan(1);
+ expect(group[0].id).toBe(parent.id);
+ });
+
+ it('getGroupCards returns null for solo card', () => {
+ const solo = state.cards.find(c => !c.parent_id && !state.cards.some(ch => ch.parent_id == c.id));
+ if (!solo) return;
+ expect(getGroupCards(solo)).toBe(null);
+ });
+});
+
+describe('State: Recent Cards', () => {
+ it('markRecent adds card to recent list', () => {
+ const id = 99999;
+ markRecent(id);
+ expect(getRecent()).toContain(String(id));
+ // Clean up
+ const r = getRecent().filter(i => i != id);
+ localStorage.setItem('hyprcrd-recent', JSON.stringify(r));
+ });
+
+ it('markRecent puts card at front', () => {
+ markRecent(88881);
+ markRecent(88882);
+ const r = getRecent();
+ expect(r[0]).toBe('88882');
+ expect(r[1]).toBe('88881');
+ // Clean up
+ const cleaned = r.filter(i => i != '88881' && i != '88882');
+ localStorage.setItem('hyprcrd-recent', JSON.stringify(cleaned));
+ });
+
+ it('markRecent deduplicates', () => {
+ markRecent(77771);
+ markRecent(77772);
+ markRecent(77771);
+ const r = getRecent();
+ const count = r.filter(i => i == '77771').length;
+ expect(count).toBe(1);
+ expect(r[0]).toBe('77771');
+ const cleaned = r.filter(i => i != '77771' && i != '77772');
+ localStorage.setItem('hyprcrd-recent', JSON.stringify(cleaned));
+ });
+
+ it('markRecent caps at 10', () => {
+ for (let i = 0; i < 15; i++) markRecent(60000 + i);
+ expect(getRecent().length).toBeLessThan(12);
+ const cleaned = getRecent().filter(i => parseInt(i) < 60000 || parseInt(i) > 60020);
+ localStorage.setItem('hyprcrd-recent', JSON.stringify(cleaned));
+ });
+});
+
+describe('State: Favorites', () => {
+ it('toggleFavorite adds and removes', () => {
+ const id = 55555;
+ const wasFav = isFavorite(id);
+ toggleFavorite(id);
+ expect(isFavorite(id)).toBe(!wasFav);
+ toggleFavorite(id);
+ expect(isFavorite(id)).toBe(wasFav);
+ });
+
+ it('isFavorite returns boolean', () => {
+ expect(typeof isFavorite(1)).toBe('boolean');
+ });
+
+ it('getFavorites returns array', () => {
+ expect(Array.isArray(getFavorites())).toBe(true);
+ });
+});
diff --git a/public/js/test/tests.js b/public/js/test/tests.js
index a0396f0..d0622ea 100644
--- a/public/js/test/tests.js
+++ b/public/js/test/tests.js
@@ -6,14 +6,18 @@ import './settings.test.js';
import './board.test.js';
import './toolbar.test.js';
import './buckets.test.js';
+import './bucket-context.test.js';
import './context-menu.test.js';
import './card-interactions.test.js';
import './filtering.test.js';
import './sorting.test.js';
+import './state-functions.test.js';
+import './favorites.test.js';
import './search.test.js';
import './search-logic.test.js';
import './calendar.test.js';
import './calendar-popup.test.js';
+import './rrule.test.js';
import './datepicker.test.js';
import './gallery.test.js';
import './help.test.js';
@@ -23,6 +27,7 @@ import './detail-interactions.test.js';
import './editor.test.js';
import './editor-features.test.js';
import './editor-save.test.js';
+import './editor-toolbar.test.js';
import './snippets.test.js';
import './image-fullscreen.test.js';
import './markdown.test.js';
@@ -33,4 +38,5 @@ import './api-edge.test.js';
import './export.test.js';
import './relay.test.js';
import './notifications.test.js';
+import './quick-capture.test.js';
import './theme.test.js';
diff --git a/public/js/test/tests.md b/public/js/test/tests.md
index 861595a..9346582 100644
--- a/public/js/test/tests.md
+++ b/public/js/test/tests.md
@@ -8,229 +8,253 @@ Self-contained in-app test suite. No external dependencies.
- **Keyboard**: `Ctrl+Shift+T`
- **Safety**: automatic backup before run, auto-restore after
- **Visibility**: panel hides during tests so you can watch interactions, shows results after
+- **Sync paused**: background sync and autoSync disabled during tests
-## Test Files
+## Test Files (39 files, ~280 tests)
-| File | Suites | Tests | Focus |
-|---|---|---|---|
-| `shell.test.js` | 1 | 7 | Header, footer, toolbar, file inputs |
-| `settings.test.js` | 6 | 11 | All 7 tabs, open/close, account, behavior, CalDAV, relay, notifications |
-| `board.test.js` | 3 | 11 | Card rendering, split preview, card lifecycle |
-| `toolbar.test.js` | 2 | 7 | Filter toggles (done/termine/tagged), superview, sort modes |
-| `buckets.test.js` | 2 | 8 | Bucket bar, filtering, create/delete appearance |
-| `context-menu.test.js` | 1 | 5 | Right-click, actions, submenus, close |
-| `card-interactions.test.js` | 4 | 10 | Tags, due dates, done badges, favorites, color, bucket, grouping |
-| `search.test.js` | 1 | 6 | Open (Ctrl+K, button), input focus, typing, ESC |
-| `calendar.test.js` | 1 | 7 | Open, month title, navigation, today, close |
-| `gallery.test.js` | 1 | 5 | Open, image count, select-none, close |
-| `help.test.js` | 1 | 4 | F1 toggle, button, content sections |
-| `multiselect.test.js` | 1 | 5 | Ctrl+click select/deselect, multiple, click-outside clear |
-| `detail.test.js` | 1 | 6 | Structure, controls, open, title, ESC, close |
-| `detail-interactions.test.js` | 4 | 4 | Done toggle, edit from detail, zen mode, backlinks |
-| `editor.test.js` | 3 | 8 | Structure, open/close, card create |
-| `editor-features.test.js` | 5 | 7 | Color picker, preview, line numbers, save flow, edit existing |
-| `api.test.js` | 3 | 10 | Cards CRUD, buckets CRUD, auth & misc endpoints |
-| `relay.test.js` | 2 | 3 | Push API, poll API (unconfigured state) |
-| `notifications.test.js` | 2 | 4 | Banner element, localStorage, settings selects |
-| `theme.test.js` | 2 | 6 | CSS vars, localStorage, applyTheme, themes object |
+| File | Focus |
+|---|---|
+| `shell.test.js` | Header, footer, toolbar, file inputs |
+| `settings.test.js` | All 7 tabs, open/close, account, behavior, CalDAV, relay, notifications |
+| `board.test.js` | Card rendering, split preview, card lifecycle |
+| `toolbar.test.js` | Filter toggles (done/termine/tagged), superview, sort modes |
+| `buckets.test.js` | Bucket bar rendering, filtering, create/delete |
+| `bucket-context.test.js` | Bucket right-click menu, rename, color, delete options, move-left disabled |
+| `context-menu.test.js` | Card right-click, actions, submenus, close |
+| `card-interactions.test.js` | Tags, due dates, done badges, overdue, favorites, color, bucket, grouping |
+| `filtering.test.js` | Tag AND logic, hierarchical match, unsorted bucket, hide filters |
+| `sorting.test.js` | All 5 sort modes, effectiveDate priority |
+| `state-functions.test.js` | parseTags, getChildren, getGroupCards, recent, favorites, toggleFavorite |
+| `favorites.test.js` | Favorites bucket filtering, todo progress bar, detail split pane |
+| `search.test.js` | Open (Ctrl+K, button), input focus, typing, ESC |
+| `search-logic.test.js` | Tag+text combo search, empty search recent, keyboard nav |
+| `calendar.test.js` | Open, month title, navigation, today, close |
+| `calendar-popup.test.js` | Day click, January/December year wrapping |
+| `rrule.test.js` | RRULE persistence (yearly, weekly+BYDAY, COUNT, UNTIL) |
+| `datepicker.test.js` | Rendering, 42-cell grid, today/selected highlight, Monday start, navigation, year wrap, click, double-click submit |
+| `gallery.test.js` | Open, image count, select-none, close |
+| `help.test.js` | F1 toggle, button, content sections |
+| `multiselect.test.js` | Ctrl+click select/deselect, multiple, click-outside clear |
+| `detail.test.js` | Structure, controls, open, title, ESC, close |
+| `detail-interactions.test.js` | Done toggle, edit from detail, zen mode, backlinks |
+| `editor.test.js` | Structure, open/close, card create |
+| `editor-features.test.js` | Color picker, preview, line numbers, save flow, edit existing |
+| `editor-save.test.js` | Empty title prevention, Ctrl+S, Tab title→body, update existing |
+| `editor-toolbar.test.js` | Bold, italic, todo, heading, hr buttons, auto-continue list on Enter |
+| `snippets.test.js` | All snippets (?dat, ?time, ?now, ?week, ?todo, ?hr, ?h1, ?table, ?code), inline calculator |
+| `image-fullscreen.test.js` | Overlay structure, bindDetailImageFullscreen export |
+| `markdown.test.js` | Basic rendering, wiki links (resolve, broken, case-insensitive), gallery block, todos, stripMd |
+| `markdown-edge.test.js` | Images/headings/lists/code/hr/todos/links after blank lines, multi-paragraph spacing |
+| `undo.test.js` | Color, title, body, due_date revert, delete+recreate, parent_id, bucket, multiple undos, empty stack |
+| `api.test.js` | Cards CRUD, buckets CRUD, auth & misc endpoints |
+| `api-edge.test.js` | XSS in title, empty body, null due_date, nonexistent card, empty bucket name, CSRF, positions |
+| `export.test.js` | JSON ZIP, vault ZIP, round-trip integrity (card count, caldav_uid, tags, due_dates) |
+| `relay.test.js` | Push/poll API (unconfigured state) |
+| `notifications.test.js` | Banner element, localStorage, settings selects |
+| `theme.test.js` | CSS vars, localStorage, applyTheme, themes object |
-**Total: 21 files, ~45 suites, ~150 tests**
+## Coverage Summary
-## Coverage by Area
+### Data Layer
+- [x] parseTags (comma-split, empty, hierarchical)
+- [x] getChildren / getGroupCards
+- [x] getRecent / markRecent (add, front, dedup, cap at 10)
+- [x] getFavorites / toggleFavorite / isFavorite
+- [x] sortedCards all 5 modes (manual, newest, oldest, alpha, tag)
+- [x] effectiveDate priority (due_date > title date > created_at)
-### App Shell
-- [x] Header with logo
-- [x] Board element (relative positioning)
-- [x] All footer buttons (logout, import, export, logseq, vault, settings)
-- [x] All header buttons (search, calendar, gallery, add)
-- [x] Toolbar controls (split, superview, hide-done, hide-termine, hide-tagged, sort)
-- [x] Footer card count
-- [x] Hidden file inputs
+### Board & Cards
+- [x] Card rendering (data-id, draggable, color bar)
+- [x] Split preview (open/close, empty state, card preview, active highlight)
+- [x] Card create appears, delete disappears, delete clears preview
+- [x] Todo progress bar segments
+- [x] Overdue danger border
+- [x] Done badge
+- [x] Due date pill
-### Toolbar Toggles
-- [x] hide-done toggles state and `.active` class
-- [x] hide-termine toggles state and `.active` class
-- [x] hide-tagged toggles state and `.active` class
-- [x] superview toggles state and `.active` class
-- [x] sort cycles through all modes (manual, newest, oldest, alpha, tag)
-- [x] superview renders list items
+### Toolbar
+- [x] All filter toggles with active class (done, termine, tagged)
+- [x] Superview toggle with active class
+- [x] Sort mode cycling
+- [x] Superview renders list items
-### Bucket Bar
-- [x] Renders bucket tabs
-- [x] "all" bucket exists
-- [x] Clicking bucket filters cards
-- [x] "current" bucket works
-- [x] "journal" bucket works
-- [x] "unsorted" bucket works
-- [x] New bucket appears in bar
-- [x] Deleted bucket disappears from bar
+### Buckets
+- [x] Bucket bar renders tabs, all special buckets (all, current, journal, favorites, unsorted)
+- [x] Bucket click filters cards
+- [x] New bucket appears, deleted disappears
+- [x] Bucket context menu (rename, color, move-left disabled on first)
+- [x] Empty bucket: single delete option
+- [x] Bucket with cards: both delete options
+- [x] Favorites bucket shows only favorited cards
-### Settings Panel
-- [x] Opens on button click
-- [x] All 7 tabs exist (branding, account, theme, caldav, relay, behavior, notifications)
-- [x] Tab switching shows correct panels
-- [x] Closes on X button
-- [x] Closes on overlay background click
-- [x] Account: password fields exist and masked
-- [x] Account: vault key field
-- [x] Behavior: all selects (current-days, card-height, card-width, body-lines, click-action)
-- [x] Behavior: all toggles (show-tags, show-thumbnails, show-done-badge, confirm-delete, auto-save)
-- [x] CalDAV: connection fields (url, user, pass, test, save, sync-now)
-- [x] Relay: fields (url, key, save, test)
-- [x] Notifications: lookahead, time controls
-
-### Board
-- [x] Cards render with data-id
-- [x] Cards are draggable
-- [x] Cards have color bar
-- [x] Valid data-id attributes
-- [x] Split preview opens/closes
-- [x] Split preview empty state
-- [x] Split preview shows card on click
-- [x] Active card highlighted in split mode
-- [x] New card appears on board after create
-- [x] Deleted card gone from board
-- [x] Deleted card clears split preview
-
-### Card Interactions
-- [x] Tag chips visible (if setting on)
-- [x] Tag click filters board
-- [x] Tag right-click removes tag
-- [x] Due date shows due pill
-- [x] Done tag shows done badge
-- [x] Overdue card has danger border
-- [x] Favorite toggles via context menu
-- [x] Color change via context menu
-- [x] Bucket assignment via context menu
-- [x] Cards can be grouped (parent_id)
-- [x] Child card hidden under parent on board
+### Filtering
+- [x] Single tag filter
+- [x] Multiple tags AND logic
+- [x] Hierarchical tag parent match
+- [x] Unsorted bucket excludes dated titles
+- [x] hideTermine excludes caldav cards
+- [x] hideTagged excludes tagged cards
+- [x] hideDone excludes #done cards
### Context Menu
-- [x] Opens on right-click
-- [x] All actions present (open, edit, duplicate, due-date, save-md, push-relay, favorite, color, bucket, tags, delete)
-- [x] Color submenu with presets
+- [x] Opens on right-click, closes on click
+- [x] All actions (open, edit, duplicate, due-date, save-md, push-relay, favorite, color, bucket, tags, delete)
+- [x] Color submenu presets
- [x] Tag submenu with input
-- [x] Closes on document click
+- [x] Favorite toggle
+- [x] Color change via menu
+- [x] Bucket assignment via menu
+- [x] Viewport-aware positioning (opens upward when near bottom)
### Search
-- [x] Opens with Ctrl+K
-- [x] Opens with button
+- [x] Opens with Ctrl+K and button
- [x] Input focused on open
-- [x] Input and results area exist
- [x] Typing triggers results
-- [x] Closes with ESC
+- [x] Tag-only filter (#tag)
+- [x] Tag + text combined
+- [x] Empty search shows recent
+- [x] Keyboard navigation
+- [x] ESC closes
### Calendar
-- [x] Button exists
-- [x] Opens on click
-- [x] Month title displayed
-- [x] Navigation buttons (prev, next, today)
-- [x] Month navigation works
-- [x] Today resets to current month
-- [x] Closes on close button
+- [x] Open/close
+- [x] Month title, navigation (prev, next, today)
+- [x] January→December year wrap
+- [x] December→January year wrap
+- [x] Day click
+
+### Datepicker
+- [x] Correct month for default value
+- [x] 42 day cells (6-week grid)
+- [x] Today highlight, selected highlight
+- [x] Monday start
+- [x] Month navigation, Dec→Jan year wrap
+- [x] Click day selects, double-click submits
+
+### RRULE
+- [x] FREQ=YEARLY persistence
+- [x] FREQ=WEEKLY;BYDAY=MO persistence
+- [x] COUNT persistence
+- [x] UNTIL persistence
### Gallery
-- [x] Button exists
-- [x] Opens on click
-- [x] Grid has user-select: none
-- [x] Image count shown
-- [x] Closes on close button
+- [x] Open/close, image count, select-none
### Help
-- [x] Opens with F1
-- [x] Toggles with F1
-- [x] Opens with button
-- [x] Has content sections
+- [x] F1 toggle, button open, content sections
### Multi-Select
-- [x] Select bar hidden by default
-- [x] All action buttons (group, bucket, unbucket, color, tag, export, delete, clear)
-- [x] Ctrl+click selects card
-- [x] Ctrl+click deselects card
-- [x] Multiple cards selectable with count
-- [x] Click outside clears selection
+- [x] Bar hidden by default, all action buttons
+- [x] Ctrl+click select/deselect
+- [x] Multiple cards, click-outside clear
### Detail View
-- [x] Structural elements (title, body, meta, tags)
-- [x] Control buttons (edit, delete, done, zen, close)
-- [x] Opens on card click (non-split mode)
-- [x] Shows card title
-- [x] Closes on ESC
-- [x] Closes on close button
-- [x] Done button toggles #done tag
-- [x] Edit button opens editor with card data
+- [x] Structure, controls, open via click, title, ESC, close
+- [x] Done toggle saves #done tag
+- [x] Edit opens editor with card data
- [x] Zen mode button exists
- [x] Backlinks section exists
+- [x] Split pane button exists
### Editor
-- [x] Structural elements (modal, title, body, preview, color presets)
-- [x] Save buttons (save, save-close, cancel)
-- [x] Due date and done toggle
-- [x] Opens on add button
+- [x] Structure, save buttons, due date, done toggle
+- [x] Opens (add button, board right-click), closes (cancel, ESC)
- [x] Title focused on open
-- [x] Closes on cancel
-- [x] Closes on ESC
-- [x] Opens on board right-click
-- [x] Color presets render
-- [x] Color preset changes card color
-- [x] Preview pane and toggle exist
-- [x] Line numbers gutter and toggle exist
-- [x] Full save flow (type title+body → save-close → card exists)
-- [x] Edit existing card loads data
+- [x] Color presets render and change color
+- [x] Preview pane and toggle
+- [x] Line numbers gutter and toggle
+- [x] Full save flow (title + body → save-close → card exists)
+- [x] Edit existing loads data, update persists
+- [x] Empty/whitespace title blocks save
+- [x] Ctrl+S saves card
+- [x] Tab moves title → body
+- [x] Toolbar: bold, italic, todo, heading, hr buttons
+- [x] Auto-continue bullet list on Enter
+- [x] Auto-continue todo list on Enter
+- [x] Empty bullet cleared on Enter
-### API Endpoints
-- [x] GET /api/cards — returns array
-- [x] GET /api/cards/meta — returns array
-- [x] POST /api/cards — creates card
-- [x] PATCH /api/cards/:id — updates (returns card object)
-- [x] DELETE /api/cards/:id — deletes (returns {ok})
-- [x] Tag extraction from body
-- [x] Due date persistence
-- [x] Bucket assignment
-- [x] GET /api/buckets — returns array
-- [x] POST /api/buckets — creates bucket
-- [x] PATCH /api/buckets/:id — renames (returns bucket object)
-- [x] DELETE /api/buckets/:id — deletes (returns {ok})
-- [x] GET /api/auth/status — logged in
-- [x] GET /api/relay/settings — reachable
-- [x] GET /api/caldav/settings — reachable
-- [x] Unknown endpoint returns 404
+### Snippets & Calculator
+- [x] All snippets: ?dat, ?time, ?now, ?week, ?todo, ?hr, ?h1, ?table, ?code
+- [x] Inline calculator: ?3+5→8, 34+56?→34+56=90, multiplication, division
-### Relay API
-- [x] Push without config returns error
-- [x] Push without card_id returns 400
-- [x] Poll without config returns 0 pulled
+### Markdown
+- [x] Paragraphs, bold, italic, headings, code blocks
+- [x] External links with target=_blank
+- [x] Wiki links: resolve, broken, case-insensitive
+- [x] Gallery fenced block, empty gallery
+- [x] formatTodoItems, checked items
+- [x] stripMd
+- [x] Images/headings/lists/code/hr/todos/wiki-links after blank lines
+- [x] Multiple blank lines spacing, images between blank lines
+
+### Undo
+- [x] Color change revert
+- [x] Title, body, due_date revert from editor
+- [x] Delete → recreate with body, color
+- [x] Delete preserves parent_id
+- [x] Bucket assignment revert
+- [x] Multiple sequential undos (stack order)
+- [x] Empty stack no-op
+
+### API
+- [x] Cards: GET, GET/meta, POST, PATCH (returns card), DELETE (returns {ok})
+- [x] Tag extraction, due_date persistence, bucket assignment
+- [x] Buckets: GET, POST, PATCH (returns bucket), DELETE
+- [x] Auth status, relay settings, caldav settings, 404
+- [x] XSS in title, empty body, null due_date, nonexistent card
+- [x] Empty bucket name error, CSRF required, GET no CSRF
+- [x] Position create and update
+
+### Export/Import
+- [x] JSON ZIP endpoint (status, content-type, ZIP magic)
+- [x] Vault export endpoint
+- [x] Round-trip: card count preserved
+- [x] Round-trip: caldav_uid preserved
+- [x] Round-trip: tags and due_dates preserved
+- [x] parent_id correctly remapped on import
+
+### Relay
+- [x] Push without config → error
+- [x] Push without card_id → 400
+- [x] Poll without config → 0 pulled
### Notifications
-- [x] Banner element exists
-- [x] Settings in localStorage
-- [x] Lookahead select has options
-- [x] Time select has options
+- [x] Banner element, settings in localStorage
+- [x] Lookahead and time selects
### Theme
-- [x] CSS custom properties set (bg, accent, text, surface, border, muted, dim, bright)
-- [x] Theme persisted in localStorage
-- [x] applyTheme function available
-- [x] Themes object with 5+ themes
+- [x] CSS custom properties (8 vars)
+- [x] localStorage persistence
+- [x] applyTheme function, themes object
- [x] Card layout CSS vars
## Not Yet Covered
-- Drag and drop (card reorder, bucket assignment via drag)
-- Box-select rubber band
+- Drag and drop (card reorder, bucket reorder, search-to-board)
+- Box-select rubber band visual
- CalDAV sync flow (requires server)
-- Relay push/poll with configured relay
+- Relay push/poll with configured relay (requires relay)
- File upload (paste, drag into editor)
-- Image fullscreen viewer (zoom, pan)
-- Zen mode resize handles
-- Notification banner dismiss
-- Undo (Ctrl+Z)
-- Logseq import flow (requires ZIP file)
-- Export download verification
-- Datepicker dialog interaction
-- Editor autocomplete (tags, links, snippets)
-- Calendar day popup and card creation
-- Search drag-and-drop to group
+- Image fullscreen zoom/pan interaction
+- Zen mode resize handles drag
+- Notification banner dismiss click
+- Calendar hover card popups
+- Calendar appointment/journal creation via double-click
+- Editor divider drag resize
+- Editor image resize handles
+- Superview hover popup system
+- Detail sidebar card navigation
+- Detail image gallery prev/next navigation
+
+## Bugs Found & Fixed by Tests
+
+1. **Import lost all CalDAV fields** — caldav_uid, tags, due_date, parent_id, relay_uid not preserved on import → CalDAV sync created duplicates
+2. **Import lost parent_id mapping** — grouped cards lost their group after export→import (IDs changed)
+3. **Undo delete lost parent_id** — undo after deleting a grouped card didn't restore group membership
+4. **Undo editor lost due_date** — undo after editing didn't restore previous due_date
+5. **_reloadAll didn't reload buckets** — new/deleted buckets not reflected without manual page reload
+6. **Blank lines broke images in markdown** — `
\n` before `![img]()` prevented rendering
+7. **Context menu clipped at viewport bottom** — fixed position calculation, now opens upward
## Adding Tests