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
26 changed files with 1036 additions and 202 deletions
+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
+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.
+2
View File
@@ -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 }) => {