• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jcubic / 10xDevs / 18619660514

18 Oct 2025 06:52PM UTC coverage: 19.299% (-2.7%) from 21.953%
18619660514

push

github

jcubic
improve performance

77 of 110 branches covered (70.0%)

Branch coverage included in aggregate %.

35 of 162 new or added lines in 4 files covered. (21.6%)

189 existing lines in 8 files now uncovered.

468 of 2714 relevant lines covered (17.24%)

2.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.7
/src/components/notes/NotesContext.tsx
1
'use client';
1✔
2

3
import {
×
4
  createContext,
5
  useContext,
6
  useState,
7
  useEffect,
8
  useCallback,
9
  useMemo,
10
  type ReactNode
11
} from 'react';
UNCOV
12
import { usePathname } from 'next/navigation';
×
13
import type { NoteTreeNode } from '@/types/tree';
14
import type { SaveStatus } from '@/types/notes';
15

16
interface NotesContextValue {
17
  notes: NoteTreeNode[];
18
  selectedNoteId: number | null;
19
  saveStatus: SaveStatus;
20
  setNotes: (notes: NoteTreeNode[] | ((prev: NoteTreeNode[]) => NoteTreeNode[])) => void;
21
  setSelectedNoteId: (noteId: number | null) => void;
22
  setSaveStatus: (status: SaveStatus) => void;
23
  updateNoteContent: (noteId: number, content: string) => void;
24
  updateNoteName: (noteId: number, name: string) => void;
25
  markNoteDirty: (noteId: number, dirty: boolean) => void;
26
  getSelectedNote: () => NoteTreeNode | null;
27
  getNote: (noteId: number) => NoteTreeNode | null;
28
  selectNote: (noteId: number) => void;
29
  syncUrlToState: () => void;
30
}
31

UNCOV
32
const NotesContext = createContext<NotesContextValue | undefined>(undefined);
×
33

UNCOV
34
export function useNotesContext() {
×
UNCOV
35
  const context = useContext(NotesContext);
×
UNCOV
36
  if (context === undefined) {
×
UNCOV
37
    throw new Error('useNotesContext must be used within a NotesProvider');
×
38
  }
×
39
  return context;
×
40
}
×
41

42
interface NotesProviderProps {
43
  children: ReactNode;
44
  initialNotes?: NoteTreeNode[];
45
  initialSelectedNoteId?: number | null;
46
}
47

UNCOV
48
export function NotesProvider({
×
49
  children,
×
50
  initialNotes = [],
×
51
  initialSelectedNoteId = null
×
52
}: NotesProviderProps) {
×
53
  const [notes, setNotes] = useState<NoteTreeNode[]>(initialNotes);
×
54
  const [selectedNoteId, setSelectedNoteId] = useState<number | null>(
×
55
    initialSelectedNoteId
×
56
  );
×
57
  const [saveStatus, setSaveStatus] = useState<SaveStatus>('idle');
×
58
  const pathname = usePathname();
×
59

NEW
60
  const updateNoteContent = useCallback((noteId: number, content: string) => {
×
NEW
61
    setNotes((prevNotes) => {
×
NEW
62
      const noteIndex = prevNotes.findIndex((note) => note.id === noteId);
×
NEW
63
      if (noteIndex === -1) return prevNotes;
×
64

NEW
65
      const currentNote = prevNotes[noteIndex];
×
66
      // Don't update if content hasn't actually changed
NEW
67
      if (currentNote.data?.content === content) return prevNotes;
×
68

69
      // Create new array with only the changed note updated
NEW
70
      const newNotes = [...prevNotes];
×
NEW
71
      newNotes[noteIndex] = {
×
NEW
72
        ...currentNote,
×
NEW
73
        data: {
×
NEW
74
          ...currentNote.data!,
×
NEW
75
          content,
×
NEW
76
          dirty: true
×
NEW
77
        }
×
NEW
78
      };
×
NEW
79
      return newNotes;
×
NEW
80
    });
×
NEW
81
  }, []);
×
82

NEW
83
  const updateNoteName = useCallback((noteId: number, name: string) => {
×
NEW
84
    setNotes((prevNotes) => {
×
NEW
85
      const noteIndex = prevNotes.findIndex((note) => note.id === noteId);
×
NEW
86
      if (noteIndex === -1) return prevNotes;
×
87

NEW
88
      const currentNote = prevNotes[noteIndex];
×
89
      // Don't update if name hasn't actually changed
NEW
90
      if (currentNote.name === name) return prevNotes;
×
91

92
      // Create new array with only the changed note updated
NEW
93
      const newNotes = [...prevNotes];
×
NEW
94
      newNotes[noteIndex] = { ...currentNote, name };
×
NEW
95
      return newNotes;
×
NEW
96
    });
×
NEW
97
  }, []);
×
98

NEW
99
  const markNoteDirty = useCallback((noteId: number, dirty: boolean) => {
×
NEW
100
    setNotes((prevNotes) => {
×
NEW
101
      const noteIndex = prevNotes.findIndex((note) => note.id === noteId);
×
NEW
102
      if (noteIndex === -1) return prevNotes;
×
103

NEW
104
      const currentNote = prevNotes[noteIndex];
×
105
      // Don't update if dirty state hasn't actually changed
NEW
106
      if (currentNote.data?.dirty === dirty) return prevNotes;
×
107

108
      // Create new array with only the changed note updated
NEW
109
      const newNotes = [...prevNotes];
×
NEW
110
      newNotes[noteIndex] = {
×
NEW
111
        ...currentNote,
×
NEW
112
        data: {
×
NEW
113
          ...currentNote.data!,
×
NEW
114
          dirty
×
NEW
115
        }
×
NEW
116
      };
×
NEW
117
      return newNotes;
×
NEW
118
    });
×
NEW
119
  }, []);
×
120

NEW
121
  const getSelectedNote = useCallback((): NoteTreeNode | null => {
×
122
    if (!selectedNoteId) return null;
×
123
    return notes.find((note) => note.id === selectedNoteId) || null;
×
NEW
124
  }, [notes, selectedNoteId]);
×
125

NEW
126
  const getNote = useCallback(
×
NEW
127
    (noteId: number): NoteTreeNode | null => {
×
NEW
128
      return notes.find((note) => note.id === noteId) || null;
×
NEW
129
    },
×
NEW
130
    [notes]
×
NEW
131
  );
×
132

133
  // URL synchronization - extract note ID from current URL
134
  const syncUrlToState = useCallback(() => {
×
UNCOV
135
    const noteMatch = pathname.match(/\/note\/(\d+)/);
×
UNCOV
136
    const urlNoteId = noteMatch ? parseInt(noteMatch[1], 10) : null;
×
137

UNCOV
138
    if (urlNoteId !== selectedNoteId) {
×
UNCOV
139
      setSelectedNoteId(urlNoteId);
×
UNCOV
140
    }
×
UNCOV
141
  }, [pathname, selectedNoteId]);
×
142

143
  // Instant note selection with History API
UNCOV
144
  const selectNote = useCallback((noteId: number) => {
×
145
    // Update URL instantly without triggering navigation
UNCOV
146
    const newUrl = `/note/${noteId}`;
×
UNCOV
147
    window.history.pushState(null, '', newUrl);
×
148

149
    // Update state immediately
UNCOV
150
    setSelectedNoteId(noteId);
×
151

152
    // Dispatch custom event for parallel route components
UNCOV
153
    window.dispatchEvent(
×
UNCOV
154
      new CustomEvent('note-selected', {
×
UNCOV
155
        detail: { noteId }
×
UNCOV
156
      })
×
UNCOV
157
    );
×
UNCOV
158
  }, []);
×
159

160
  // Sync URL to state on mount and URL changes
UNCOV
161
  useEffect(() => {
×
UNCOV
162
    syncUrlToState();
×
UNCOV
163
  }, [syncUrlToState]);
×
164

165
  // Handle browser back/forward
UNCOV
166
  useEffect(() => {
×
UNCOV
167
    const handlePopState = () => {
×
UNCOV
168
      syncUrlToState();
×
169
      // Dispatch event to update parallel route components
UNCOV
170
      const noteMatch = pathname.match(/\/note\/(\d+)/);
×
UNCOV
171
      const noteId = noteMatch ? parseInt(noteMatch[1], 10) : null;
×
172

UNCOV
173
      window.dispatchEvent(
×
UNCOV
174
        new CustomEvent('note-selected', {
×
UNCOV
175
          detail: { noteId }
×
UNCOV
176
        })
×
UNCOV
177
      );
×
UNCOV
178
    };
×
179

UNCOV
180
    window.addEventListener('popstate', handlePopState);
×
UNCOV
181
    return () => window.removeEventListener('popstate', handlePopState);
×
UNCOV
182
  }, [syncUrlToState, pathname]);
×
183

NEW
184
  const value: NotesContextValue = useMemo(
×
NEW
185
    () => ({
×
NEW
186
      notes,
×
NEW
187
      selectedNoteId,
×
NEW
188
      saveStatus,
×
NEW
189
      setNotes,
×
NEW
190
      setSelectedNoteId,
×
NEW
191
      setSaveStatus,
×
NEW
192
      updateNoteContent,
×
NEW
193
      updateNoteName,
×
NEW
194
      markNoteDirty,
×
NEW
195
      getSelectedNote,
×
NEW
196
      getNote,
×
NEW
197
      selectNote,
×
NEW
198
      syncUrlToState
×
NEW
199
    }),
×
NEW
200
    [
×
NEW
201
      notes,
×
NEW
202
      selectedNoteId,
×
NEW
203
      saveStatus,
×
NEW
204
      updateNoteContent,
×
NEW
205
      updateNoteName,
×
NEW
206
      markNoteDirty,
×
NEW
207
      getSelectedNote,
×
NEW
208
      getNote,
×
NEW
209
      selectNote,
×
NEW
210
      syncUrlToState
×
NEW
211
    ]
×
NEW
212
  );
×
213

UNCOV
214
  return <NotesContext.Provider value={value}>{children}</NotesContext.Provider>;
×
UNCOV
215
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc