Skip to content

Redesign adding a new note#351

Merged
chmurson merged 8 commits intomainfrom
redesign-note-adder
Dec 26, 2025
Merged

Redesign adding a new note#351
chmurson merged 8 commits intomainfrom
redesign-note-adder

Conversation

@chmurson
Copy link
Collaborator

@chmurson chmurson commented Dec 21, 2025

What?

Adding new note has been redesigned:

  • selecting a text from a pdf creates an empty note that needs to be saved
  • previous component for adding new notes has been removed

Extras:

  • improvements around useEffects in NoteProvider to cancel their logic when a new useEffect is triggered (saves few heavy re-renders)
  • added skeleton component for notes

Summary by CodeRabbit

  • New Features

    • New note creation UI with validation and Save/Cancel actions
    • Visual skeleton placeholders for inactive notes while loading
    • New consistent note container/layout components
    • Hook to keep a stable reference to the latest callback
  • Improvements

    • Newly created notes remain visible when appropriate
    • Safer async handling to avoid updates after unmount
    • Refined list and card rendering for more consistent loading/interaction states

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

@netlify
Copy link

netlify bot commented Dec 21, 2025

Deploy Preview for graypaper-reader ready!

Name Link
🔨 Latest commit 95ef9e4
🔍 Latest deploy log https://app.netlify.com/projects/graypaper-reader/deploys/694e4df00f831d0008dd3ac7
😎 Deploy Preview https://deploy-preview-351--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 Dec 21, 2025

📝 Walkthrough

Walkthrough

Replaces inline add/edit UI with a NewNote-driven component and inactive skeletons; introduces NoteContainer and useLatestCallback; stabilizes add/update/delete via ref-backed callbacks and keepShowingNewNote; tightens async effects with cancellation guards; relaxes areSelectionsEqual to accept optional selections.

Changes

Cohort / File(s) Summary
NoteManager core
src/components/NoteManager/NoteManager.tsx
Replaced inline textarea/add UI with NewNote and InactiveNoteSkeleton; added keepShowingNewNote ref; switched add/save/cancel/delete to useLatestCallback wrappers; updated render logic for loading/empty/active notes and container styling.
New UI components
src/components/NoteManager/components/NewNote.tsx, src/components/NoteManager/components/InactiveNoteSkeleton.tsx, src/components/NoteManager/components/SimpleComponents/NoteContainer.tsx
Added NewNote (local state, validation, onSave/onCancel), InactiveNoteSkeleton (skeleton placeholder), and NoteContainer (wrapping container with active prop and class merging).
Note card rendering
src/components/NoteManager/components/Note.tsx
Wrapped note card in NoteContainer; removed manual root class composition while preserving interaction logic.
Notes list behavior
src/components/NoteManager/components/NotesList.tsx
Removed early return for empty notes so list mapping always occurs (empty arrays render no items).
Selection utility
src/components/NotesProvider/utils/areSelectionsEqual.ts
Made ISelection internal/optional; areSelectionsEqual and areSelectionPointsEqual now accept optional inputs and use optional chaining.
NotesProvider async guards
src/components/NotesProvider/NotesProvider.tsx, src/components/NotesProvider/hooks/useRemoteNotes.ts
Added cancellation flags and cleanup handlers to avoid state updates after unmount during decoration and remote loading/decoration flows.
Hook: latest callback
src/hooks/useLatestCallback.ts
Added useLatestCallback hook returning a ref to the latest callback to stabilize async/handler usage.
Note layout tweaks
src/components/NoteManager/components/NoteLayout.tsx
SelectedText now accepts optional onSelectionChanged and signals changes; NoteTextArea ref usage removed.

Sequence Diagram(s)

(omitted — changes are UI/component additions and defensive async guards; no new multi-component sequential control flow requiring a diagram)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰
I nudged a note with gentle paw,
NewNote blossoms without a flaw.
Skeletons hush while drafts take hold,
Callbacks steady, brave and bold.
Hop—now notes are snug and whole.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% 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 'Redesign adding a new note' directly and accurately describes the main objective of the changeset: reworking the note creation flow with a new component-based approach.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch redesign-note-adder

📜 Recent 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 8e242cb and 95ef9e4.

📒 Files selected for processing (2)
  • src/components/NoteManager/components/NewNote.tsx
  • src/components/NoteManager/components/NoteLayout.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/NoteManager/components/NewNote.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/NoteLayout.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/NoteLayout.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/NoteLayout.tsx
📚 Learning: 2025-10-28T20:41:06.887Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 333
File: src/components/PdfProvider/PdfProvider.tsx:34-37
Timestamp: 2025-10-28T20:41:06.887Z
Learning: In the graypaper-reader codebase, for text layer render tracking in PdfProvider, use RefObject<number[]> approach (textLayerRenderedRef) without adding reactive signals or version numbers. Consumers should check the ref imperatively when needed rather than reacting to changes.

Applied to files:

  • src/components/NoteManager/components/NoteLayout.tsx
🧬 Code graph analysis (1)
src/components/NoteManager/components/NoteLayout.tsx (3)
src/components/SelectionProvider/SelectionProvider.tsx (2)
  • SelectionContext (35-35)
  • ISelectionContext (21-29)
src/components/NoteManager/components/NoteContext.tsx (1)
  • useNoteContext (26-32)
src/hooks/usePrevious.ts (1)
  • usePrevious (4-12)
⏰ 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/NoteLayout.tsx (2)

2-3: LGTM! New imports support selection change detection.

The added imports (useEffect and usePrevious) are necessary for the new selection change detection logic in the SelectedText component.


26-32: Verify first-render callback invocation behavior.

The usePrevious hook returns undefined on the first render, so onSelectionChanged will be invoked when the component mounts if selectionString has a value—even though this is the initial state, not a change from a previous value.

Questions:

  1. Is calling onSelectionChanged on mount intentional (e.g., to notify the parent of the initial selection)?
  2. Should the callback only fire on subsequent changes?

If only subsequent changes should trigger the callback, consider adding a guard to skip the first render.

Additionally, the effect depends on onSelectionChanged. Ensure the parent component memoizes this callback (e.g., with useCallback) to prevent unnecessary effect re-runs.

🔎 Proposed fix to skip first render

If you want to skip the first render, you can track whether this is the initial mount:

 export const SelectedText = ({ onSelectionChanged }: { onSelectionChanged?: () => void }) => {
   const { selectionString } = useContext(SelectionContext) as ISelectionContext;
   const { note } = useNoteContext();
   const prevSelectionString = usePrevious(selectionString);
+  const isFirstRender = prevSelectionString === undefined;

   useEffect(() => {
-    if (prevSelectionString !== selectionString) {
+    if (!isFirstRender && prevSelectionString !== selectionString) {
       onSelectionChanged?.();
     }
   }, [prevSelectionString, selectionString, onSelectionChanged]);

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 Dec 21, 2025

Visual Regression Test Report ✅ Passed

Github run id: 20519577623

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

🧹 Nitpick comments (6)
src/hooks/useLatestCallback.ts (1)

4-7: Avoid mutating refs during render phase.

Assigning to ref.current during render (line 6) can cause issues with React's concurrent rendering features. React 19 may invoke the render function multiple times before committing, leading to unexpected behavior or stale closures.

Consider using useInsertionEffect (or useLayoutEffect) to safely update the ref after render commits:

🔎 Proposed fix
-import { type RefObject, useRef } from "react";
+import { type RefObject, useInsertionEffect, useRef } from "react";

 // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 export function useLatestCallback<T extends (...args: any[]) => any>(callback: T): RefObject<T> {
   const ref = useRef(callback);
-  ref.current = callback;
+  useInsertionEffect(() => {
+    ref.current = callback;
+  });
   return ref;
 }
src/components/NoteManager/components/InactiveNoteSkeleton.tsx (1)

6-14: Consider removing duplicate Tailwind classes.

Several classes in line 11 (note, rounded-xl, p-4, bg-[var(--inactive-note-bg)]) are already applied by NoteContainer (see lines 14-16 of NoteContainer.tsx). While cn() handles duplicates gracefully, removing them improves readability.

🔎 Proposed simplification
   <NoteContainer
     active={false}
     aria-hidden="true"
     className={cn(
-      "note relative rounded-xl p-4 bg-[var(--inactive-note-bg)] border border-[var(--border)]/60 select-none animate-pulse",
+      "border border-[var(--border)]/60 select-none animate-pulse",
       className,
     )}
   >
src/components/NoteManager/components/NewNote.tsx (1)

26-41: Simplify callback handling.

Since useLatestCallback returns a stable ref, wrapping calls in useCallback with the ref as a dependency provides no additional stability benefit. The callbacks could invoke the props directly:

🔎 Proposed simplification
-  const latestOnCancel = useLatestCallback(onCancel);
-  const latestOnSave = useLatestCallback(onSave);
-
-  const handleCancelClick = useCallback(() => {
-    latestOnCancel.current();
-  }, [latestOnCancel]);
-
-  const handleSaveClick = useCallback(() => {
+  const handleCancelClick = useCallback(() => {
+    onCancel();
+  }, [onCancel]);
+
+  const handleSaveClick = useCallback(() => {
     const mathValidationError = validateMath(noteContent);
     if (mathValidationError) {
       setNoteContentError(mathValidationError);
       return;
     }
 
-    latestOnSave.current({ noteContent });
-  }, [noteContent, latestOnSave]);
+    onSave({ noteContent });
+  }, [noteContent, onSave]);

Alternatively, if the intent is to avoid re-renders when parent callbacks change, the current pattern works but the useLatestCallback hook should be fixed per the earlier comment.

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

34-34: Consider resetting keepShowingNewNote after notes become ready.

The ref is set on save but never cleared. While the current guards prevent issues, resetting it (e.g., when notesReady transitions to true or when selection changes) would make the intent clearer and prevent potential stale state in future refactors.


119-119: Consider renaming for clarity.

The variable isActiveNotes is a boolean, but the name suggests a collection. Consider hasActiveNote or hasActiveNotes for better readability.

🔎 Suggested rename
-  const isActiveNotes = notes.some((note) => activeNotes.includes(note));
+  const hasActiveNote = notes.some((note) => activeNotes.includes(note));

Then update the usage on line 128:

-        !isActiveNotes &&
+        !hasActiveNote &&
src/components/NotesProvider/utils/areSelectionsEqual.ts (1)

15-17: Document the undefined equality semantics.

When both points are undefined, this returns true (since undefined === undefined). This is reasonable ("both empty means equal"), but could surprise future callers who might expect false for missing data.

Consider adding a brief JSDoc to clarify:

/**
 * Compares two selection points. Returns true if both are undefined
 * or if both have matching pageNumber and index values.
 */
export const areSelectionPointsEqual = (point1?: ISynctexBlockId, point2?: ISynctexBlockId): boolean => {
  return point1?.pageNumber === point2?.pageNumber && point1?.index === point2?.index;
};
📜 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 53f154a and 801ef2d.

📒 Files selected for processing (10)
  • src/components/NoteManager/NoteManager.tsx (4 hunks)
  • src/components/NoteManager/components/InactiveNoteSkeleton.tsx (1 hunks)
  • src/components/NoteManager/components/NewNote.tsx (1 hunks)
  • src/components/NoteManager/components/Note.tsx (4 hunks)
  • src/components/NoteManager/components/NotesList.tsx (0 hunks)
  • src/components/NoteManager/components/SimpleComponents/NoteContainer.tsx (1 hunks)
  • src/components/NotesProvider/NotesProvider.tsx (1 hunks)
  • src/components/NotesProvider/hooks/useRemoteNotes.ts (1 hunks)
  • src/components/NotesProvider/utils/areSelectionsEqual.ts (1 hunks)
  • src/hooks/useLatestCallback.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/components/NoteManager/components/NotesList.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/NotesProvider/NotesProvider.tsx
  • src/hooks/useLatestCallback.ts
  • src/components/NotesProvider/utils/areSelectionsEqual.ts
  • src/components/NoteManager/components/InactiveNoteSkeleton.tsx
  • src/components/NoteManager/NoteManager.tsx
  • src/components/NoteManager/components/SimpleComponents/NoteContainer.tsx
  • src/components/NoteManager/components/NewNote.tsx
  • src/components/NoteManager/components/Note.tsx
  • src/components/NotesProvider/hooks/useRemoteNotes.ts
🧠 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: 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/hooks/useLatestCallback.ts
  • src/components/NoteManager/NoteManager.tsx
  • src/components/NoteManager/components/SimpleComponents/NoteContainer.tsx
  • src/components/NoteManager/components/NewNote.tsx
  • 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: 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/hooks/useLatestCallback.ts
  • src/components/NoteManager/NoteManager.tsx
  • src/components/NoteManager/components/NewNote.tsx
  • src/components/NoteManager/components/Note.tsx
🧬 Code graph analysis (6)
src/components/NotesProvider/utils/areSelectionsEqual.ts (1)
shared/links-metadata/src/types.ts (1)
  • ISynctexBlockId (1-4)
src/components/NoteManager/components/InactiveNoteSkeleton.tsx (1)
src/components/NoteManager/components/SimpleComponents/NoteContainer.tsx (1)
  • NoteContainer (10-24)
src/components/NoteManager/NoteManager.tsx (5)
shared/links-metadata/src/types.ts (1)
  • ISynctexBlockId (1-4)
src/components/NotesProvider/types/StorageNote.ts (1)
  • IStorageNote (5-5)
src/components/NotesProvider/consts/labels.ts (1)
  • LABEL_LOCAL (1-1)
src/components/NotesProvider/utils/areSelectionsEqual.ts (1)
  • areSelectionsEqual (8-13)
src/components/NoteManager/components/NewNote.tsx (1)
  • NewNote (20-135)
src/components/NoteManager/components/NewNote.tsx (6)
src/hooks/useLatestCallback.ts (1)
  • useLatestCallback (4-8)
src/utils/validateMath.ts (1)
  • validateMath (3-14)
src/components/NotesProvider/types/DecoratedNote.ts (1)
  • IDecoratedNote (4-19)
src/components/NoteManager/components/NoteContext.tsx (1)
  • ISingleNoteContext (6-22)
src/components/NoteManager/components/NoteLayout.tsx (1)
  • NoteLayout (66-73)
src/components/NoteManager/components/SimpleComponents/NoteContainer.tsx (1)
  • NoteContainer (10-24)
src/components/NoteManager/components/Note.tsx (1)
src/components/NoteManager/components/SimpleComponents/NoteContainer.tsx (1)
  • NoteContainer (10-24)
src/components/NotesProvider/hooks/useRemoteNotes.ts (3)
src/components/NotesProvider/types/StorageNote.ts (1)
  • IStorageNote (5-5)
src/components/NotesProvider/utils/notesImportExport.ts (1)
  • importNotesFromJson (49-80)
src/components/NotesProvider/consts/labels.ts (1)
  • LABEL_REMOTE (2-2)
⏰ 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 (11)
src/components/NotesProvider/NotesProvider.tsx (1)

82-96: LGTM! Well-implemented cancellation pattern.

The cancellation guard correctly prevents state updates after the component unmounts or when dependencies change, avoiding the "can't perform a React state update on an unmounted component" warning. This aligns well with the similar pattern in useRemoteNotes.ts.

src/components/NotesProvider/hooks/useRemoteNotes.ts (2)

29-58: LGTM! Proper cancellation handling for async operations.

Good use of early-exit checks after fetch and data.text() to avoid unnecessary processing when the effect has been cancelled. The pattern correctly prevents state updates after unmount.


61-74: Consistent cancellation pattern applied.

The decoration effect mirrors the cancellation pattern from the loading effect, ensuring state updates are skipped when the component unmounts during async decoration.

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

1-24: LGTM! Clean reusable container component.

Good extraction of the note card styling into a reusable component. The use of CSS variables for theming and proper TypeScript typing with DetailedHTMLProps allows full HTML attribute forwarding. This reduces duplication across Note, NewNote, and InactiveNoteSkeleton.

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

227-293: LGTM! Clean adoption of NoteContainer.

The refactor properly delegates root element styling to NoteContainer while preserving all existing interactive behavior (click, keyboard, hover, focus, dropdown). The ref forwarding works correctly via the spread props pattern.

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

114-134: LGTM! Well-structured new note component.

The component correctly integrates with the NoteLayout context system and reuses NoteContainer for consistent styling. The validation flow and memoization strategy are appropriate.

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

1-14: LGTM!

Imports are correctly updated to support the new component-driven architecture.


43-57: LGTM!

The delete and cancel handlers correctly clear the selection, with proper dependency arrays.


59-88: LGTM!

The save handler properly validates prerequisites, constructs the note with all required fields, and captures the selection for the UI transition. The error thrown on missing selection is appropriate for catching programming errors early.


121-159: LGTM!

The render logic correctly handles all states:

  • Loading with selection (NewNote + faded UI)
  • Loading without selection (skeletons)
  • Ready with no notes (empty message)
  • Ready with notes (list)

The opacity/pointer-events approach effectively prevents interaction during loading while maintaining visual continuity.

src/components/NotesProvider/utils/areSelectionsEqual.ts (1)

3-13: LGTM!

The relaxed typing with optional chaining enables broader usage patterns while maintaining correct comparison semantics for the NoteManager use case.

@chmurson chmurson changed the title Redisgn adding a new note Redesign adding a new note Dec 21, 2025
@chmurson
Copy link
Collaborator Author

there is issue when adding labels to a while created a new - does not work yet.

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 (2)
src/components/NoteManager/NoteManager.tsx (2)

41-47: Remove stable ref from dependency array.

The latestDeleteNote ref is stable across renders and doesn't need to be in the dependency array. Including it is unnecessary and could cause the callback to be recreated more often than needed.

🔎 Proposed fix
   const memoizedHandleDeleteNote = useCallback(
     (note: IDecoratedNote) => {
       latestDeleteNote.current(note);
       handleClearSelection();
     },
-    [handleClearSelection, latestDeleteNote],
+    [handleClearSelection],
   );

49-54: Remove stable ref from dependency array.

The latestUpdateNote ref is stable across renders and doesn't need to be in the dependency array.

🔎 Proposed fix
   const memoizedHandleUpdateNote = useCallback(
     (note: IDecoratedNote, newNote: IStorageNote) => {
       latestUpdateNote.current(note, newNote);
     },
-    [latestUpdateNote],
+    [],
   );
📜 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 801ef2d and a692f5d.

📒 Files selected for processing (1)
  • src/components/NoteManager/NoteManager.tsx (3 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/NoteManager.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: 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/NoteManager.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: 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/NoteManager.tsx
⏰ 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 (5)
src/components/NoteManager/NoteManager.tsx (5)

1-15: LGTM! Clean import organization.

The new imports support the redesigned note-adding flow and are all utilized in the component.


37-39: LGTM! Proper use of useLatestCallback pattern.

The ref-backed callbacks provide stable references to the latest handlers, which is useful for avoiding stale closures.


56-58: LGTM! Clean cancellation handler.

The callback correctly clears the selection when a new note is cancelled.


122-161: Well-structured conditional rendering flow.

The rendering logic cleanly handles multiple states:

  • Loading state with skeleton components
  • Empty state with helpful message
  • Active state with notes list
  • New note creation when selection exists

The opacity and pointer-events styling on line 125 provides good visual feedback during loading.


127-138: No action required—areSelectionsEqual safely handles undefined parameters.

The function signature accepts optional parameters and uses optional chaining throughout, so passing undefined (when keepShowingNewNote.current is undefined) will not cause runtime errors. The comparison will correctly evaluate to false.

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

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

35-39: Consider adding logic to clear keepShowingNewNote.

The keepShowingNewNote ref is set when a note is saved (lines 83-86) but is never cleared. This serves as a UX optimization to prevent flickering during the async save operation, which is good.

However, if the user later returns to the same selection after the note has been deleted or is no longer active, the condition on line 132 areSelectionsEqual(locationParams, keepShowingNewNote.current) could allow NewNote to render even when notesReady is false, which might not be the intended behavior.

If this is a concern, consider clearing keepShowingNewNote.current when appropriate (e.g., when a note becomes active, or when selection changes to a different range).


123-126: Consider using Tailwind classes instead of inline styles.

The inline styles for opacity and pointerEvents could be replaced with Tailwind 4.x utility classes for consistency and better maintainability.

🔎 Proposed refactor using Tailwind classes
-    <div
-      className="note-manager flex flex-col gap-2.5"
-      style={{ opacity: notesReady ? 1.0 : 0.3, pointerEvents: notesReady ? "auto" : "none" }}
-    >
+    <div
+      className={twMerge(
+        "note-manager flex flex-col gap-2.5",
+        notesReady ? "opacity-100 pointer-events-auto" : "opacity-30 pointer-events-none"
+      )}
+    >

As per coding guidelines, prefer Tailwind 4.x conventions.


127-140: Consider extracting the complex condition to improve readability.

The condition for rendering NewNote spans multiple lines and includes several checks. Extracting this to a well-named variable would improve code clarity and make the intent more explicit.

🔎 Proposed refactor for better readability
+  const shouldShowNewNote =
+    locationParams.selectionEnd &&
+    locationParams.selectionStart &&
+    pageNumber !== null &&
+    selectedBlocks.length > 0 &&
+    !isActiveNotes &&
+    (notesReady || areSelectionsEqual(locationParams, keepShowingNewNote.current));
+
   return (
     <div
       className={twMerge(
         "note-manager flex flex-col gap-2.5",
         notesReady ? "opacity-100 pointer-events-auto" : "opacity-30 pointer-events-none"
       )}
     >
-      {locationParams.selectionEnd &&
-        locationParams.selectionStart &&
-        pageNumber !== null &&
-        selectedBlocks.length > 0 &&
-        !isActiveNotes &&
-        (notesReady || areSelectionsEqual(locationParams, keepShowingNewNote.current)) && (
+      {shouldShowNewNote && (
         <NewNote
📜 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 a692f5d and 68d53cf.

📒 Files selected for processing (1)
  • src/components/NoteManager/NoteManager.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/NoteManager.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/NoteManager.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/NoteManager.tsx
📚 Learning: 2025-10-28T20:41:06.887Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 333
File: src/components/PdfProvider/PdfProvider.tsx:34-37
Timestamp: 2025-10-28T20:41:06.887Z
Learning: In the graypaper-reader codebase, for text layer render tracking in PdfProvider, use RefObject<number[]> approach (textLayerRenderedRef) without adding reactive signals or version numbers. Consumers should check the ref imperatively when needed rather than reacting to changes.

Applied to files:

  • src/components/NoteManager/NoteManager.tsx
⏰ 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 (5)
src/components/NoteManager/NoteManager.tsx (5)

1-27: LGTM!

The imports and component setup are clean. The use of memo for NotesList is appropriate for performance optimization.

Based on learnings, this repo targets React 19+.


41-58: LGTM!

The memoized handlers correctly use the latest callback references and have appropriate dependencies. Clearing the selection after delete (line 44) provides good UX.


60-89: Add type annotation for the function parameter.

The noteContent parameter should have a type annotation for better type safety and developer experience.

🔎 Proposed type annotation
-  const handleAddNoteClick = useCallback(
-    ({ noteContent }: { noteContent: string }) => {
+  const handleAddNoteClick = useCallback(
+    ({ noteContent }: { noteContent: string }) => {

Wait, actually looking at line 61, the type annotation { noteContent: string } is already present. Let me recheck...

Actually, the annotation IS there on line 61: ({ noteContent }: { noteContent: string }). So this is fine.

Actually, reviewing line 61 again, the type annotation is present: { noteContent: string }. This is correct.


91-118: LGTM!

The locationRef pattern correctly provides access to the latest location context without causing re-renders, and memoizedHandleSelectNote properly handles the three selection modes.


142-161: LGTM!

The conditional rendering logic for skeletons, empty state, and notes list is well-structured and handles all the necessary states appropriately.

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

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

20-50: Solid state management and validation logic.

The use of dateRef for a deterministic timestamp and useLatestCallback for stable parent callbacks is excellent. Math validation before save provides good user feedback.

Optional: Move link constants outside component

Lines 44-45 define empty string constants inside the component body. While this works fine (strings are primitives), moving them outside would make the intent clearer:

+const EMPTY_VERSION_LINK = "";
+
 export const NewNote = ({ selectionEnd, selectionStart, version, onCancel, onSave }: NewNoteProps) => {
   const dateRef = useRef(new Date().getTime());
   
   // ... state declarations ...
   
-  const currentVersionLink = "";
-  const originalVersionLink = "";
+  const currentVersionLink = EMPTY_VERSION_LINK;
+  const originalVersionLink = EMPTY_VERSION_LINK;
src/components/NoteManager/NoteManager.tsx (1)

119-161: Thoughtful render logic with excellent state handling.

The conditional rendering correctly handles all states:

  • Shows NewNote when there's a valid selection and no active notes (lines 126-139)
  • Uses areSelectionsEqual cleverly to keep NewNote visible after save until notesReady (line 131)
  • Displays loading skeletons while notes are being fetched (lines 141-148)
  • Shows a clear empty state message when appropriate (line 150)
  • Renders the notes list with memoized callbacks when notes exist (lines 152-160)
Optional: Replace inline styles with CSS classes

Lines 122-125 use inline style props for opacity and pointer-events. For better separation of concerns and maintainability, consider adding CSS classes:

     <div
       className="note-manager flex flex-col gap-2.5"
-      style={{ opacity: notesReady ? 1.0 : 0.3, pointerEvents: notesReady ? "auto" : "none" }}
+      className={cn(
+        "note-manager flex flex-col gap-2.5",
+        !notesReady && "opacity-30 pointer-events-none"
+      )}
     >

(Requires importing cn utility if not already available)

📜 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 68d53cf and 68cf5bb.

📒 Files selected for processing (2)
  • src/components/NoteManager/NoteManager.tsx
  • src/components/NoteManager/components/NewNote.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/NewNote.tsx
  • src/components/NoteManager/NoteManager.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: 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/NewNote.tsx
  • src/components/NoteManager/NoteManager.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: 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/NewNote.tsx
  • src/components/NoteManager/NoteManager.tsx
📚 Learning: 2025-10-28T20:41:06.887Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 333
File: src/components/PdfProvider/PdfProvider.tsx:34-37
Timestamp: 2025-10-28T20:41:06.887Z
Learning: In the graypaper-reader codebase, for text layer render tracking in PdfProvider, use RefObject<number[]> approach (textLayerRenderedRef) without adding reactive signals or version numbers. Consumers should check the ref imperatively when needed rather than reacting to changes.

Applied to files:

  • src/components/NoteManager/NoteManager.tsx
🧬 Code graph analysis (2)
src/components/NoteManager/components/NewNote.tsx (6)
shared/links-metadata/src/types.ts (1)
  • ISynctexBlockId (1-4)
src/hooks/useLatestCallback.ts (1)
  • useLatestCallback (4-8)
src/utils/validateMath.ts (1)
  • validateMath (3-14)
src/components/NoteManager/components/NoteContext.tsx (1)
  • ISingleNoteContext (6-22)
src/components/NoteManager/components/NoteLayout.tsx (1)
  • NoteLayout (66-73)
src/components/NoteManager/components/SimpleComponents/NoteContainer.tsx (1)
  • NoteContainer (10-24)
src/components/NoteManager/NoteManager.tsx (6)
shared/links-metadata/src/types.ts (1)
  • ISynctexBlockId (1-4)
src/hooks/useLatestCallback.ts (1)
  • useLatestCallback (4-8)
src/components/NotesProvider/types/StorageNote.ts (1)
  • IStorageNote (5-5)
src/components/NotesProvider/utils/areSelectionsEqual.ts (1)
  • areSelectionsEqual (8-13)
src/components/NoteManager/components/NewNote.tsx (1)
  • NewNote (20-136)
src/components/NoteManager/components/InactiveNoteSkeleton.tsx (1)
  • InactiveNoteSkeleton (6-26)
⏰ 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 (7)
src/components/NoteManager/components/NewNote.tsx (3)

1-18: LGTM! Clean imports and type definition.

The imports are well-organized and the NewNoteProps type clearly defines the component's interface.


52-113: Well-structured context and memo usage.

The memoized objects correctly model the new note state:

  • noteDirty captures the current editing state
  • note provides the decorated note structure with empty original content (since this is unsaved)
  • noteLayoutContext properly configures UI state with active: true, isEditing: true, and appropriate callbacks

The no-op handlers (handleEditClick, onEditNote, handleSelectNote) are appropriate for a new, unsaved note context.


115-136: Clean render logic with proper error handling.

The UI correctly displays validation errors conditionally and provides clear Cancel/Save actions with test IDs for testability.

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

1-13: LGTM! Clean integration of new dependencies.

The new imports for useLatestCallback, areSelectionsEqual, InactiveNoteSkeleton, and NewNote are well-organized and necessary for the refactored functionality.


34-38: Excellent use of refs and latest callbacks pattern.

The keepShowingNewNote ref cleverly preserves the selection for a newly created note, allowing the UI to continue showing NewNote until notesReady completes. Wrapping handlers with useLatestCallback prevents stale closures while maintaining stable callback identity—a solid pattern for avoiding unnecessary re-renders.


40-57: Well-designed handler orchestration.

The handlers appropriately compose the latest callbacks with UI state updates:

  • memoizedHandleDeleteNote clears the selection after deletion (good UX)
  • memoizedHandleUpdateNote forwards to the latest callback
  • handleNewNoteCancel clears the selection when canceling new note creation

Dependency arrays are correct for all three.


59-88: Robust note creation logic with proper validation.

The refactored handleAddNoteClick correctly:

  • Validates all required selection data before proceeding (lines 61-68)
  • Constructs a complete IStorageNote with all metadata (lines 70-79)
  • Updates keepShowingNewNote to maintain UI state continuity (lines 82-85)

The validation guards will throw an error if preconditions aren't met, which is appropriate since the render logic ensures these conditions before showing NewNote.

Based on learnings from past review: including latestHandleAddNote in the dependency array satisfies linter requirements.

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

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

128-131: Consider using Tailwind classes for conditional opacity and pointer-events.

The inline styles work correctly, but could be replaced with Tailwind classes for consistency with the rest of the styling approach.

🔎 Proposed refactor using Tailwind classes
     <div
-      className="note-manager flex flex-col gap-2.5"
-      style={{ opacity: notesReady ? 1.0 : 0.3, pointerEvents: notesReady ? "auto" : "none" }}
+      className={twMerge(
+        "note-manager flex flex-col gap-2.5",
+        notesReady ? "opacity-100 pointer-events-auto" : "opacity-30 pointer-events-none"
+      )}
     >
📜 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 68cf5bb and b97cd6f.

⛔ Files ignored due to path filters (8)
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/notes-tab-after-note-activation-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/notes-tab-after-note-activation-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/notes-tab-initial-dark-mode-linux.png is excluded by !**/*.png
  • tools/snapshot-tests/tests/basic-snapshots.spec.ts-snapshots/notes-tab-initial-light-mode-linux.png is excluded by !**/*.png
📒 Files selected for processing (1)
  • src/components/NoteManager/NoteManager.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/NoteManager.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/NoteManager.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/NoteManager.tsx
📚 Learning: 2025-10-28T20:41:06.887Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 333
File: src/components/PdfProvider/PdfProvider.tsx:34-37
Timestamp: 2025-10-28T20:41:06.887Z
Learning: In the graypaper-reader codebase, for text layer render tracking in PdfProvider, use RefObject<number[]> approach (textLayerRenderedRef) without adding reactive signals or version numbers. Consumers should check the ref imperatively when needed rather than reacting to changes.

Applied to files:

  • src/components/NoteManager/NoteManager.tsx
🧬 Code graph analysis (1)
src/components/NoteManager/NoteManager.tsx (5)
src/hooks/useLatestCallback.ts (1)
  • useLatestCallback (4-8)
src/components/NotesProvider/types/StorageNote.ts (1)
  • IStorageNote (5-5)
src/components/NotesProvider/utils/areSelectionsEqual.ts (1)
  • areSelectionsEqual (8-13)
src/components/NoteManager/components/NewNote.tsx (1)
  • NewNote (20-136)
src/components/NoteManager/components/InactiveNoteSkeleton.tsx (1)
  • InactiveNoteSkeleton (6-26)
⏰ 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 (5)
src/components/NoteManager/NoteManager.tsx (5)

1-14: LGTM!

Imports are clean and all appear to be utilized. The addition of useLatestCallback, areSelectionsEqual, and the new component imports (NewNote, InactiveNoteSkeleton) align well with the redesigned note-adding flow.


36-53: Good use of useLatestCallback for stable callback references.

The pattern correctly wraps context handlers to provide stable references while always executing the latest implementation. The memoizedHandleDeleteNote appropriately clears the selection after deletion, ensuring consistent UI state.


55-88: LGTM!

The note creation flow correctly validates required state before saving and stores the selection range in keepShowingNewNote to maintain UI continuity while notes refresh. The guard conditions align with the render conditions for NewNote.


147-166: LGTM!

The render logic correctly handles all states: loading skeletons when not ready, empty state message when ready with no notes, and the memoized notes list when notes exist. The use of MemoizedNotesList with stable callback references ensures efficient re-renders.


119-119: The code correctly handles reference equality. Both notes (which is filteredNotes) and activeNotes are derived from the same source through filtering operations that preserve object references. Since activeNotes is filtered from filteredNotes, all elements in activeNotes maintain the same object references as those in notes. Therefore, Array.includes() will correctly identify matching notes.

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

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

32-40: Consider validating for empty note content.

The validation currently only checks for math syntax errors. If empty notes should not be allowed, consider adding a check before validation.

🔎 Optional enhancement to prevent empty notes
  const handleSaveClick = useCallback(() => {
+   if (noteContent.trim() === "") {
+     setNoteContentError("Note content cannot be empty");
+     return;
+   }
+
    const mathValidationError = validateMath(noteContent);
    if (mathValidationError) {
      setNoteContentError(mathValidationError);
      return;
    }

    latestOnSave.current({ noteContent, labels });
  }, [noteContent, latestOnSave, labels]);
📜 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 b97cd6f and 8e242cb.

📒 Files selected for processing (2)
  • src/components/NoteManager/NoteManager.tsx
  • src/components/NoteManager/components/NewNote.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/NoteManager.tsx
  • src/components/NoteManager/components/NewNote.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/NoteManager.tsx
  • src/components/NoteManager/components/NewNote.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/NoteManager.tsx
  • src/components/NoteManager/components/NewNote.tsx
📚 Learning: 2025-10-28T20:41:06.887Z
Learnt from: chmurson
Repo: FluffyLabs/graypaper-reader PR: 333
File: src/components/PdfProvider/PdfProvider.tsx:34-37
Timestamp: 2025-10-28T20:41:06.887Z
Learning: In the graypaper-reader codebase, for text layer render tracking in PdfProvider, use RefObject<number[]> approach (textLayerRenderedRef) without adding reactive signals or version numbers. Consumers should check the ref imperatively when needed rather than reacting to changes.

Applied to files:

  • src/components/NoteManager/NoteManager.tsx
🧬 Code graph analysis (2)
src/components/NoteManager/NoteManager.tsx (6)
shared/links-metadata/src/types.ts (1)
  • ISynctexBlockId (1-4)
src/hooks/useLatestCallback.ts (1)
  • useLatestCallback (4-8)
src/components/NotesProvider/types/StorageNote.ts (1)
  • IStorageNote (5-5)
src/components/NotesProvider/utils/areSelectionsEqual.ts (1)
  • areSelectionsEqual (8-13)
src/components/NoteManager/components/NewNote.tsx (1)
  • NewNote (20-88)
src/components/NoteManager/components/InactiveNoteSkeleton.tsx (1)
  • InactiveNoteSkeleton (6-26)
src/components/NoteManager/components/NewNote.tsx (7)
shared/links-metadata/src/types.ts (1)
  • ISynctexBlockId (1-4)
src/hooks/useLatestCallback.ts (1)
  • useLatestCallback (4-8)
src/utils/validateMath.ts (1)
  • validateMath (3-14)
src/components/NoteManager/components/NoteLayout.tsx (1)
  • NoteLayout (66-73)
src/components/NoteManager/components/SimpleComponents/NoteContainer.tsx (1)
  • NoteContainer (10-24)
src/components/NotesProvider/types/DecoratedNote.ts (1)
  • IDecoratedNote (4-19)
src/components/NoteManager/components/NoteContext.tsx (1)
  • ISingleNoteContext (6-22)
⏰ 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 (12)
src/components/NoteManager/components/NewNote.tsx (6)

1-18: LGTM! Clean imports and type definitions.

The component structure is well-organized with appropriate type safety for the note creation flow.


20-30: LGTM! State and callback initialization is correct.

The use of useLatestCallback ensures stable callback references while always invoking the latest version, and the default "local" label is a sensible starting point for new notes.


42-48: LGTM! Empty version links are appropriate for new notes.

New notes don't have version history yet, so empty strings are correct. The error clearing on content change provides good user experience.


50-88: LGTM! Well-structured component render and layout context.

The helper hooks properly memoize derived state, and the JSX composition using NoteLayout components provides a clean, consistent UI. The conditional error message display enhances user feedback.


90-119: LGTM! Correct structure for new note representation.

The memoized note object correctly represents an empty, local note with appropriate default values for a new note that hasn't been saved yet.


121-198: LGTM! Correct dirty state tracking and layout context.

The dirty note object properly tracks the current editing state, and the layout context correctly provides all required handlers and metadata. The empty/no-op functions for unused handlers are appropriate for the new note creation flow.

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

1-15: LGTM! Clean import additions for the redesigned note flow.

The new imports properly support the refactored architecture with NewNote component, skeleton UI, and callback stabilization.


35-39: LGTM! Proper ref initialization and callback stabilization.

The keepShowingNewNote ref correctly tracks the selection for post-save UI continuity, and wrapping the note handlers with useLatestCallback ensures stable references while always invoking the latest versions.


41-58: LGTM! Well-designed handler memoization with appropriate side effects.

The delete handler correctly clears the selection to prevent showing a deleted note's selection. The cancel handler also appropriately clears the selection when abandoning note creation.


60-89: LGTM! Robust save handler with proper validation and UX continuity.

The callback correctly validates all required selection state before saving, constructs the note with proper metadata, and uses keepShowingNewNote to maintain UI consistency during the async save/reload cycle. The dependency array is correct per your linter requirements.


120-126: LGTM! Correct state tracking and cleanup logic.

The isActiveNotes flag properly prevents concurrent editing, and the effect correctly resets the keepShowingNewNote ref once notes have loaded, completing the save cycle.


129-165: LGTM! Well-designed conditional rendering with excellent UX.

The render logic elegantly handles multiple states:

  • Loading state with skeleton placeholders
  • Empty state with clear messaging
  • New note creation with selection validation
  • Existing notes list

The areSelectionsEqual(locationParams, keepShowingNewNote.current) condition is particularly clever—it keeps the NewNote form visible during the reload cycle after saving, providing seamless UX continuity.

@chmurson chmurson merged commit a6f5a6d into main Dec 26, 2025
9 checks passed
@chmurson chmurson deleted the redesign-note-adder branch December 26, 2025 10:17
@chmurson chmurson restored the redesign-note-adder branch December 26, 2025 11:03
@chmurson chmurson deleted the redesign-note-adder branch December 26, 2025 21: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.

1 participant