Intercept touch checkbox changes before TipTap's focusing handler and
update the task-item transaction directly. This prevents the mobile
keyboard from starting to open while preserving checkbox state changes.
Strengthen the mobile regression test to assert that checking and
unchecking emit no editor focus events.
Co-authored-by: Codex <codex@openai.com>
Add interactive-widget=resizes-content to the viewport meta so the
Android keyboard shrinks the layout viewport through the native inset
path. Chromium's visualViewport.height can under-report the visible
height by the system nav-bar height, which left the editor toolbar
floating above the keyboard; the native resize path is accurate. iOS
ignores the attribute and keeps using the visualViewport tracking in
NoteEditor.
Position toolbar dropdown portals with window.innerHeight instead of
visualViewport.height: CSS bottom on position:fixed and
getBoundingClientRect() both use layout-viewport coordinates, so
mixing in the visual-viewport height pushed dropdowns behind the
keyboard while it was open.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restore portal dropdown test IDs so the formatting e2e suite can
find heading and link controls again.
Let the mobile formatting toolbar pan horizontally, keep dropdown
portals from drifting by closing them on scroll or resize, and use
visualViewport height for keyboard-aware positioning.
Remove the app-wide maximum-scale lock, keep editor focus targets at
16px on mobile to avoid input auto-zoom, and move toolbar scrollbar
styling to a top-level scoped style block.
Align the remove-link action with the destructive design token, use
the standard 150ms editor slide-up animation, and replace
save/restore body overflow locking with a resilient body class.
Make editor full-screen natively on mobile with back button. Add
visualViewport tracking to keep bottom toolbars above the software keyboard.
disable pull-to-refresh for native PWA feel
prevent auto-zoom on input focus in mobile Safari
prevent background scrolling when editor is open
prevent touch scroll on editor toolbars to stop background scrolling
hide visual gap at bottom of editor when iOS keyboard opens
move note actions to top-right menu on mobile devices
wrap background colors in mobile menu to reduce width
make formatting toolbar scrollable on mobile and extract dropdowns
remove empty else block causing svelte compilation error
add missing data-dropdown attributes to fix click interception in toolbar portals
keep mobile keyboard open when using toolbar menus and prevent right-edge overflow
Co-authored-by: Google Gemini <gemini@google.com>
deleteUser() ran each child-table cleanup as a separate statement, so a
foreign-key violation on any step (e.g. sync_log rows from other users
referencing the deleted user's notes) would abort mid-way and leave the
database in a partial state. The API swallowed the error and the admin
page silently ignored the non-ok response, so clicking Delete and
confirming appeared to do nothing.
- Wrap the full cascade in a single sqlite transaction so the deletion
is atomic.
- Also remove rows in sync_log, note_user_state, note_collaborators,
note_versions, and attachments that belong to other users but
reference the deleted user's notes, so the final DELETE FROM notes
cannot trip a foreign-key check.
- Surface delete failures in the users settings UI instead of silently
swallowing them.
- Add data-testids on the user row and delete button to support tests.
- Add e2e coverage for the admin user deletion flow (happy path,
cancelled confirmation, and a user with notes / tags / attachments /
preferences / api keys).
https://claude.ai/code/session_015T2HE6VzB64ZmSSyy1Wz4R
Use e.currentTarget instead of e.target so the reference is always the
contenteditable div, not a descendant <a> tag. Use el.contains() instead
of strict equality to avoid linkifying while focus is still inside the
editable element.
https://claude.ai/code/session_01VZceWPhHMHCA6SELBdCPfN
When a contenteditable loses focus, the onblur handler immediately modified
innerHTML to linkify URLs. On mobile, this DOM mutation causes a layout reflow
between touchstart and click, making the browser cancel taps on the adjacent
delete button. Deferring via requestAnimationFrame ensures the click event
fires before the DOM is modified.
https://claude.ai/code/session_01VZceWPhHMHCA6SELBdCPfN
Browsers inject in contenteditable elements, which was not being
decoded by unlinkifyHtml, causing double-escaped " " to appear in
checklist item text.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add noreferrer to rel attribute to prevent Referer header leaks
- Strip newlines from pasted text to prevent checklist corruption
- Add aria-placeholder for screen reader accessibility
- Dismiss LinkPopover on scroll/resize
- Stop keydown propagation on links in NoteCard preview
The reactive {#html} binding inside contenteditable replaced innerHTML on
every keystroke, destroying the cursor and causing text duplication.
Instead, use a use:editableContent action that sets innerHTML on mount
and only syncs when the element is NOT focused. Linkification happens
on blur, matching Google Keep's behavior. Update e2e tests to blur
before asserting link visibility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add entity decoding (& < > ") to unlinkifyHtml to
prevent double-encoding on contenteditable edit cycles
- Fix indentation of LinkPopover import and state declaration
- Move LinkPopover inside root div
- Add unit test for entity decoding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On installed PWAs, closing the editor by tapping the overlay would cause
the browser's delayed synthetic click to land on the NoteCard underneath,
immediately reopening a different note. Fixed by keeping a transparent
shield in the DOM for 400ms after close to absorb the ghost click.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two issues fixed:
1. Attachment operations (upload, feature, remove) only updated the local
editor state, not the notes store. Before auto-save, this was masked
because saveAndClose always called updateNote, whose PATCH response
refreshed the store. With auto-save skipping redundant saves, the store
was never updated with attachment changes. Now attachment handlers
update the store directly.
2. TiptapEditor fires onUpdate on init with round-tripped markdown that
can differ from the original. Added an autoSaveReady guard to absorb
this initial normalization as the baseline, preventing spurious saves
when reopening existing notes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Snapshot fields at start of performSave to avoid race with user edits
during async save (fields could change between await and lastSaved update)
- Only update lastSaved tracking on successful save, so failed saves
are retried on the next debounce cycle
- Stop restoring title after Untitled fallback in autoSave(), which was
re-triggering the auto-save effect
- Capture note count before opening editor in empty-note e2e test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Notes are now automatically saved 2 seconds after the user stops editing,
preventing data loss when the browser tab is closed without closing the
editor first. A beforeunload handler provides an additional safety net.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generate random passwords in global-setup and share via temp file so
all Playwright workers use the same credentials. Screenshot scripts
use module-level crypto.randomUUID() since they run single-process.
Unit test auth passwords also use randomUUID().
Eliminates security scanner alerts for passwords committed to git.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add missing nested task list rule for .prose context (matching TipTap's
existing rule) and adjust checkbox vertical alignment with margin-top.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a reusable `use:tooltip` Svelte action as a drop-in replacement for
native title attributes. Styled to match the retro parchment theme with
JetBrains Mono font, dark background, and 400ms hover delay.
Replace all title attributes in NoteEditor, NoteCard, and NotesView
with the custom tooltip. The action supports top/bottom positioning
and auto-clamps to viewport edges.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a checkbox icon next to the "Add a crumb..." button that opens a new
note directly in checklist mode, similar to Google Keep's quick-create
bar. The NoteEditor accepts a new initialChecklistMode prop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The popover was opening upward (bottom-full) which got clipped by the
NoteCard's overflow-hidden. Changed to open downward (top-full) so it
stays within the card's visual area.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Nested TipTap task lists (indented children) broke the flex layout
because the nested ul was treated as an inline flex child. Now the
li uses flex-wrap with align-items: flex-start, and nested task lists
take full width with left padding for indentation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add vertical dots (⋮) overflow menu to NoteEditor bottom action bar
- New checklist actions: "Delete checked" and "Uncheck all"
- Move secondary actions to menu: checklist toggle, markdown toggle,
version history, move to trash
- Promote archive to the action bar for quick access
- Keep in bar: color picker, image upload, share, archive, ⋮, close
- Menu renders as fixed-position portal to escape card overflow-hidden
- Click-outside handler closes the menu
- Update e2e tests to use overflow menu for checklist/markdown toggles
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The server's 3-way content merge was incorrectly triggering on sequential
edits by the same user. When a user checked then unchecked a checklist
item, the merge saw local === base (both unchecked) and kept the server's
checked state, silently discarding the uncheck.
Now the merge only runs when there's an actual concurrent edit
(baseVersion < existing.version), meaning another user saved between the
client's load and this save. For single-user sequential edits, the
client's content is used directly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused flushSync import
- Fix removeItem single-item branch: emit change and trigger reactivity
- Remove misleading role="button" + tabindex from drag handle
- Use anchored regex for checked detection instead of line.includes('[x]')
in NoteCard and public share page (prevents false positive on item text)
- Fix NoteCard preview E2E test: scope assertion to specific card, use
unique title to avoid collision
- Restore arrow key navigation E2E test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When unchecking a child from the done section, it stayed at the end of
the items array (where done items live), appearing as the last active
item instead of under its parent. Now repositions after the last sibling
or parent in the array.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update checklist preview parsers to detect 2-space child prefix and
render indented items with extra left padding.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Checklist component changes:
- Import logic from checklist utility module
- Tab/Shift+Tab indent/outdent with sibling adoption
- Indented rendering (pl-8) for child items in active section
- Done section groups checked children under read-only parent labels
- Enter on child creates sibling at same indent level
- Backspace on parent orphans children to top-level
- Directional lock drag: horizontal = indent/outdent, vertical = reorder
(uses window capture-phase listeners + pending indent applied after
DnD finalize to avoid corrupting svelte-dnd-action internal state)
- flushSync for state updates from window event listeners
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract checklist logic into testable utility module:
- ChecklistItem type with parentId field for single-level nesting
- parseChecklist/serializeChecklist with 2-space prefix for children
- toggleItemWithCascade: parent check/uncheck cascades to children
- indentItem/outdentItem with sibling adoption rules
- 21 unit tests covering parse, serialize, toggle, indent, outdent
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mergeNotesByVersion only iterated incoming notes, dropping any
local-only notes (e.g. just created but not yet in the server/IDB
response). This caused note cards to disappear briefly after creation
when loadNotes raced with createNote.
Also rename E2E test note title to avoid name collision with existing
"Groceries" test.
https://claude.ai/code/session_01TdPpATUkmdY7DhBkmqrvWs
Two issues caused checked items to revert after closing:
1. loadNotes() blindly overwrote the store with notes.set(), so a stale
response from the periodic sync (every 30s) could replace a just-saved
note with older server data. Now merges by version number, keeping the
higher-versioned note.
2. Checklist component ignored external content prop changes (from sync or
history restore). Added $effect that re-parses items when the content
prop changes from an external source, using lastEmitted tracking to
distinguish self-originated vs external updates.
https://claude.ai/code/session_01TdPpATUkmdY7DhBkmqrvWs
ArrowUp/ArrowDown move focus between active checklist inputs.
At list boundaries, focus stays on the current item (no wrap).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The addItem() focus logic used positional indexing (inputs[afterIndex+1])
but afterIndex came from the full items[] array while DOM inputs only
render active (unchecked) items. On reopened notes, checked items are
interleaved in the parsed content, causing the index offset to be wrong.
Fix: use data-item-id attribute for targeted querySelector lookup.
Test: reopened note with interleaved checked/unchecked items.
Introduced in e5688c9 (active/done split).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wrap readFile in try/catch, return 404 for missing attachment files
- Remove unused CollaboratorPopover import from NoteCard
- Make darkMode a proper $state rune for reactivity (add svelte-runes
polyfill for Vitest so $state works in unit tests)
- Add error toasts for failed share link toggle (non-2xx responses)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch NoteCard, NoteEditor, and ColorPicker from static NOTE_COLORS
lookups to getNoteColor(color, getIsDarkMode()), so inline background
styles react to the active theme. Also fix the theme preference button
type cast in settings/preferences to resolve the svelte-check error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds src/lib/utils/theme.svelte.ts with applyTheme() (sets data-theme
attribute and meta theme-color) and getIsDarkMode(). Uses a plain
variable instead of $state since Vitest's node environment does not
compile Svelte 5 runes in .svelte.ts files. Adds jsdom dev dependency
and $app/environment alias in vitest config to support DOM-level unit
tests; fixes preferences.test.ts to include the theme default added
in Task 2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>