Commit Graph
159 Commits
Author SHA1 Message Date
Frederic CabassutandClaude Opus 4.6 0124c5e7f6 fix: unchecked child repositions after its parent and siblings
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>
2026-03-21 23:51:46 +01:00
Frederic CabassutandClaude Opus 4.6 087fe8c713 feat: NoteCard and public share page support nested checklist items
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>
2026-03-21 23:51:46 +01:00
Frederic CabassutandClaude Opus 4.6 2306883503 feat: nested checklist UI — indentation, parent labels, drag-to-indent
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>
2026-03-21 23:51:46 +01:00
Frederic CabassutandClaude Opus 4.6 9fb8acc719 feat: checklist utility module with parentId and nesting logic
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>
2026-03-21 23:51:46 +01:00
Claude 6278f11184 fix: preserve local-only notes during loadNotes merge
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
2026-03-21 20:27:38 +01:00
Claude ee017ab550 fix: prevent checklist state loss from sync race condition
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
2026-03-21 20:27:38 +01:00
Frederic CabassutandClaude Opus 4.6 f9d29a192a feat: arrow key navigation between checklist items
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>
2026-03-21 10:17:58 +01:00
Frederic CabassutandClaude Opus 4.6 e0eca4228b fix: checklist Enter key focuses new item on reopened notes
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>
2026-03-21 10:11:00 +01:00
Frederic CabassutandClaude Opus 4.6 4eb97b86f9 fix: address PR review comments
- 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>
2026-03-21 08:47:20 +01:00
Frederic CabassutandClaude Sonnet 4.6 5dfde02749 feat: use dark note card colors in all components
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>
2026-03-21 08:47:20 +01:00
Frederic CabassutandClaude Sonnet 4.6 b820ee3368 feat: add dark note card colors and getNoteColor helper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 08:47:20 +01:00
Frederic CabassutandClaude Sonnet 4.6 80d18478da feat: add theme utility with applyTheme and isDarkMode
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>
2026-03-21 08:47:20 +01:00
Frederic Cabassut 35cd888547 feat: add theme preference type (system/light/dark) 2026-03-21 08:47:20 +01:00
Frederic CabassutandClaude Opus 4.6 92d0581186 fix: address PR review — sanitize attachments, SSR safety, a11y
- Strip server paths from public attachment responses (new PublicAttachment type)
- Use $page.url.origin instead of window.location.origin (SSR-safe)
- Fix toggle switch left-4.5 → left-[1.125rem] (valid Tailwind)
- Add aria-label to SharingIndicator button for screen readers
- Add opportunistic pruning to IP rate limiter to prevent unbounded growth
- Reduce shared attachment cache to 1 hour (respect share revocation)
- Update OpenAPI schema with PublicAttachment type
- Add unit test asserting server paths are not leaked

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:06:41 +01:00
Frederic CabassutandClaude Opus 4.6 971963e0cc test: add unit and e2e tests for public note sharing
Unit tests:
- shares-service: create, idempotent create, revoke, get, batch fetch
- ip-rate-limit: under/over limit, IP independence, window reset

E2E tests:
- Create public share link via UI
- Public page renders without auth
- Revoke share returns 404
- Trashed note share returns 404
- Globe icon on shared card
- Copy link to clipboard

Also fix sharing-indicator test-id in existing sharing.spec.ts
and add shared_notes table to test-helpers and db/index.ts init.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:06:41 +01:00
Frederic CabassutandClaude Opus 4.6 8a453e3b98 feat: add Globe icon for public shares, update NoteCard and NoteEditor
- Create SharingIndicator component with Globe/Users icon priority
- NoteCard shows Globe icon (gold) for public shares, Users for collabs
- NoteEditor share button reflects public link state
- Share token propagated through store on create/revoke

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:06:41 +01:00
Frederic CabassutandClaude Opus 4.6 8be07091bd feat: add public link sharing section to ShareDialog
Add toggle switch, URL display, and copy button for public share
links above the existing collaborators section. Gold border when
active, retro-styled toggle with hard shadows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:06:41 +01:00
Frederic CabassutandClaude Opus 4.6 e18af0cc43 feat: add share API endpoints and in-memory rate limiting
- POST/DELETE /api/notes/[id]/share for creating/revoking public links
- GET /api/shared/[token] for fetching shared note data (no auth)
- GET /api/shared/[token]/attachment/[attachmentId] for public images
- Add /s/ and /api/shared to PUBLIC_PATHS in auth middleware
- Add IP-based rate limiter (60 req/min) for public endpoints

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:06:41 +01:00
Frederic CabassutandClaude Opus 4.6 2798307ed1 feat: add shares service layer with batch fetch and hydration
Create shares-service.ts with fetchSharesForNotes, createShare,
revokeShare, getShareByNoteId, and getSharedNote. Integrate share
tokens into hydrateNotes pipeline alongside tags, attachments,
and collaborators.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:06:41 +01:00
Frederic CabassutandClaude Opus 4.6 8da2de7703 feat: add shared_notes schema and NoteShare types
Add sharedNotes table with noteId primary key, unique token, and
cascade delete. Add NoteShare and SharedNoteData interfaces.
Extend Note type with optional shareToken field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:06:41 +01:00
Frederic CabassutandClaude Opus 4.6 f6dee3e2a9 fix: cap note card height to prevent tall checklist cards stretching rows
Add max-h-[17rem] overflow-hidden on note cards so checklist notes
don't force text notes in the same grid row to stretch with
truncated content.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 21:56:48 +01:00
copilot-swe-agent[bot]andfredicious d02af48b50 fix: snapshot pre-update state to enable 3-way merge on concurrent PATCH
Co-authored-by: fredicious <1580688+fredicious@users.noreply.github.com>
2026-03-12 20:19:10 +01:00
Claude 57a05bbee0 fix: resolve lost updates in multi-user collaboration sync
Concurrent edits to shared notes (e.g., one user checks a shopping list
item while another adds an item) were lost because the sync system used
whole-document Last-Write-Wins. This replaces it with a 3-way line-level
content merge using node-diff3, so changes to different lines are
preserved from both users.

Key changes:
- Add node-diff3 for 3-way line-level content merging
- Remove the timestamp gate in processSyncPush that rejected entire
  offline sync pushes when another user had updated the note
- Add content merge to both the sync push path and the PATCH endpoint,
  using version snapshots as the merge base
- Send baseVersion from the client so the server can find the correct
  common ancestor for 3-way merge
- Protect pending local fields during client pull merge by reading
  the sync queue and preserving queued-but-unpushed changes
- Add comprehensive tests for content merge, server sync, and CRDT merge

https://claude.ai/code/session_01FNpVBBjM6vKzfR8y9NM9Ci
2026-03-12 20:19:10 +01:00
Claude 8475ec807e fix: make checklist checkboxes read-only in note card list view
Checking items directly from the card view caused glitches. Checkboxes
in the list preview are now disabled — users open the note to interact.

https://claude.ai/code/session_01HoLKBDNrHWHUVgTUbhSLE4
2026-03-12 18:56:16 +01:00
Frederic CabassutandClaude Opus 4.6 2c16ed5870 fix: delete all user-related records before removing user to prevent FK constraint error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 11:15:47 +01:00
Frederic CabassutandClaude Opus 4.6 c24b0f7d86 fix: prevent ghost click tap-through on mobile editor toolbar
On mobile, tapping a note card could trigger action buttons in the
editor that rendered at the same coordinates (ghost click). Suppress
pointer events on both toolbars for 150ms after mount — matching the
pop-in animation — so the synthetic click passes harmlessly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 10:10:47 +01:00
Claude 8108e62cf1 fix: remove stopPropagation on editor card that blocked toolbar dropdown close
The editor card's onpointerdown stopPropagation prevented the
FormattingToolbar's document-level pointerdown listener from
detecting clicks outside the dropdown. Since both overlay handlers
already use e.target === e.currentTarget to ignore bubbled events,
no stopPropagation is needed on the card.

https://claude.ai/code/session_018DYwSysqysMmaRi5XkqTpo
2026-03-12 09:30:47 +01:00
Claude b0a3a1fcb8 fix: use pointer events for backdrop close instead of timeout guard
Pointer events are never synthetically re-dispatched from touch
(unlike mouse events), so the tap that opened the editor cannot
ghost-click the overlay. This eliminates the mobile first-load bug
without relying on an arbitrary delay.

Both pointerdown and pointerup must target the overlay itself to
trigger close, which also handles the text-selection-outside-editor
edge case.

https://claude.ai/code/session_018DYwSysqysMmaRi5XkqTpo
2026-03-12 09:30:47 +01:00
Claude c411975bb9 fix: use timestamp guard instead of rAF for mobile backdrop click
On first page load on mobile, the synthetic mousedown from a tap can
arrive ~300ms after the touch event — well after a single
requestAnimationFrame (~16ms). Replace the rAF-based overlayReady
guard with a 400ms timestamp check from mount time to reliably
ignore stale synthetic events on slow first loads.

https://claude.ai/code/session_018DYwSysqysMmaRi5XkqTpo
2026-03-12 09:30:47 +01:00
copilot-swe-agent[bot]andfredicious 5f6149e282 feat: add email notifications for user, account, and collaboration events
Co-authored-by: fredicious <1580688+fredicious@users.noreply.github.com>
2026-03-11 21:17:28 +01:00
copilot-swe-agent[bot]andfredicious 633727b58e fix: only create version snapshot when content actually changes
Co-authored-by: fredicious <1580688+fredicious@users.noreply.github.com>
2026-03-11 21:02:00 +01:00
Claude 0b1416908e feat: add email notifications when a note is shared
Send an email to collaborators when they are added to a shared note.
Uses nodemailer with optional SMTP configuration (auto-enabled when
SMTP_HOST is set). Users can opt out via the notifyOnShare preference.

https://claude.ai/code/session_01P4HSMr7QvKw9TMNikUJD9e
2026-03-11 20:33:48 +01:00
copilot-swe-agent[bot]andfredicious 07ea482bad fix: address code review feedback on NoteHistory component
Co-authored-by: fredicious <1580688+fredicious@users.noreply.github.com>
2026-03-11 20:28:36 +01:00
copilot-swe-agent[bot]andfredicious 0b34654736 feat: add note version history and restore (#note-history)
Co-authored-by: fredicious <1580688+fredicious@users.noreply.github.com>
2026-03-11 20:28:36 +01:00
Claude 8ed915bc69 fix: add vertical padding to checklist add-item button
https://claude.ai/code/session_0163L1u9MfL1C6Rv3yAw8RKs
2026-03-11 20:20:23 +01:00
Claude 917aef1865 fix: increase checklist item padding and fix text overflow in editor
Add more vertical padding (py-1 → py-1.5) to checklist items for better
readability. Add min-w-0 to the text input so long text is constrained
within the flex container instead of overflowing out of the note.

https://claude.ai/code/session_0163L1u9MfL1C6Rv3yAw8RKs
2026-03-11 20:20:23 +01:00
Claude 53aa32e07f fix: improve checklist note card text wrapping, spacing, and padding
- Replace `truncate` with `break-words min-w-0` so long checklist text
  wraps visibly instead of being clipped
- Increase vertical spacing between items (space-y-1 → space-y-2)
- Add bottom margin (mb-6) to prevent action buttons overlapping content
- Align checkboxes to top of multi-line items (items-start + mt-0.5)

https://claude.ai/code/session_01UVNA5wucVEjRQzwRxMhTZ9
2026-03-11 19:43:13 +01:00
Claude 51bfda3aec fix: prevent modal backdrop from catching the originating tap on mobile
On mobile, tapping a NoteCard to open the editor produces synthetic mouse
events (mousedown → mouseup → click) at the original touch coordinates.
If those coordinates land on the overlay backdrop rather than the editor
card, the mousedown sets mousedownOnOverlay = true and the click
immediately closes the editor — causing a brief flicker.

Guard against this by deferring overlay readiness to the next animation
frame after mount, so any synthetic events from the originating tap are
ignored.

https://claude.ai/code/session_011C8bfgShjDSmDr3rUCb2gw
2026-03-11 19:24:06 +01:00
Frederic CabassutandClaude Opus 4.6 871d17e55f feat: add About settings page with version and project info
Add /settings/about page showing version, project description,
Bretzel ecosystem info, open source details, and tech stack.
Move version display from sidebar to About page and align
Settings nav item styling with other sidebar items.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:54:40 +01:00
Frederic CabassutandClaude Opus 4.6 93a632e78a feat: add user preferences system
Implement 4 user preferences (default note mode, default note color,
hide footer, sidebar default state) with offline-first localStorage
backed by server-side key-value storage.

- Add user_preferences table (EAV pattern with unique user_id+key index)
- Create server service with get/upsert operations
- Add GET/PUT /api/preferences endpoints
- Build Svelte 5 runes-based client store (.svelte.ts)
- Wire preferences into app layout (footer toggle, sidebar state)
- Wire default note mode/color into NoteEditor for new notes
- Add Preferences tab as first item in Settings navigation
- Add 7 unit tests and 4 E2E tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:07:19 +01:00
Frederic CabassutandClaude Opus 4.6 8d5f27cd12 fix: prevent note editor from closing when text selection ends outside overlay
Track mousedown origin so click-outside-to-close only fires when both
mousedown and mouseup occur on the overlay backdrop, not when dragging a
text selection from inside the editor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:16:51 +01:00
Frederic CabassutandClaude Opus 4.6 e1484b0af9 test: add 52 unit tests for notes-service, api-utils, rate-limit, tags, collaborators
- notes-service.test.ts (24 tests): listNotes, getNote, createNote, updateNote,
  deleteNote, searchNotes, reorderNotes with owner/collaborator patterns
- api-utils.test.ts (10 tests): canAccessNote, requireNoteAccess, requireNoteOwnership, getUserId
- rate-limit.test.ts (8 tests): checkRateLimit, recordLoginAttempt, clearOldAttempts
- tags.test.ts (6 tests): syncNoteTags, fetchTagsForNotes
- collaborators.test.ts (4 tests): fetchCollaboratorsForNotes, addCollaborator, removeCollaborator

Also fixes test-helpers.ts missing thumbnail_path/featured columns
and rate-limit.ts raw SQL Date binding issue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:14:09 +01:00
Frederic CabassutandClaude Opus 4.6 8795a69b48 refactor: add mandatory db parameter to all service functions for testability
Make db a required first parameter on all service functions (notes-service,
api-utils, tags, collaborators, attachments, rate-limit, sync/server).
This enables unit testing with in-memory databases via createTestDb().

Also removes the NODE_ENV=test bypass from rate-limit and adds optional
now parameter for time control in tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:14:09 +01:00
Frederic CabassutandClaude Opus 4.6 b42dedfd53 feat: add task list button to rich text editor toolbar
Register TaskList and TaskItem TipTap extensions, add "Task list" option
to the List dropdown in the formatting toolbar, and consolidate task list
CSS between the editor and markdown-it preview into shared styles in
app.css.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 23:57:29 +01:00
Frederic CabassutandClaude Opus 4.6 2c9ba1f03d fix: toolbar buttons now reactively show active formatting state
All isActive() and getAttributes() calls in the template were not
reading the tick prop, so Svelte 5 never re-evaluated them when the
editor state changed. Extracted isActive() and getAttrs() helpers
that consume tick to establish the reactive dependency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 23:37:38 +01:00
Frederic CabassutandClaude Opus 4.6 a069a2df2b refactor: make toolbar wrap instead of scroll, fix overflow
Use flex-wrap instead of overflow-x-auto for the formatting toolbar —
scrolling clips absolutely-positioned dropdown popovers (CSS forces
both axes to auto). All buttons are now always visible, wrapping to a
second line on narrow screens. Remove the More dropdown entirely since
all items are top-level. Add overflow-hidden to the editor card to
prevent horizontal overflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 23:30:21 +01:00
Frederic CabassutandClaude Opus 4.6 2e302463fb refactor: promote all formatting buttons to top-level on desktop
Move undo/redo to the start of the toolbar, promote inline code,
blockquote, code block, and divider to top-level desktop buttons.
The More dropdown is now mobile-only since all items are directly
accessible on desktop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 23:24:08 +01:00
Frederic CabassutandClaude Opus 4.6 00b2d54eac refactor: expand toolbar on desktop with alignment dropdown
- Widen editor modal on desktop (max-w-xl → md:max-w-2xl)
- Extract text alignment from More into its own dropdown with contextual
  icon reflecting the active alignment
- Promote undo/redo to top-level buttons on desktop (hidden on mobile,
  still available in More dropdown)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 23:22:01 +01:00
Frederic CabassutandClaude Opus 4.6 dbe040322c feat: add row/column management to table toolbar
Replace single "Insert table" button with a Table dropdown that shows
contextual row/column operations when the cursor is inside a table:
add row, add column, delete row, delete column, delete table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 23:19:14 +01:00
Frederic CabassutandClaude Opus 4.6 c1e2cf3c8b feat: add table support to TipTap editor
Install @tiptap/extension-table packages, register Table/TableRow/
TableHeader/TableCell extensions, add Table button to the More dropdown
(inserts a 3×3 grid), and style tables to match the parchment theme.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 23:15:17 +01:00