Skip to content

Notes dropdown#344

Merged
chmurson merged 14 commits intomainfrom
notes-dropdown
Nov 19, 2025
Merged

Notes dropdown#344
chmurson merged 14 commits intomainfrom
notes-dropdown

Conversation

@chmurson
Copy link
Collaborator

@chmurson chmurson commented Nov 18, 2025

Screenshot 2025-11-18 at 23 42 47

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced an interactive dropdown menu for note actions including copy, edit, and remove options.
    • Implemented a two-step confirmation process for note deletion to prevent accidental removal.
    • Added copy-to-clipboard functionality for version-specific note links.
    • Enhanced note navigation to support opening notes in their current or original versions.

✏️ Tip: You can customize this high-level summary in your review settings.

@netlify
Copy link

netlify bot commented Nov 18, 2025

Deploy Preview for graypaper-reader ready!

Name Link
🔨 Latest commit e200f5a
🔍 Latest deploy log https://app.netlify.com/projects/graypaper-reader/deploys/691e4341ccdafb00081c19ac
😎 Deploy Preview https://deploy-preview-344--graypaper-reader.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link

coderabbitai bot commented Nov 18, 2025

Warning

Rate limit exceeded

@chmurson has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 13 minutes and 59 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e2513f4 and e200f5a.

📒 Files selected for processing (1)
  • src/components/NoteManager/components/NoteDropdown.tsx (1 hunks)

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Notes dropdown' directly relates to the primary change: adding a dropdown menu UI component for notes with various actions (Open, Edit, Remove).

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.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 18, 2025

Visual Regression Test Report ✅ Passed

Github run id: 19518352473

🔗 Artifacts: Download

Copy link

@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

🧹 Nitpick comments (5)
tools/snapshot-tests/tests/basic-snapshots.spec.ts (1)

78-86: Dropdown screenshot test may be slightly brittle

The new "notes tab - after opening dropdown" test clicks the dropdown trigger and immediately takes a screenshot. If the dropdown content animates in or renders asynchronously, you may capture a partially-open state. Consider also waiting for a deterministic dropdown content selector (e.g., the menu container) to be visible before taking the screenshot to reduce flakiness.

src/components/NoteManager/NoteManager.tsx (1)

87-114: Consider preserving other locationParams fields when updating selection

memoizedHandleSelectNote rebuilds selectionStart, selectionEnd, and version and then calls setLocationParams with a new object containing only those three fields. If locationParams ever grows additional properties (e.g., page-level metadata), this will drop them on each selection change. To future-proof this, consider spreading the existing params before overriding the selection-specific fields, for example:

-      locationRef.current.setLocationParams({
-        selectionStart,
-        selectionEnd,
-        version: version,
-      });
+      locationRef.current.setLocationParams({
+        ...locationRef.current.locationParams,
+        selectionStart,
+        selectionEnd,
+        version,
+      });

This keeps the current behavior while avoiding accidental loss of extra state if locationParams evolves.

src/components/NoteManager/components/NoteDropdown.tsx (1)

20-47: Guard against undefined context links and clipboard failures

currentVersionLink and noteOriginalVersionShort come from context where the types allow undefined. If either is missing, copyLink will generate a /#undefined URL and the “Open in v…“ label can render as vundefined. It’s probably impossible in normal flows, but defensively checking (or narrowing the types in ISingleNoteContext) before using them would make this component more robust.

navigator.clipboard.writeText also returns a Promise and can reject (permissions, non-secure context). If that happens today, it fails silently while still showing “Link copied!”. Consider adding a .catch (even if it just logs) or handling the rejection to keep behavior consistent with the UI feedback.

Also applies to: 81-103

src/components/NoteManager/components/NoteLink.tsx (1)

24-25: Tighten handling of context links and clean up unused prop

Two minor points:

  • currentVersionLink / originalVersionLink are typed as possibly undefined, but are interpolated into href. If either is missing you’ll end up with #undefined. Either narrow the types at the context level for notes that render NoteLink, or add a small guard/fallback here before rendering the anchors.
  • NoteLinkProps still declares active?: boolean, but the component no longer uses it. Dropping that prop (and the active={false} at the call-site) will avoid confusion about behavior.

Also applies to: 59-69, 72-77

src/components/NoteManager/components/Note.tsx (1)

34-39: Version-link computation & context wiring are sound; consider small robustness tweaks

The way you derive:

  • noteOriginalVersionShort from metadata.versions[note.original.version]?.name,
  • currentVersionLink / originalLink via getHashFromLocationParams, and
  • pass everything through noteLayoutContext with satisfies ISingleNoteContext

is coherent and keeps all link/version data centralized in the context. Two minor refinements to consider:

  • noteOriginalVersionShort can be undefined; any consumers that render v{noteOriginalVersionShort} (e.g., NoteDropdown/NoteLink) will show vundefined if metadata is missing. Either fall back to note.original.version here or have downstream components guard against undefined.
  • The useMemo deps currently use note.current and note. If note or its nested selection fields are ever mutated in place, the memos won’t see the change. Depending on how IDecoratedNote is produced, you may want to depend on the specific selection fields instead (e.g., note.current.selectionStart, note.current.selectionEnd, note.original.selectionStart, note.original.selectionEnd) to make the memoization more robust.

Also applies to: 128-146, 148-184

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea52180 and 5556a74.

⛔ Files ignored due to path filters (24)
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/homepage-outline-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/homepage-outline-light-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/notes-tab-after-note-edit-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/notes-tab-after-note-edit-light-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/notes-tab-after-opening-dropdown-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/notes-tab-after-opening-dropdown-light-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/search-initial-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/search-initial-light-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/search-with-query-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/search-with-query-light-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/search-with-results-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/search-with-results-light-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/topbar-github-expanded-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/topbar-github-expanded-light-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/topbar-github-option-hover-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/topbar-github-option-hover-light-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/topbar-more-clicked-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/topbar-more-clicked-light-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/topbar-notes-dropdown-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/topbar-notes-dropdown-light-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/topbar-notes-selected-off-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/topbar-notes-selected-off-light-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/topbar-notes-selected-on-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/topbar-notes-selected-on-light-mode-linux.png is excluded by !**/*.png
📒 Files selected for processing (8)
  • src/components/NoteManager/NoteManager.tsx (4 hunks)
  • src/components/NoteManager/components/Note.tsx (7 hunks)
  • src/components/NoteManager/components/NoteContext.tsx (2 hunks)
  • src/components/NoteManager/components/NoteDropdown.tsx (1 hunks)
  • src/components/NoteManager/components/NoteLayout.tsx (2 hunks)
  • src/components/NoteManager/components/NoteLink.tsx (4 hunks)
  • src/components/NoteManager/components/NotesList.tsx (1 hunks)
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

⚙️ CodeRabbit configuration file

When reviewing Tailwind CSS classes, ensure they follow Tailwind 4.x conventions and suggest modern 4.x alternatives for deprecated patterns.

Files:

  • tools/snapshot-tests/tests/basic-snapshots.spec.ts
  • src/components/NoteManager/components/NoteLayout.tsx
  • src/components/NoteManager/components/NotesList.tsx
  • src/components/NoteManager/components/Note.tsx
  • src/components/NoteManager/NoteManager.tsx
  • src/components/NoteManager/components/NoteLink.tsx
  • src/components/NoteManager/components/NoteDropdown.tsx
  • src/components/NoteManager/components/NoteContext.tsx
🧠 Learnings (3)
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: Project targets React 19+; prefer passing refs as a regular prop in function components and avoid React.forwardRef in new code. File: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/NotesList.tsx
  • src/components/NoteManager/components/Note.tsx
  • src/components/NoteManager/NoteManager.tsx
  • src/components/NoteManager/components/NoteLink.tsx
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: This repo targets React 19+; prefer passing refs as a normal prop to function components and avoid React.forwardRef in new code. File context: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/NotesList.tsx
  • src/components/NoteManager/components/Note.tsx
  • src/components/NoteManager/NoteManager.tsx
  • src/components/NoteManager/components/NoteLink.tsx
  • src/components/NoteManager/components/NoteDropdown.tsx
  • src/components/NoteManager/components/NoteContext.tsx
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: Tailwind CSS v4 is used; avoid non-default width utilities like border-b-1 unless configured. Prefer border-b (1px) or border-b-[1px]. File context: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/NoteLink.tsx
🧬 Code graph analysis (7)
src/components/NoteManager/components/NoteLayout.tsx (1)
src/components/NoteManager/components/NoteDropdown.tsx (1)
  • NoteDropdown (20-105)
src/components/NoteManager/components/NotesList.tsx (1)
src/components/NotesProvider/types/DecoratedNote.ts (1)
  • IDecoratedNote (4-19)
src/components/NoteManager/components/Note.tsx (7)
src/components/NotesProvider/types/DecoratedNote.ts (1)
  • IDecoratedNote (4-19)
src/components/MetadataProvider/MetadataProvider.tsx (1)
  • useMetadataContext (37-43)
src/components/LocationProvider/VersionProvider.tsx (1)
  • useVersionContext (6-14)
src/components/LocationProvider/hooks/useGetLocationParamsToHash.ts (1)
  • useGetLocationParamsToHash (6-18)
src/components/NoteManager/components/NoteContext.tsx (1)
  • ISingleNoteContext (6-22)
src/components/NoteManager/components/NoteLayout.tsx (1)
  • NoteLayout (66-73)
src/components/NoteManager/components/NoteLink.tsx (1)
  • NoteLink (14-83)
src/components/NoteManager/NoteManager.tsx (3)
src/components/NotesProvider/types/DecoratedNote.ts (1)
  • IDecoratedNote (4-19)
src/components/NotesProvider/types/StorageNote.ts (1)
  • IStorageNote (5-5)
shared/links-metadata/src/types.ts (1)
  • ISynctexBlockId (1-4)
src/components/NoteManager/components/NoteLink.tsx (2)
src/components/NotesProvider/types/DecoratedNote.ts (1)
  • IDecoratedNote (4-19)
src/components/NoteManager/components/NoteContext.tsx (1)
  • useNoteContext (26-32)
src/components/NoteManager/components/NoteDropdown.tsx (1)
src/components/NoteManager/components/NoteContext.tsx (1)
  • useNoteContext (26-32)
src/components/NoteManager/components/NoteContext.tsx (1)
src/components/NotesProvider/types/DecoratedNote.ts (1)
  • IDecoratedNote (4-19)
🔇 Additional comments (10)
tools/snapshot-tests/tests/basic-snapshots.spec.ts (1)

88-90: Updated edit button selector matches new dropdown markup

Switching the selector to [data-testid="edit-note-button"] aligns with the DropdownMenuItem in src/components/NoteManager/components/NoteDropdown.tsx, keeping the test in sync with the new dropdown-based edit action.

src/components/NoteManager/components/NoteLayout.tsx (1)

6-7: Exposing NoteDropdown via NoteLayout looks good

Importing NoteDropdown and wiring it as NoteLayout.Dropdown keeps the note-related components discoverable under a single namespace and avoids ad-hoc imports at call sites. No issues from a typing or structure perspective.

Also applies to: 66-73

src/components/NoteManager/NoteManager.tsx (1)

3-3: Ref-backed handlers for add/update/delete are a solid stabilization pattern

Storing handleAddNote, handleDeleteNote, and handleUpdateNote in refs and calling them from useCallback-memoized wrappers gives you stable function identities for MemoizedNotesList while avoiding stale closures. The updated handleAddNoteClick using handleAddNoteRef.current and passing the memoized handlers into MemoizedNotesList is consistent and should reduce unnecessary re-renders of note items.

Also applies to: 33-36, 38-43, 45-52, 72-83, 85-86, 141-147

src/components/NoteManager/components/NoteContext.tsx (1)

6-25: Typed note context plus runtime guard improves safety

Defining ISingleNoteContext and updating noteContext to createContext<ISingleNoteContext | null>(null) with a throwing useNoteContext guard makes misuse (accessing the hook outside a provider) fail fast and keeps all consumers aligned on a single, explicit context shape. The added fields for selection and version links fit well with the dropdown and selection flows introduced elsewhere.

src/components/NoteManager/components/NotesList.tsx (1)

14-20: Type contract inconsistency between prop declaration and context exposure

The verification reveals a type mismatch. NotesList.tsx declares onSelectNote with a required opts parameter, but internally through the context (NoteContext.tsx), it's exposed as handleSelectNote with optional parameters. While the wrapper (memoizedOnSelectNote) provides defaults and always passes both arguments to the parent callback, this creates an inconsistent contract:

  • NotesList prop (line 19): onSelectNote: (note, opts: { type: ... }) => void (required)
  • NoteContext (line 10): handleSelectNote: (opts?: { type?: ... }) => void (optional)

Call sites like NoteDropdown.tsx (line 51) and NoteLink.tsx (line 51) call handleSelectNote() without arguments, which is valid for the context type but obscures the intent.

Recommendation: Clarify the type contract—either make the context type match the prop's required signature, or explicitly document why the wrapper provides optional parameters with defaults.

src/components/NoteManager/components/NoteDropdown.tsx (2)

107-152: Delayed alert & Escape-simulation behavior looks solid

The delayed-alert menu item correctly:

  • Uses useEffect with cleanup to clear the timeout.
  • Prevents default item behavior while the alert is active.
  • Ensures onClick fires only once per activation and then simulates Escape to close the dropdown.

No issues from a correctness or UX perspective here.


154-194: Two-step confirmation flow is well-implemented

The two-step confirmation item:

  • Uses transient state plus a timeout with proper cleanup.
  • Prevents propagation on the first click and lets the second click go through to onClick.
  • Applies destructive styling only in the confirmation window.

This matches the intended destructive-action UX and looks correct.

src/components/NoteManager/components/NoteLink.tsx (1)

14-52: Context‑driven navigation wiring looks consistent

Routing both the migration badge and the main OutlineLink through handleSelectNote centralizes note selection logic nicely, and the async section/subsection title resolution keeps the component focused on display concerns. This change aligns well with the shared NoteContext design.

Also applies to: 72-78

src/components/NoteManager/components/Note.tsx (2)

80-93: Click and keyboard activation behavior for notes looks correct

The root note card:

  • Ignores clicks originating from nested buttons/links.
  • Avoids re-triggering selection when active is already true.
  • Exposes button-like semantics for inactive notes via role="button", tabIndex=0, and handling both Enter and Space in handleNoteEnter.

This keeps activation predictable without interfering with inner controls or edit mode.

Also applies to: 95-110, 221-233


118-127: Stable handleSelectNote via ref + callback is a good pattern

Storing onSelectNote in a ref and exposing memoizedOnSelectNote through the context avoids stale-closure issues when the parent callback changes while children hold onto the old version. Combined with the satisfies ISingleNoteContext check, this is a solid and type-safe way to provide the handler to nested components like NoteDropdown and NoteLink.

Also applies to: 148-166

@tomusdrw
Copy link
Member

Couple of notes:

  1. Can you make the dropdown menu elements have cursor: pointer?
  2. Can "Open & Open in vxxx" be regular a href elements and have proper href value, so that the destination they point to can be opened in a new tab / link copied by right clicking?
  3. The "Close" thing is imho unnecessary, I'd rather see the regular menu there (with "Open" being a href, so that it can be opened in a new tab.
  4. Ideally the "Copy link" would just be an icon in the same row as "Open", but that's a stretch goal - it's good enough as it is currently.

@chmurson
Copy link
Collaborator Author

chmurson commented Nov 19, 2025

Couple of notes:

  1. Can you make the dropdown menu elements have cursor: pointer?
  2. Can "Open & Open in vxxx" be regular a href elements and have proper href value, so that the destination they point to can be opened in a new tab / link copied by right clicking?
  3. The "Close" thing is imho unnecessary, I'd rather see the regular menu there (with "Open" being a href, so that it can be opened in a new tab.
  4. Ideally the "Copy link" would just be an icon in the same row as "Open", but that's a stretch goal - it's good enough as it is currently.
  1. What would you say if we can cursor: pointer for al DropdownMenuItem ? I think other places could use cursor: pointer as well, like this for instance
Screenshot 2025-11-19 at 22 10 37
  1. Good point, I've missed that. Done
  2. Okay, I've replaced that with open
  3. Okay, It's in one row. Could you please tell if it's good enough ?

Copy link

@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: 2

🧹 Nitpick comments (3)
src/components/NoteManager/components/icons/CopyIcon.tsx (1)

1-19: Copy icon implementation looks solid

SVG structure and accessibility title look good; no functional issues spotted. If you foresee reuse at different sizes, you could later extend this to accept a className or size prop, but what’s here is fine for now.

src/components/NoteManager/components/icons/CheckIcon.tsx (1)

1-15: Check icon is fine; consider more specific title text

The checkmark SVG is well-formed and accessible. Since this is used to indicate a successful copy, you might optionally rename the <title> to something like “Copied” or “Link copied” for slightly clearer screen‑reader output.

src/components/NoteManager/components/NoteDropdown.tsx (1)

26-40: Clarify whether “Open” should ever trigger a “close” action when the note is active

handleOpenClose currently does:

handleSelectNote({ type: active ? "close" : "currentVersion" });

but the corresponding menu item is rendered with disabled={active}, which likely prevents clicks when active === true. In that case the "close" path is effectively unreachable.

If the intention is:

  • “Open” is disabled when the note is already active, and closing happens elsewhere → you can simplify this to always use "currentVersion" and keep disabled={active}.
  • “Open” should toggle between open/close → you probably want to drop disabled={active} so the item remains clickable, and rely on the active ? "close" : "currentVersion" logic.

Worth double‑checking the desired UX and aligning the implementation to avoid dead branches.

Also applies to: 86-91, 123-163

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5556a74 and 7a9144f.

📒 Files selected for processing (6)
  • src/components/NoteManager/components/NoteDropdown.tsx (1 hunks)
  • src/components/NoteManager/components/NoteLabels.tsx (1 hunks)
  • src/components/NoteManager/components/NoteLayout.tsx (2 hunks)
  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (1 hunks)
  • src/components/NoteManager/components/icons/CheckIcon.tsx (1 hunks)
  • src/components/NoteManager/components/icons/CopyIcon.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

⚙️ CodeRabbit configuration file

When reviewing Tailwind CSS classes, ensure they follow Tailwind 4.x conventions and suggest modern 4.x alternatives for deprecated patterns.

Files:

  • src/components/NoteManager/components/NoteLabels.tsx
  • src/components/NoteManager/components/icons/CopyIcon.tsx
  • src/components/NoteManager/components/NoteLayout.tsx
  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx
  • src/components/NoteManager/components/icons/CheckIcon.tsx
  • src/components/NoteManager/components/NoteDropdown.tsx
🧠 Learnings (1)
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: This repo targets React 19+; prefer passing refs as a normal prop to function components and avoid React.forwardRef in new code. File context: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/NoteLabels.tsx
  • src/components/NoteManager/components/NoteDropdown.tsx
🧬 Code graph analysis (3)
src/components/NoteManager/components/NoteLayout.tsx (1)
src/components/NoteManager/components/NoteDropdown.tsx (1)
  • NoteDropdown (21-121)
src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (2)
src/components/NoteManager/components/icons/CheckIcon.tsx (1)
  • CheckIcon (1-15)
src/components/NoteManager/components/icons/CopyIcon.tsx (1)
  • CopyIcon (1-19)
src/components/NoteManager/components/NoteDropdown.tsx (2)
src/components/NoteManager/components/NoteContext.tsx (1)
  • useNoteContext (26-32)
src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (1)
  • DropdownMenuItemCopyButton (6-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: visual-tests
🔇 Additional comments (2)
src/components/NoteManager/components/NoteLabels.tsx (1)

9-9: LGTM! Import path typo corrected.

The import path has been correctly updated from SiimpleComponents to SimpleComponents, fixing the typo and aligning with the broader SimpleComponents refactor across the codebase.

src/components/NoteManager/components/NoteLayout.tsx (1)

6-9: NoteLayout wiring for Dropdown and textarea import looks correct

Importing NoteDropdown and exposing it via NoteLayout.Dropdown, plus fixing the NoteSimpleTextarea import path, are consistent with the surrounding structure and should integrate cleanly.

Also applies to: 66-73

Copy link

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
src/components/NoteManager/components/Note.tsx (1)

80-86: Remove e.preventDefault() from handleWholeNoteClick to allow normal anchor navigation

The issue is confirmed. The handleWholeNoteClick handler at lines 80-86 in src/components/NoteManager/components/Note.tsx calls e.preventDefault() on clicks that target anchors or buttons. However, the "Open" and "Open in vX" items in the dropdown (in src/components/NoteManager/components/NoteDropdown.tsx, lines 87 and 96-103) are real <a> elements with href attributes. When users click these links, the click bubbles to the card's click handler, which prevents the default anchor navigation, breaking:

  • Normal left-click navigation
  • Ctrl/Cmd+click to open in new tab
  • Middle-click
  • Right-click context menu options

The fix is to remove the e.preventDefault() call and only return early to short-circuit card activation:

 const handleWholeNoteClick = (e: React.MouseEvent<HTMLDivElement>) => {
   const target = e.target;

   if (target instanceof Element && (target.closest("button") || target.closest("a"))) {
-    e.preventDefault();
     return;
   }

   if (active) {
     return;
   }

   onSelectNote(note, { type: "currentVersion" });
 };

This preserves the intent of ignoring card activation for inner controls while allowing anchors to behave as normal links.

Note: The reference to "lines 289-295" appears to be inaccurate—those lines only render the dropdown component, not a similar handler.

🧹 Nitpick comments (3)
src/components/NoteManager/components/Note.tsx (3)

34-39: Version/metadata link computation and context exposure look consistent

Using useMetadataContext, useVersionContext, and useGetLocationParamsToHash to derive noteOriginalVersionShort, currentVersionLink, and originalVersionLink lines up with how NoteLink consumes these fields via useNoteContext. The dependency arrays on both useMemo hooks and the enclosing noteLayoutContext memo look correct and should avoid unnecessary recomputations.

If you want to be a bit more explicit, you could narrow the useMemo dependencies to the exact subfields used (e.g. note.original instead of note) for readability, but this is purely optional.

Also applies to: 128-147, 148-166


186-203: Hover/focus/dropdown state resolves the previous keyboard accessibility issue

The new isHovered/isFocused/isDropdownOpen trio plus noteRef and handleIsDropdownOpen give the dropdown a clear visibility model: it’s rendered when the note is hovered, focused, or the dropdown is open, and the focus/mouse tracking on close prevents flicker. This also fixes the prior problem where the dropdown wasn’t reachable via keyboard because it was only shown on hover.

If you ever want to trim global listeners, you could switch from a window‑level mousemove handler to pointer‑enter/leave on a slightly larger wrapper around the dropdown trigger, but the current implementation is acceptable and low‑cost.

Also applies to: 204-222, 227-250, 289-294


252-288: Minor JSX cleanup: redundant fragment in editing block

In the active && isEditing branch you have nested empty fragments (<> inside another <>) which don’t add structure and slightly increase visual noise:

{active && isEditing && (
  <>
    <>
      <NoteLayout.SelectedText />
      ...
    </>
  </>
)}

You can safely collapse this to a single fragment:

- {active && isEditing && (
-   <>
-     <>
-       <NoteLayout.SelectedText />
-       ...
-     </>
-   </>
- )}
+ {active && isEditing && (
+   <>
+     <NoteLayout.SelectedText />
+     ...
+   </>
+ )}

Purely cosmetic, but makes the JSX a bit easier to scan.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a9144f and e4e6215.

📒 Files selected for processing (1)
  • src/components/NoteManager/components/Note.tsx (7 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

⚙️ CodeRabbit configuration file

When reviewing Tailwind CSS classes, ensure they follow Tailwind 4.x conventions and suggest modern 4.x alternatives for deprecated patterns.

Files:

  • src/components/NoteManager/components/Note.tsx
🧠 Learnings (2)
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: Project targets React 19+; prefer passing refs as a regular prop in function components and avoid React.forwardRef in new code. File: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/Note.tsx
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: This repo targets React 19+; prefer passing refs as a normal prop to function components and avoid React.forwardRef in new code. File context: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/Note.tsx
🧬 Code graph analysis (1)
src/components/NoteManager/components/Note.tsx (7)
src/components/NotesProvider/types/DecoratedNote.ts (1)
  • IDecoratedNote (4-19)
src/components/MetadataProvider/MetadataProvider.tsx (1)
  • useMetadataContext (37-43)
src/components/LocationProvider/VersionProvider.tsx (1)
  • useVersionContext (6-14)
src/components/LocationProvider/hooks/useGetLocationParamsToHash.ts (1)
  • useGetLocationParamsToHash (6-18)
src/components/NoteManager/components/NoteContext.tsx (1)
  • ISingleNoteContext (6-22)
src/components/NoteManager/components/NoteLayout.tsx (1)
  • NoteLayout (66-73)
src/components/NoteManager/components/NoteLink.tsx (1)
  • NoteLink (14-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: visual-tests
🔇 Additional comments (2)
src/components/NoteManager/components/Note.tsx (2)

34-39: Validation and error-state handling for note editing looks solid

noteContentError is consistently cleared on save/cancel/edit and only set from validateMath, so editing flows won’t get stuck in an error state. The conditional rendering of the validation message and error class on NoteLayout.TextArea is coherent with this state.

Also applies to: 67-79, 266-283


24-25: Code review approval confirmed through comprehensive inspection

The onSelectNote signature at lines 24–25 and the ref wrapper pattern at lines 118–126 are correctly implemented. All call sites across Note.tsx, NotesList.tsx, and NoteManager.tsx match the signature and type constraints, with proper context wiring to pass memoizedOnSelectNote through the layout context. No issues found.

Copy link

@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: 3

🧹 Nitpick comments (1)
src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (1)

43-43: Consider removing negative margin workaround.

The my-[-8px] negative margin suggests a layout issue elsewhere. If possible, adjust the parent container's spacing rather than compensating here.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e4e6215 and 1118a6c.

📒 Files selected for processing (2)
  • src/components/NoteManager/components/NoteDropdown.tsx (1 hunks)
  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/NoteManager/components/NoteDropdown.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

⚙️ CodeRabbit configuration file

When reviewing Tailwind CSS classes, ensure they follow Tailwind 4.x conventions and suggest modern 4.x alternatives for deprecated patterns.

Files:

  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx
🧬 Code graph analysis (1)
src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (2)
src/components/NoteManager/components/icons/CheckIcon.tsx (1)
  • CheckIcon (1-15)
src/components/NoteManager/components/icons/CopyIcon.tsx (1)
  • CopyIcon (1-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: visual-tests

Copy link

@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: 2

🧹 Nitpick comments (1)
src/components/NoteManager/components/NoteDropdown.tsx (1)

60-68: Consider an explicit dropdown close mechanism instead of dispatching synthetic events.

While the callback pattern addresses the previous concern, dispatching a synthetic Escape event remains fragile—any component listening for Escape will receive this fabricated event, potentially triggering unintended side effects.

If the dropdown library (DropdownMenu from shared-ui) exposes a programmatic close method or an open state controller, use that instead. For example, if onOpenChange can be paired with controlled open state, you could manage closing explicitly.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1118a6c and 27dae62.

📒 Files selected for processing (2)
  • src/components/NoteManager/components/NoteDropdown.tsx (1 hunks)
  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

⚙️ CodeRabbit configuration file

When reviewing Tailwind CSS classes, ensure they follow Tailwind 4.x conventions and suggest modern 4.x alternatives for deprecated patterns.

Files:

  • src/components/NoteManager/components/NoteDropdown.tsx
  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx
🧠 Learnings (3)
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: Project targets React 19+; prefer passing refs as a regular prop in function components and avoid React.forwardRef in new code. File: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/NoteDropdown.tsx
  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: This repo targets React 19+; prefer passing refs as a normal prop to function components and avoid React.forwardRef in new code. File context: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/NoteDropdown.tsx
  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: Tailwind CSS v4 is used; avoid non-default width utilities like border-b-1 unless configured. Prefer border-b (1px) or border-b-[1px]. File context: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/NoteDropdown.tsx
🧬 Code graph analysis (2)
src/components/NoteManager/components/NoteDropdown.tsx (2)
src/components/NoteManager/components/NoteContext.tsx (1)
  • useNoteContext (26-32)
src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (1)
  • DropdownMenuItemCopyButton (6-53)
src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (2)
src/components/NoteManager/components/icons/CopyIcon.tsx (1)
  • CopyIcon (1-19)
src/components/NoteManager/components/icons/CheckIcon.tsx (1)
  • CheckIcon (1-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: visual-tests
🔇 Additional comments (6)
src/components/NoteManager/components/NoteDropdown.tsx (3)

1-35: LGTM!

The imports, component signature, and context usage are clean and well-structured.


117-128: LGTM!

The conditional edit and remove actions are well-structured with proper event handling and the two-step confirmation pattern for the destructive action.


133-173: LGTM!

The TwoStepDropdownMenuItem component provides a clean two-step confirmation pattern with proper timeout cleanup, conditional styling, and event handling for destructive actions.

src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (3)

1-9: LGTM!

The imports, props definition, and state setup are clean. Using useRef for the callback is a good pattern to maintain a stable reference.


11-24: LGTM!

The useEffect properly manages the timeout lifecycle with cleanup and invokes the callback after the 2-second delay.


48-50: LGTM!

The conditional icon rendering provides clear visual feedback for each state (idle, success, error).

Copy link

@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

♻️ Duplicate comments (1)
src/components/NoteManager/components/NoteDropdown.tsx (1)

96-114: Nested button inside anchor elements.

The "Open" and "Open in vX" menu items nest a <button> (from DropdownMenuItemCopyButton) inside an <a> element, producing invalid HTML (<a><button>…</button></a>).

This was previously discussed in past review comments, and you indicated that you prefer to keep this structure to maintain consistent hover/click areas, accepting the accessibility trade-offs.

🧹 Nitpick comments (1)
src/components/NoteManager/components/NoteDropdown.tsx (1)

60-68: Consider using the dropdown's state management instead of synthetic events.

Dispatching a synthetic Escape event to close the dropdown is fragile and couples this component to the dropdown library's keyboard handling implementation. If the library changes how it processes keyboard events, this could break.

A more robust approach would be to control the dropdown's open state directly:

 export const NoteDropdown = ({
   buttonClassName,
   onDelete,
   onOpenChange,
 }: { buttonClassName?: string; onDelete?: () => void; onOpenChange?: (open: boolean) => void }) => {
+  const [open, setOpen] = useState(false);
   const {
     active,
     handleSelectNote,
     noteOriginalVersionShort,
     note,
     handleEditClick,
     currentVersionLink,
     isEditable,
     originalVersionLink,
   } = useNoteContext();

   // ... other handlers ...

   const handleCopyComplete = () => {
-    const escapeEvent = new KeyboardEvent("keydown", {
-      key: "Escape",
-      code: "Escape",
-      keyCode: 27,
-      bubbles: true,
-    });
-    document.dispatchEvent(escapeEvent);
+    setOpen(false);
   };

   return (
-    <DropdownMenu onOpenChange={onOpenChange}>
+    <DropdownMenu open={open} onOpenChange={(isOpen) => {
+      setOpen(isOpen);
+      onOpenChange?.(isOpen);
+    }}>
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27dae62 and e2513f4.

⛔ Files ignored due to path filters (2)
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/notes-tab-after-opening-dropdown-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/notes-tab-after-opening-dropdown-light-mode-linux.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • src/components/NoteManager/components/NoteDropdown.tsx (1 hunks)
  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

⚙️ CodeRabbit configuration file

When reviewing Tailwind CSS classes, ensure they follow Tailwind 4.x conventions and suggest modern 4.x alternatives for deprecated patterns.

Files:

  • src/components/NoteManager/components/NoteDropdown.tsx
🧠 Learnings (3)
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: Project targets React 19+; prefer passing refs as a regular prop in function components and avoid React.forwardRef in new code. File: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/NoteDropdown.tsx
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: This repo targets React 19+; prefer passing refs as a normal prop to function components and avoid React.forwardRef in new code. File context: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/NoteDropdown.tsx
📚 Learning: 2025-10-22T20:36:10.440Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 330
File: src/components/Outline/OutlineLink.tsx:17-21
Timestamp: 2025-10-22T20:36:10.440Z
Learning: Tailwind CSS v4 is used; avoid non-default width utilities like border-b-1 unless configured. Prefer border-b (1px) or border-b-[1px]. File context: src/components/Outline/OutlineLink.tsx.

Applied to files:

  • src/components/NoteManager/components/NoteDropdown.tsx
🧬 Code graph analysis (1)
src/components/NoteManager/components/NoteDropdown.tsx (2)
src/components/NoteManager/components/NoteContext.tsx (1)
  • useNoteContext (26-32)
src/components/NoteManager/components/SimpleComponents/DropdownMenuItemCopyButton.tsx (1)
  • DropdownMenuItemCopyButton (6-53)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: visual-tests
🔇 Additional comments (2)
src/components/NoteManager/components/NoteDropdown.tsx (2)

96-127: Verify that cursor: pointer was applied to dropdown menu items.

The PR comments indicate that the reviewer requested cursor: pointer on dropdown menu elements and you agreed to apply it. However, I don't see any cursor-pointer Tailwind class on the DropdownMenuItem elements in this file (lines 96, 105, 120, and 165).

Please confirm:

  1. Was cursor: pointer added to the DropdownMenuItem component definition itself (in the shared-ui library)?
  2. Or should the cursor-pointer class be added to the individual menu items in this file?

The anchor elements (lines 97, 106) will have pointer cursor by default, but the non-anchor items (lines 120, 165-171) may need explicit styling.

If the style needs to be added here, consider:

-          <DropdownMenuItem onClick={editNode} data-testid="edit-note-button">
+          <DropdownMenuItem onClick={editNode} data-testid="edit-note-button" className="cursor-pointer">
             <span>Edit note</span>
           </DropdownMenuItem>

And similarly for the TwoStepDropdownMenuItem, either in its definition or in its className prop.


52-58: No action required—calling handleSelectNote() without arguments is valid.

The function signature in NoteContext.tsx defines the parameter as optional: handleSelectNote: (opts?: { type?: "currentVersion" | "originalVersion" | "close" }) => void;

The opts? notation makes the parameter fully optional, so both calling patterns are valid:

  • handleSelectNote() — uses default behavior
  • handleSelectNote({ type: "currentVersion" }) — explicitly specifies type

This pattern is consistent across the codebase (e.g., NoteLink.tsx line 51 also calls handleSelectNote() without arguments). The code is correct as written.

Likely an incorrect or invalid review comment.

@chmurson chmurson merged commit 17aad03 into main Nov 19, 2025
8 of 9 checks passed
@chmurson chmurson deleted the notes-dropdown branch November 19, 2025 22:23
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.

2 participants