Skip to content

feat: add pin notes#276

Draft
katsyuta wants to merge 15 commits intoDeepinkApp:masterfrom
katsyuta:90-add-pin-notes
Draft

feat: add pin notes#276
katsyuta wants to merge 15 commits intoDeepinkApp:masterfrom
katsyuta:90-add-pin-notes

Conversation

@katsyuta
Copy link
Copy Markdown
Collaborator

@katsyuta katsyuta commented Apr 28, 2026

Closes #90

Pinned notes are always at the top of the notes list, except in search results, because search is ordered by relevance and raising pinned notes could break this behavior.

pin.mp4

Summary by CodeRabbit

  • New Features
    • Add note pinning: pin/unpin notes via interactive thumbtack controls in the notes list and editor.
    • Pinned state is persisted and shown at the top of the list (pinned first).
    • Pin controls reveal on hover, stop click propagation, and show localized tooltips for pin vs unpin.
    • Editor and list visuals update to reflect pin state immediately.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 04a81897-2282-4073-bb76-2d251378dc52

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds note pinning: schema migration and DB field, controller and types to surface pin state and ordering, a global toggle command and handler with telemetry, UI updates to show a pin control in previews and editor, and new English translations for pin/unpin actions.

Changes

Cohort / File(s) Summary
Database Schema & Migrations
packages/app/src/core/database/sqlite/migrations/sql/2_add_pinned_to_notes.sql, packages/app/src/core/database/sqlite/migrations/index.ts
Adds non-null pinned column (default 0) to notes and registers the new migration.
Core Feature Logic
packages/app/src/core/features/notes/controller/NotesController.ts, packages/app/src/core/features/notes/controller/index.ts, packages/app/src/core/features/telemetry/index.ts
Expose pinnedisPinned mapping, include isPinned in NoteMeta, enforce ORDER BY pinned DESC for listings, and add NOTE_PIN_TOGGLE telemetry event.
UI Components
packages/app/src/components/NotePreview/NotePreview.tsx, packages/app/src/features/MainScreen/NotesListPanel/NotesList.tsx, packages/app/src/features/NoteEditor/index.tsx
NotePreview requires a pin prop and renders a thumbtack button; NotesList and NoteEditor pass pin state/titles and dispatch the toggle command.
Command Infrastructure
packages/app/src/hooks/commands/index.ts, packages/app/src/hooks/notes/useNoteCommandHandlers.ts
Adds TOGGLE_NOTE_PIN global command and registers handler that toggles isPinned, emits NOTE_UPDATED, and records telemetry.
Localization
packages/app/src/locales/en/features.json
Adds English strings for note.actions.pinNote and note.actions.unpinNote.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as NotePreview / NoteEditor
    participant Cmd as Command System
    participant Controller as NotesController
    participant DB as SQLite Database
    participant Telemetry as Telemetry & Events

    User->>UI: Click pin icon
    UI->>Cmd: Dispatch TOGGLE_NOTE_PIN(noteId)
    Cmd->>Controller: Invoke toggle handler
    Controller->>Controller: Toggle isPinned state
    Controller->>DB: UPDATE notes SET pinned = ? WHERE id = ?
    DB-->>Controller: Acknowledge
    Controller->>Telemetry: Emit NOTE_UPDATED & NOTE_PIN_TOGGLE
    Controller-->>Cmd: Respond / emit event
    Cmd-->>UI: UI updates (re-render)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Poem

🐰 I found a pin, a tiny tack,
I hop it up to lead the pack,
Top of list with joyful spin,
Tap the tack — let sorting grin,
A hop, a pin, a tidy track!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add pin notes' directly summarizes the main feature addition and aligns with the primary objective of implementing note pinning functionality.
Linked Issues check ✅ Passed The PR successfully implements core requirements from issue #90: note pinning with database schema support, UI controls for toggling pin state, and sorting pinned notes to the top via ORDER BY pinned DESC.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the pin notes feature. Database migrations, UI components, command handlers, telemetry, and localization additions all support the core pinning functionality without unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@katsyuta katsyuta changed the title 90 add pin notes feat: add pin notes Apr 28, 2026
@katsyuta katsyuta marked this pull request as ready for review April 28, 2026 21:05
@katsyuta
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/app/src/core/features/notes/controller/NotesController.ts (1)

169-200: ⚠️ Potential issue | 🟠 Major

Pinned order is lost in the search branch.

PoC: when the text index returns [unpinned, pinned] and the DB filter set is larger, intersectSets() keeps the index order, so the pinned note can still land below an unpinned one.

ORDER BY pinned DESC only affects the SQL-backed path. Please apply the same pin-first ordering to the final search result list before returning, otherwise the “pinned notes always appear at the top” guarantee is still bypassed for searches.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/core/features/notes/controller/NotesController.ts` around
lines 169 - 200, The search path currently bypasses the SQL ORDER BY pinned
DESC, so after intersectSets() or wherever the search branch constructs the
final results list in NotesController (look for the method using intersectSets
and the orderQuery/ORDER BY logic), apply a stable sort to the final results
array that places items with pinned=true before pinned=false while preserving
the existing relative order (i.e., stable partition). In other words, after the
search-result assembly but before returning from the NotesController method,
reorder the array so pinned notes come first (use a stable algorithm or two-pass
filter/concat) to guarantee the "pinned first" requirement regardless of whether
results came from the DB or text index.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/app/src/components/NotePreview/NotePreview.tsx`:
- Around line 62-76: The FaThumbtack currently rendered as a clickable Box in
NotePreview should be a real button to support keyboard focus/activation and
prevent click bubbling; replace the Box-as={FaThumbtack} with an IconButton (or
Chakra IconButton) wired to the same styling/props and change the click handler
to call event.stopPropagation() before invoking pin.onToggle (or wrap
pin.onToggle in a handler that stops propagation and then toggles). Ensure the
IconButton uses the same visual props (boxSize, color, transform, opacity,
_groupHover/_active equivalents) and sets an accessible aria-label/title so
keyboard users can activate it without triggering the note row's click handler.

---

Outside diff comments:
In `@packages/app/src/core/features/notes/controller/NotesController.ts`:
- Around line 169-200: The search path currently bypasses the SQL ORDER BY
pinned DESC, so after intersectSets() or wherever the search branch constructs
the final results list in NotesController (look for the method using
intersectSets and the orderQuery/ORDER BY logic), apply a stable sort to the
final results array that places items with pinned=true before pinned=false while
preserving the existing relative order (i.e., stable partition). In other words,
after the search-result assembly but before returning from the NotesController
method, reorder the array so pinned notes come first (use a stable algorithm or
two-pass filter/concat) to guarantee the "pinned first" requirement regardless
of whether results came from the DB or text index.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69f5d96a-ac0e-4454-8f37-5629f0629ec3

📥 Commits

Reviewing files that changed from the base of the PR and between 82adda2 and f2bbcb7.

📒 Files selected for processing (11)
  • packages/app/src/components/NotePreview/NotePreview.tsx
  • packages/app/src/core/database/sqlite/migrations/index.ts
  • packages/app/src/core/database/sqlite/migrations/sql/2_add_pinned_to_notes.sql
  • packages/app/src/core/features/notes/controller/NotesController.ts
  • packages/app/src/core/features/notes/controller/index.ts
  • packages/app/src/core/features/telemetry/index.ts
  • packages/app/src/features/MainScreen/NotesListPanel/NotesList.tsx
  • packages/app/src/features/NoteEditor/index.tsx
  • packages/app/src/hooks/commands/index.ts
  • packages/app/src/hooks/notes/useNoteCommandHandlers.ts
  • packages/app/src/locales/en/features.json

Comment thread packages/app/src/components/NotePreview/NotePreview.tsx Outdated
@katsyuta katsyuta marked this pull request as draft April 28, 2026 21:18
@katsyuta katsyuta marked this pull request as ready for review April 29, 2026 21:06
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/app/src/components/NotePreview/NotePreview.tsx (1)

63-73: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the pin toggle keyboard-reachable when inactive.

PoC (unit test):

it('lets keyboard users focus the pin toggle when note is not pinned', async () => {
  render(
    <NotePreview
      title="t"
      text="x"
      pin={{ title: 'Pin note', isActive: false, onToggle: vi.fn() }}
    />,
  );

  await userEvent.tab();
  expect(screen.getByRole('button', { name: 'Pin note' })).toHaveFocus();
});

This currently fails because Line 70 sets visibility="hidden", which removes the button from keyboard focus order until mouse hover.
Use opacity-based hiding (and reveal on hover/focus) instead of visibility.

Suggested fix
-					visibility={pin.isActive ? 'visible' : 'hidden'}
-					_groupHover={{
-						visibility: 'visible',
-					}}
+					opacity={pin.isActive ? 1 : 0}
+					_groupHover={{ opacity: 1 }}
+					_groupFocusWithin={{ opacity: 1 }}
+					_focusVisible={{ opacity: 1 }}

Based on learnings: in TSX files, bug findings should start with a minimal reproducing unit test (PoC).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/app/src/components/NotePreview/NotePreview.tsx` around lines 63 -
73, The pin Button in NotePreview currently uses visibility="hidden" which
removes it from keyboard focus; update the Button (component named Button used
with props aria-label={pin.title}, title={pin.title}, color={...}) to hide via
opacity (e.g., opacity={pin.isActive ? undefined : 0}) instead of visibility and
ensure it becomes visible on hover and keyboard focus by adding the reveal
styles on both _groupHover and a focus state (e.g., _groupHover and
_focusVisible or _groupFocusWithin) so keyboard users can tab to the pin toggle
even when it’s not pinned; keep the existing aria-label/title and pin.onToggle
behavior intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/app/src/components/NotePreview/NotePreview.tsx`:
- Around line 63-73: The pin Button in NotePreview currently uses
visibility="hidden" which removes it from keyboard focus; update the Button
(component named Button used with props aria-label={pin.title},
title={pin.title}, color={...}) to hide via opacity (e.g., opacity={pin.isActive
? undefined : 0}) instead of visibility and ensure it becomes visible on hover
and keyboard focus by adding the reveal styles on both _groupHover and a focus
state (e.g., _groupHover and _focusVisible or _groupFocusWithin) so keyboard
users can tab to the pin toggle even when it’s not pinned; keep the existing
aria-label/title and pin.onToggle behavior intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1e11ec8c-5ab4-4646-a8e3-478368aaeb43

📥 Commits

Reviewing files that changed from the base of the PR and between a14aff8 and 1fb3b00.

📒 Files selected for processing (1)
  • packages/app/src/components/NotePreview/NotePreview.tsx

@katsyuta katsyuta marked this pull request as draft May 2, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pin notes

2 participants