Skip to content

feat: add pin notes#276

Open
katsyuta wants to merge 58 commits into
DeepinkApp:masterfrom
katsyuta:90-add-pin-notes
Open

feat: add pin notes#276
katsyuta wants to merge 58 commits into
DeepinkApp:masterfrom
katsyuta:90-add-pin-notes

Conversation

@katsyuta

@katsyuta katsyuta commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator

Closes #90

Pinned notes provide quick access to important notes and always appear first in the list.
They are sorted by pinning time, except in the bin and search results, where pin status is ignored.
You can pin a note using the note context menu or the button in the note editor.

Changes:

  • Update NotesController sort API to support sorting pinned notes by pin time and other notes by update time
  • Add tests
to-21.mp4

Summary by CodeRabbit

  • New Features
    • Pin notes to prioritize them — pinned notes show a pin icon and are listed before others.
    • Toggle pin from the note header button or the note context menu; tooltips update to “Pin/Unpin”.
    • Sorting respects pinned time so pinned items maintain their pinned order; locales updated with Pin/Unpin labels.

Review Change Stack

@coderabbitai

coderabbitai Bot commented Apr 28, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Implements note pinning end-to-end: DB migration adds pinned_at, types and controller map pinned state and support pinnedAt sorting, adds toggle command+handler and telemetry, updates event payloads, UI components (preview/editor/context menu), list hooks, query callers/tests, and locales.

Changes

Note pinning feature

Layer / File(s) Summary
Database schema and migration
packages/app/src/core/database/sqlite/migrations/sql/2_add_pinned_to_notes.sql, packages/app/src/core/database/sqlite/migrations/index.ts
Adds nullable pinned_at INTEGER column to notes table and registers the migration.
Type definitions
packages/app/src/core/features/notes/controller/index.ts
Extends NoteMeta with isPinned: boolean, adds pinnedAt sort field, and changes sort parameter from single object to array of sort descriptors.
Controller implementation
packages/app/src/core/features/notes/controller/NotesController.ts
Decodes pinned_at to isPinned in RowScheme, handles isPinned in formatNoteMeta, adds isPinned filtering and pinnedAt sort mapping, and updates sort to iterate over array of rules.
Commands & telemetry
packages/app/src/hooks/commands/index.ts, packages/app/src/core/features/telemetry/index.ts, packages/app/src/hooks/notes/useNoteCommandHandlers.ts
Adds TOGGLE_NOTE_PIN command with typed payload, registers handler that toggles isPinned, emits NOTE_UPDATED with reason:'meta', and records NOTE_PIN_TOGGLE telemetry.
Event payload types & emissions
packages/app/src/api/events/workspace/index.tsx, packages/app/src/hooks/notes/useNoteCommandHandlers.ts, packages/app/src/features/NoteEditor/index.tsx
Changes NOTE_UPDATED payload from bare NoteId to `{ noteId; reason?: 'meta'
NotePreview component and theme
packages/app/src/components/NotePreview/NotePreview.tsx, packages/app/src/components/NotePreview/NotePreview.theme.ts
Adds isPinned prop to preview, renders conditional thumbtack icon in header, updates imports, and extends theme with header and pinIcon style slots.
Editor pin button
packages/app/src/features/NoteEditor/index.tsx
Converts pin button to toggle: dispatches command on click, binds state and tooltip to isPinned, and emits NOTE_UPDATED with object payload.
Context menu integration
packages/app/src/features/NotesContainer/NoteContextMenu/index.ts, packages/app/src/features/NotesContainer/NoteContextMenu/useNoteContextMenu.ts
Adds PIN_NOTE action, wires handler to dispatch TOGGLE_NOTE_PIN, and renders conditional pin/unpin menu item.
List panel and hooks
packages/app/src/features/MainScreen/NotesListPanel/NotesList.tsx, packages/app/src/features/MainScreen/NotesListPanel/useNotesData.tsx, packages/app/src/features/NotesContainer/index.tsx, packages/app/src/features/MainScreen/NotesListPanel/useScrollToActiveNote.tsx, packages/app/src/hooks/notes/useUpdateNotes.ts
NotesList passes isPinned prop; useNotesData handles NOTE_UPDATED reason to refresh meta immediately; NotesContainer and scroll hook adapt to new payload and ignore meta-only updates; useUpdateNotes conditionally includes pinnedAt in sort.
Query caller standardization
packages/app/src/core/features/notes/bin/DeletedNotesController.ts, packages/app/src/core/features/notes/controller/NotesTextIndexer.ts, packages/app/src/core/storage/interop/export/index.ts, packages/app/src/core/storage/interop/import/NotesImporter.test.ts
Updates note fetch calls to pass sort as array of descriptors instead of single object.
Controller and filter tests
packages/app/src/core/features/notes/controller/NotesController.filters.test.ts, packages/app/src/core/features/notes/controller/NotesController.test.ts
Updates test sort parameters to array format; adds tests for pinned-status ordering, combined pinned/update ordering, filtering by pinned status, and meta toggle behavior.
Translations
packages/app/src/locales/en/features.json, packages/app/src/locales/en/menu.json
Adds English translations for pinNote and unpinNote in features and menu locales.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • DeepinkApp/deepink#195: Extends the command-driven note interaction system where TOGGLE_NOTE_PIN handler and WorkspaceEvents.NOTE_UPDATED payload changes are built directly on the commands architecture.

Suggested reviewers

  • vitonsky

Poem

🐰 A note pinned true at the top it stays,
Through sorts and swaps across the UI's ways.
Thumbtacks and toggles make the feature shine,
Events now carry reasons in their line. ✨

🚥 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 title 'feat: add pin notes' directly and concisely summarizes the main feature addition in the changeset.
Linked Issues check ✅ Passed All primary coding requirements from issue #90 are met: pinned notes appear at the top, are sorted by pin time, non-pinned notes sort by update time, pin/unpin actions are exposed in UI, and the NotesController API supports multi-key sorting.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the pin notes feature; no unrelated modifications detected outside the scope of issue #90.

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

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

coderabbitai Bot commented Apr 28, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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
@katsyuta

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 10, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@katsyuta katsyuta marked this pull request as ready for review May 10, 2026 10:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app/src/core/features/notes/controller/NotesController.ts`:
- Around line 85-87: The isPinned flag is being transformed by the update
mapping in the case 'isPinned' inside formatNoteMeta() (pinned_at =
timestamp/null) and those transformed values are later used verbatim to build
WHERE predicates; extract the incoming isPinned filter out of meta before
calling formatNoteMeta(), do not pass it through the pinned_at update mapping,
and in the filter-building block (where you already handle isDeleted with IS
NULL/IS NOT NULL) add explicit handling for isPinned so that true maps to
"pinned_at IS NOT NULL" and false maps to "pinned_at IS NULL" instead of
generating equality against a timestamp or NULL. Ensure you still call
formatNoteMeta() for other meta fields and keep the 'case "isPinned"' update
mapping only for UPDATE operations.

In `@packages/app/src/features/MainScreen/NotesListPanel/usePinNoteEffect.tsx`:
- Around line 22-24: The flash timeout in usePinNoteEffect.tsx is racy and lacks
cleanup: before calling setTimeout that sets setFlashingNoteId(null) store the
timer id in a ref (e.g., flashTimerRef), clear any existing timer with
clearTimeout(flashTimerRef.current) so previous timeouts can't clear a new
flash, assign the new id to flashTimerRef.current, and add a cleanup return in
the effect to clearTimeout(flashTimerRef.current) on unmount; optionally check
the ref value before clearing to ensure only the intended timer is cleared.
🪄 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 Plus

Run ID: 3c13a384-d075-4186-bf48-76c27a374c60

📥 Commits

Reviewing files that changed from the base of the PR and between d267f34 and 484363d.

📒 Files selected for processing (24)
  • packages/app/src/components/NotePreview/NotePreview.theme.ts
  • 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/bin/DeletedNotesController.ts
  • packages/app/src/core/features/notes/controller/NotesController.filters.test.ts
  • packages/app/src/core/features/notes/controller/NotesController.test.ts
  • packages/app/src/core/features/notes/controller/NotesController.ts
  • packages/app/src/core/features/notes/controller/NotesTextIndexer.ts
  • packages/app/src/core/features/notes/controller/index.ts
  • packages/app/src/core/features/telemetry/index.ts
  • packages/app/src/core/storage/interop/export/index.ts
  • packages/app/src/core/storage/interop/import/NotesImporter.test.ts
  • packages/app/src/features/MainScreen/NotesListPanel/NotesList.tsx
  • packages/app/src/features/MainScreen/NotesListPanel/usePinNoteEffect.tsx
  • packages/app/src/features/NoteEditor/NoteMenu.tsx
  • packages/app/src/features/NoteEditor/index.tsx
  • packages/app/src/features/NotesContainer/NoteContextMenu/index.ts
  • packages/app/src/features/NotesContainer/NoteContextMenu/useNoteContextMenu.ts
  • packages/app/src/hooks/commands/index.ts
  • packages/app/src/hooks/notes/useNoteCommandHandlers.ts
  • packages/app/src/hooks/notes/useUpdateNotes.ts
  • packages/app/src/locales/en/features.json
  • packages/app/src/locales/en/menu.json

Comment thread packages/app/src/core/features/notes/controller/NotesController.ts
Comment thread packages/app/src/features/MainScreen/NotesListPanel/usePinNoteEffect.tsx Outdated
@vitonsky

Copy link
Copy Markdown
Member

@katsyuta what's status here? Is there any screenshots/description how it does work?

@katsyuta

Copy link
Copy Markdown
Collaborator Author

@katsyuta what's status here? Is there any screenshots/description how it does work?

I added a video to the pull request definition
The pull request is ready to review

@katsyuta katsyuta force-pushed the 90-add-pin-notes branch from f17cd06 to c7ee0e2 Compare May 18, 2026 21:41
@vitonsky

vitonsky commented May 19, 2026

Copy link
Copy Markdown
Member

@katsyuta I see a but on 0:27
You pin note, then unpin and notes list tries to focus on that note.
It feels bad. Everything in that list constantly jumps and jiggle, it looks disgusting.

The pin icon in notes list looks nice.

For me it seems we have too many buttons to pin the note (3 or 4 if tabs have that option too, but they must not have, because it is different context). Choose most important ones.

Also I don't like the pin button position in hamburger menu. It's too far down, but my expectation is pin feature will be relatively frequently used. And all features in that menu must be sorted by some meaning. For example the most frequent used features is on top, the most dangerous at the bottom. Now it seems the pin button is not frequently used and quite dangerous.

Also the purple border looks like bug:

591795094-2eff8a0a-8969-49da-8724-641dba3e5157

I don't know why it shown here.

@katsyuta

Copy link
Copy Markdown
Collaborator Author

@vitonsky , Ok, i fix you comments.

Now pin and unpin actions never change the scroll position. Because when a user is browsing notes and pins the note, changing the scroll position leads to loss of context, and the user would need to scroll the notes list manually to come back. I also removed note highlighting.

I agree about the number of pin buttons. I think the important places are the note context menu (only in the notes list context menu) and the note editor. This allows for quickly managing the note pin status.

@katsyuta

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/app/src/hooks/notes/useNoteCommandHandlers.ts (1)

156-157: ⚡ Quick win

Emit reason: 'meta' for metadata-only update events.

These handlers only change meta flags, so emitting an explicit meta reason keeps NOTE_UPDATED consumers consistent with the pin flow and avoids unnecessary content-update reactions.

♻️ Proposed change
-			eventBus.emit(WorkspaceEvents.NOTE_UPDATED, { noteId });
+			eventBus.emit(WorkspaceEvents.NOTE_UPDATED, { noteId, reason: 'meta' });
...
-			eventBus.emit(WorkspaceEvents.NOTE_UPDATED, { noteId });
+			eventBus.emit(WorkspaceEvents.NOTE_UPDATED, { noteId, reason: 'meta' });

Also applies to: 177-178

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/src/hooks/notes/useNoteCommandHandlers.ts` around lines 156 -
157, The NOTE_UPDATED emit currently sends only { noteId } for metadata-only
updates; update the event payload in the metadata-only handlers to include
reason: 'meta' (i.e., change eventBus.emit(WorkspaceEvents.NOTE_UPDATED, {
noteId }) to eventBus.emit(WorkspaceEvents.NOTE_UPDATED, { noteId, reason:
'meta' })) so consumers can distinguish meta-only updates; apply the same change
in both places that emit NOTE_UPDATED (the two eventBus.emit calls in
useNoteCommandHandlers).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/app/src/hooks/notes/useNoteCommandHandlers.ts`:
- Around line 156-157: The NOTE_UPDATED emit currently sends only { noteId } for
metadata-only updates; update the event payload in the metadata-only handlers to
include reason: 'meta' (i.e., change eventBus.emit(WorkspaceEvents.NOTE_UPDATED,
{ noteId }) to eventBus.emit(WorkspaceEvents.NOTE_UPDATED, { noteId, reason:
'meta' })) so consumers can distinguish meta-only updates; apply the same change
in both places that emit NOTE_UPDATED (the two eventBus.emit calls in
useNoteCommandHandlers).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b45fd885-9dc8-4e2a-a658-b22a3c94cfdc

📥 Commits

Reviewing files that changed from the base of the PR and between c0a5387 and 10b265c.

📒 Files selected for processing (26)
  • packages/app/src/api/events/workspace/index.tsx
  • packages/app/src/components/NotePreview/NotePreview.theme.ts
  • 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/bin/DeletedNotesController.ts
  • packages/app/src/core/features/notes/controller/NotesController.filters.test.ts
  • packages/app/src/core/features/notes/controller/NotesController.test.ts
  • packages/app/src/core/features/notes/controller/NotesController.ts
  • packages/app/src/core/features/notes/controller/NotesTextIndexer.ts
  • packages/app/src/core/features/notes/controller/index.ts
  • packages/app/src/core/features/telemetry/index.ts
  • packages/app/src/core/storage/interop/export/index.ts
  • packages/app/src/core/storage/interop/import/NotesImporter.test.ts
  • packages/app/src/features/MainScreen/NotesListPanel/NotesList.tsx
  • packages/app/src/features/MainScreen/NotesListPanel/useNotesData.tsx
  • packages/app/src/features/MainScreen/NotesListPanel/useScrollToActiveNote.tsx
  • packages/app/src/features/NoteEditor/index.tsx
  • packages/app/src/features/NotesContainer/NoteContextMenu/index.ts
  • packages/app/src/features/NotesContainer/NoteContextMenu/useNoteContextMenu.ts
  • packages/app/src/features/NotesContainer/index.tsx
  • packages/app/src/hooks/commands/index.ts
  • packages/app/src/hooks/notes/useNoteCommandHandlers.ts
  • packages/app/src/hooks/notes/useUpdateNotes.ts
  • packages/app/src/locales/en/features.json
  • packages/app/src/locales/en/menu.json

align: 'center',
userSelect: 'none',
paddingInlineEnd: '.3rem',
overflowAnchor: 'none',

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Disable the browser scroll anchoring. Without it when notes are reordered (for example, when pinning from the context menu or the editor), the scroll position changes and jumps to the top.

The browser tries to keep the shifted element in view and therefore triggers automatic scrolling.
See: https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-anchor

@katsyuta

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/app/src/core/database/sqlite/migrations/sql/2_add_pinned_to_notes.sql (1)

1-2: ⚡ Quick win

Add an index for the new pin-ordering column.

Pinned-first sorting will likely query/order by pinned_at; without an index this can degrade list performance as notes grow. Add a follow-up index migration (potentially composite with existing sort columns used by notes list queries).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/app/src/core/database/sqlite/migrations/sql/2_add_pinned_to_notes.sql`
around lines 1 - 2, The migration adds a pinned_at column to the notes table but
does not create an index for queries that order/filter by pinned_at; add a
follow-up SQL migration that creates an index on notes(pinned_at) (and if your
notes-list queries also sort by another column, add a composite index such as
notes(pinned_at, updated_at) or notes(pinned_at, created_at) to match that query
pattern) so pinned-first listing remains performant as the table grows.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@packages/app/src/core/database/sqlite/migrations/sql/2_add_pinned_to_notes.sql`:
- Around line 1-2: The migration adds a pinned_at column to the notes table but
does not create an index for queries that order/filter by pinned_at; add a
follow-up SQL migration that creates an index on notes(pinned_at) (and if your
notes-list queries also sort by another column, add a composite index such as
notes(pinned_at, updated_at) or notes(pinned_at, created_at) to match that query
pattern) so pinned-first listing remains performant as the table grows.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 77fd7eb7-b371-43ff-9642-ae583239a46c

📥 Commits

Reviewing files that changed from the base of the PR and between c0a5387 and fb1d07c.

📒 Files selected for processing (26)
  • packages/app/src/api/events/workspace/index.tsx
  • packages/app/src/components/NotePreview/NotePreview.theme.ts
  • 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/bin/DeletedNotesController.ts
  • packages/app/src/core/features/notes/controller/NotesController.filters.test.ts
  • packages/app/src/core/features/notes/controller/NotesController.test.ts
  • packages/app/src/core/features/notes/controller/NotesController.ts
  • packages/app/src/core/features/notes/controller/NotesTextIndexer.ts
  • packages/app/src/core/features/notes/controller/index.ts
  • packages/app/src/core/features/telemetry/index.ts
  • packages/app/src/core/storage/interop/export/index.ts
  • packages/app/src/core/storage/interop/import/NotesImporter.test.ts
  • packages/app/src/features/MainScreen/NotesListPanel/NotesList.tsx
  • packages/app/src/features/MainScreen/NotesListPanel/useNotesData.tsx
  • packages/app/src/features/MainScreen/NotesListPanel/useScrollToActiveNote.tsx
  • packages/app/src/features/NoteEditor/index.tsx
  • packages/app/src/features/NotesContainer/NoteContextMenu/index.ts
  • packages/app/src/features/NotesContainer/NoteContextMenu/useNoteContextMenu.ts
  • packages/app/src/features/NotesContainer/index.tsx
  • packages/app/src/hooks/commands/index.ts
  • packages/app/src/hooks/notes/useNoteCommandHandlers.ts
  • packages/app/src/hooks/notes/useUpdateNotes.ts
  • packages/app/src/locales/en/features.json
  • packages/app/src/locales/en/menu.json

@katsyuta

Copy link
Copy Markdown
Collaborator Author

@vitonsky, PR is ready to review

@katsyuta katsyuta force-pushed the 90-add-pin-notes branch from 5dd8b41 to 3835210 Compare June 8, 2026 11:47
@katsyuta katsyuta requested a review from vitonsky June 8, 2026 12:43
Comment thread packages/app/src/locales/en/features.json
[WorkspaceEvents.NOTE_UPDATED]: NoteId;
[WorkspaceEvents.NOTE_UPDATED]: {
noteId: NoteId;
reason?: NoteUpdateReason;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why we needed in a reason here?


export const notePreviewRecipe = defineSlotRecipe({
slots: ['root', 'body', 'title', 'text', 'meta'],
slots: ['root', 'body', 'title', 'text', 'meta', 'pinIcon', 'header'],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

  1. Order slots in order of appearance in DOM
  2. Don't use so specific slots as pinIcon. What happens if we would have 2 or 3 icons? Would you add 3 slots for that? It would be difficult to maintain.

]);
});

test('sorts notes by pinned time', async () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Too much code for that file. Should we split it? Maybe introduce the NotesController.sort.test.ts or something like that?

).resolves.toStrictEqual([note3, note1, note2]);
});

test('sorts notes by pinned time and update time', async () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That test names gives me no information about your expectations. Read the internet how to write test names. It must define the expected behaviour.

I want to get known from the test name the answer on question "what about that test?" and get high level summary about the test idea. I don't want to see the generic names.

Comment on lines +48 to +50
// Meta changes (e.g. pin/unpin) reorder the notes list, but note data is updated with debounce,
// which can cause stale UI (e.g. after unpinning the note may still show the pin icon)
// To avoid inconsistency immediately refresh the updated note

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It sounds as a workaround. Think better how to solve a problem with no that hack.

To be honest, i don't understand what the problem we have here.
If our data design are bad, improve that. We must not to handle "meta" updates different than the "data" updates, because it bloat the complexity.

Keep it simple and stupid.

Comment thread packages/app/src/features/NoteEditor/index.tsx
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