Compare commits

..
Author SHA1 Message Date
Frederic CabassutandClaude Opus 4.6 22b97469e1 chore: add pixelarticons dependencies and design docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:13:36 +01:00
Frederic CabassutandClaude Opus 4.6 bae7934ceb docs: update CLAUDE.md and FEATURES.md for pixelarticons + favorites rename
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:05:48 +01:00
Frederic CabassutandClaude Opus 4.6 214fdec341 refactor: migrate icons to pixelarticons for SyncIndicator, small components, and settings
Replace lucide-svelte imports with @iconify/svelte pixelarticons in
SyncIndicator, NoteHistory, ShareDialog, Checklist, CollaboratorPopover,
ImageLightbox, ImageUpload, NotesView, settings layout, and MCP settings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:04:44 +01:00
Frederic CabassutandClaude Opus 4.6 03481fea8a feat: migrate FormattingToolbar to pixelarticons (keep B/I/S/U as Lucide)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:02:01 +01:00
Frederic CabassutandClaude Opus 4.6 92743cda5f refactor: migrate Header, SearchBar, Sidebar, NoteCard, NoteEditor icons to Iconify pixelarticons
Replace lucide-svelte imports with @iconify/svelte using pixelarticons
icon set across the five main UI components.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:59:40 +01:00
Frederic CabassutandClaude Opus 4.6 e8c3aca5ec refactor: rename pinned to favorites in UI-facing code
Rename user-facing "pinned" terminology to "favorites" across stores,
components, MCP server, and e2e tests while keeping the `pinned` DB
column unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:57:40 +01:00
github-actions[bot] d748abb8c2 chore: pin docker-compose to v0.22.3 [skip ci] 2026-03-18 09:48:09 +00:00
github-actions[bot] 31682ccf37 chore: bump version to 0.22.3 2026-03-18 09:37:31 +00:00
Frederic CabassutandClaude Opus 4.6 4e9990518e fix: add linux/arm64 support to Docker image builds
Add QEMU emulation and multi-platform build config so ARM64 users
can pull the image without "no matching manifest" errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 09:29:57 +01:00
github-actions[bot] 197930a24e chore: pin docker-compose to v0.22.2 [skip ci] 2026-03-17 22:44:48 +00:00
github-actions[bot] 54d54bf56e chore: bump version to 0.22.2 2026-03-17 22:40:08 +00:00
Fred Cabassut 664aa1f617 Revert "fix: set ghcr.io package visibility to public after Docker push"
This reverts commit 49cc7e0317.
2026-03-17 23:37:16 +01:00
github-actions[bot] 661b898e24 chore: bump version to 0.22.1 2026-03-17 22:13:20 +00:00
Claude 49cc7e0317 fix: set ghcr.io package visibility to public after Docker push
Users pulling the image without authentication get "unauthorized" because
ghcr.io packages default to private under an organization. Add a step in
both release workflows to explicitly set the container package to public.

https://claude.ai/code/session_01QovQeW26KfEEmU1KdFfCPU
2026-03-17 23:12:59 +01:00
Frederic CabassutandClaude Opus 4.6 9fe2705584 fix: use npx tsx for openapi generation in build script
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 06:58:42 +01:00
github-actions[bot] d2bf683744 chore: pin docker-compose to v0.22.0 [skip ci] 2026-03-12 23:05:13 +00:00
github-actions[bot] 78194ccec0 chore: bump version to 0.22.0 2026-03-12 23:00:57 +00:00
Frederic CabassutandClaude Opus 4.6 b4d1f94c6f feat: auto-generate discoverable OpenAPI spec from route tree
Adds a script that scans src/routes/api/ for exported HTTP methods and
generates an OpenAPI 3.1 spec at /api/openapi.json. Runs automatically
before each build.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:59:52 +01:00
Frederic CabassutandClaude Opus 4.6 d23f0c2f35 chore: update about page wording to "bretzel.app universe"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:53:40 +01:00
github-actions[bot] e1f38b4df3 chore: pin docker-compose to v0.21.9 [skip ci] 2026-03-12 22:31:07 +00:00
github-actions[bot] d722a842c9 chore: bump version to 0.21.9 2026-03-12 22:26:57 +00:00
Frederic CabassutandClaude Opus 4.6 870fdb8205 fix: replace emoji PWA icons with pixel-art pretzel from favicon.svg
The PWA manifest icons were a generic pretzel emoji on a dark background,
not matching the app's pixel-art pretzel favicon. Regenerated from the
favicon.svg source.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:26:43 +01:00
github-actions[bot] 7317b13f62 chore: pin docker-compose to v0.21.8 [skip ci] 2026-03-12 22:16:28 +00:00
github-actions[bot] a968c39b6a chore: bump version to 0.21.8 2026-03-12 22:12:11 +00:00
Frederic CabassutandClaude Opus 4.6 f8937d287d fix: use absolute paths for service worker registration
SvelteKit sets Vite's base to './' for client builds, causing the PWA
plugin to generate relative SW paths (./sw.js). On sub-routes like
/settings or /tag/foo, this resolves to /settings/sw.js which returns
HTML, breaking SW registration. Force scope and buildBase to '/'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:11:56 +01:00
github-actions[bot] fe034f4f98 chore: pin docker-compose to v0.21.7 [skip ci] 2026-03-12 21:16:06 +00:00
github-actions[bot] 65d72b9662 chore: bump version to 0.21.7 2026-03-12 21:11:35 +00:00
Frederic CabassutandClaude Opus 4.6 b7fc8a5ca4 fix: remove maskable icon purpose to prevent double-cropping
The maskable purpose caused Android to crop the already-round icon
into its adaptive shape, resulting in a square-inside-circle appearance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 22:11:08 +01:00
github-actions[bot] 794bd3a0c7 chore: pin docker-compose to v0.21.6 [skip ci] 2026-03-12 20:13:13 +00:00
github-actions[bot] b63d4b26fc chore: bump version to 0.21.6 2026-03-12 20:08:34 +00:00
Frederic CabassutandClaude Opus 4.6 17ccba4757 fix: block service worker in e2e tests to prevent response interception
Active service workers change the fetch lifecycle enough that Playwright's
waitForResponse() misses SW-mediated responses, causing preferences tests
to timeout. Block SW registration during e2e tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 21:08:20 +01:00
github-actions[bot] b41a54677b chore: bump version to 0.21.5 2026-03-12 20:01:20 +00:00
Frederic CabassutandClaude Opus 4.6 4b233ba360 fix: restrict SW API caching to GET requests to avoid intercepting mutations
Workbox route matching intercepts all HTTP methods, which caused PUT/POST
requests (e.g. preferences updates) to be handled by the SW strategy
handler instead of passing through directly. Restrict API and attachment
cache rules to GET-only using function-based urlPattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 21:00:56 +01:00
github-actions[bot] 202065380a chore: bump version to 0.21.4 2026-03-12 19:56:04 +00:00
Frederic CabassutandClaude Opus 4.6 962eee3637 fix: register PWA service worker via virtual:pwa-register for offline support
@vite-pwa/sveltekit does not auto-inject SW registration into SSR HTML.
Add explicit registration via virtual:pwa-register in root layout onMount,
inject webmanifest link via virtual:pwa-info, disable SvelteKit's built-in
SW registration to avoid conflicts, and add required dependencies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:53:38 +01:00
github-actions[bot] 9253fae548 chore: pin docker-compose to v0.21.3 [skip ci] 2026-03-12 19:24:11 +00:00
github-actions[bot] a75e3e6ef3 chore: bump version to 0.21.3 2026-03-12 19:19:56 +00:00
copilot-swe-agent[bot]andfredicious ff3570f5ad 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 9ed5c52fb6 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
Frederic CabassutandClaude Opus 4.6 3e7f5188d1 fix: enable offline page caching in service worker for PWA refresh support
Navigation requests had no caching rule, so refreshing offline showed the
browser's default "you are offline" page. Add NetworkFirst runtime caching
for navigations (3s timeout), cache Google Fonts for offline use, remove
conflicting static webmanifest in favor of the plugin-generated one.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:16:23 +01:00
Claude f49f6f74f3 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 b781668475 fix: resolve dependabot vulnerabilities — override hono, remove stale package-lock.json
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 11:49:57 +01:00
Frederic CabassutandClaude Opus 4.6 ef01d03bb2 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 7b4337ee14 docs: add SMTP config to README, document recent features, expose all env vars in docker-compose
- Add SMTP configuration example to README install section
- Add note version history, sharing, and email notifications to README feature list
- Expand FEATURES.md with all 9 email notification types and version history section
- Add note sharing, version history, and email alerts feature cards to landing page
- Add all OAuth/SSO env vars to docker-compose.yml with empty defaults

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 10:49:55 +01:00
github-actions[bot] 85d9b6c187 chore: pin docker-compose to v0.21.2 [skip ci] 2026-03-12 09:32:50 +00:00
github-actions[bot] 6d4b265396 chore: bump version to 0.21.2 2026-03-12 09:28:39 +00:00
Frederic CabassutandClaude Opus 4.6 0e74409348 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
github-actions[bot] 9316428262 chore: pin docker-compose to v0.21.1 [skip ci] 2026-03-12 08:49:35 +00:00
github-actions[bot] bd6bab4093 chore: bump version to 0.21.1 2026-03-12 08:45:06 +00:00
Frederic CabassutandClaude Opus 4.6 a2d6ecdccf fix: resolve flaky offline sync e2e test in CI
Playwright's setOffline(false) doesn't reliably emit the browser's
'online' DOM event in headless/CI environments. Manually dispatch it
as a safety net so the sync client always triggers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 09:31:11 +01:00
Claude 98db72f5af 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 33236ac531 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 fe2f534720 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
github-actions[bot] cf6867dc64 chore: pin docker-compose to v0.21.0 [skip ci] 2026-03-11 20:22:43 +00:00
github-actions[bot] 722989a6c7 chore: bump version to 0.21.0 2026-03-11 20:18:05 +00:00
copilot-swe-agent[bot]andfredicious a5a454ef12 fix: only create version snapshot when content actually changes
Co-authored-by: fredicious <1580688+fredicious@users.noreply.github.com>
2026-03-11 21:17:28 +01:00
copilot-swe-agent[bot]andfredicious 735e1c06d7 chore: exclude package-lock.json from git (project uses pnpm)
Co-authored-by: fredicious <1580688+fredicious@users.noreply.github.com>
2026-03-11 21:17:28 +01:00
copilot-swe-agent[bot]andfredicious ded1f4e2a3 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 e777513a79 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
github-actions[bot] f7ede655e9 chore: pin docker-compose to v0.20.0 [skip ci] 2026-03-11 19:45:32 +00:00
github-actions[bot] df196f189e chore: bump version to 0.20.0 2026-03-11 19:41:24 +00:00
Claude 5561e1d9c2 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 a54c117c76 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 32b57eb0c6 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 57e34f5e66 fix: add vertical padding to checklist add-item button
https://claude.ai/code/session_0163L1u9MfL1C6Rv3yAw8RKs
2026-03-11 20:20:23 +01:00
Claude ce8b6c97b9 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
github-actions[bot] bcbff2ec4f chore: pin docker-compose to v0.19.3 [skip ci] 2026-03-11 19:00:59 +00:00
github-actions[bot] bafd7cb48a chore: bump version to 0.19.3 2026-03-11 18:56:19 +00:00
Claude f951baaba6 fix: resolve race condition in offline sync e2e test
Set up waitForResponse before calling setOffline(false) so the sync
response listener is registered before the online event fires.

https://claude.ai/code/session_014yTnHzjkCJLBcYcPTSLVkE
2026-03-11 19:53:47 +01:00
Claude 5f52864945 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 b08ef84d43 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
github-actions[bot] 81867c57d4 chore: pin docker-compose to v0.19.2 [skip ci] 2026-03-10 22:47:34 +00:00
github-actions[bot] f23d2f854b chore: bump version to 0.19.2 2026-03-10 22:43:26 +00:00
Frederic CabassutandClaude Opus 4.6 4c4756a346 ci: use Playwright container image instead of downloading browsers
Run E2E tests inside mcr.microsoft.com/playwright container which has
Chromium pre-installed, eliminating the ~30s browser download on each run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:39:29 +01:00
Frederic CabassutandClaude Opus 4.6 06c9dda98a fix: eliminate e2e test flakiness in settings preference and API key tests
Three sources of flakiness fixed:

1. **Preferences sync race** — page.goto() triggers a full reload which
   re-runs initPreferences() + syncPreferencesFromServer(). On slow CI,
   the async server sync can return stale data from parallel test workers
   that share the same user's server-side preferences. Fixed with
   gotoWithStablePrefs() which blocks the one GET /api/preferences sync
   request, so initPreferences() falls back to localStorage (always correct).

2. **localStorage not reset** — resetPreferences() only reset the server.
   After a full page reload, loadFromLocalStorage() could return stale
   values from previous test runs. Now resets both server and localStorage.

3. **API key name collision** — parallel test runs created keys with the
   same name ("Settings Test Key"), causing Playwright strict-mode
   violations when multiple elements matched. Fixed with Date.now()
   unique suffixes.

Verified: 167/167 settings tests pass (20x repeat), 173/173 full suite (5x).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:39:29 +01:00
Frederic CabassutandClaude Opus 4.6 590c8f5cae docs: add E2E BDD testing best practices and improve test suite
- Add docs/TESTING.md with 10 BDD testing best practices
- Add createNote() helper to fixtures to DRY up state setup
- Add createChecklistNote() helper in checklist tests
- Rename scenario titles to describe outcomes, not actions
- Replace imperative Given steps with createNote() helper
- Fix fragile conditional in tag filtering test (silent pass)
- Remove implementation details from Gherkin comments
- Improve Given/When/Then rhythm consistency across all tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:39:29 +01:00
github-actions[bot] 2937211be3 chore: pin docker-compose to v0.19.0 [skip ci] 2026-03-10 16:07:11 +00:00
github-actions[bot] bcf5aa424d chore: bump version to 0.19.1 2026-03-10 15:16:11 +00:00
Frederic Cabassut 890f0ade94 fix: isolate preference e2e tests with API reset before each test
Each preference test now resets to default preferences via API at the
start, ensuring parallel tests don't pollute each other's state.
Also await PUT response before reload in persist test to prevent
race condition.
2026-03-10 16:13:12 +01:00
github-actions[bot] 11bdde0d88 chore: bump version to 0.19.0 2026-03-10 14:55:19 +00:00
Frederic CabassutandClaude Opus 4.6 d2dbad6e10 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 Cabassut 7a2fa6ef25 fix: prevent e2e test pollution from preferences shared state
Settings tests set defaultNoteMode to 'markdown' without resetting,
causing parallel sharing test to timeout when TiptapEditor wasn't
rendered (textarea shown instead). Two fixes:
- Reset preferences in settings tests after changing them
- Create note via API in sharing test (tests sharing, not note creation)
2026-03-10 15:54:40 +01:00
github-actions[bot] f2c12994ac chore: bump version to 0.18.0 2026-03-10 14:11:18 +00:00
Frederic CabassutandClaude Opus 4.6 ec4ab031ee fix: redirect /settings to /settings/preferences
Move Profile page to /settings/profile and add a 302 redirect from
/settings to /settings/preferences so the first settings tab is
Preferences, not Profile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:10:31 +01:00
Frederic CabassutandClaude Opus 4.6 1f3a8d1e36 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 bcc9d8aa87 chore: remove plan docs from repo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 14:54:20 +01:00
Frederic CabassutandClaude Opus 4.6 ddf192cfeb docs: add user preferences implementation plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:33:44 +01:00
Frederic CabassutandClaude Opus 4.6 ece876ab6e docs: add user preferences design document
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:25:54 +01:00
Frederic CabassutandClaude Opus 4.6 e954fd25e3 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 0b417bef0e test: consolidate E2E tests — remove duplicates, fix timeouts, parallelize sharing
- Remove duplicate bold formatting test from notes-crud.spec.ts
- Trim formatting.spec.ts from 9 to 5 tests (remove italic, inline code,
  toolbar bold, toolbar visibility — same mechanism as bold shortcut)
- Remove hardcoded waitForTimeout(500) from search.spec.ts (Playwright
  assertions already retry with built-in timeouts)
- Restructure sharing.spec.ts: 9 serial tests → 5 parallel self-contained
  tests using collabPage fixture and API-based setup
- Add collabPage fixture to helpers/fixtures.ts for sharing tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:14:09 +01:00
Frederic CabassutandClaude Opus 4.6 914b4fc772 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 e794d09978 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
github-actions[bot] 72b10016ca chore: pin docker-compose to v0.17.0 [skip ci] 2026-03-10 08:57:49 +00:00
github-actions[bot] 5b6c918629 chore: bump version to 0.17.0 2026-03-10 08:53:40 +00:00
Frederic CabassutandClaude Opus 4.6 7e1125c00d feat: add coverage summary to GitHub Actions job summary
Write a markdown coverage table to $GITHUB_STEP_SUMMARY for both unit
and E2E test jobs. Uses a small bash script that parses json-summary
output from the coverage reporters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:30:08 +01:00
Frederic CabassutandClaude Opus 4.6 a724770af4 feat: add code coverage with artifact upload for unit and E2E tests
Add V8 coverage for unit tests via @vitest/coverage-v8 and Istanbul
instrumentation for E2E tests via vite-plugin-istanbul. Coverage data
is collected from browser pages after each Playwright test and merged
with nyc. CI uploads both coverage reports as GitHub Actions artifacts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 09:30:08 +01:00
Frederic CabassutandClaude Opus 4.6 e3015c0e20 chore: set e2e retries to 0 — fix flaky tests, don't mask them
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 07:50:33 +01:00
Frederic CabassutandClaude Opus 4.6 ebdac3da13 perf: speed up e2e tests in CI
- Pre-build before Playwright instead of building inside webServer
- Reduce retries from 2 to 1 (fix flaky tests instead of masking them)
- Set CI workers to 4 for more parallelism
- Reduce artifact retention from 30 to 7 days

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 07:46:22 +01:00
github-actions[bot] 9917b737c2 chore: pin docker-compose to v0.16.1 [skip ci] 2026-03-10 06:40:49 +00:00
github-actions[bot] f761b34396 chore: bump version to 0.16.1 2026-03-10 06:36:44 +00:00
Frederic CabassutandClaude Opus 4.6 c29df8a908 fix: update e2e test for renamed API settings tab
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 07:36:03 +01:00
github-actions[bot] 4c7064d9fa chore: bump version to 0.16.0 2026-03-09 23:11:31 +00:00
Frederic CabassutandClaude Opus 4.6 009eac617e chore: rename Client Configuration to MCP Client Configuration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 00:10:45 +01:00
Frederic CabassutandClaude Opus 4.6 9a4eb5ecb3 feat: extend API key auth to all API endpoints
Bearer token authentication now works on all /api/* routes, not just
/api/mcp. If a Bearer token is present it's validated first; otherwise
session cookie auth is used as fallback. Updates settings UI to reflect
the broader scope (tab renamed to "API", REST API curl example added)
and updates the landing page feature card.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 00:10:45 +01:00
github-actions[bot] c3713c0391 chore: pin docker-compose to v0.15.0 [skip ci] 2026-03-09 23:02:47 +00:00
github-actions[bot] 5e2624d4c1 chore: bump version to 0.15.0 2026-03-09 22:57:53 +00:00
Frederic CabassutandClaude Opus 4.6 f42005a482 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
github-actions[bot] 6992bde0e0 chore: pin docker-compose to v0.14.0 [skip ci] 2026-03-09 22:42:53 +00:00
github-actions[bot] bd8c1fcee8 chore: bump version to 0.14.0 2026-03-09 22:38:22 +00:00
Frederic CabassutandClaude Opus 4.6 132c0c1fc9 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 a6a330ca59 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 8cc919bb03 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 295358ea31 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 b0166b9a5e 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 23887c7050 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
Frederic CabassutandClaude Opus 4.6 108ea21e36 docs: remove tables from FEATURES.md — not implemented
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 23:05:27 +01:00
github-actions[bot] 37f2af6bba chore: pin docker-compose to v0.13.1 [skip ci] 2026-03-09 21:47:39 +00:00
github-actions[bot] 00b40bc180 chore: bump version to 0.13.1 2026-03-09 21:43:19 +00:00
Frederic CabassutandClaude Opus 4.6 89f77800f0 fix: anchor collaborator popover to right edge to prevent overflow
The popover is inside a top-right positioned indicator, so anchoring
to right-0 keeps it on screen on mobile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:39:32 +01:00
github-actions[bot] 9f92579a86 chore: pin docker-compose to v0.13.0 [skip ci] 2026-03-09 21:36:34 +00:00
github-actions[bot] 7d9ab9bcdd chore: bump version to 0.13.0 2026-03-09 21:32:25 +00:00
Frederic CabassutandClaude Opus 4.6 3f5c177472 feat: pressing Enter in note title moves cursor to content
Prevents default newline and focuses the content area (textarea or
TipTap editor) when pressing Enter in the title input.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:29:44 +01:00
Frederic CabassutandClaude Opus 4.6 d0f11a97da fix: remove tag chips from note cards in list view
Tags are still visible as hashtags in the note content and usable
via the tag filter bar. Reduces card clutter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:28:47 +01:00
Frederic CabassutandClaude Opus 4.6 3d17abbcf2 fix: increase touch drag delay to 400ms to prevent scroll conflicts
The default 80ms delay was too short, causing drag to activate
during normal scrolling on mobile.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:15:05 +01:00
github-actions[bot] bc32ab841a chore: pin docker-compose to v0.12.1 [skip ci] 2026-03-09 21:11:35 +00:00
github-actions[bot] 44112ab568 chore: bump version to 0.12.1 2026-03-09 21:07:31 +00:00
Frederic CabassutandClaude Opus 4.6 05d2a505f9 test: update sorting tests for hybrid sort order
Remove tests for sort selector UI (removed). Replace with a single
test verifying most recently updated note appears first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:06:39 +01:00
github-actions[bot] a4844f9cdf chore: bump version to 0.12.0 2026-03-09 20:58:34 +00:00
Frederic CabassutandClaude Opus 4.6 484468e3f8 fix: remove focus outline from note cards during drag
Add outline-none to both the NoteCard article and the NoteGrid
wrapper div to prevent browser focus outlines when dragging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:56:42 +01:00
Frederic CabassutandClaude Opus 4.6 20b3e6d3b6 refactor: remove drag handles, use long-press to reorder notes
Replace drag handle zones with dndzone for whole-card dragging via
long-press on both desktop and mobile. Removes GripHorizontal icon
and dragHandle imports entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:56:42 +01:00
Frederic CabassutandClaude Opus 4.6 d1c9c88ab5 refactor: replace sort selector with hybrid sort order
Remove the sort mode selector UI. Notes now sort by custom order
first (drag to reorder), falling back to most-recently-updated for
ties. Drag handles are always available.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:56:42 +01:00
Frederic CabassutandClaude Opus 4.6 bde40981cb fix: stack tag filter and sort buttons vertically on mobile
Tags get full width for horizontal scrolling, sort buttons sit
below aligned to the right. Side by side on desktop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:56:42 +01:00
Frederic CabassutandClaude Opus 4.6 c8b20a6ab4 fix: hide scrollbar on tag filter horizontal scroll
Use scrollbar-width: none and ::-webkit-scrollbar to visually hide
the scrollbar while keeping swipe-to-scroll functional.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:56:42 +01:00
Frederic CabassutandClaude Opus 4.6 c2e7f4a851 fix: prevent tag chips from wrapping text across lines
Add whitespace-nowrap to TagChip so long tags stay on a single line
within the horizontally scrollable filter bar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:56:42 +01:00
Frederic CabassutandClaude Opus 4.6 3dcadc280d fix: reload notes when navigating to archive or trash views
Call loadNotes(filter) when the filter prop changes so the store
gets populated with the correct note subset for each view.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:56:42 +01:00
Frederic CabassutandClaude Opus 4.6 196d265635 feat: click sync indicator to trigger manual sync
Change sync indicator from div to button that calls sync() on click,
with hover feedback and updated tooltip.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:56:42 +01:00
Frederic CabassutandClaude Opus 4.6 51a832b3b9 fix: make tag filter horizontally scrollable on mobile
Keep tags on a single line with horizontal scroll on small screens,
wrap normally on desktop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:56:42 +01:00
Frederic CabassutandClaude Opus 4.6 b6150cafed feat: add floating action button for creating notes on mobile
Show a fixed FAB at bottom-right on mobile instead of the inline
button that scrolls out of view. Desktop keeps the inline button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:56:42 +01:00
Frederic CabassutandClaude Opus 4.6 448c2127d5 fix: prevent settings page elements from overflowing on mobile
Stack MCP key form, API key items, user list rows, and delete
account confirmation vertically on small screens using
flex-col/sm:flex-row. Add truncate on user names/emails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:56:42 +01:00
Frederic CabassutandClaude Opus 4.6 8e630d9bb7 fix: prevent drag handle from overlapping note card title
Position drag handle as a centered horizontal grip at the top edge
of the card, out of the way of title and content.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:56:42 +01:00
github-actions[bot] e3c9f5d0c7 chore: pin docker-compose to v0.11.1 [skip ci] 2026-03-09 20:17:06 +00:00
github-actions[bot] 6b6654a94f chore: bump version to 0.11.1 2026-03-09 20:12:19 +00:00
Frederic CabassutandClaude Opus 4.6 1a5edc0fdd fix: sync endpoint returns per-user state overlay for shared notes
getChangesSince was returning the owner's pinned/archived/sortOrder
for shared notes instead of the collaborator's per-user values from
noteUserState. Also filtered trashed notes from collaborator sync.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:06:04 +01:00
github-actions[bot] fafb76c0d5 chore: pin docker-compose to v0.11.0 [skip ci] 2026-03-09 11:18:27 +00:00
github-actions[bot] 2d5b4a0cdc chore: bump version to 0.11.0 2026-03-09 11:12:31 +00:00
Frederic CabassutandClaude Opus 4.6 250e458c31 docs: add collaboration to FEATURES, ARCHITECTURE, and OpenAPI spec
- Add note sharing section to FEATURES.md
- Document collaboration access control model in ARCHITECTURE.md
- Add collaborators and user search endpoints to openapi.yaml
- Regenerate API.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:12:01 +01:00
Frederic CabassutandClaude Opus 4.6 a7c8699542 test: add e2e tests for note sharing workflow
Serial test suite covering: owner shares note, collaborator sees it,
collaborator edits content, independent pin state, leave action shown
for non-owners, collaborator leaves and note disappears.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:12:01 +01:00
Frederic CabassutandClaude Opus 4.6 3ee6c1d0b5 test: add unit tests for collaboration schema tables
Test noteCollaborators and noteUserState tables: CRUD, unique constraints,
defaults, and independent per-user state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:12:01 +01:00
Frederic CabassutandClaude Opus 4.6 346256a628 feat: add sharing UI components and integrate into NoteCard/NoteEditor
- Create ShareDialog with user search autocomplete, collaborator list, and remove
- Create CollaboratorPopover for hover/click indicator on shared notes
- Add collaborator indicator to NoteCard (always visible Users icon)
- Replace trash with Leave action for non-owner shared notes
- Add share button to NoteEditor toolbar (UserPlus → Users when shared)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:12:01 +01:00
Frederic CabassutandClaude Opus 4.6 bbacc5f6b3 feat: widen notes service and sync for shared notes
- Refactor notes-service to include shared notes in list/get/search/reorder
- Route per-user fields (pinned/archived/sortOrder) to noteUserState for collaborators
- Update API routes to delegate to service layer with access control
- Update sync server to handle shared note updates bidirectionally
- Add leaveNote() to client store
- Refactor search and notes API routes to use service layer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:12:01 +01:00
Frederic CabassutandClaude Opus 4.6 850730ec88 feat: add note collaboration schema, access control, and API routes
Add noteCollaborators and noteUserState tables for sharing notes between
users. Implement access control helpers (canAccessNote, requireNoteAccess,
requireNoteOwnership) and collaborators CRUD service. Create API endpoints
for managing collaborators and searching users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:12:01 +01:00
Frederic CabassutandClaude Opus 4.6 7aedec0953 docs: remove Google Keep references from website and docs
Replace brand-specific "Google Keep alternative" copy with generic
privacy-focused messaging across meta tags, hero subtitle, README,
and feature docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:08:42 +01:00
Frederic CabassutandClaude Opus 4.6 74d3228ddd fix: use apple-touch-icon as og:image for link previews
Platforms like WhatsApp require an explicit og:image to display
anything in link previews. Point it to the existing apple-touch-icon.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:04:34 +01:00
Frederic CabassutandClaude Opus 4.6 bb97d4fd71 fix: remove OG image and let apple-touch-icon handle link previews
The apple-touch-icon already provides the pretzel logo for link
previews (like the app does). Remove the redundant OG image and switch
twitter:card to "summary" (icon-sized) instead of "summary_large_image".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:03:08 +01:00
Frederic CabassutandClaude Opus 4.6 d38736cdf5 fix: simplify OG image to just the pretzel logo on parchment
Title and description are already shown in the link preview text,
so the image doesn't need to repeat them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:02:15 +01:00
Frederic CabassutandClaude Opus 4.6 b4bcd33f88 fix: add apple-touch-icon to website for link preview icon
Messaging apps (WhatsApp, iMessage, etc.) use the apple-touch-icon to
display a site icon in link previews. Copy the existing icon from the
app and add the link tag to the landing page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:58:19 +01:00
Frederic CabassutandClaude Opus 4.6 b035fbb25e feat: add OG image and Twitter Card meta tags for link previews
The og-image.png was referenced in meta tags but never created. Generate
a 1200x630 parchment-themed OG image and add missing twitter:title,
twitter:description, and twitter:image meta tags for proper social
media link previews.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:54:47 +01:00
github-actions[bot] 6d2ed4c07a chore: pin docker-compose to v0.10.1 [skip ci] 2026-03-09 10:52:33 +00:00
github-actions[bot] 4ca23a4b6f chore: bump version to 0.10.1 2026-03-09 10:48:47 +00:00
Frederic CabassutandClaude Opus 4.6 57b3e7700f fix: migrate password_hash column to nullable for OAuth-only users
Existing databases created from the original migration had password_hash
as NOT NULL. This adds an auto-migration that recreates the users table
with a nullable password_hash column, enabling OAuth-only user creation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:47:48 +01:00
Frederic CabassutandClaude Opus 4.6 06f77a6117 docs: use Authentik OIDC example in README instead of Google OAuth
Self-hosted auth provider is more relevant for a self-hosted app.
Also adds AUTH.md to the documentation table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:42:25 +01:00
github-actions[bot] 8c92925806 chore: pin docker-compose to v0.10.0 [skip ci] 2026-03-09 10:42:14 +00:00
github-actions[bot] a7a42104a9 chore: bump version to 0.10.0 2026-03-09 10:37:11 +00:00
Frederic CabassutandClaude Opus 4.6 23ff44dfaf feat: allow creating OAuth-only users without a password
Admin can now toggle "OAuth-only (no password)" when creating a user.
These users have no password hash and authProvider set to 'none', so
they can only log in via an OAuth/SSO provider.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:35:12 +01:00
Frederic CabassutandClaude Opus 4.6 a37ee75242 docs: add dedicated AUTH.md and deduplicate auth info across docs
Create AUTH.md as the canonical reference for authentication: password
auth, OAuth/SSO setup, provider-specific guides (Authentik, Keycloak,
Okta/Auth0), env vars, callback URLs, security notes, and
troubleshooting. Fixes incorrect env var names and callback URL patterns
that had drifted in DEPLOYMENT.md. Other docs now cross-link to AUTH.md
instead of duplicating content.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:35:12 +01:00
github-actions[bot] 16f4e2abe3 chore: pin docker-compose to v0.9.1 [skip ci] 2026-03-09 10:04:53 +00:00
github-actions[bot] 005071f703 chore: bump version to 0.9.1 2026-03-09 10:00:54 +00:00
Frederic CabassutandClaude Opus 4.6 af75f76dc9 fix: use correct MCP server type in settings page config
Change "type": "streamable-http" to "type": "http" to match Claude
Code's actual MCP schema. Also clarify that the config targets
.claude/settings.json specifically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:47:25 +01:00
Frederic CabassutandClaude Opus 4.6 838051b1ab perf: lazy-load zxcvbn password library to reduce initial bundle size
- Dynamic import zxcvbn (820KB) in PasswordStrengthMeter so it only
  loads when user starts typing a password
- Raise chunkSizeWarningLimit to 850KB for the lazy-loaded chunk

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:42:27 +01:00
Frederic CabassutandClaude Opus 4.6 48f23cbecc fix: resolve all svelte-check warnings (a11y + state_referenced_locally)
- Add svelte-ignore for intentional state_referenced_locally patterns
- Wrap clickable images in button elements for keyboard accessibility
- Add proper ARIA roles (dialog, textbox) with tabindex and focus management
- Fix lightbox Escape key handling to not close parent editor overlay

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:37:07 +01:00
github-actions[bot] d3334ad91d chore: pin docker-compose to v0.9.0 [skip ci] 2026-03-09 01:36:11 +00:00
github-actions[bot] f840cc1b57 chore: bump version to 0.9.0 2026-03-09 01:32:03 +00:00
Frederic CabassutandClaude Opus 4.6 d9b4a34bac fix: resolve type errors in notes-service, MCP server, and test helpers
- Add missing userId arg to syncNoteTags() calls in notes-service
- Add seedUser option to createTestDb() for test setup
- Add explicit type annotations to MCP server.tool() callbacks (zod v4)
- Fix ambiguous getByText selector in settings e2e test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:31:18 +01:00
Frederic CabassutandClaude Opus 4.6 3cc3ecad77 docs: update CLAUDE.md to reflect multi-user auth and current project structure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:25:56 +01:00
Frederic CabassutandClaude Opus 4.6 cb80102a21 fix: clarify multi-user feature description on landing page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:25:11 +01:00
Frederic CabassutandClaude Opus 4.6 f32e81c2ae feat: add MCP, multi-user, and OAuth features to landing page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:25:11 +01:00
Frederic CabassutandClaude Opus 4.6 790ff4404c docs: add OAuth/auth docs and install instructions in README
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:20:07 +01:00
Frederic CabassutandClaude Opus 4.6 6b6f34cdef feat: admin can revoke user sessions (force logout)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:20:03 +01:00
Frederic CabassutandClaude Opus 4.6 f001164d0f feat: URL-based routing for archive, trash, and tags
Add /archive, /trash, /tag/[name] routes with proper <a> links
in the sidebar. Tags visible regardless of current filter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:19:55 +01:00
Frederic CabassutandClaude Opus 4.6 31b3cc78b4 feat: add logout button and delete account to settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:19:47 +01:00
Frederic CabassutandClaude Opus 4.6 09f2d3c928 refactor: remove user avatar dropdown from header
Logout and Settings access moved to sidebar and settings page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:19:43 +01:00
Frederic CabassutandClaude Opus 4.6 11aa542b4a docs: add MCP server and API key documentation
- Update FEATURES.md, ARCHITECTURE.md, DEPLOYMENT.md with MCP sections
- Add MCP endpoints to OpenAPI spec and regenerate API.md
- Add rebase-before-PR best practice to CLAUDE.md git conventions
- Update README with MCP/API feature mention

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:03:04 +01:00
Frederic CabassutandClaude Opus 4.6 d338da1d1a test: add E2E tests for MCP endpoint and Settings page
- MCP tests: initialize, create/get note, search, auth rejection
- Settings tests: create key, revoke key, sidebar nav, subpage nav

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:03:04 +01:00
Frederic CabassutandClaude Opus 4.6 8e1f8f0413 feat: add Settings subpages with API key management UI
- Settings layout with sidebar nav (Profile, MCP Server, Users)
- Profile page: display name, email, password, sessions
- MCP page: API key CRUD, JSON config with copy button, LLM prompt
- Config auto-populates with actual API key when one is created
- Remove duplicate Settings link from sidebar
- Users page integrated into settings nav (admin only)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:03:04 +01:00
Frederic CabassutandClaude Opus 4.6 2a95174c07 feat: add MCP server with 14 tools and shared notes service
- Shared notes-service.ts with userId-scoped queries for DRY
- McpServer with tools: list/get/create/update/trash/restore/
  archive/unarchive/delete notes, search, list tags, pin,
  reorder, upload image
- Streamable HTTP endpoint at POST /api/mcp
- Session management with GET/DELETE handlers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:03:04 +01:00
Frederic CabassutandClaude Opus 4.6 63feddf231 feat: add API key CRUD and Bearer token auth for MCP
- API key generation with SHA-256 hashing and crumbs_ prefix
- User-scoped key validation, listing, and deletion
- Bearer token auth in hooks.server.ts for /api/mcp paths
- Populates event.locals.user from API key for downstream scoping
- 13 unit tests covering CRUD, validation, and user isolation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:03:04 +01:00
Frederic CabassutandClaude Opus 4.6 34ad34915d feat: add API keys schema and MCP SDK dependency
- Add `api_keys` table with userId foreign key to schema
- Add `@modelcontextprotocol/sdk` and `zod` dependencies
- Add auto-create SQL and test helpers for api_keys table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 02:03:04 +01:00
Frederic CabassutandClaude Opus 4.6 d5a079a4bd fix: enforce note ownership across all API endpoints
- Add userId check to /api/notes/reorder (was completely unprotected)
- Scope syncNoteTags by userId to prevent cross-user tag pollution
- Verify attachmentId belongs to noteId in attachment operations (IDOR fix)
- Remove DEFAULT 0 from userId columns so missing userId fails loudly
- Update test helpers and test data to provide required userId

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:50:12 +01:00
github-actions[bot] 15ba5629f7 chore: pin docker-compose to v0.8.1 [skip ci] 2026-03-09 00:33:34 +00:00
github-actions[bot] 69d3824fe7 chore: bump version to 0.8.1 2026-03-09 00:29:30 +00:00
Frederic CabassutandClaude Opus 4.6 ac83ebc4f0 style: restyle settings pages to match parchment design system
- Move settings routes into (app) layout group for header/sidebar
- Replace all gray/white/dark-mode Tailwind classes with CSS variables
- Use rounded-sm, card-shadow, and parchment color tokens throughout
- Fix PasswordStrengthMeter to use themed colors
- Fix page titles from "KeepNotes" to "Crumbs"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:26:15 +01:00
Frederic CabassutandClaude Opus 4.6 d07607bcf1 fix: remove minlength attribute to fix e2e setup validation test
The HTML minlength="8" attribute triggered native browser validation
before the JS handler could run, preventing the custom error-message
element from appearing. JS already validates password length.

Also install missing zxcvbn and arctic dependencies from phase2 merge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:23:11 +01:00
Frederic CabassutandClaude Opus 4.6 ad8ba982c2 docs: add mobile hover actions best practice to CLAUDE.md
Document the opacity-based pattern for hover actions that ensures
they're always visible on touch devices.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:16:59 +01:00
Frederic CabassutandClaude Opus 4.6 6758eb1e36 fix: move drag handle to top-left to avoid overlap with pin icon
The drag handle was positioned top-right, overlapping with the pin
bookmark icon on pinned notes. Move it to top-left instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:16:46 +01:00
Frederic CabassutandClaude Opus 4.6 7530dc1d47 fix: increase focusable text area on mobile for rich text editor
Make the Tiptap editor fill the full wrapper height using flexbox so
tapping anywhere in the content area focuses the editor, not just the
small text area at the top.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:16:34 +01:00
Frederic CabassutandClaude Opus 4.6 dac3ac235a fix: improve image actions for mobile and always show delete/star
- Use opacity-based visibility (max-md:opacity-100) for image hover
  actions so they're always accessible on touch devices
- Always show ImageUpload with delete/star when attachments exist,
  not just when in image upload mode
- Replace drag-and-drop zone with compact "Add images" button on mobile
- Add showDropZone prop to ImageUpload for conditional drop zone display
- Clean up unused lightbox state from NoteEditor

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:16:08 +01:00
github-actions[bot] 2a06b2f739 chore: bump version to 0.8.0 2026-03-09 00:11:02 +00:00
Frederic CabassutandClaude Opus 4.6 2c5f8130c4 chore: regenerate lockfile after rebase onto main
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 b2634f8b13 fix(test): proper e2e test isolation with project dependencies
Add globalSetup to clean test DB before each run. Split Playwright into
two projects: auth-setup (serial, runs first) and app (parallel, depends
on auth-setup). Skip rate limiting in test environment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 80c6744559 fix(test): resolve strict mode violations in e2e organization tests
Use .first() for pin-btn and tag filter assertions to handle cases
where multiple matching elements exist in the DOM.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 3e1e01c539 fix: resolve type errors in OAuth and API utils
Fix null vs undefined in GitHub OAuth, use arctic's
createAuthorizationURLWithPKCE for OIDC, loosen api-utils type
signature to avoid SvelteKit route-specific generics, add @types/zxcvbn.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 3baeec29a8 docs: update .env.example with OAuth configuration
Add Google, GitHub, and generic OIDC environment variables. Providers
are auto-enabled when their env vars are set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 93a9379f5e feat(ui): dynamic OAuth buttons on login page
Login page loads enabled providers from server and shows "or continue
with" divider + branded buttons for Google/GitHub/OIDC. Also handles
rate limit 429 and OAuth no_account error display.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 a9c771cc9f feat(auth): OAuth authorize and callback endpoints
Dynamic [provider] routes for Google, GitHub, and OIDC. Stores state and
PKCE verifier in httpOnly cookies. Callback validates code, finds/links
user by provider ID or email (invite-only), and creates session.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 8806ffd50a feat(auth): OAuth provider configuration with arctic
Add Google, GitHub, and generic OIDC provider modules using arctic v3.
Providers are auto-enabled when env vars are set. Google/GitHub use PKCE,
OIDC uses OpenID Connect discovery.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 54ec5b9833 feat(auth): session list and revocation
Track session metadata (createdAt, userAgent, ip, lastUsedAt). Add API
for listing and revoking sessions. Settings page shows active sessions
with "Revoke" per session and "Log out everywhere else" option.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 7803fc531a feat(ui): password strength meter with zxcvbn
Add PasswordStrengthMeter component showing visual bar and feedback.
Integrated in setup, settings change-password, and admin create-user forms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 e3d3d5c499 feat(auth): login rate limiting
5 failed attempts per IP triggers 15-minute lockout. Uses loginAttempts
table added in Phase 1. Returns 429 with Retry-After header.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 b90ab18ea7 fix(test): update tests for multi-user schema and improve e2e reliability
Update unit test helpers with full multi-user schema, fix auth tests to
include email field, and improve e2e tests with specific card targeting
and force clicks for hover-revealed buttons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 c0b5aa3bcb chore: add .worktrees/ to .gitignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 c9cc5d5967 fix(auth): resolve session validation and layout rendering issues
Fix session expiry check (seconds vs milliseconds mismatch in raw SQL),
conditionally render app chrome only for authenticated users, use full
page reload after login/setup for proper session propagation, remove
HTML minlength to allow JS validation, fix store to remove notes on
trash/archive, and make auth tests race-condition safe.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 6324d8f094 fix(db): reorder migration to run before index creation
Move migration logic (ALTER TABLE) before CREATE INDEX statements so
that user_id columns exist on old schemas before indexes reference them.
Disable foreign keys during schema setup to avoid constraint issues.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 5b7f8852ad test: update auth tests for multi-user
Update e2e fixtures to use email+password for setup and login.
Update auth tests to fill email field. Add multi-user test for
admin creating a new user via API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 abb9ffcf95 refactor(sync): namespace IndexedDB by userId
Each user gets their own IDB database (keepnotes-{userId}), preventing
data leaks between users on the same device. Add initDb(userId) called
on mount and destroyDb() for logout cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 263fbbdf15 feat(ui): settings page with user profile and admin user management
- Settings page: edit display name/email, change password
- Admin user management page: create/delete users, toggle roles
- Profile update API endpoint (PUT /api/auth/profile)
- Settings link added to sidebar navigation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 33864d9355 feat(api): admin user management endpoints
- GET/POST /api/admin/users — list and create users (admin only)
- PATCH/DELETE /api/admin/users/[id] — update role/reset password, delete user
- PUT /api/auth/password — change own password
All admin routes guarded by requireAdmin().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 133690994b feat(layout): expose user data to app shell
Add layout.server.ts to pass event.locals.user to the client.
Header now shows user avatar initial with dropdown menu
(Settings link, Log out button).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 061336ae8b refactor(api): scope all queries by authenticated user
Add getUserId/requireAdmin helpers in api-utils.ts. All notes, tags,
search, sync, and attachment routes now filter by userId to ensure
complete data isolation between users. Sync server accepts userId
parameter for push/pull operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 4dea359362 feat(auth): email-based login and setup flows
Login page now requires email + password. Setup page adds email and
optional display name fields. API endpoints updated to pass email
to verifyPassword/setupUser and explicit userId to createSession.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 59b5e92d91 refactor(hooks): populate event.locals.user from session
validateSession now returns user data. Middleware sets event.locals.user
on every authenticated request. Add /api/auth/oauth to public paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 823fc27b73 refactor(auth): email-based login and user CRUD
Rewrite auth module for multi-user support:
- setupUser now takes email, password, displayName; returns User
- verifyPassword takes email+password; returns User or null
- createSession takes explicit userId
- validateSession joins sessions+users; returns user data
- Add createUser, deleteUser, changePassword, resetPassword,
  updateUserRole, updateUserProfile, listUsers, getUser

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 c13d7c0ea3 feat(types): add User type and App.Locals
Define App.Locals.user interface for session user data.
Add User type to shared types (mirrors DB columns minus passwordHash).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 9405acdb4d feat(db): add multi-user columns and login_attempts table
Add email, displayName, role, authProvider, providerId to users table.
Add userId foreign key to notes, tags, attachments, syncLog tables.
Add loginAttempts table for rate limiting. Include migration logic
to detect old single-user schema and backfill existing data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:10:37 +01:00
Frederic CabassutandClaude Opus 4.6 f21c98df19 style: add vertical padding to checklist items for better spacing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:46:32 +01:00
github-actions[bot] da7e60ed97 chore: pin docker-compose to v0.7.2 [skip ci] 2026-03-08 23:45:47 +00:00
github-actions[bot] 29a25d23b0 chore: bump version to 0.7.2 2026-03-08 23:42:16 +00:00
Frederic CabassutandClaude Opus 4.6 8ef4b0733d fix: add error resilience for IDB failures and API error feedback
loadNotes() crashed entirely if IndexedDB was unavailable because
getAllNotes() was called outside try/catch. Non-OK API responses
(401, 500) were silently ignored across loadNotes, createNote, and
sync operations — users saw empty data with no feedback.

Changes:
- Wrap all IDB calls in try/catch so the app works without IndexedDB
- Show toast on non-OK API responses in loadNotes and createNote
- Redirect to /login on 401 responses
- Make sync pull/push resilient to IDB unavailability

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:41:45 +01:00
github-actions[bot] 762057c55a chore: pin docker-compose to v0.7.1 [skip ci] 2026-03-08 23:37:55 +00:00
github-actions[bot] ad3a1daa40 chore: bump version to 0.7.1 2026-03-08 23:33:55 +00:00
Frederic CabassutandClaude Opus 4.6 88e2714ec8 fix: add missing thumbnail_path migration and upload error feedback
DBs created before the image upload feature had no thumbnail_path column
in the attachments table. Since CREATE TABLE IF NOT EXISTS is a no-op for
existing tables, the column was never added, causing silent insert failures.

Add idempotent ALTER TABLE migration for thumbnail_path and surface upload
errors via toast notifications instead of swallowing them silently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:33:32 +01:00
github-actions[bot] 188e36013b chore: pin docker-compose to v0.7.0 [skip ci] 2026-03-08 21:42:42 +00:00
github-actions[bot] 5e4df2d00f chore: bump version to 0.7.0 2026-03-08 21:38:51 +00:00
Frederic CabassutandClaude Opus 4.6 a5312b60f5 feat: user-selectable featured images for note card headers
Images are hidden from card thumbnails by default. Users explicitly
feature images via a star toggle in the upload panel, and only featured
images appear in the card header strip.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 e1f7a4c455 feat: add image lightbox for full-size preview on click
Clicking a thumbnail in the editor, closed-panel view, or note card
opens a full-screen overlay with the full-size image. Closes on Escape,
background click, or X button. Escape is scoped so it won't close the
editor underneath.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 f61a567cea feat: auto-save note when clicking image toggle on new note
Instead of disabling the image button for unsaved notes, clicking it
now auto-saves the note first (with title "Untitled" if empty), then
opens the upload panel. This removes friction from the upload flow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 0fec60a56a fix: E2E test fixes — static test image, thumbnail_path in init SQL, hover for remove
- Use static test-image.png fixture instead of dynamic beforeAll creation
- Add thumbnail_path column to auto-create SQL in db/index.ts
- Hover on thumbnail before clicking remove button (group-hover:block CSS)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 501a5d3920 feat: replace Checklists with Image Attachments on landing page
Swap Checklists feature card for Image Attachments with photo SVG icon.
Mention checklists in the Rich Editor card description instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 2d001383fa docs: update FEATURES, ARCHITECTURE, and OpenAPI spec for image attachments
- FEATURES.md: document client-side optimization, thumbnails, offline support, SW caching
- ARCHITECTURE.md: add attachment data flow section (client → optimize → IDB/server → SW cache)
- openapi.yaml: add &thumb=1 query param, thumbnail FormData field, thumbnailPath in Attachment schema, attachments array in Note schema

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 6e1cf941fc test: add E2E tests for image attachments
Covers: upload → thumbnail in editor, persistence across close/reopen,
thumbnail on NoteCard, image removal, disabled toggle for unsaved notes.
Uses programmatically generated 1x1 PNG for test uploads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 087d7a48e6 feat: add CacheFirst SW rule for attachment images
Attachment images are immutable (UUID-based), so CacheFirst is ideal.
Previously-viewed images remain available offline even after blob URLs
expire. Rule placed before general NetworkFirst API rule to match first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 c3aa81949c docs: add git conventions section to CLAUDE.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 7328a4a3a9 feat: add offline attachment support with IDB pending store and sync
- Bump IDB version to 2, add pendingAttachments object store with noteId index
- Add CRUD helpers: addPendingAttachment, getPendingAttachments, removePendingAttachment
- ImageUpload stores blobs in IDB when offline, displays via blob URLs
- Show pending sync indicator (RefreshCw icon) on not-yet-uploaded images
- Add pushPendingAttachments to sync client, runs after note push
- Pending attachments auto-upload on reconnect via existing online event listener

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 cc6181aba1 feat: wire ImageUpload into NoteEditor and show thumbnails on NoteCard
- Import ImageUpload into NoteEditor with attachment state management
- Add $effect to fetch attachments for existing notes loaded from IDB
- Add upload/remove handlers and image toggle button in toolbar
- Show ImageUpload panel between content and formatting toolbar
- Add full-bleed thumbnail strip at top of NoteCard (up to 3 images)
- Use &thumb=1 URL for card previews with lazy loading
- Show +N badge when more than 3 attachments

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 f7e7aaa97d feat: add image attachment backend — batch fetch, API responses, optimization, thumbnails
- Add fetchAttachmentsForNotes() batch helper mirroring fetchTagsForNotes pattern
- Include attachments in all note API responses (list, single, sync)
- Fix ImageUpload.svelte URL bug (path-based → query param)
- Add client-side image optimization (resize, compress, WebP conversion)
- Add thumbnailPath column to attachments schema with migration
- Update saveAttachment/deleteAttachment for thumbnail file handling
- Update attachment API: accept thumbnail in POST, serve via &thumb=1 in GET
- Add Cache-Control immutable header for served attachments

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 175c821e8d chore: add .worktrees/ to .gitignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:37:50 +01:00
Frederic CabassutandClaude Opus 4.6 df3fff0438 feat: link Bretzel in footer to bretzel.app
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:25:31 +01:00
github-actions[bot] bb3a3c3369 chore: pin docker-compose to v0.6.2 [skip ci] 2026-03-08 18:19:43 +00:00
github-actions[bot] 7499350c50 chore: bump version to 0.6.2 2026-03-08 18:16:27 +00:00
Frederic CabassutandClaude Opus 4.6 0a0eef12f3 ci: always upload Playwright test results as artifacts
Upload both playwright-report/ and test-results/ (screenshots, traces)
on every run, not just failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:11:25 +01:00
github-actions[bot] 84c5b42bbc chore: pin docker-compose to v0.6.1 [skip ci] 2026-03-08 18:09:02 +00:00
Frederic CabassutandClaude Opus 4.6 c487091dd7 fix: show checklist actions on mobile
Apply the same max-md:opacity-100 pattern from NoteCard to Checklist
drag handles and remove buttons, making them visible on touch devices.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:08:33 +01:00
github-actions[bot] 43c095073f chore: bump version to 0.6.1 2026-03-08 18:05:13 +00:00
Frederic CabassutandClaude Opus 4.6 f70529d1d0 fix: update checklist e2e tests for done-expanded default
Tests were clicking the done toggle expecting to expand it, but since
fed0fc1 changed doneExpanded to true by default, the click was
collapsing it instead — causing visibility assertions to fail.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:15:25 +01:00
Frederic CabassutandClaude Opus 4.6 4a596cefec ci: run all jobs in parallel to reduce pipeline time
Remove `needs` dependencies from e2e-tests and docker-build jobs.
They don't depend on lint/unit outputs — failures are visible from
their own jobs. Saves ~22s off the critical path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:14:02 +01:00
Claude a8591825eb fix: show action buttons on mobile note cards
On touch devices, hover states don't work, so the pin/archive/delete
buttons were invisible. Now they're always visible on screens below the
md breakpoint and only hover-toggled on desktop.

https://claude.ai/code/session_013DdAk1yj6r3VF6Ze4ycidp
2026-03-08 07:52:32 +01:00
github-actions[bot] f9af20554f chore: bump version to 0.6.0 2026-03-07 22:41:38 +00:00
Frederic CabassutandClaude Opus 4.6 60b5ed9442 feat: add Umami analytics to landing page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:40:24 +01:00
Frederic CabassutandClaude Opus 4.6 cf099d3114 docs: remove dark mode references (not implemented)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:48:41 +01:00
Frederic CabassutandClaude Opus 4.6 f19caa1b9b fix: checklist UX polish — spacing, disable markdown toggle, default to raw mode
- Increase gap between checkbox and text input (gap-1 → gap-2)
- Disable markdown toggle button in checklist mode
- Default to raw markdown mode when opening checklist notes as text

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:44:31 +01:00
Frederic CabassutandClaude Opus 4.6 fed0fc1450 fix: show delete button on row hover and expand done items by default
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:34:09 +01:00
Frederic CabassutandClaude Opus 4.6 c4d53f4661 feat: enable delete for completed checklist items
Add X button to done/completed checklist items, matching the
existing remove button on active items.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:27:33 +01:00
Frederic CabassutandClaude Opus 4.6 df2481bf67 fix: note grid drag-and-drop improvements
- Use dragHandleZone with grip icon instead of whole-card dragging,
  so card interactions (checkboxes, pin, archive) still work
- Scope pinned/unpinned grids to separate dnd types to prevent
  cross-zone drag bugs
- Add h-full to flip wrapper and NoteCard for equal card heights

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 22:17:58 +01:00
Frederic CabassutandClaude Opus 4.6 da57418de9 fix: always separate completed checklist items from active list
Completed items now consistently appear below unchecked items in both
the editor and the note card preview. Replaces the flaky two-state
hide/show toggle with a simple expand/collapse for the done section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:56:51 +01:00
Frederic CabassutandClaude Opus 4.6 5ce8601a92 feat: note sorting with drag-and-drop custom order
- Add sort mode selector (Updated/Created/Custom) with localStorage persistence
- Sort notes client-side via derived stores based on selected mode
- Enable drag-to-reorder in Custom mode via svelte-dnd-action on NoteGrid
- Add POST /api/notes/reorder endpoint for bulk sortOrder updates
- E2E tests for sorting by updated/created and persistence across reload

Closes #12

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:40:04 +01:00
Frederic CabassutandClaude Opus 4.6 e5688c99b9 feat: checklist improvements — drag reorder and hide completed
- Add drag-to-reorder via svelte-dnd-action with grip handles
- Add "hide completed" toggle with collapsible done section
- Each checklist item gets a unique ID for stable drag operations
- E2E test for hide/show completed items

Closes #11

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:33:56 +01:00
Frederic CabassutandClaude Opus 4.6 948d855ed6 fix: exclude archived/trashed notes from tag list
The allTags derived store now filters out archived and trashed notes
before collecting tags, so the sidebar tag filter only shows tags that
yield visible results.

Closes #9

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:08:16 +01:00
Frederic CabassutandClaude Opus 4.6 b20b6c383c fix: close mobile sidebar on navigation
Sidebar filter and tag selection handlers now call onClose() on mobile
viewports (< 1024px), so the overlay dismisses after tapping a link.

Closes #10

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 21:08:16 +01:00
github-actions[bot] b5314a1610 chore: pin docker-compose to v0.5.0 [skip ci] 2026-03-07 19:44:03 +00:00
github-actions[bot] b4e4408de2 chore: bump version to 0.5.0 2026-03-07 19:40:05 +00:00
Frederic CabassutandClaude Opus 4.6 edfb371b4c fix: make release script sed portable for Linux CI
macOS sed requires -i '' while GNU sed requires -i without argument.
Detect OS with uname to use the correct syntax.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:38:15 +01:00
github-actions[bot] b96312c2aa chore: pin docker-compose to v0.4.3 [skip ci] 2026-03-07 19:34:45 +00:00
Frederic CabassutandClaude Opus 4.6 9d1f66dcf7 feat: add app screenshots, lightbox, and polish landing page
- 6 screenshots (3 desktop + 3 mobile) with real app data
- Click-to-zoom lightbox on all screenshots
- Align header logo color (gold) between website and app
- Remove placeholder images

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:32:04 +01:00
Frederic CabassutandClaude Opus 4.6 39510e62cb feat: move app version to sidebar, auto-update website version on release
- Move version badge from footer to bottom of sidebar (right-aligned)
- Update release script to sed-replace version in website/index.html

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:32:04 +01:00
github-actions[bot] 4fda73875a chore: bump version to 0.4.3 2026-03-07 19:30:50 +00:00
github-actions[bot] 7a34e02f9d chore: pin docker-compose to v0.4.2 [skip ci] 2026-03-07 19:12:29 +00:00
github-actions[bot] 421c3e483b chore: bump version to 0.4.2 2026-03-07 19:08:57 +00:00
github-actions[bot] 7c7413b1e6 chore: pin docker-compose to v0.4.1 [skip ci] 2026-03-07 19:06:10 +00:00
github-actions[bot] 95ebb63cd3 chore: bump version to 0.4.1 2026-03-07 19:02:42 +00:00
github-actions[bot] 425ee4213b chore: pin docker-compose to v0.4.0 [skip ci] 2026-03-07 18:52:47 +00:00
github-actions[bot] f35e307378 chore: bump version to 0.4.0 2026-03-07 18:48:50 +00:00
Frederic CabassutandClaude Opus 4.6 56372bc855 chore: update favicon assets from RealFaviconGenerator
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 19:48:13 +01:00
Frederic CabassutandClaude Opus 4.6 bf6462b59b feat: add marketing landing page for crumbs.bretzel.app
Static landing page with parchment/retro theme matching the app aesthetic.
Includes GitHub Pages deploy workflow, responsive layout, feature grid,
deploy quick-start, and 404 page. Screenshot placeholders ready for
real captures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 19:48:13 +01:00
Frederic CabassutandClaude Opus 4.6 5dc1eda514 fix: patch transitive dependency vulnerabilities via pnpm overrides
Override serialize-javascript (>=7.0.3), esbuild (<0.25.0 → 0.25.12),
and cookie (>=0.7.0) to resolve Dependabot alerts #1, #2, #3.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:18:52 +01:00
Frederic CabassutandClaude Opus 4.6 bc527c25cc docs: add open source community files and README badges
Add LICENSE, CONTRIBUTING, CHANGELOG, SECURITY, CODE_OF_CONDUCT,
GitHub issue/PR templates, CODEOWNERS. Update package.json with
license/repo/author metadata. Add badges and logo to README.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:07:56 +01:00
github-actions[bot] 4641bad551 chore: pin docker-compose to v0.3.3 [skip ci] 2026-03-07 07:09:56 +00:00
github-actions[bot] cc590b756c chore: bump version to 0.3.3 2026-03-07 07:04:44 +00:00
Frederic CabassutandClaude Opus 4.6 8f40626f2f fix: update docker-compose only after Docker image is pushed
Move docker-compose.yml update from release.sh (pre-build) to a
post-release job in both bump.yml and release.yml. This ensures the
image tag in docker-compose only appears on main after the image
exists on ghcr.io, preventing Dokploy from trying to pull a
non-existent image.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 08:04:18 +01:00
github-actions[bot] c83d0f6968 chore: bump version to 0.3.2 2026-03-07 06:56:57 +00:00
Frederic CabassutandClaude Opus 4.6 b04d04f731 fix: include tags in sync pull response and update API spec
- GET /api/sync now attaches tags to notes (matching GET /api/notes
  and GET /api/search behavior)
- Add trashedAt field to Note schema in OpenAPI spec
- Add optional id field to NoteInput schema
- Add 400 error response for POST /api/sync
- Add response schema for GET attachments list
- Regenerate API.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 07:52:36 +01:00
Frederic CabassutandClaude Opus 4.6 1bacd69443 docs: update FEATURES.md, DEPLOYMENT.md, and CLAUDE.md
- FEATURES.md: add rich text editor section (Tiptap, formatting toolbar,
  raw markdown mode) and expand text formatting list
- DEPLOYMENT.md: fix docker-compose example to use image from ghcr.io
  instead of local build
- CLAUDE.md: add documentation maintenance instructions, update CI/CD
  section to reflect consolidated bump.yml workflow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 07:50:10 +01:00
github-actions[bot] 046ebf4539 chore: bump version to 0.3.1 2026-03-07 06:45:06 +00:00
Frederic CabassutandClaude Opus 4.6 85ff7cbbf5 fix: consolidate release pipeline into bump workflow
- Update release.sh to also bump docker-compose.yml image tag in the
  same commit, keeping the tagged commit in sync
- Add CI and Docker release jobs to bump.yml so it's self-contained
  (GITHUB_TOKEN pushes don't trigger other workflows)
- Remove "Pin docker-compose" post-step from release.yml since it's
  now handled by release.sh

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 07:44:15 +01:00
github-actions[bot] d84e3fa97e chore: bump version to 0.3.0 2026-03-07 06:08:01 +00:00
Frederic CabassutandClaude Opus 4.6 5cba95bcac feat: replace pretzel emoji with favicon across the app
Use the new favicon SVG in the header, login, setup, empty state,
and error pages. Keep the pretzel emoji in the footer tagline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 07:05:59 +01:00
Frederic CabassutandClaude Opus 4.6 7457847238 feat: add proper favicon set from RealFaviconGenerator
Replace inline emoji favicon with full cross-platform favicon support
including SVG, PNG, ICO, Apple touch icon, and PWA manifest icons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 07:05:59 +01:00
Frederic CabassutandClaude Opus 4.6 992fd7c1de rename "Horizontal rule" to "Divider" in formatting toolbar
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 07:05:59 +01:00
github-actions[bot] de7288c268 chore: bump version to 0.2.3 2026-03-07 05:32:22 +00:00
Frederic CabassutandClaude Opus 4.6 e5f49448b3 fix: update docker-compose image tag after CI builds the image
Move docker-compose version pinning from release script to the release
workflow so it runs after the image is pushed to GHCR. Dokploy will
always find the referenced image.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:48:28 +01:00
Frederic Cabassut 805951e9d5 chore: bump version to 0.2.2 2026-03-06 23:46:32 +01:00
Frederic CabassutandClaude Opus 4.6 85caa25636 chore: pin docker-compose image to version tag on release
Release script now updates docker-compose.yml with the exact version
tag so Dokploy always pulls the correct image.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:45:47 +01:00
Frederic Cabassut c2a3d20f0d chore: bump version to 0.2.1 2026-03-06 23:38:13 +01:00
Frederic CabassutandClaude Opus 4.6 db769f0ea5 chore: switch to GHCR image in docker-compose, remove dokploy webhook
- docker-compose.yml now uses pre-built ghcr.io image instead of build
- ORIGIN is configurable via env var for deployment
- Remove deploy job from release workflow (Dokploy pulls compose from repo)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:35:08 +01:00
Frederic CabassutandClaude Opus 4.6 f0361732fe fix: make E2E tests resilient to shared DB in parallel execution
- Add noteCard() helper to target specific cards by title instead of
  generic getByTestId('note-card') which fails with multiple matches
- Scope action button clicks (trash, pin, archive) within their card
- Scope tag-chip filter clicks to the tag-filter container
- Wait for networkidle after auth to ensure full hydration
- Remove "empty notes list" test incompatible with shared DB
- Use toHaveCount(0) instead of not.toBeVisible for search empty state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:32:42 +01:00
Frederic Cabassut ae94952163 chore: bump version to 0.2.0 2026-03-06 23:13:34 +01:00
Frederic CabassutandClaude Opus 4.6 3843726424 refactor: isolate auth layout, unify tags, add editor animation and pin indicator
- Create (auth) and (app) layout groups to isolate login/setup pages
  from the main app chrome (header, sidebar, footer)
- Use window.location.href instead of goto() after login/setup to
  ensure clean full-page load across layout groups
- Add reusable TagChip component, used in TagFilter and NoteCard
- Add fade-in/pop-in entrance animation to note editor overlay
- Add persistent clickable pin indicator on pinned cards (top-right
  bookmark icon, replaces hover-only pin button for pinned notes)
- Improve "Add a crumb..." button with + icon and dashed border
- Update CLAUDE.md with retro parchment design system guidelines
- Add /.claude/ to .gitignore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 e463c233c6 style: unify color system and add retro card shadows
Replace hardcoded colors (gray-500, #b44, red-300/red-50/red-600,
green-600) with CSS variables for theme consistency. Add --destructive,
--error-*, --success-*, and --card-shadow variables to :root. Soften
default card borders from --border to --border-subtle and add 8-bit
style hard-offset shadows with hover effects. Apply accent-color for
themed checkboxes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 d6f668b31d fix: prevent "No crumbs yet" flash on page refresh
Add notesLoaded store flag set after IDB read completes. Gate the empty
state message behind it so it only shows once we know the store is empty.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 d9c9663af2 style: update toggle icons to show destination mode
Checklist toggle: SquareCheck (→ checklist) / Type (→ text).
Markdown toggle: FileCode (→ markdown) / FileText (→ rich text).
Icons now show what you'll switch to, not the current state.
Remove active color highlight since icon swap communicates state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 fa806c5799 fix: color palette opens upward on mobile to avoid overflow
Change color picker popup from top-full to bottom-full so it appears
above the toolbar instead of overflowing below the editor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 948eb0d0fc fix: close sidebar when tapping outside on mobile
Add a semi-transparent backdrop overlay (lg:hidden) behind the sidebar
on mobile. Clicking/tapping the backdrop closes the sidebar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 00f0c82b09 fix: toolbar dropdowns open upward and no longer overflow on mobile
Remove scroll from toolbar, move undo/redo into More dropdown to fit
single line. All dropdowns now open upward (bottom-full) so they don't
overflow below the editor on small screens. Use Ellipsis icon for More.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 c19963135f fix: prevent note content overflow on mobile
Wrap editor content in a scrollable container with max-h-[60vh] so long
notes scroll within the editor instead of pushing it off-screen. Also make
the overlay itself scrollable with bottom padding for small viewports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 eaa0a832e3 style: close sidebar by default on mobile
Use window.matchMedia to check viewport width on mount. Sidebar defaults
to closed, then opens automatically on desktop (>= 1024px).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 3edde569f0 chore: add todo.md to gitignore and remove from tracking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 2df5353d0b style: collapse formatting toolbar into single line with More dropdown on mobile
Reorganize toolbar: inline formatting (bold/italic/strike/underline) as
primary buttons, move blockquote/code/alignment/HR into a "More" dropdown.
Use overflow-x-auto instead of flex-wrap for single-line scrollable toolbar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 9c10dd2679 style: swap icons based on toggle state for markdown and checklist modes
Markdown toggle: Eye (rich text) / Code (raw MD, highlighted).
Checklist toggle: List (text mode) / ListChecks (checklist, highlighted).
Icons now change on toggle so the active state is visually clear.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 da483ecaec fix: prevent color picker overflow on mobile
Constrain color picker popup width to viewport minus padding with a
max-width, preventing horizontal overflow on small screens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 0c403eba87 style: add subtle outline to unselected color swatches
Use --border-subtle for unselected swatches instead of transparent,
making each color option visually distinct.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 0a245672e1 style: collapse search bar into icon on mobile
On mobile, show a search icon that expands into a full-width search bar
with a back arrow to close. Desktop keeps the always-visible search bar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 4b247b2627 style: replace all inline SVGs with Lucide icons
Swap every inline SVG across Header, SearchBar, NoteCard, NoteEditor,
Checklist, and ImageUpload for Lucide components. Use Palette for the
color picker icon instead of the paintbrush. Zero inline SVGs remain.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 84cc47aabb style: use theme colors for sync status icon
Replace green/blue/yellow/red with palette colors: --primary for synced,
--primary with opacity for syncing, --text-muted for offline, muted red
for error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 c49e127d0f feat: offline-first note loading from IDB cache
Load notes from IndexedDB first for instant display, then sync from
server in background. All mutations now persist to IDB when online,
keeping the cache always current. Eliminates empty state flash on load.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 beec78fb47 feat: deep link to open notes via URL hash
Opening a note sets the URL to /#note-id, closing clears it.
On page load, the hash is read and the corresponding note is opened.
Uses replaceState to avoid polluting browser history.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 a71c8c927a fix: allow sidebar toggle on desktop via menu button
Remove lg:translate-x-0 override that forced sidebar open on desktop.
Main content and footer margin now conditional on sidebar state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 3d69a8ad74 style: remove redundant pin indicator from note card
The hover action button already provides pin/unpin functionality.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 85c2bf69e9 feat: allow checking/unchecking checklist items directly from the note card
Checkboxes in the card preview are now interactive — clicking one toggles
the item without opening the editor. Uses stopPropagation to prevent
the card click handler from firing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 6d3ab0ba04 fix: extract tags client-side for offline notes and server-side on sync
- Import extractTags in notes store to populate tags on optimistic notes
- Re-extract tags on offline update when title or content changes
- Run extractTags + syncNoteTags in processSyncPush after transaction

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 99602d6a24 style: remove bretzel emoji from header
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Frederic CabassutandClaude Opus 4.6 8f0c18d5fa fix: handle offline note editing with sync queue, toasts, and live sync indicator
- Extract toast store to standalone module so notes store can show toasts
- Add try/catch to all note mutations with optimistic updates + IDB queue on network error
- Wire up startSync/stopSync in layout to enable online/offline event listeners
- Reload notes from server after successful sync push to prevent data loss
- Connect SyncIndicator to real sync status with Lucide cloud icons
- Add 300ms minimum display time for status transitions
- Add e2e tests for offline create, edit, trash, and sync-on-reconnect

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:12:36 +01:00
Fred Cabassut 4a3219d6c6 Merge pull request #1 from bretzel-app/add-claude-github-actions-1772829898393
Add Claude Code GitHub Workflow
2026-03-06 21:45:29 +01:00
Fred Cabassut 5638cd5204 "Claude Code Review workflow" 2026-03-06 21:45:01 +01:00
Fred Cabassut ff16e05624 "Claude PR Assistant workflow" 2026-03-06 21:45:00 +01:00
Frederic CabassutandClaude Opus 4.6 c80a6dc088 ci: add automated version bump, tag, and release script
Add scripts/release.sh that infers bump type from conventional commits
(major/minor/patch), bumps package.json, commits, tags, and pushes.
Includes safety checks for branch, clean tree, and remote sync.

Also adds a workflow_dispatch GitHub Actions workflow (bump.yml) and
Makefile targets (release, release-patch, release-minor, release-major).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:40:48 +01:00
Frederic CabassutandClaude Opus 4.6 72b9097498 chore: bump version to 0.1.8
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:21:04 +01:00
Frederic CabassutandClaude Opus 4.6 2ab8c063af fix: resolve N+1 queries, add indexes, and improve DB operations
- Batch-fetch tags for notes in a single query instead of per-note loops
- Add composite indexes on notes, note_tags, and sync_log tables
- Wrap sync push in a transaction for atomicity
- Replace O(n) cleanExpiredSessions with single DELETE query
- Use SQL WHERE in getChangesSince instead of loading all notes
- Extract shared syncNoteTags utility to eliminate duplication
- Add root +error.svelte boundary matching app theme

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:12:39 +01:00
Frederic CabassutandClaude Opus 4.6 3ff08f9e35 chore: add agent skills for Svelte 5, SvelteKit, TypeScript, and Drizzle
Install project-level skills via skills.sh CLI:
- svelte5-best-practices (runes, snippets, events, migration)
- sveltekit-structure (routing, layouts, error boundaries)
- sveltekit-data-flow (load functions, form actions, serialization)
- typescript-best-practices (type-first development, discriminated unions)
- drizzle-migrations (SQLite schema, query patterns)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:11:36 +01:00
Frederic CabassutandClaude Opus 4.6 6fe2978657 style: redesign UI with parchment theme and JetBrains Mono font
Replace the default dark/white theme with a warm parchment aesthetic
using CSS custom properties, retro pixel overlay, and JetBrains Mono
monospace font across all components. Bump version to 0.1.7.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:51:10 +01:00
Frederic CabassutandClaude Opus 4.6 9222b46efb fix: pass app version via server load instead of Vite define
The Svelte 5 SSR compiler was optimizing away the __APP_VERSION__
constant set via Vite's define, causing the version to be missing
from the production footer. Use a +layout.server.ts load function
to pass the version from package.json instead.

Also bumps version to 0.1.6.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 07:18:59 +01:00
Frederic CabassutandClaude Opus 4.6 6fada9222a chore: bump version to 0.1.5
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 06:55:03 +01:00
Frederic CabassutandClaude Opus 4.6 7c4d83cafd fix: run Dokploy redeploy after Docker image push
Move the Dokploy webhook into a separate deploy job that depends on
the release job, ensuring the image is fully pushed before redeploying.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 06:36:56 +01:00
Frederic CabassutandClaude Opus 4.6 4d897c96aa fix: resolve __APP_VERSION__ type error in svelte-check
Move the global type declaration inside declare global block and
reference the constant via a local variable in the script block.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 06:14:59 +01:00
Frederic CabassutandClaude Opus 4.6 08d6dbc13d feat: display app version in footer
Inject version from package.json at build time via Vite's define and
show it in the layout footer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 00:05:25 +01:00
Frederic CabassutandClaude Opus 4.6 10371f6c94 feat: enable PWA offline support with service worker
Add SvelteKitPWA plugin to generate a service worker that precaches
static assets and uses NetworkFirst for API routes, enabling the app
to load offline. Generate pretzel emoji PWA icons and remove the old
static manifest (now generated by the plugin).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 00:03:34 +01:00
Frederic CabassutandClaude Opus 4.6 ab13461b8f feat: trigger Dokploy redeploy after release
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:55:45 +01:00
Frederic CabassutandClaude Opus 4.6 a95388fe3c fix: add .dockerignore to prevent host node_modules in image
Excludes node_modules, build artifacts, and test data from the Docker
build context. Fixes invalid ELF header error when building on macOS
for Linux containers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:29:10 +01:00
Frederic CabassutandClaude Opus 4.6 05dd13c08c fix: add workflow_call trigger to CI for reuse by release workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:25:54 +01:00
Frederic CabassutandClaude Opus 4.6 c81d5c8e6a chore: bump version to 0.1.1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:24:59 +01:00
Frederic CabassutandClaude Opus 4.6 475c457435 fix: type error on tiptap-markdown storage access
Cast editor storage to Record<string, any> since tiptap-markdown
does not ship type declarations for its storage extension.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:24:25 +01:00
Frederic CabassutandClaude Opus 4.6 91f241ecc7 feat: push Docker image to GitHub Container Registry on release
Replace generic registry secrets with ghcr.io using the built-in
GITHUB_TOKEN. Use docker/metadata-action for automatic semver tagging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:19:12 +01:00
Frederic CabassutandClaude Opus 4.6 acb43bfbf8 feat: rename "notes" to "crumbs" throughout the UI
Replace all user-facing "note(s)" with "crumb(s)" — sidebar, search
placeholder, editor placeholders, empty states. Use Lucide icons
(StickyNote, Archive, Trash2, Tag) in sidebar for consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:17:30 +01:00
Frederic CabassutandClaude Opus 4.6 d2bdcb695f feat: add sticky footer with branding
Add "Crumbs by Bretzel — made with 🥨 in Strasbourg" footer pinned
to the bottom of the page using flexbox layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:08:51 +01:00
Frederic CabassutandClaude Opus 4.6 33a8cd6afc feat: remove dark mode support
Remove ThemeToggle component, theme store, dark: Tailwind variants,
and dark color values from NOTE_COLORS. Simplify color logic to use
a single bg property. Delete dark mode e2e tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:07:18 +01:00
Frederic CabassutandClaude Opus 4.6 510d15e548 fix: undo/redo buttons now reactively update on editor changes
Add a tick counter that increments on every TipTap transaction,
propagated from TiptapEditor through NoteEditor to FormattingToolbar.
This forces Svelte 5 to re-evaluate canUndo/canRedo state in the
template since plain object method calls aren't tracked reactively.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:00:58 +01:00
Frederic CabassutandClaude Opus 4.6 3db759ab82 feat: icon-based formatting toolbar with dropdowns and link popover
Replace plain text toolbar labels with Lucide SVG icons. Group heading,
list, and alignment options into dropdown menus to reduce toolbar width.
Replace window.prompt link insertion with an inline popover (input, apply,
open, remove). Add underline and text-align TipTap extensions. Remove
task list from TipTap editor (broken styling). Fix task list bullet
rendering in note card preview. Increase editor min-height for markdown
mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:50:52 +01:00
Frederic CabassutandClaude Opus 4.6 2913e19f7c feat: replace textarea with TipTap rich text editor
- Add TipTap WYSIWYG editor (headless @tiptap/core) with StarterKit,
  TaskList, TaskItem, Link, Placeholder, and tiptap-markdown extensions
- Always-visible formatting toolbar with undo/redo, H1-H3, bold, italic,
  strikethrough, inline code, code block, blockquote, bullet/ordered/task
  lists, horizontal rule, and link
- Raw markdown toggle for power users
- Render rich text (HTML) on note cards instead of stripped plain text
- Update all e2e tests for TipTap contenteditable editor

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 22:24:33 +01:00
Frederic CabassutandClaude Opus 4.6 30475172c3 fix: render checklist items with checkboxes on note cards
Checklist-mode notes now display as a visual checklist preview (up to
8 items with overflow indicator) instead of raw markdown checkbox syntax.
Regular notes keep the truncated plain-text preview.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:32:53 +01:00
Frederic CabassutandClaude Opus 4.6 a550514a39 fix: flaky search e2e test due to duplicate title across test suites
Use a unique title "Grocery Errands" instead of "Shopping List" which
collided with the checklist test suite in the shared test database.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:26:54 +01:00
Frederic CabassutandClaude Opus 4.6 b55b6de571 docs: deduplicate README, add OpenAPI spec with auto-generated API docs
- Slim README to essentials, link to docs/ for details
- Add docs/openapi.yaml as single source of truth for the API
- Add scripts/generate-api-docs.ts to produce docs/API.md from the spec
- Add pnpm docs:api / make docs-api target

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:24:29 +01:00
Claude 1becc17724 feat: Crumbs by Bretzel — self-hostable notes app MVP
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 21:16:45 +01:00
30 changed files with 1047 additions and 219 deletions
+4 -7
View File
@@ -70,6 +70,9 @@ jobs:
type=semver,pattern={{major}}.{{minor}},value=${{ needs.bump.outputs.tag }}
type=raw,value=latest
- name: Set up QEMU (for multi-platform builds)
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -84,19 +87,13 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Make package public
run: |
gh api orgs/${{ github.repository_owner }}/packages/container/crumbs -X PATCH \
-f visibility=public
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
+4 -7
View File
@@ -30,6 +30,9 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest
- name: Set up QEMU (for multi-platform builds)
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -44,19 +47,13 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Make package public
run: |
gh api orgs/${{ github.repository_owner }}/packages/container/crumbs -X PATCH \
-f visibility=public
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
+1 -1
View File
@@ -54,7 +54,7 @@ Note colors for cards are defined in `src/lib/utils/colors.ts`.
- **Animations**: Crisp and fast (150ms `ease-out`). No spring physics, no bounce. The retro feel comes from snappy transitions.
- **Background texture**: A subtle 4px pixel grid overlay at 3% opacity (defined in `body::before`).
- **Checkboxes**: Use `accent-color: var(--primary)` globally — gold checkboxes match the theme.
- **Icons**: Lucide icons throughout. Keep at 16-20px size.
- **Icons**: Pixelarticons via `@iconify/svelte` — use `<Icon icon="pixelarticons:name" width={size} class="..." />`. Keep at 16-20px size. Exception: Bold, Italic, Strikethrough, Underline stay as Lucide (`lucide-svelte`). See full mapping in `docs/superpowers/specs/2026-03-20-pixelarticons-migration-design.md`.
- **Hover actions**: Never use `hidden group-hover:block` for action buttons — they're invisible on mobile (no hover). Use the opacity pattern instead: `max-md:opacity-100 md:opacity-0 transition-opacity md:group-hover:opacity-100`. This keeps actions always visible on touch devices and hover-revealed on desktop.
### Layout groups
+1 -1
View File
@@ -1,6 +1,6 @@
services:
crumbs:
image: ghcr.io/bretzel-app/crumbs:0.22.0
image: ghcr.io/bretzel-app/crumbs:0.22.3
container_name: crumbs
ports:
- "3000:3000"
+5 -5
View File
@@ -64,10 +64,10 @@
- Fog, Storm, Dusk, Blossom, Clay, Chalk
- Colors adapt to note theme
### Pin Notes
- Pin important notes to always appear at top
- Pinned section separated from unpinned notes
- Toggle pin from note card hover actions
### Favorite Notes
- Favorite important notes to always appear at top
- Favorites section separated from other notes
- Toggle favorite from note card hover actions
### Archive
- Archive notes to declutter main view
@@ -138,7 +138,7 @@
### MCP Server (Model Context Protocol)
- Built-in MCP endpoint at `POST /api/mcp` (Streamable HTTP transport)
- 14 tools: list, get, create, update, trash, restore, archive, unarchive, delete, search notes; list tags; pin, reorder notes; upload images
- 14 tools: list, get, create, update, trash, restore, archive, unarchive, delete, search notes; list tags; favorite, reorder notes; upload images
- API key authentication (Bearer token)
- Compatible with Claude Code, Claude Desktop, and other MCP clients
- Stateful sessions with automatic cleanup
@@ -0,0 +1,672 @@
# Pixelarticons Migration + Favorites Rename — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace Lucide icons with pixelarticons via @iconify/svelte across the app (except 4 formatting letterforms), and rename user-facing "pinned" to "favorites" with a heart icon.
**Architecture:** Each component file gets its Lucide imports replaced with a single `Icon` import from `@iconify/svelte`. The `size` prop becomes `width`, and the icon name becomes a `pixelarticons:name` string. The 4 formatting letterforms (Bold, Italic, Strikethrough, Underline) stay as Lucide. The favorites rename touches stores, components, MCP server, and one e2e test.
**Tech Stack:** `@iconify/svelte` v5.2.1, `@iconify-json/pixelarticons` v1.2.4 (both already installed), `lucide-svelte` (kept for 4 icons)
**Spec:** `docs/superpowers/specs/2026-03-20-pixelarticons-migration-design.md`
---
## File Structure
No new files created. All changes are modifications to existing files:
**Icon migration (16 files):** Replace Lucide imports with `@iconify/svelte` Icon component.
**Favorites rename (4 source files + 1 test file):** Rename stores, functions, labels, test IDs.
---
### Task 1: Favorites Rename — Stores
**Files:**
- Modify: `src/lib/stores/notes.ts:38-44,234-236`
- [ ] **Step 1: Rename store exports and function**
In `src/lib/stores/notes.ts`, make these changes:
```typescript
// Line 38-44: Rename stores
export const favoriteNotes = derived(filteredNotes, ($notes) =>
sortNotes($notes.filter((n) => n.pinned))
);
export const unfavoritedNotes = derived(filteredNotes, ($notes) =>
sortNotes($notes.filter((n) => !n.pinned))
);
// Line 234-236: Rename function
export async function toggleFavorite(id: string, currentPinned: boolean): Promise<Note | null> {
return updateNote(id, { pinned: !currentPinned });
}
```
Note: The `pinned` field in updateNote stays — it's the DB column name.
- [ ] **Step 2: Verify no type errors**
Run: `pnpm check 2>&1 | head -30`
Expected: Errors in files that still reference old names (NoteCard, NotesView) — that's expected, we fix those next.
- [ ] **Step 3: Commit**
```bash
git add src/lib/stores/notes.ts
git commit -m "refactor: rename pinned stores to favorites"
```
---
### Task 2: Favorites Rename — NoteCard Component
**Files:**
- Modify: `src/lib/components/NoteCard.svelte:4,110-118,170-178`
- [ ] **Step 1: Update import**
Change line 4:
```typescript
import { toggleFavorite, trashNote, archiveNote, unarchiveNote, restoreNote, deleteNote, leaveNote, currentFilter } from '$lib/stores/notes.js';
```
- [ ] **Step 2: Update favorite indicator (lines 110-118)**
Replace the pinned indicator block:
```svelte
{#if note.pinned}
<button
onclick={stop(() => toggleFavorite(note.id, note.pinned))}
class="rounded-sm p-1 text-[var(--primary)] hover:bg-[var(--border)]/10"
title="Unfavorite"
data-testid="favorite-indicator"
>
<Bookmark class="h-4 w-4 fill-[var(--primary)]" />
</button>
{/if}
```
- [ ] **Step 3: Update favorite button (lines 170-178)**
Replace the pin button block:
```svelte
{#if !note.pinned}
<button
onclick={stop(() => toggleFavorite(note.id, note.pinned))}
class="rounded-sm p-1.5 hover:bg-[var(--border)]/10"
title="Favorite"
data-testid="favorite-btn"
>
<Bookmark class="h-4 w-4" />
</button>
{/if}
```
- [ ] **Step 4: Commit**
```bash
git add src/lib/components/NoteCard.svelte
git commit -m "refactor: rename pin to favorite in NoteCard"
```
---
### Task 3: Favorites Rename — NotesView Component
**Files:**
- Modify: `src/lib/components/NotesView.svelte:5,89-104`
- [ ] **Step 1: Update imports (line 5)**
```typescript
import { favoriteNotes, unfavoritedNotes, selectedTag, currentFilter, notes, notesLoaded, loadNotes, updateSortOrders } from '$lib/stores/notes.js';
```
- [ ] **Step 2: Update template references**
Replace all `$pinnedNotes` with `$favoriteNotes`, `$unpinnedNotes` with `$unfavoritedNotes`, and label `"Pinned"` with `"Favorites"`:
Line 89: `{#if $favoriteNotes.length > 0}`
Line 91: `<NoteGrid notes={$favoriteNotes} label="Favorites" onEdit={openEditor} draggable dndType="pinned-notes" onReorder={handleReorder} />`
Line 96: `notes={$unfavoritedNotes}`
Line 97: `label={$favoriteNotes.length > 0 ? 'Others' : ''}`
Line 104: `{#if $notesLoaded && $favoriteNotes.length === 0 && $unfavoritedNotes.length === 0}`
Note: Keep `dndType="pinned-notes"` unchanged — it's an internal DnD identifier, not user-facing.
- [ ] **Step 3: Commit**
```bash
git add src/lib/components/NotesView.svelte
git commit -m "refactor: rename pinned to favorites in NotesView"
```
---
### Task 4: Favorites Rename — MCP Server
**Files:**
- Modify: `src/lib/server/mcp/server.ts:241-265`
- [ ] **Step 1: Rename MCP tool**
Replace the `pin_note` tool definition (lines 241-265):
```typescript
server.tool(
'favorite_note',
'Favorite or unfavorite a note',
{
id: z.string().describe('Note ID'),
pinned: z.boolean().describe('Whether to favorite (true) or unfavorite (false)')
},
async ({ id, pinned }: { id: string; pinned: boolean }) => {
const result = updateNote(db, userId, id, { pinned });
if (!result) {
return {
content: [{ type: 'text' as const, text: 'Note not found' }],
isError: true
};
}
return {
content: [
{
type: 'text' as const,
text: `Note "${result.title}" ${pinned ? 'favorited' : 'unfavorited'}`
}
]
};
}
);
```
Note: The `pinned` parameter name stays since it maps to the DB column.
- [ ] **Step 2: Commit**
```bash
git add src/lib/server/mcp/server.ts
git commit -m "refactor: rename pin_note MCP tool to favorite_note"
```
---
### Task 5: Favorites Rename — E2E Test
**Files:**
- Modify: `tests/e2e/organization.spec.ts:4-15`
- [ ] **Step 1: Update test scenario**
Replace lines 4-15:
```typescript
test('Scenario: Favorited note appears under the Favorites section', async ({ authenticatedPage: page }) => {
// Given a note titled "Favorite Me" exists
await createNote(page, 'Favorite Me');
// When the user favorites the note
const favCard = noteCard(page, 'Favorite Me');
await favCard.hover();
await favCard.getByTestId('favorite-btn').first().click({ force: true });
// Then the "Favorites" section is visible
await expect(page.getByText('Favorites')).toBeVisible();
});
```
- [ ] **Step 2: Run check to verify favorites rename is complete**
Run: `pnpm check 2>&1 | head -20`
Expected: PASS (no errors related to old pin names)
- [ ] **Step 3: Commit**
```bash
git add tests/e2e/organization.spec.ts
git commit -m "test: update pin test to favorites"
```
---
### Task 6: Icon Migration — Header + SearchBar
**Files:**
- Modify: `src/lib/components/Layout/Header.svelte:2-5`
- Modify: `src/lib/components/SearchBar.svelte:4-6`
- [ ] **Step 1: Migrate Header.svelte**
Replace imports (lines 2-5):
```typescript
import SearchBar from '../SearchBar.svelte';
import SyncIndicator from '../SyncIndicator.svelte';
import Icon from '@iconify/svelte';
```
Replace icon usage in template:
- `<Menu class="h-6 w-6 text-[var(--text)]" />` → `<Icon icon="pixelarticons:menu" class="h-6 w-6 text-[var(--text)]" />`
- `<Search class="h-5 w-5 text-[var(--text)]" />` → `<Icon icon="pixelarticons:search" class="h-5 w-5 text-[var(--text)]" />`
- [ ] **Step 2: Migrate SearchBar.svelte**
Replace imports (lines 4-6):
```typescript
import Icon from '@iconify/svelte';
```
Replace icon usage:
- `<Search .../>` → `<Icon icon="pixelarticons:search" .../>`
- `<X .../>` → `<Icon icon="pixelarticons:close" .../>`
- `<ArrowLeft .../>` → `<Icon icon="pixelarticons:arrow-left" .../>`
Match existing `class` attributes exactly — just change the component and add the `icon` prop.
- [ ] **Step 3: Commit**
```bash
git add src/lib/components/Layout/Header.svelte src/lib/components/SearchBar.svelte
git commit -m "feat: migrate Header and SearchBar to pixelarticons"
```
---
### Task 7: Icon Migration — Sidebar
**Files:**
- Modify: `src/lib/components/Layout/Sidebar.svelte:2-4,19-23,47,65,81`
- [ ] **Step 1: Replace imports and nav items**
Replace the import (line 4):
```typescript
import Icon from '@iconify/svelte';
```
Replace navItems array (lines 19-23):
```typescript
const navItems = [
{ href: '/', label: 'Crumbs', icon: 'pixelarticons:sticky-note', match: (p: string) => p === '/' },
{ href: '/archive', label: 'Archive', icon: 'pixelarticons:archive', match: (p: string) => p === '/archive' },
{ href: '/trash', label: 'Trash', icon: 'pixelarticons:trash', match: (p: string) => p === '/trash' }
];
```
- [ ] **Step 2: Update template icon usage**
Line 47: `<item.icon size={20} />` → `<Icon icon={item.icon} width={20} />`
Line 65: `<Tag size={16} />` → `<Icon icon="pixelarticons:label" width={16} />`
Line 81: `<Settings size={20} />` → `<Icon icon="pixelarticons:settings-cog" width={20} />`
- [ ] **Step 3: Commit**
```bash
git add src/lib/components/Layout/Sidebar.svelte
git commit -m "feat: migrate Sidebar to pixelarticons"
```
---
### Task 8: Icon Migration — NoteCard
**Files:**
- Modify: `src/lib/components/NoteCard.svelte:5-13,117,159,167,177,187,196,206,215`
- [ ] **Step 1: Replace imports**
Replace lines 5-13:
```typescript
import Icon from '@iconify/svelte';
```
Remove all individual Lucide imports (Undo2, Trash2, Bookmark, Archive, ArchiveRestore, UserMinus).
- [ ] **Step 2: Replace all icon usages in template**
- `<Bookmark class="h-4 w-4 fill-[var(--primary)]" />` (favorite indicator) → `<Icon icon="pixelarticons:heart" class="h-4 w-4 fill-[var(--primary)]" />`
- `<Bookmark class="h-4 w-4" />` (favorite button) → `<Icon icon="pixelarticons:heart" class="h-4 w-4" />`
- `<Undo2 class="h-4 w-4" />` (restore) → `<Icon icon="pixelarticons:undo" class="h-4 w-4" />`
- `<Trash2 class="h-4 w-4 text-[var(--destructive)]" />` → `<Icon icon="pixelarticons:trash" class="h-4 w-4 text-[var(--destructive)]" />`
- `<Trash2 class="h-4 w-4" />` → `<Icon icon="pixelarticons:trash" class="h-4 w-4" />`
- `<Archive class="h-4 w-4" />` → `<Icon icon="pixelarticons:archive" class="h-4 w-4" />`
- `<ArchiveRestore class="h-4 w-4" />` → `<Icon icon="pixelarticons:archive" class="h-4 w-4" />`
- `<UserMinus class="h-4 w-4" />` → `<Icon icon="pixelarticons:user-minus" class="h-4 w-4" />`
- [ ] **Step 3: Commit**
```bash
git add src/lib/components/NoteCard.svelte
git commit -m "feat: migrate NoteCard to pixelarticons"
```
---
### Task 9: Icon Migration — NoteEditor
**Files:**
- Modify: `src/lib/components/NoteEditor.svelte:16-24`
- [ ] **Step 1: Replace imports**
Replace lines 16-24:
```typescript
import Icon from '@iconify/svelte';
```
- [ ] **Step 2: Replace all icon usages**
Search the file for each Lucide component and replace:
- `<Palette .../>` → `<Icon icon="pixelarticons:colors-swatch" .../>`
- `<SquareCheck .../>` → `<Icon icon="pixelarticons:checkbox-on" .../>`
- `<ImageIcon .../>` → `<Icon icon="pixelarticons:image" .../>`
- `<Type .../>` → `<Icon icon="pixelarticons:art-text" .../>`
- `<FileCode .../>` → `<Icon icon="pixelarticons:script" .../>`
- `<FileText .../>` → `<Icon icon="pixelarticons:file-text" .../>`
- `<UserPlus .../>` → `<Icon icon="pixelarticons:user-plus" .../>`
- `<Users .../>` → `<Icon icon="pixelarticons:users" .../>`
- `<History .../>` → `<Icon icon="pixelarticons:timeline" .../>`
For each, keep the existing `size` prop but rename to `width`. Keep `class` as-is.
- [ ] **Step 3: Commit**
```bash
git add src/lib/components/NoteEditor.svelte
git commit -m "feat: migrate NoteEditor to pixelarticons"
```
---
### Task 10: Icon Migration — FormattingToolbar
This is the largest file. Bold, Italic, Strikethrough, Underline stay as Lucide. Everything else migrates.
**Files:**
- Modify: `src/lib/components/FormattingToolbar.svelte:2-31,131-482`
- [ ] **Step 1: Replace imports**
Replace the import block (lines 3-31):
```typescript
import {
Bold,
Italic,
Strikethrough,
Underline
} from 'lucide-svelte';
import Icon from '@iconify/svelte';
```
- [ ] **Step 2: Replace icon usages in template**
Replace every non-B/I/S/U icon. The pattern: `<IconName size={iconSize} />` → `<Icon icon="pixelarticons:name" width={iconSize} />`. Full mapping:
- `<Undo2 size={iconSize} />` → `<Icon icon="pixelarticons:undo" width={iconSize} />`
- `<Redo2 size={iconSize} />` → `<Icon icon="pixelarticons:redo" width={iconSize} />`
- `<Heading size={iconSize} />` → `<Icon icon="pixelarticons:heading" width={iconSize} />`
- `<List size={iconSize} />` (appears 3x) → `<Icon icon="pixelarticons:list" width={iconSize} />`
- `<ListOrdered size={iconSize} />` → `<Icon icon="pixelarticons:bulletlist" width={iconSize} />`
- `<ListChecks size={iconSize} />` → `<Icon icon="pixelarticons:checklist" width={iconSize} />`
- `<Link size={iconSize} />` → `<Icon icon="pixelarticons:link" width={iconSize} />`
- `<CornerDownLeft size={16} />` → `<Icon icon="pixelarticons:corner-down-left" width={16} />`
- `<ExternalLink size={16} />` → `<Icon icon="pixelarticons:external-link" width={16} />`
- `<Trash2 size={16} />` → `<Icon icon="pixelarticons:trash" width={16} />`
- `<Table2 size={iconSize} />` (appears 2x) → `<Icon icon="pixelarticons:table" width={iconSize} />`
- `<ChevronDown size={chevronSize} />` (appears 4x) → `<Icon icon="pixelarticons:chevron-down" width={chevronSize} />`
- `<BetweenHorizontalEnd size={iconSize} />` → `<Icon icon="pixelarticons:add-row" width={iconSize} />`
- `<BetweenVerticalEnd size={iconSize} />` → `<Icon icon="pixelarticons:add-col" width={iconSize} />`
- `<Minus size={iconSize} />` (appears 3x) → `<Icon icon="pixelarticons:minus" width={iconSize} />`
- `<RemoveFormatting size={iconSize} />` → `<Icon icon="pixelarticons:remove-box" width={iconSize} />`
- `<Code size={iconSize} />` → `<Icon icon="pixelarticons:braces" width={iconSize} />`
- `<TextQuote size={iconSize} />` → `<Icon icon="pixelarticons:quote-text-inline" width={iconSize} />`
- `<CodeXml size={iconSize} />` → `<Icon icon="pixelarticons:braces" width={iconSize} />`
- `<AlignLeft size={iconSize} />` (2x) → `<Icon icon="pixelarticons:text-align-left" width={iconSize} />`
- `<AlignCenter size={iconSize} />` (2x) → `<Icon icon="pixelarticons:text-align-center" width={iconSize} />`
- `<AlignRight size={iconSize} />` (2x) → `<Icon icon="pixelarticons:text-align-right" width={iconSize} />`
- `<AlignJustify size={iconSize} />` (2x) → `<Icon icon="pixelarticons:text-align-justify" width={iconSize} />`
Leave these unchanged: `<Bold size={iconSize} />`, `<Italic size={iconSize} />`, `<Strikethrough size={iconSize} />`, `<Underline size={iconSize} />`
- [ ] **Step 3: Commit**
```bash
git add src/lib/components/FormattingToolbar.svelte
git commit -m "feat: migrate FormattingToolbar to pixelarticons (keep B/I/S/U as Lucide)"
```
---
### Task 11: Icon Migration — SyncIndicator
**Files:**
- Modify: `src/lib/components/SyncIndicator.svelte:4-7,54-61`
- [ ] **Step 1: Replace imports**
Replace lines 4-7:
```typescript
import Icon from '@iconify/svelte';
```
- [ ] **Step 2: Replace icon usages**
Replace the status icon block (lines 53-61):
```svelte
{#if displayStatus === 'synced'}
<Icon icon="pixelarticons:cloud-done" class="h-5 w-5 text-[var(--primary)]" />
{:else if displayStatus === 'syncing'}
<Icon icon="pixelarticons:sync" class="h-5 w-5 text-[var(--primary)] opacity-60" />
{:else if displayStatus === 'offline'}
<Icon icon="pixelarticons:cloud" class="h-5 w-5 text-[var(--text-muted)]" />
{:else}
<Icon icon="pixelarticons:cloud" class="h-5 w-5 text-[var(--destructive)]" />
{/if}
```
- [ ] **Step 3: Commit**
```bash
git add src/lib/components/SyncIndicator.svelte
git commit -m "feat: migrate SyncIndicator to pixelarticons"
```
---
### Task 12: Icon Migration — Remaining Small Components
**Files:**
- Modify: `src/lib/components/NoteHistory.svelte:3-5`
- Modify: `src/lib/components/ShareDialog.svelte:3-5`
- Modify: `src/lib/components/Checklist.svelte:2-5`
- Modify: `src/lib/components/CollaboratorPopover.svelte:3`
- Modify: `src/lib/components/ImageLightbox.svelte:2`
- Modify: `src/lib/components/ImageUpload.svelte:2-4`
- Modify: `src/lib/components/NotesView.svelte:8`
- [ ] **Step 1: Migrate NoteHistory.svelte**
Replace imports:
```typescript
import Icon from '@iconify/svelte';
```
Replace usages:
- `<X .../>` → `<Icon icon="pixelarticons:close" .../>`
- `<RotateCcw .../>` → `<Icon icon="pixelarticons:undo" .../>`
- `<Clock .../>` → `<Icon icon="pixelarticons:clock" .../>`
- [ ] **Step 2: Migrate ShareDialog.svelte**
Replace imports:
```typescript
import Icon from '@iconify/svelte';
```
Replace usages:
- `<X .../>` → `<Icon icon="pixelarticons:close" .../>`
- `<UserMinus .../>` → `<Icon icon="pixelarticons:user-minus" .../>`
- `<Search .../>` → `<Icon icon="pixelarticons:search" .../>`
- [ ] **Step 3: Migrate Checklist.svelte**
Replace imports:
```typescript
import Icon from '@iconify/svelte';
```
Replace usages:
- `<XIcon .../>` → `<Icon icon="pixelarticons:close" .../>`
- `<Plus .../>` → `<Icon icon="pixelarticons:plus" .../>`
- `<GripVertical .../>` → `<Icon icon="pixelarticons:drag-and-drop" .../>`
- `<ChevronRight .../>` → `<Icon icon="pixelarticons:chevron-right" .../>`
- [ ] **Step 4: Migrate CollaboratorPopover.svelte**
Replace import:
```typescript
import Icon from '@iconify/svelte';
```
Replace: `<Users .../>` → `<Icon icon="pixelarticons:users" .../>`
- [ ] **Step 5: Migrate ImageLightbox.svelte**
Replace import:
```typescript
import Icon from '@iconify/svelte';
```
Replace: `<XIcon .../>` → `<Icon icon="pixelarticons:close" .../>`
- [ ] **Step 6: Migrate ImageUpload.svelte**
Replace imports:
```typescript
import Icon from '@iconify/svelte';
```
Replace usages:
- `<XIcon .../>` → `<Icon icon="pixelarticons:close" .../>`
- `<Star .../>` → `<Icon icon="pixelarticons:pin" .../>`
- `<RefreshCw .../>` → `<Icon icon="pixelarticons:reload" .../>`
- [ ] **Step 7: Migrate NotesView.svelte (Plus icon)**
Replace import (line 8):
```typescript
import Icon from '@iconify/svelte';
```
Replace usages:
- `<Plus class="h-4 w-4" />` → `<Icon icon="pixelarticons:plus" class="h-4 w-4" />`
- `<Plus class="h-5 w-5" />` → `<Icon icon="pixelarticons:plus" class="h-5 w-5" />`
- [ ] **Step 8: Commit**
```bash
git add src/lib/components/NoteHistory.svelte src/lib/components/ShareDialog.svelte src/lib/components/Checklist.svelte src/lib/components/CollaboratorPopover.svelte src/lib/components/ImageLightbox.svelte src/lib/components/ImageUpload.svelte src/lib/components/NotesView.svelte
git commit -m "feat: migrate remaining components to pixelarticons"
```
---
### Task 13: Icon Migration — Settings Pages
**Files:**
- Modify: `src/routes/(app)/settings/+layout.svelte:3`
- Modify: `src/routes/(app)/settings/mcp/+page.svelte:2`
- [ ] **Step 1: Migrate settings layout**
Replace import (line 3):
```typescript
import Icon from '@iconify/svelte';
```
Replace usages:
- `<SlidersHorizontal size={16} />` → `<Icon icon="pixelarticons:sliders" width={16} />`
- `<User size={16} />` → `<Icon icon="pixelarticons:user" width={16} />`
- `<Cpu size={16} />` → `<Icon icon="pixelarticons:cpu" width={16} />`
- `<ShieldCheck size={16} />` → `<Icon icon="pixelarticons:shield" width={16} />`
- `<Info size={16} />` → `<Icon icon="pixelarticons:info-box" width={16} />`
- [ ] **Step 2: Migrate MCP settings page**
Replace import (line 2):
```typescript
import Icon from '@iconify/svelte';
```
Replace usages:
- `<Key .../>` → `<Icon icon="pixelarticons:lock" .../>`
- `<Copy .../>` → `<Icon icon="pixelarticons:copy" .../>`
- `<Trash2 .../>` → `<Icon icon="pixelarticons:trash" .../>`
- `<Plus .../>` → `<Icon icon="pixelarticons:plus" .../>`
- `<Check .../>` → `<Icon icon="pixelarticons:check" .../>`
- [ ] **Step 3: Commit**
```bash
git add src/routes/(app)/settings/+layout.svelte src/routes/(app)/settings/mcp/+page.svelte
git commit -m "feat: migrate settings pages to pixelarticons"
```
---
### Task 14: Update docs/FEATURES.md
**Files:**
- Modify: `docs/FEATURES.md:67-70,141`
- [ ] **Step 1: Rename "Pin Notes" section to "Favorite Notes"**
Replace lines 67-70:
```markdown
### Favorite Notes
- Favorite important notes to always appear at top
- Favorites section separated from other notes
- Toggle favorite from note card hover actions
```
- [ ] **Step 2: Update MCP tools description**
Line 141: replace `pin` with `favorite` in the tools list:
```markdown
- 14 tools: list, get, create, update, trash, restore, archive, unarchive, delete, search notes; list tags; favorite, reorder notes; upload images
```
- [ ] **Step 3: Commit**
```bash
git add docs/FEATURES.md
git commit -m "docs: rename pin to favorites in FEATURES.md"
```
---
### Task 15: Verification
- [ ] **Step 1: Run type check**
Run: `pnpm check`
Expected: PASS with no errors
- [ ] **Step 2: Run unit tests**
Run: `pnpm test:unit`
Expected: All tests pass (DB/CRDT tests use `pinned` field which is unchanged)
- [ ] **Step 3: Run e2e tests**
Run: `pnpm test:e2e`
Expected: All tests pass including the updated organization spec
- [ ] **Step 4: Update CLAUDE.md icons section**
Add to the "Visual rules" section in CLAUDE.md after the "Icons" line:
```markdown
- **Icons**: Pixelarticons via `@iconify/svelte` — use `<Icon icon="pixelarticons:name" width={size} class="..." />`. Exception: Bold, Italic, Strikethrough, Underline stay as Lucide (`lucide-svelte`). See full mapping in `docs/superpowers/specs/2026-03-20-pixelarticons-migration-design.md`.
```
- [ ] **Step 5: Commit docs update**
```bash
git add CLAUDE.md
git commit -m "docs: update CLAUDE.md with pixelarticons icon usage"
```
@@ -0,0 +1,182 @@
# Icon Migration: Lucide → Pixelarticons + "Pinned" → "Favorites" Rename
**Date:** 2026-03-20
**Status:** Approved
## Summary
Replace Lucide icons with pixelarticons (via `@iconify/svelte`) across the app to match the retro parchment aesthetic. Keep Lucide only for 4 text-formatting letterform icons (Bold, Italic, Strikethrough, Underline). Additionally, rename the user-facing "pinned" concept to "favorites" with a heart icon, while keeping the `pinned` database column unchanged.
## Motivation
The current Lucide icons have smooth, modern strokes that clash with the 8-bit retro parchment design system. Pixelarticons are drawn on a strict 24×24 pixel grid with no anti-aliasing — a natural fit for the `Press Start 2P` pixel font and hard-offset shadow aesthetic already in use.
## Technical Approach
### Icon Library
- **Package:** `@iconify/svelte` (v5.2.1, already installed) + `@iconify-json/pixelarticons` (v1.2.4, already installed)
- **Usage:** `<Icon icon="pixelarticons:icon-name" width={size} class="..." />`
- **Tree-shaking:** Iconify handles this automatically — only icons used in code are bundled
### Icon Mapping
56 icons migrated to pixelarticons, 4 kept as Lucide:
#### Navigation & UI
| Lucide | Pixelarticons |
|--------|--------------|
| Menu | `menu` |
| Search | `search` |
| X / XIcon | `close` |
| Plus | `plus` |
| Check | `check` |
| ArrowLeft | `arrow-left` |
| ChevronRight | `chevron-right` |
| ChevronDown | `chevron-down` |
| Settings | `settings-cog` |
| ExternalLink | `external-link` |
#### Notes & Content
| Lucide | Pixelarticons | Notes |
|--------|--------------|-------|
| StickyNote | `sticky-note` | |
| Archive | `archive` | |
| ArchiveRestore | `archive` | Context makes it clear |
| Trash2 | `trash` | |
| Bookmark | `heart` | Renamed to "favorite" |
| Clock | `clock` | |
| History | `timeline` | |
| FileText | `file-text` | |
| FileCode | `script` | |
| ImageIcon | `image` | |
| Palette | `colors-swatch` | |
| GripVertical | `drag-and-drop` | |
| Star | `pin` | Used in ImageUpload for "featured image" toggle — not related to favorites |
| SquareCheck | `checkbox-on` | |
| Type | `art-text` | |
#### Text Formatting (toolbar)
| Lucide | Pixelarticons | Notes |
|--------|--------------|-------|
| **Bold** | **Keep Lucide** | Letterform |
| **Italic** | **Keep Lucide** | Letterform |
| **Strikethrough** | **Keep Lucide** | Letterform |
| **Underline** | **Keep Lucide** | Letterform |
| Code | `braces` | |
| CodeXml | `braces` | |
| Heading | `heading` | |
| Link | `link` | |
| TextQuote | `quote-text-inline` | |
| List | `list` | |
| ListOrdered | `bulletlist` | |
| ListChecks | `checklist` | |
| AlignLeft | `text-align-left` | |
| AlignCenter | `text-align-center` | |
| AlignRight | `text-align-right` | |
| AlignJustify | `text-align-justify` | |
| Table2 | `table` | |
| Minus | `minus` | |
| RemoveFormatting | `remove-box` | |
| CornerDownLeft | `corner-down-left` | |
| BetweenHorizontalEnd | `add-row` | |
| BetweenVerticalEnd | `add-col` | |
#### Undo/Redo
| Lucide | Pixelarticons |
|--------|--------------|
| Undo2 | `undo` |
| Redo2 | `redo` |
| RotateCcw | `undo` |
| RefreshCw | `reload` |
#### Sync Status
| Lucide | Pixelarticons | Notes |
|--------|--------------|-------|
| CloudCheck | `cloud-done` | |
| CloudSync | `sync` | |
| CloudOff | `cloud` | Color conveys offline state |
| CloudAlert | `cloud` | Color conveys error state |
#### Users & Admin
| Lucide | Pixelarticons |
|--------|--------------|
| User | `user` |
| Users | `users` |
| UserPlus | `user-plus` |
| UserMinus | `user-minus` |
| Cpu | `cpu` |
| ShieldCheck | `shield` |
| SlidersHorizontal | `sliders` |
| Info | `info-box` |
| Key | `lock` |
| Copy | `copy` |
| Tag | `label` |
### "Pinned" → "Favorites" Rename
**Scope:** User-facing labels, function names, store names, MCP tool names, test IDs. The database column `pinned` stays unchanged to avoid a migration.
**Renames:**
| Current | New |
|---------|-----|
| `pinnedNotes` (store) | `favoriteNotes` |
| `unpinnedNotes` (store) | `unfavoritedNotes` |
| `togglePin()` | `toggleFavorite()` |
| `"Pinned"` label in NotesView | `"Favorites"` |
| `"Pin"` / `"Unpin"` tooltips in NoteCard | `"Favorite"` / `"Unfavorite"` |
| `pin_note` MCP tool | `favorite_note` |
| `pin-btn` / `pin-indicator` test IDs | `favorite-btn` / `favorite-indicator` |
**Not renamed (internal/DB):**
- `pinned` column in `notes` and `noteUserState` tables
- `pinned` field in TypeScript types (`Note`, `NoteUpdate`, `NoteChange`)
- `pinned` references in CRDT sync logic
- `pinned` in server-side sync overlay
## Files to Modify
### Icon migration (16 files)
1. `src/lib/components/Layout/Sidebar.svelte`
2. `src/lib/components/Layout/Header.svelte`
3. `src/lib/components/FormattingToolbar.svelte`
4. `src/lib/components/NoteCard.svelte`
5. `src/lib/components/NoteEditor.svelte`
6. `src/lib/components/NoteHistory.svelte`
7. `src/lib/components/SyncIndicator.svelte`
8. `src/lib/components/SearchBar.svelte`
9. `src/lib/components/ShareDialog.svelte`
10. `src/lib/components/Checklist.svelte`
11. `src/lib/components/CollaboratorPopover.svelte`
12. `src/lib/components/ImageLightbox.svelte`
13. `src/lib/components/ImageUpload.svelte`
14. `src/lib/components/NotesView.svelte`
15. `src/routes/(app)/settings/+layout.svelte`
16. `src/routes/(app)/settings/mcp/+page.svelte`
### Favorites rename (7 files)
1. `src/lib/stores/notes.ts` — store names + `toggleFavorite`
2. `src/lib/components/NoteCard.svelte` — tooltips, test IDs, icon
3. `src/lib/components/NotesView.svelte` — label, store imports
4. `src/lib/server/mcp/server.ts` — tool name + description
5. `docs/FEATURES.md` — update feature description
6. `tests/e2e/organization.spec.ts` — `pin-btn` test ID + `getByText('Pinned')` → `getByText('Favorites')` + scenario names/comments
7. Note: `tests/e2e/sharing.spec.ts` references `pinned` as a data field — does NOT need renaming (DB field stays `pinned`)
### Cleanup
- Remove `lucide-svelte` from `package.json` once only 4 icons remain — or keep it as a dependency for those 4. Decision: keep it, the 4 formatting icons are deeply integrated.
### Documentation
- Update `CLAUDE.md` "Icons" section to document pixelarticons usage
- Update `docs/FEATURES.md` if it mentions pinning
## Testing Strategy
- Run `pnpm check` after migration to catch type errors
- Run `pnpm test:unit` to verify sync/CRDT tests still pass (they use `pinned` field which is unchanged)
- Run `pnpm test:e2e` to verify UI still works — tests referencing `pin-btn`/`pin-indicator` will need updating
- Visual verification: icons render correctly at all sizes (12-20px)
## Rendering Note
Pixelarticons are designed for the 24×24 grid and render sharpest at multiples of 24px. At the sizes used in this app (12-20px), they will still look good because `@iconify/svelte` renders them as SVG (vector), but the pixel-art crispness is maximized at 24px. Consider using `image-rendering: pixelated` on icon SVGs if sub-24px rendering looks blurry, though SVG scaling typically handles this well.
+3 -1
View File
@@ -1,7 +1,7 @@
{
"name": "crumbs",
"private": true,
"version": "0.22.1",
"version": "0.22.3",
"type": "module",
"license": "MIT",
"author": "Bretzel (https://bretzel.app)",
@@ -33,6 +33,8 @@
"docs:api": "tsx scripts/generate-api-docs.ts"
},
"dependencies": {
"@iconify-json/pixelarticons": "^1.2.4",
"@iconify/svelte": "^5.2.1",
"@modelcontextprotocol/sdk": "^1.27.1",
"@sveltejs/adapter-node": "^5.5.4",
"@tiptap/core": "^3.20.1",
+28
View File
@@ -14,6 +14,12 @@ importers:
.:
dependencies:
'@iconify-json/pixelarticons':
specifier: ^1.2.4
version: 1.2.4
'@iconify/svelte':
specifier: ^5.2.1
version: 5.2.1(svelte@5.53.7)
'@modelcontextprotocol/sdk':
specifier: ^1.27.1
version: 1.27.1(zod@4.3.6)
@@ -1018,6 +1024,17 @@ packages:
peerDependencies:
hono: '>=4.12.7'
'@iconify-json/pixelarticons@1.2.4':
resolution: {integrity: sha512-nADJKEI3mlxDSmJMLqGvNuvNl1TGa+VbFJIwrtj/8vUA4m15qPNiwxMyw8YxW3gVokuO9MNU462B8uK1hb4rqw==}
'@iconify/svelte@5.2.1':
resolution: {integrity: sha512-zHmsIPmnIhGd5gc95bNN5FL+GifwMZv7M2rlZEpa7IXYGFJm/XGHdWf6PWQa6OBoC+R69WyiPO9NAj5wjfjbow==}
peerDependencies:
svelte: '>5.0.0'
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@@ -4851,6 +4868,17 @@ snapshots:
dependencies:
hono: 4.12.7
'@iconify-json/pixelarticons@1.2.4':
dependencies:
'@iconify/types': 2.0.0
'@iconify/svelte@5.2.1(svelte@5.53.7)':
dependencies:
'@iconify/types': 2.0.0
svelte: 5.53.7
'@iconify/types@2.0.0': {}
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
+6 -9
View File
@@ -1,8 +1,5 @@
<script lang="ts">
import XIcon from 'lucide-svelte/icons/x';
import Plus from 'lucide-svelte/icons/plus';
import GripVertical from 'lucide-svelte/icons/grip-vertical';
import ChevronRight from 'lucide-svelte/icons/chevron-right';
import Icon from '@iconify/svelte';
import { dragHandleZone, dragHandle, type DndEvent } from 'svelte-dnd-action';
import { flip } from 'svelte/animate';
@@ -123,7 +120,7 @@
{#each activeItems as item (item.id)}
<div class="group flex items-center gap-2 py-1.5" animate:flip={{ duration: flipDurationMs }}>
<div use:dragHandle aria-label="drag handle" class="drag-handle cursor-grab max-md:opacity-100 md:opacity-0 md:group-hover:opacity-100 focus:opacity-100 transition-opacity duration-150" data-testid="checklist-drag-handle">
<GripVertical class="h-4 w-4 text-[var(--text-muted)]" />
<Icon icon="pixelarticons:drag-and-drop" class="h-4 w-4 text-[var(--text-muted)]" />
</div>
<input
type="checkbox"
@@ -147,7 +144,7 @@
aria-label="Remove item"
data-testid="checklist-remove"
>
<XIcon class="h-4 w-4 text-[var(--text-muted)]" />
<Icon icon="pixelarticons:close" class="h-4 w-4 text-[var(--text-muted)]" />
</button>
</div>
{/each}
@@ -158,7 +155,7 @@
class="flex items-center gap-2 py-1.5 text-sm text-[var(--text-muted)] hover:text-[var(--text)]"
data-testid="checklist-add"
>
<Plus class="h-4 w-4" />
<Icon icon="pixelarticons:plus" class="h-4 w-4" />
Add item
</button>
@@ -169,7 +166,7 @@
class="flex items-center gap-1 text-xs text-[var(--text-muted)] hover:text-[var(--text)]"
data-testid="checklist-toggle-done"
>
<ChevronRight class="h-3 w-3 transition-transform duration-150 {doneExpanded ? 'rotate-90' : ''}" />
<Icon icon="pixelarticons:chevron-right" class="h-3 w-3 transition-transform duration-150 {doneExpanded ? 'rotate-90' : ''}" />
{doneCount} done
</button>
@@ -191,7 +188,7 @@
aria-label="Remove item"
data-testid="checklist-done-remove"
>
<XIcon class="h-4 w-4 text-[var(--text-muted)]" />
<Icon icon="pixelarticons:close" class="h-4 w-4 text-[var(--text-muted)]" />
</button>
</div>
{/each}
@@ -1,6 +1,6 @@
<script lang="ts">
import type { Collaborator } from '$lib/types/index.js';
import Users from 'lucide-svelte/icons/users';
import Icon from '@iconify/svelte';
interface Props {
collaborators: Collaborator[];
@@ -42,7 +42,7 @@
onclick={handleClick}
data-testid="collaborator-indicator"
>
<Users class="h-4 w-4" />
<Icon icon="pixelarticons:users" class="h-4 w-4" />
</button>
{#if showPopover}
+36 -58
View File
@@ -1,34 +1,12 @@
<script lang="ts">
import type { Editor } from '@tiptap/core';
import {
Undo2,
Redo2,
Heading,
List,
ListOrdered,
ListChecks,
TextQuote,
CodeXml,
Bold,
Italic,
Strikethrough,
Code,
Underline,
Link,
CornerDownLeft,
ExternalLink,
Trash2,
AlignLeft,
AlignCenter,
AlignRight,
AlignJustify,
Minus,
ChevronDown,
Table2,
BetweenHorizontalEnd,
BetweenVerticalEnd,
RemoveFormatting
Underline
} from 'lucide-svelte';
import Icon from '@iconify/svelte';
interface Props {
tick?: number;
@@ -128,7 +106,7 @@
title="Undo"
data-testid="format-undo"
>
<Undo2 size={iconSize} />
<Icon icon="pixelarticons:undo" width={iconSize} />
</button>
<button
onclick={() => editor?.chain().focus().redo().run()}
@@ -137,7 +115,7 @@
title="Redo"
data-testid="format-redo"
>
<Redo2 size={iconSize} />
<Icon icon="pixelarticons:redo" width={iconSize} />
</button>
<div class="mx-1 h-4 w-px shrink-0 bg-[var(--border-subtle)]"></div>
@@ -186,8 +164,8 @@
title="Heading"
data-testid="format-heading"
>
<Heading size={iconSize} />
<ChevronDown size={chevronSize} />
<Icon icon="pixelarticons:heading" width={iconSize} />
<Icon icon="pixelarticons:chevron-down" width={chevronSize} />
</button>
{#if openDropdown === 'heading'}
<div class="absolute left-0 bottom-full z-50 mb-1 min-w-[150px] rounded-sm border border-[var(--border)] bg-[var(--bg-surface)] py-1">
@@ -227,8 +205,8 @@
title="Lists"
data-testid="format-list"
>
<List size={iconSize} />
<ChevronDown size={chevronSize} />
<Icon icon="pixelarticons:list" width={iconSize} />
<Icon icon="pixelarticons:chevron-down" width={chevronSize} />
</button>
{#if openDropdown === 'list'}
<div class="absolute left-0 bottom-full z-50 mb-1 min-w-[170px] rounded-sm border border-[var(--border)] bg-[var(--bg-surface)] py-1">
@@ -237,7 +215,7 @@
class={dropdownItemClass(isActive('bulletList'))}
data-testid="format-bullet-list"
>
<List size={iconSize} />
<Icon icon="pixelarticons:list" width={iconSize} />
<span>Bullet list</span>
</button>
<button
@@ -245,7 +223,7 @@
class={dropdownItemClass(isActive('orderedList'))}
data-testid="format-ordered-list"
>
<ListOrdered size={iconSize} />
<Icon icon="pixelarticons:bulletlist" width={iconSize} />
<span>Ordered list</span>
</button>
<button
@@ -253,7 +231,7 @@
class={dropdownItemClass(isActive('taskList'))}
data-testid="format-task-list"
>
<ListChecks size={iconSize} />
<Icon icon="pixelarticons:checklist" width={iconSize} />
<span>Task list</span>
</button>
</div>
@@ -268,7 +246,7 @@
title="Insert link (Ctrl+K)"
data-testid="format-link"
>
<Link size={iconSize} />
<Icon icon="pixelarticons:link" width={iconSize} />
</button>
{#if openDropdown === 'link'}
<div class="absolute left-1/2 bottom-full z-50 mb-1 -translate-x-1/2 rounded-sm border border-[var(--border)] bg-[var(--bg-surface)] px-2 py-1.5">
@@ -290,7 +268,7 @@
title="Apply link"
data-testid="format-link-apply"
>
<CornerDownLeft size={16} />
<Icon icon="pixelarticons:corner-down-left" width={16} />
</button>
<div class="mx-0.5 h-4 w-px bg-[var(--border-subtle)]"></div>
<button
@@ -301,7 +279,7 @@
title="Open link"
data-testid="format-link-open"
>
<ExternalLink size={16} />
<Icon icon="pixelarticons:external-link" width={16} />
</button>
<button
type="button"
@@ -311,7 +289,7 @@
title="Remove link"
data-testid="format-unlink"
>
<Trash2 size={16} />
<Icon icon="pixelarticons:trash" width={16} />
</button>
</form>
</div>
@@ -326,8 +304,8 @@
title="Table"
data-testid="format-table"
>
<Table2 size={iconSize} />
<ChevronDown size={chevronSize} />
<Icon icon="pixelarticons:table" width={iconSize} />
<Icon icon="pixelarticons:chevron-down" width={chevronSize} />
</button>
{#if openDropdown === 'table'}
<div class="absolute left-0 bottom-full z-50 mb-1 min-w-[190px] rounded-sm border border-[var(--border)] bg-[var(--bg-surface)] py-1">
@@ -336,7 +314,7 @@
class={dropdownItemClass()}
data-testid="format-table-insert"
>
<Table2 size={iconSize} />
<Icon icon="pixelarticons:table" width={iconSize} />
<span>Insert table</span>
</button>
{#if isActive('table')}
@@ -346,7 +324,7 @@
class={dropdownItemClass()}
data-testid="format-table-add-row"
>
<BetweenHorizontalEnd size={iconSize} />
<Icon icon="pixelarticons:add-row" width={iconSize} />
<span>Add row</span>
</button>
<button
@@ -354,7 +332,7 @@
class={dropdownItemClass()}
data-testid="format-table-add-col"
>
<BetweenVerticalEnd size={iconSize} />
<Icon icon="pixelarticons:add-col" width={iconSize} />
<span>Add column</span>
</button>
<div class="my-1 h-px bg-[var(--border-subtle)]"></div>
@@ -363,7 +341,7 @@
class={dropdownItemClass()}
data-testid="format-table-delete-row"
>
<Minus size={iconSize} />
<Icon icon="pixelarticons:minus" width={iconSize} />
<span>Delete row</span>
</button>
<button
@@ -371,7 +349,7 @@
class={dropdownItemClass()}
data-testid="format-table-delete-col"
>
<Minus size={iconSize} />
<Icon icon="pixelarticons:minus" width={iconSize} />
<span>Delete column</span>
</button>
<button
@@ -379,7 +357,7 @@
class={`${dropdownItemClass()} text-[var(--destructive)]`}
data-testid="format-table-delete"
>
<RemoveFormatting size={iconSize} />
<Icon icon="pixelarticons:remove-box" width={iconSize} />
<span>Delete table</span>
</button>
{/if}
@@ -396,15 +374,15 @@
data-testid="format-align"
>
{#if isActive({ textAlign: 'center' })}
<AlignCenter size={iconSize} />
<Icon icon="pixelarticons:text-align-center" width={iconSize} />
{:else if isActive({ textAlign: 'right' })}
<AlignRight size={iconSize} />
<Icon icon="pixelarticons:text-align-right" width={iconSize} />
{:else if isActive({ textAlign: 'justify' })}
<AlignJustify size={iconSize} />
<Icon icon="pixelarticons:text-align-justify" width={iconSize} />
{:else}
<AlignLeft size={iconSize} />
<Icon icon="pixelarticons:text-align-left" width={iconSize} />
{/if}
<ChevronDown size={chevronSize} />
<Icon icon="pixelarticons:chevron-down" width={chevronSize} />
</button>
{#if openDropdown === 'align'}
<div class="absolute right-0 bottom-full z-50 mb-1 min-w-[160px] rounded-sm border border-[var(--border)] bg-[var(--bg-surface)] py-1">
@@ -413,7 +391,7 @@
class={dropdownItemClass(isActive({ textAlign: 'left' }))}
data-testid="format-align-left"
>
<AlignLeft size={iconSize} />
<Icon icon="pixelarticons:text-align-left" width={iconSize} />
<span>Left</span>
</button>
<button
@@ -421,7 +399,7 @@
class={dropdownItemClass(isActive({ textAlign: 'center' }))}
data-testid="format-align-center"
>
<AlignCenter size={iconSize} />
<Icon icon="pixelarticons:text-align-center" width={iconSize} />
<span>Center</span>
</button>
<button
@@ -429,7 +407,7 @@
class={dropdownItemClass(isActive({ textAlign: 'right' }))}
data-testid="format-align-right"
>
<AlignRight size={iconSize} />
<Icon icon="pixelarticons:text-align-right" width={iconSize} />
<span>Right</span>
</button>
<button
@@ -437,7 +415,7 @@
class={dropdownItemClass(isActive({ textAlign: 'justify' }))}
data-testid="format-align-justify"
>
<AlignJustify size={iconSize} />
<Icon icon="pixelarticons:text-align-justify" width={iconSize} />
<span>Justify</span>
</button>
</div>
@@ -453,7 +431,7 @@
title="Inline code"
data-testid="format-code"
>
<Code size={iconSize} />
<Icon icon="pixelarticons:braces" width={iconSize} />
</button>
<button
onclick={() => editor?.chain().focus().toggleBlockquote().run()}
@@ -461,7 +439,7 @@
title="Blockquote"
data-testid="format-blockquote"
>
<TextQuote size={iconSize} />
<Icon icon="pixelarticons:quote-text-inline" width={iconSize} />
</button>
<button
onclick={() => editor?.chain().focus().toggleCodeBlock().run()}
@@ -469,7 +447,7 @@
title="Code block"
data-testid="format-code-block"
>
<CodeXml size={iconSize} />
<Icon icon="pixelarticons:braces" width={iconSize} />
</button>
<button
onclick={() => editor?.chain().focus().setHorizontalRule().run()}
@@ -477,6 +455,6 @@
title="Divider"
data-testid="format-hr"
>
<Minus size={iconSize} />
<Icon icon="pixelarticons:minus" width={iconSize} />
</button>
</div>
+2 -2
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import XIcon from 'lucide-svelte/icons/x';
import Icon from '@iconify/svelte';
interface Props {
src: string;
@@ -46,7 +46,7 @@
aria-label="Close lightbox"
data-testid="lightbox-close"
>
<XIcon class="h-6 w-6" />
<Icon icon="pixelarticons:close" class="h-6 w-6" />
</button>
<img
+4 -6
View File
@@ -1,7 +1,5 @@
<script lang="ts">
import XIcon from 'lucide-svelte/icons/x';
import Star from 'lucide-svelte/icons/star';
import RefreshCw from 'lucide-svelte/icons/refresh-cw';
import Icon from '@iconify/svelte';
import ImageLightbox from './ImageLightbox.svelte';
import type { Attachment } from '$lib/types/index.js';
import { optimizeImage } from '$lib/utils/image-optimize.js';
@@ -133,7 +131,7 @@
</button>
{#if isPending}
<div class="absolute bottom-0.5 left-0.5 rounded-sm bg-black/50 p-0.5" title="Pending sync">
<RefreshCw class="h-3 w-3 text-white" />
<Icon icon="pixelarticons:reload" class="h-3 w-3 text-white" />
</div>
{:else if onToggleFeatured}
<button
@@ -142,7 +140,7 @@
aria-label={attachment.featured ? 'Unfeature image' : 'Feature image'}
data-testid="toggle-featured"
>
<Star class="h-3 w-3 {attachment.featured ? 'fill-[var(--primary)] text-[var(--primary)]' : 'text-white'}" />
<Icon icon="pixelarticons:pin" class="h-3 w-3 {attachment.featured ? 'fill-[var(--primary)] text-[var(--primary)]' : 'text-white'}" />
</button>
{/if}
<button
@@ -151,7 +149,7 @@
aria-label="Remove attachment"
data-testid="remove-attachment"
>
<XIcon class="h-3 w-3" />
<Icon icon="pixelarticons:close" class="h-3 w-3" />
</button>
</div>
{/each}
+3 -4
View File
@@ -1,8 +1,7 @@
<script lang="ts">
import SearchBar from '../SearchBar.svelte';
import SyncIndicator from '../SyncIndicator.svelte';
import Menu from 'lucide-svelte/icons/menu';
import Search from 'lucide-svelte/icons/search';
import Icon from '@iconify/svelte';
interface Props {
onMenuToggle: () => void;
@@ -24,7 +23,7 @@
class="rounded-sm p-2 hover:bg-[var(--border)]/10"
aria-label="Toggle sidebar"
>
<Menu class="h-6 w-6 text-[var(--text)]" />
<Icon icon="pixelarticons:menu" class="h-6 w-6 text-[var(--text)]" />
</button>
<div class="flex items-center gap-2">
@@ -43,7 +42,7 @@
class="ml-auto rounded-sm p-2 hover:bg-[var(--border)]/10 lg:hidden"
aria-label="Search"
>
<Search class="h-5 w-5 text-[var(--text)]" />
<Icon icon="pixelarticons:search" class="h-5 w-5 text-[var(--text)]" />
</button>
<div class="flex items-center gap-2">
+7 -7
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import { allTags } from '$lib/stores/notes.js';
import { StickyNote, Archive, Trash2, Tag, Settings } from 'lucide-svelte';
import Icon from '@iconify/svelte';
interface Props {
open: boolean;
@@ -17,9 +17,9 @@
}
const navItems = [
{ href: '/', label: 'Crumbs', icon: StickyNote, match: (p: string) => p === '/' },
{ href: '/archive', label: 'Archive', icon: Archive, match: (p: string) => p === '/archive' },
{ href: '/trash', label: 'Trash', icon: Trash2, match: (p: string) => p === '/trash' }
{ href: '/', label: 'Crumbs', icon: 'pixelarticons:sticky-note', match: (p: string) => p === '/' },
{ href: '/archive', label: 'Archive', icon: 'pixelarticons:archive', match: (p: string) => p === '/archive' },
{ href: '/trash', label: 'Trash', icon: 'pixelarticons:trash', match: (p: string) => p === '/trash' }
];
</script>
@@ -44,7 +44,7 @@
onclick={closeMobile}
class="flex w-full items-center gap-3 rounded-sm px-6 py-3 text-left text-sm transition-colors {item.match($page.url.pathname) ? 'bg-[var(--primary)]/15 text-[var(--primary)]' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
>
<item.icon size={20} />
<Icon icon={item.icon} width={20} />
{item.label}
</a>
</li>
@@ -62,7 +62,7 @@
onclick={closeMobile}
class="flex w-full items-center gap-3 rounded-sm px-6 py-2 text-left text-sm transition-colors {$page.url.pathname === `/tag/${tag}` ? 'bg-[var(--primary)]/15 text-[var(--primary)]' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
>
<Tag size={16} />
<Icon icon="pixelarticons:label" width={16} />
#{tag}
</a>
</li>
@@ -78,7 +78,7 @@
onclick={closeMobile}
data-testid="settings-link"
>
<Settings size={20} />
<Icon icon="pixelarticons:settings-cog" width={20} />
Settings
</a>
</div>
+16 -21
View File
@@ -1,16 +1,11 @@
<script lang="ts">
import { NOTE_COLORS } from '$lib/utils/colors.js';
import { renderMarkdown } from '$lib/utils/markdown.js';
import { togglePin, trashNote, archiveNote, unarchiveNote, restoreNote, deleteNote, leaveNote, currentFilter } from '$lib/stores/notes.js';
import { toggleFavorite, trashNote, archiveNote, unarchiveNote, restoreNote, deleteNote, leaveNote, currentFilter } from '$lib/stores/notes.js';
import ImageLightbox from './ImageLightbox.svelte';
import CollaboratorPopover from './CollaboratorPopover.svelte';
import type { Note } from '$lib/types/index.js';
import Undo2 from 'lucide-svelte/icons/undo-2';
import Trash2 from 'lucide-svelte/icons/trash-2';
import Bookmark from 'lucide-svelte/icons/bookmark';
import Archive from 'lucide-svelte/icons/archive';
import ArchiveRestore from 'lucide-svelte/icons/archive-restore';
import UserMinus from 'lucide-svelte/icons/user-minus';
import Icon from '@iconify/svelte';
interface ChecklistItem {
text: string;
@@ -109,12 +104,12 @@
{/if}
{#if note.pinned}
<button
onclick={stop(() => togglePin(note.id, note.pinned))}
onclick={stop(() => toggleFavorite(note.id, note.pinned))}
class="rounded-sm p-1 text-[var(--primary)] hover:bg-[var(--border)]/10"
title="Unpin"
data-testid="pin-indicator"
title="Unfavorite"
data-testid="favorite-indicator"
>
<Bookmark class="h-4 w-4 fill-[var(--primary)]" />
<Icon icon="pixelarticons:heart" class="h-4 w-4 fill-[var(--primary)]" />
</button>
{/if}
</div>
@@ -156,7 +151,7 @@
title="Restore"
data-testid="restore-btn"
>
<Undo2 class="h-4 w-4" />
<Icon icon="pixelarticons:undo" class="h-4 w-4" />
</button>
<button
onclick={stop(() => deleteNote(note.id))}
@@ -164,17 +159,17 @@
title="Delete forever"
data-testid="delete-forever-btn"
>
<Trash2 class="h-4 w-4 text-[var(--destructive)]" />
<Icon icon="pixelarticons:trash" class="h-4 w-4 text-[var(--destructive)]" />
</button>
{:else}
{#if !note.pinned}
<button
onclick={stop(() => togglePin(note.id, note.pinned))}
onclick={stop(() => toggleFavorite(note.id, note.pinned))}
class="rounded-sm p-1.5 hover:bg-[var(--border)]/10"
title="Pin"
data-testid="pin-btn"
title="Favorite"
data-testid="favorite-btn"
>
<Bookmark class="h-4 w-4" />
<Icon icon="pixelarticons:heart" class="h-4 w-4" />
</button>
{/if}
{#if $currentFilter === 'archived'}
@@ -184,7 +179,7 @@
title="Unarchive"
data-testid="unarchive-btn"
>
<ArchiveRestore class="h-4 w-4" />
<Icon icon="pixelarticons:archive" class="h-4 w-4" />
</button>
{:else}
<button
@@ -193,7 +188,7 @@
title="Archive"
data-testid="archive-btn"
>
<Archive class="h-4 w-4" />
<Icon icon="pixelarticons:archive" class="h-4 w-4" />
</button>
{/if}
{#if note.isShared && !note.isOwner}
@@ -203,7 +198,7 @@
title="Leave note"
data-testid="leave-btn"
>
<UserMinus class="h-4 w-4" />
<Icon icon="pixelarticons:user-minus" class="h-4 w-4" />
</button>
{:else}
<button
@@ -212,7 +207,7 @@
title="Delete"
data-testid="trash-btn"
>
<Trash2 class="h-4 w-4" />
<Icon icon="pixelarticons:trash" class="h-4 w-4" />
</button>
{/if}
{/if}
+11 -19
View File
@@ -13,15 +13,7 @@
import { getPreferences } from '$lib/stores/preferences.svelte.js';
import type { Editor } from '@tiptap/core';
import type { Note, NoteColor, Attachment, Collaborator } from '$lib/types/index.js';
import Palette from 'lucide-svelte/icons/palette';
import SquareCheck from 'lucide-svelte/icons/square-check';
import ImageIcon from 'lucide-svelte/icons/image';
import Type from 'lucide-svelte/icons/type';
import FileCode from 'lucide-svelte/icons/file-code';
import FileText from 'lucide-svelte/icons/file-text';
import UserPlus from 'lucide-svelte/icons/user-plus';
import Users from 'lucide-svelte/icons/users';
import History from 'lucide-svelte/icons/history';
import Icon from '@iconify/svelte';
interface Props {
note: Note | null;
@@ -345,7 +337,7 @@
title="Background color"
data-testid="color-picker-toggle"
>
<Palette class="h-5 w-5 text-[var(--text-muted)]" />
<Icon icon="pixelarticons:colors-swatch" class="h-5 w-5 text-[var(--text-muted)]" />
</button>
{#if showColorPicker}
<div class="absolute left-0 bottom-full z-10 mb-2 w-[calc(100vw-4rem)] max-w-xs rounded-sm border border-[var(--border)] bg-[var(--bg-surface)] p-2">
@@ -362,9 +354,9 @@
data-testid="checklist-toggle"
>
{#if checklistMode}
<Type class="h-5 w-5" />
<Icon icon="pixelarticons:art-text" class="h-5 w-5" />
{:else}
<SquareCheck class="h-5 w-5" />
<Icon icon="pixelarticons:checkbox-on" class="h-5 w-5" />
{/if}
</button>
@@ -375,7 +367,7 @@
title="Image attachments"
data-testid="image-toggle"
>
<ImageIcon class="h-5 w-5 {showImageUpload ? 'text-[var(--primary)]' : ''}" />
<Icon icon="pixelarticons:image" class="h-5 w-5 {showImageUpload ? 'text-[var(--primary)]' : ''}" />
</button>
<!-- Raw markdown mode toggle -->
@@ -387,9 +379,9 @@
data-testid="markdown-toggle"
>
{#if rawMarkdownMode}
<FileText class="h-5 w-5" />
<Icon icon="pixelarticons:file-text" class="h-5 w-5" />
{:else}
<FileCode class="h-5 w-5" />
<Icon icon="pixelarticons:script" class="h-5 w-5" />
{/if}
</button>
@@ -402,14 +394,14 @@
data-testid="share-toggle"
>
{#if isShared}
<Users class="h-5 w-5 text-[var(--primary)]" />
<Icon icon="pixelarticons:users" class="h-5 w-5 text-[var(--primary)]" />
{:else}
<UserPlus class="h-5 w-5" />
<Icon icon="pixelarticons:user-plus" class="h-5 w-5" />
{/if}
</button>
{:else if isShared}
<span class="flex items-center gap-1 rounded-sm p-2 text-[var(--text-muted)]" title="Shared note">
<Users class="h-5 w-5" />
<Icon icon="pixelarticons:users" class="h-5 w-5" />
</span>
{/if}
@@ -421,7 +413,7 @@
title="Version history"
data-testid="history-toggle"
>
<History class="h-5 w-5 {showHistory ? 'text-[var(--primary)]' : ''}" />
<Icon icon="pixelarticons:timeline" class="h-5 w-5 {showHistory ? 'text-[var(--primary)]' : ''}" />
</button>
{/if}
</div>
+5 -7
View File
@@ -1,8 +1,6 @@
<script lang="ts">
import type { NoteVersionSummary, NoteVersion } from '$lib/types/index.js';
import X from 'lucide-svelte/icons/x';
import RotateCcw from 'lucide-svelte/icons/rotate-ccw';
import Clock from 'lucide-svelte/icons/clock';
import Icon from '@iconify/svelte';
import { showToast } from '$lib/stores/toast.js';
interface Props {
@@ -111,7 +109,7 @@
<!-- Header -->
<div class="flex items-center justify-between border-b border-[var(--border-subtle)] px-4 py-3">
<div class="flex items-center gap-2">
<Clock class="h-4 w-4 text-[var(--text-muted)]" />
<Icon icon="pixelarticons:clock" class="h-4 w-4 text-[var(--text-muted)]" />
<h2 class="text-sm font-semibold text-[var(--text)]">Version History</h2>
</div>
<button
@@ -120,7 +118,7 @@
title="Close history"
data-testid="close-history-btn"
>
<X class="h-4 w-4 text-[var(--text-muted)]" />
<Icon icon="pixelarticons:close" class="h-4 w-4 text-[var(--text-muted)]" />
</button>
</div>
@@ -190,7 +188,7 @@
class="flex items-center gap-1.5 rounded-sm border border-[var(--destructive)] bg-[var(--destructive)] px-3 py-1.5 text-xs font-medium text-white hover:opacity-90 disabled:opacity-50"
data-testid="confirm-restore-btn"
>
<RotateCcw class="h-3.5 w-3.5" />
<Icon icon="pixelarticons:undo" class="h-3.5 w-3.5" />
{isRestoring ? 'Restoring…' : 'Confirm Restore'}
</button>
<button
@@ -207,7 +205,7 @@
class="flex items-center gap-1.5 rounded-sm border border-[var(--border-subtle)] px-3 py-1.5 text-xs font-medium text-[var(--text)] hover:border-[var(--primary)] hover:text-[var(--primary)] transition-colors"
data-testid="restore-btn"
>
<RotateCcw class="h-3.5 w-3.5" />
<Icon icon="pixelarticons:undo" class="h-3.5 w-3.5" />
Restore this version
</button>
{/if}
+9 -9
View File
@@ -2,10 +2,10 @@
import NoteGrid from '$lib/components/NoteGrid.svelte';
import NoteEditor from '$lib/components/NoteEditor.svelte';
import TagFilter from '$lib/components/TagFilter.svelte';
import { pinnedNotes, unpinnedNotes, selectedTag, currentFilter, notes, notesLoaded, loadNotes, updateSortOrders } from '$lib/stores/notes.js';
import { favoriteNotes, unfavoritedNotes, selectedTag, currentFilter, notes, notesLoaded, loadNotes, updateSortOrders } from '$lib/stores/notes.js';
import { onMount } from 'svelte';
import type { Note, NoteFilter } from '$lib/types/index.js';
import Plus from 'lucide-svelte/icons/plus';
import Icon from '@iconify/svelte';
interface Props {
filter: NoteFilter;
@@ -60,7 +60,7 @@
class="flex w-full items-center gap-3 rounded-sm border border-dashed border-[var(--border-subtle)] bg-[var(--bg-surface)] px-4 py-3 text-left text-sm text-[var(--text-muted)] transition-colors hover:border-[var(--primary)] hover:text-[var(--primary)]"
data-testid="new-note-btn"
>
<Plus class="h-4 w-4" />
<Icon icon="pixelarticons:plus" class="h-4 w-4" />
Add a crumb...
</button>
</div>
@@ -70,7 +70,7 @@
aria-label="Add a crumb"
data-testid="new-note-fab"
>
<Plus class="h-5 w-5" />
<Icon icon="pixelarticons:plus" class="h-5 w-5" />
</button>
{/if}
@@ -86,22 +86,22 @@
<h2 class="mb-4 text-lg font-medium text-[var(--text-muted)]">#{tag}</h2>
{/if}
{#if $pinnedNotes.length > 0}
{#if $favoriteNotes.length > 0}
<div class="mb-6">
<NoteGrid notes={$pinnedNotes} label="Pinned" onEdit={openEditor} draggable dndType="pinned-notes" onReorder={handleReorder} />
<NoteGrid notes={$favoriteNotes} label="Favorites" onEdit={openEditor} draggable dndType="pinned-notes" onReorder={handleReorder} />
</div>
{/if}
<NoteGrid
notes={$unpinnedNotes}
label={$pinnedNotes.length > 0 ? 'Others' : ''}
notes={$unfavoritedNotes}
label={$favoriteNotes.length > 0 ? 'Others' : ''}
onEdit={openEditor}
draggable
dndType="unpinned-notes"
onReorder={handleReorder}
/>
{#if $notesLoaded && $pinnedNotes.length === 0 && $unpinnedNotes.length === 0}
{#if $notesLoaded && $favoriteNotes.length === 0 && $unfavoritedNotes.length === 0}
<div class="flex flex-col items-center justify-center py-20 text-[var(--text-muted)]">
<img src="/favicon.svg" alt="" class="mb-4 h-18 w-18" />
<p class="font-['Press_Start_2P'] text-xs">
+4 -6
View File
@@ -1,9 +1,7 @@
<script lang="ts">
import { notes, loadNotes, currentFilter } from '$lib/stores/notes.js';
import type { Note } from '$lib/types/index.js';
import Search from 'lucide-svelte/icons/search';
import X from 'lucide-svelte/icons/x';
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
import Icon from '@iconify/svelte';
interface Props {
onClose?: () => void;
@@ -59,10 +57,10 @@
<div class="flex items-center rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 focus-within:border-[var(--primary)]">
{#if onClose}
<button onclick={close} class="mr-2 text-[var(--text-muted)] hover:text-[var(--text)]" aria-label="Back">
<ArrowLeft class="h-5 w-5" />
<Icon icon="pixelarticons:arrow-left" class="h-5 w-5" />
</button>
{:else}
<Search class="mr-3 h-5 w-5 text-[var(--text-muted)]" />
<Icon icon="pixelarticons:search" class="mr-3 h-5 w-5 text-[var(--text-muted)]" />
{/if}
<input
bind:this={inputEl}
@@ -75,7 +73,7 @@
/>
{#if query}
<button onclick={clearSearch} class="ml-2 text-[var(--text-muted)] hover:text-[var(--text)]" aria-label="Clear search">
<X class="h-5 w-5" />
<Icon icon="pixelarticons:close" class="h-5 w-5" />
</button>
{/if}
</div>
+4 -6
View File
@@ -1,8 +1,6 @@
<script lang="ts">
import type { Collaborator } from '$lib/types/index.js';
import X from 'lucide-svelte/icons/x';
import UserMinus from 'lucide-svelte/icons/user-minus';
import Search from 'lucide-svelte/icons/search';
import Icon from '@iconify/svelte';
import { showToast } from '$lib/stores/toast.js';
interface UserResult {
@@ -119,14 +117,14 @@
class="rounded-sm p-1 hover:bg-[var(--border)]/10"
title="Close"
>
<X class="h-4 w-4" />
<Icon icon="pixelarticons:close" class="h-4 w-4" />
</button>
</div>
<!-- Search input -->
<div class="relative px-4 pt-3">
<div class="relative">
<Search class="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-[var(--text-muted)]" />
<Icon icon="pixelarticons:search" class="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-[var(--text-muted)]" />
<input
type="text"
placeholder="Search users..."
@@ -181,7 +179,7 @@
title="Remove"
data-testid="remove-collaborator-btn"
>
<UserMinus class="h-4 w-4" />
<Icon icon="pixelarticons:user-minus" class="h-4 w-4" />
</button>
</li>
{/each}
+5 -8
View File
@@ -1,10 +1,7 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { getSyncStatus, onSyncStatusChange, sync, type SyncStatus } from '$lib/sync/client.js';
import CloudCheck from 'lucide-svelte/icons/cloud-check';
import CloudSync from 'lucide-svelte/icons/cloud-sync';
import CloudOff from 'lucide-svelte/icons/cloud-off';
import CloudAlert from 'lucide-svelte/icons/cloud-alert';
import Icon from '@iconify/svelte';
let status: SyncStatus = $state(getSyncStatus());
let displayStatus: SyncStatus = $state(getSyncStatus());
@@ -51,12 +48,12 @@
data-testid="sync-indicator"
>
{#if displayStatus === 'synced'}
<CloudCheck class="h-5 w-5 text-[var(--primary)]" />
<Icon icon="pixelarticons:cloud-done" class="h-5 w-5 text-[var(--primary)]" />
{:else if displayStatus === 'syncing'}
<CloudSync class="h-5 w-5 text-[var(--primary)] opacity-60" />
<Icon icon="pixelarticons:sync" class="h-5 w-5 text-[var(--primary)] opacity-60" />
{:else if displayStatus === 'offline'}
<CloudOff class="h-5 w-5 text-[var(--text-muted)]" />
<Icon icon="pixelarticons:cloud" class="h-5 w-5 text-[var(--text-muted)]" />
{:else}
<CloudAlert class="h-5 w-5 text-[var(--destructive)]" />
<Icon icon="pixelarticons:cloud" class="h-5 w-5 text-[var(--destructive)]" />
{/if}
</button>
+3 -3
View File
@@ -239,8 +239,8 @@ export function createMcpServer(userId: number): McpServer {
});
server.tool(
'pin_note',
'Pin or unpin a note',
'favorite_note',
'Favorite or unfavorite a note',
{
id: z.string().describe('Note ID'),
pinned: z.boolean().describe('Whether to pin (true) or unpin (false)')
@@ -257,7 +257,7 @@ export function createMcpServer(userId: number): McpServer {
content: [
{
type: 'text' as const,
text: `Note "${result.title}" ${pinned ? 'pinned' : 'unpinned'}`
text: `Note "${result.title}" ${pinned ? 'favorited' : 'unfavorited'}`
}
]
};
+3 -3
View File
@@ -35,11 +35,11 @@ function sortNotes(list: Note[]): Note[] {
});
}
export const pinnedNotes = derived(filteredNotes, ($notes) =>
export const favoriteNotes = derived(filteredNotes, ($notes) =>
sortNotes($notes.filter((n) => n.pinned))
);
export const unpinnedNotes = derived(filteredNotes, ($notes) =>
export const unfavoritedNotes = derived(filteredNotes, ($notes) =>
sortNotes($notes.filter((n) => !n.pinned))
);
@@ -231,7 +231,7 @@ export async function unarchiveNote(id: string): Promise<Note | null> {
return updateNote(id, { archived: false });
}
export async function togglePin(id: string, currentPinned: boolean): Promise<Note | null> {
export async function toggleFavorite(id: string, currentPinned: boolean): Promise<Note | null> {
return updateNote(id, { pinned: !currentPinned });
}
+6 -6
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import { page } from '$app/state';
import { User, Cpu, ShieldCheck, SlidersHorizontal, Info } from 'lucide-svelte';
import Icon from '@iconify/svelte';
let { data, children } = $props();
let pathname = $derived(page.url.pathname);
@@ -23,7 +23,7 @@
class="flex items-center gap-2 rounded-sm px-3 py-2 text-sm whitespace-nowrap transition-colors {pathname === '/settings/preferences' ? 'bg-[var(--primary)]/15 text-[var(--primary)] font-medium' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
data-testid="settings-nav-preferences"
>
<SlidersHorizontal size={16} />
<Icon icon="pixelarticons:sliders" width={16} />
Preferences
</a>
</li>
@@ -32,7 +32,7 @@
href="/settings/profile"
class="flex items-center gap-2 rounded-sm px-3 py-2 text-sm whitespace-nowrap transition-colors {pathname === '/settings/profile' ? 'bg-[var(--primary)]/15 text-[var(--primary)] font-medium' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
>
<User size={16} />
<Icon icon="pixelarticons:user" width={16} />
Profile
</a>
</li>
@@ -41,7 +41,7 @@
href="/settings/mcp"
class="flex items-center gap-2 rounded-sm px-3 py-2 text-sm whitespace-nowrap transition-colors {pathname === '/settings/mcp' ? 'bg-[var(--primary)]/15 text-[var(--primary)] font-medium' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
>
<Cpu size={16} />
<Icon icon="pixelarticons:cpu" width={16} />
API
</a>
</li>
@@ -51,7 +51,7 @@
href="/settings/users"
class="flex items-center gap-2 rounded-sm px-3 py-2 text-sm whitespace-nowrap transition-colors {pathname === '/settings/users' ? 'bg-[var(--primary)]/15 text-[var(--primary)] font-medium' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
>
<ShieldCheck size={16} />
<Icon icon="pixelarticons:shield" width={16} />
Users
</a>
</li>
@@ -61,7 +61,7 @@
href="/settings/about"
class="flex items-center gap-2 rounded-sm px-3 py-2 text-sm whitespace-nowrap transition-colors {pathname === '/settings/about' ? 'bg-[var(--primary)]/15 text-[var(--primary)] font-medium' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
>
<Info size={16} />
<Icon icon="pixelarticons:info-box" width={16} />
About
</a>
</li>
+10 -10
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { Key, Copy, Trash2, Plus, Check } from 'lucide-svelte';
import Icon from '@iconify/svelte';
interface ApiKey {
id: string;
@@ -118,7 +118,7 @@ ${mcpJsonConfig}`);
<!-- API Keys -->
<section class="mb-6 rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
<div class="mb-4 flex items-center gap-2">
<Key size={16} class="text-[var(--primary)]" />
<Icon icon="pixelarticons:lock" width={16} class="text-[var(--primary)]" />
<h2 class="text-lg font-semibold text-[var(--text)]">API Keys</h2>
</div>
<p class="mb-4 text-xs text-[var(--text-muted)]">
@@ -140,7 +140,7 @@ ${mcpJsonConfig}`);
class="flex items-center gap-1 rounded-sm bg-[var(--primary)] px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-[var(--primary-hover)] disabled:opacity-50"
data-testid="create-api-key-btn"
>
<Plus size={14} />
<Icon icon="pixelarticons:plus" width={14} />
Create
</button>
</form>
@@ -167,9 +167,9 @@ ${mcpJsonConfig}`);
data-testid="copy-key-btn"
>
{#if copiedKey}
<Check size={14} class="text-[var(--success-text,#3a5a40)]" />
<Icon icon="pixelarticons:check" width={14} class="text-[var(--success-text,#3a5a40)]" />
{:else}
<Copy size={14} />
<Icon icon="pixelarticons:copy" width={14} />
{/if}
</button>
</div>
@@ -219,7 +219,7 @@ ${mcpJsonConfig}`);
data-testid="delete-api-key-btn"
title="Revoke key"
>
<Trash2 size={14} />
<Icon icon="pixelarticons:trash" width={14} />
</button>
{/if}
</div>
@@ -253,9 +253,9 @@ ${mcpJsonConfig}`);
title="Copy JSON config"
>
{#if copiedJson}
<Check size={12} class="text-[var(--success-text,#3a5a40)]" />
<Icon icon="pixelarticons:check" width={12} class="text-[var(--success-text,#3a5a40)]" />
{:else}
<Copy size={12} />
<Icon icon="pixelarticons:copy" width={12} />
{/if}
</button>
</div>
@@ -279,9 +279,9 @@ ${mcpJsonConfig}`);
title="Copy LLM prompt"
>
{#if copiedPrompt}
<Check size={12} class="text-[var(--success-text,#3a5a40)]" />
<Icon icon="pixelarticons:check" width={12} class="text-[var(--success-text,#3a5a40)]" />
{:else}
<Copy size={12} />
<Icon icon="pixelarticons:copy" width={12} />
{/if}
</button>
</div>
+1 -1
View File
@@ -3,7 +3,7 @@
"info": {
"title": "Crumbs API",
"description": "Auto-generated API spec for crumbs",
"version": "0.21.9",
"version": "0.22.3",
"contact": {
"name": "Bretzel",
"url": "https://bretzel.app"
+9 -9
View File
@@ -1,17 +1,17 @@
import { test, expect, noteCard, createNote } from './helpers/fixtures.js';
test.describe('Organization Features', () => {
test('Scenario: Pinned note appears under the Pinned section', async ({ authenticatedPage: page }) => {
// Given a note titled "Pin Me" exists
await createNote(page, 'Pin Me');
test('Scenario: Favorited note appears under the Favorites section', async ({ authenticatedPage: page }) => {
// Given a note titled "Favorite Me" exists
await createNote(page, 'Favorite Me');
// When the user pins the note
const pinCard = noteCard(page, 'Pin Me');
await pinCard.hover();
await pinCard.getByTestId('pin-btn').first().click({ force: true });
// When the user favorites the note
const favCard = noteCard(page, 'Favorite Me');
await favCard.hover();
await favCard.getByTestId('favorite-btn').first().click({ force: true });
// Then the "Pinned" section is visible
await expect(page.getByText('Pinned')).toBeVisible();
// Then the "Favorites" section is visible
await expect(page.getByText('Favorites')).toBeVisible();
});
test('Scenario: Archived note is removed from the main view', async ({ authenticatedPage: page }) => {
+1 -1
View File
@@ -280,7 +280,7 @@
<li><a href="https://github.com/bretzel-app/crumbs/blob/main/docs/DEPLOYMENT.md" target="_blank" rel="noopener">Docs</a></li>
<li><a href="https://github.com/bretzel-app/crumbs/blob/main/LICENSE" target="_blank" rel="noopener">License</a></li>
</ul>
<span class="footer-version">v0.22.1</span>
<span class="footer-version">v0.22.3</span>
</div>
</footer>