From ab4e3e17a8d2574ba241f7d0ac3172582238ad44 Mon Sep 17 00:00:00 2001 From: tomymaritano Date: Fri, 2 Jan 2026 23:41:36 -0300 Subject: [PATCH 1/3] chore: bump version to 0.1.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/desktop/package.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index aa3929a..b60b234 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@readied/desktop", - "version": "0.1.3", + "version": "0.1.4", "private": true, "description": "Markdown-first, offline-forever note app for developers", "author": { diff --git a/package.json b/package.json index 52c6ba0..e8a6ee0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "readied", - "version": "0.1.2", + "version": "0.1.4", "private": true, "license": "UNLICENSED", "description": "Offline-first Markdown editor for developers", From ea4da033d08bb137e7e1d61050fc28a2f30cca74 Mon Sep 17 00:00:00 2001 From: tomymaritano Date: Sat, 3 Jan 2026 14:07:00 -0300 Subject: [PATCH 2/3] feat: add pin to context menu and preview metadata header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Pin/Unpin option to note list context menu - Show pin icon on pinned notes in list - Add unpin animation effect (PinOff fades out) - Pin/unpin no longer updates updatedAt (organizational metadata) - Add preview metadata header with: - Task progress bar (only shown if note has checkboxes) - Created At timestamp - Updated At timestamp - Keep countMarkdownTasks in renderer (not core) per architecture 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- apps/desktop/src/renderer/App.tsx | 17 ++++ .../src/renderer/components/NoteList.tsx | 36 ++++++++- .../NoteListContextMenu.tsx | 19 ++++- .../components/editor/MarkdownPreview.tsx | 77 ++++++++++++------- .../desktop/src/renderer/styles/note-list.css | 32 ++++++++ apps/desktop/src/renderer/styles/preview.css | 75 ++++++++++++++++++ apps/desktop/src/renderer/utils/date.ts | 10 +++ apps/desktop/src/renderer/utils/markdown.ts | 18 +++++ packages/core/src/domain/note.ts | 16 +--- 9 files changed, 256 insertions(+), 44 deletions(-) create mode 100644 apps/desktop/src/renderer/utils/markdown.ts diff --git a/apps/desktop/src/renderer/App.tsx b/apps/desktop/src/renderer/App.tsx index 9a711d4..fea29b6 100644 --- a/apps/desktop/src/renderer/App.tsx +++ b/apps/desktop/src/renderer/App.tsx @@ -85,6 +85,8 @@ function NotesApp() { duplicateNote, moveNote, setNoteStatus, + pinNote, + unpinNote, } = useNoteMutations(); // Determine which notes to display @@ -203,6 +205,20 @@ function NotesApp() { [duplicateNote, goToAllNotes] ); + // Pin/unpin note (toggle) + const handlePinNote = useCallback( + async (id: string) => { + const note = displayedNotes.find(n => n.id === id); + if (!note) return; + if (note.isPinned) { + await unpinNote.mutateAsync(id); + } else { + await pinNote.mutateAsync(id); + } + }, + [displayedNotes, pinNote, unpinNote] + ); + // Move note to notebook const handleMoveNote = useCallback( async (noteId: string, notebookId: string) => { @@ -308,6 +324,7 @@ function NotesApp() { onDelete={handleDeleteNote} onArchive={handleArchiveNote} onDuplicate={handleDuplicateNote} + onPin={handlePinNote} onMove={handleMoveNote} onSearch={handleSearch} onNewNote={handleNewNote} diff --git a/apps/desktop/src/renderer/components/NoteList.tsx b/apps/desktop/src/renderer/components/NoteList.tsx index 8b943fd..9041cf9 100644 --- a/apps/desktop/src/renderer/components/NoteList.tsx +++ b/apps/desktop/src/renderer/components/NoteList.tsx @@ -1,5 +1,15 @@ import { useState, useCallback, useRef, useEffect } from 'react'; -import { Sparkles, Archive, Search, X, Check, SquarePen, ArrowUpDown } from 'lucide-react'; +import { + Sparkles, + Archive, + Search, + X, + Check, + SquarePen, + ArrowUpDown, + Pin, + PinOff, +} from 'lucide-react'; import { useNotebookList, useNotebook } from '../hooks/useNotebooks'; import type { NoteWithExcerpt, SortBy, SortOrder } from '../hooks/useNavigation'; import { formatRelativeTime } from '../utils/date'; @@ -20,6 +30,7 @@ interface NoteListProps { onDelete: (id: string) => void; onArchive: (id: string) => void; onDuplicate: (id: string) => void; + onPin: (id: string) => void; onMove: (noteId: string, notebookId: string) => void; onSearch: (query: string) => void; onNewNote: () => void; @@ -89,6 +100,7 @@ interface ContextMenuState { noteId: string; notebookId: string | null; isArchived: boolean; + isPinned: boolean; x: number; y: number; } @@ -111,6 +123,7 @@ export function NoteList({ onDelete, onArchive, onDuplicate, + onPin, onMove, onSearch, onNewNote, @@ -183,6 +196,7 @@ export function NoteList({ noteId: note.id, notebookId: note.notebookId, isArchived: note.isArchived, + isPinned: note.isPinned, x: e.clientX, y: e.clientY, }); @@ -328,8 +342,10 @@ export function NoteList({ noteId={contextMenu.noteId} currentNotebookId={contextMenu.notebookId} isArchived={contextMenu.isArchived} + isPinned={contextMenu.isPinned} position={{ x: contextMenu.x, y: contextMenu.y }} onClose={() => setContextMenu(null)} + onPin={onPin} onDuplicate={onDuplicate} onArchive={onArchive} onDelete={onDelete} @@ -366,6 +382,18 @@ function NoteListItem({ onContextMenu, }: NoteListItemProps) { const getColor = useTagColorsStore(state => state.getColor); + const [showUnpinEffect, setShowUnpinEffect] = useState(false); + const prevPinnedRef = useRef(note.isPinned); + + // Detect unpin transition (pinned → unpinned) + useEffect(() => { + if (prevPinnedRef.current && !note.isPinned) { + setShowUnpinEffect(true); + const timer = setTimeout(() => setShowUnpinEffect(false), 600); + return () => clearTimeout(timer); + } + prevPinnedRef.current = note.isPinned; + }, [note.isPinned]); return (
  • -
    {note.title || 'Untitled'}
    +
    + {note.isPinned && } + {showUnpinEffect &&
    {formatRelativeTime(note.updatedAt)} {note.tags.length > 0 && ( diff --git a/apps/desktop/src/renderer/components/NoteListContextMenu/NoteListContextMenu.tsx b/apps/desktop/src/renderer/components/NoteListContextMenu/NoteListContextMenu.tsx index 6750c1e..57ff62d 100644 --- a/apps/desktop/src/renderer/components/NoteListContextMenu/NoteListContextMenu.tsx +++ b/apps/desktop/src/renderer/components/NoteListContextMenu/NoteListContextMenu.tsx @@ -1,6 +1,6 @@ import { useRef, useEffect, useState, useCallback } from 'react'; import { createPortal } from 'react-dom'; -import { FolderOpen, Copy, Archive, ArchiveRestore, Trash2 } from 'lucide-react'; +import { FolderOpen, Copy, Archive, ArchiveRestore, Trash2, Pin, PinOff } from 'lucide-react'; import styles from './NoteListContextMenu.module.css'; export interface NoteListContextMenuProps { @@ -10,10 +10,14 @@ export interface NoteListContextMenuProps { currentNotebookId: string | null; /** Whether the note is archived */ isArchived: boolean; + /** Whether the note is pinned */ + isPinned: boolean; /** Menu position */ position: { x: number; y: number }; /** Called when menu should close */ onClose: () => void; + /** Called when pin/unpin is clicked */ + onPin: (id: string) => void; /** Called when duplicate is clicked */ onDuplicate: (id: string) => void; /** Called when archive/restore is clicked */ @@ -28,8 +32,10 @@ export function NoteListContextMenu({ noteId, currentNotebookId, isArchived, + isPinned, position, onClose, + onPin, onDuplicate, onArchive, onDelete, @@ -85,6 +91,11 @@ export function NoteListContextMenu({ }; }, [onClose]); + const handlePin = useCallback(() => { + onPin(noteId); + onClose(); + }, [noteId, onPin, onClose]); + const handleDuplicate = useCallback(() => { onDuplicate(noteId); onClose(); @@ -122,6 +133,12 @@ export function NoteListContextMenu({ M + {/* Pin / Unpin */} + + {/* Duplicate */}