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

jcubic / 10xDevs / 18576195518

16 Oct 2025 10:11PM UTC coverage: 21.344% (+0.2%) from 21.131%
18576195518

push

github

jcubic
fix lint warnings

62 of 88 branches covered (70.45%)

Branch coverage included in aggregate %.

5 of 20 new or added lines in 2 files covered. (25.0%)

87 existing lines in 5 files now uncovered.

424 of 2189 relevant lines covered (19.37%)

2.41 hits per line

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

0.52
/src/components/notes/MainNotesClient.tsx
1
'use client';
1✔
2

3
import { useEffect, useMemo, useRef, useCallback } from 'react';
×
UNCOV
4
import { useRouter } from 'next/navigation';
×
5
import type { NoteTreeNode } from '@/types/tree';
6
import { useNotesContext } from './NotesContext';
×
7
import { createNote, updateNote, deleteNote } from '@/app/actions/notes';
×
8
import { extractHeaders } from '@/lib/parser/markdown-parser';
×
9
import TopNavigationBar from './TopNavigationBar';
×
10
import LeftPanel from './LeftPanel';
×
11
import MiddlePanel from './MiddlePanel';
×
12
import RightPanel from './RightPanel';
×
UNCOV
13
import Footer from '@/components/Footer';
×
14

UNCOV
15
import styles from './MainNotesLayout.module.css';
×
16

17
interface MainNotesClientProps {
18
  lineNumber?: number;
19
}
20

21
export default function MainNotesClient({ lineNumber }: MainNotesClientProps) {
×
22
  const router = useRouter();
×
23
  const {
×
24
    notes,
×
25
    selectedNoteId,
×
26
    saveStatus,
×
27
    setSelectedNoteId,
×
28
    setSaveStatus,
×
29
    updateNoteContent,
×
30
    updateNoteName,
×
31
    markNoteDirty,
×
32
    getSelectedNote,
×
UNCOV
33
    setNotes
×
34
  } = useNotesContext();
×
35

36
  const editorRef = useRef<import('@/types/editor').EditorRef | null>(null);
×
37

38
  const selectedNote = getSelectedNote();
×
UNCOV
39
  const content = selectedNote?.data?.content || '';
×
UNCOV
40
  const hasUnsavedChanges = selectedNote?.data?.dirty || false;
×
41

42
  // Extract headers from current content
43
  const headers = useMemo(() => extractHeaders(content), [content]);
×
44

UNCOV
45
  const handleNoteSelect = useCallback(
×
46
    (noteId: number) => {
×
47
      // Client-side selection only - no router navigation
48
      setSelectedNoteId(noteId);
×
49
    },
×
UNCOV
50
    [setSelectedNoteId]
×
51
  );
×
52

53
  const handleNewNote = useCallback(async () => {
×
UNCOV
54
    try {
×
UNCOV
55
      const newNote = await createNote('New Note');
×
56

57
      // Add the new note to our local state
58
      const newTreeNode: NoteTreeNode = {
×
59
        id: newNote.id,
×
60
        name: newNote.name,
×
61
        data: {
×
62
          content: newNote.content || '',
×
63
          dirty: false
×
UNCOV
64
        }
×
65
      };
×
66

67
      setNotes((prevNotes: NoteTreeNode[]) => [newTreeNode, ...prevNotes]);
×
68
      setSelectedNoteId(newNote.id);
×
69
    } catch (error) {
×
70
      console.error('Failed to create note:', error);
×
UNCOV
71
    }
×
72
  }, [setNotes, setSelectedNoteId]);
×
73

74
  const handleContentChange = (newContent: string) => {
×
75
    if (selectedNoteId) {
×
76
      updateNoteContent(selectedNoteId, newContent);
×
UNCOV
77
    }
×
78
  };
×
79

UNCOV
80
  const handleSave = useCallback(async () => {
×
81
    if (!selectedNote) return;
×
82

83
    try {
×
84
      setSaveStatus('saving');
×
85
      await updateNote(selectedNote.id, { content });
×
UNCOV
86
      setSaveStatus('saved');
×
UNCOV
87
      markNoteDirty(selectedNote.id, false);
×
88

89
      // Reset status after 2 seconds
90
      setTimeout(() => setSaveStatus('idle'), 2000);
×
91
    } catch (error) {
×
92
      setSaveStatus('error');
×
93
      console.error('Failed to save note:', error);
×
UNCOV
94
    }
×
95
  }, [selectedNote, content, setSaveStatus, markNoteDirty]);
×
96

UNCOV
97
  const handleRefresh = useCallback(async () => {
×
98
    if (!selectedNote) return;
×
99

100
    try {
×
101
      setSaveStatus('saving');
×
102
      const freshNote = await updateNote(selectedNote.id, {}); // Fetch without changes
×
103
      updateNoteContent(selectedNote.id, freshNote.content || '');
×
104
      markNoteDirty(selectedNote.id, false);
×
105
      setSaveStatus('idle');
×
106
    } catch (error) {
×
107
      setSaveStatus('error');
×
108
      console.error('Failed to refresh note:', error);
×
UNCOV
109
    }
×
110
  }, [selectedNote, setSaveStatus, updateNoteContent, markNoteDirty]);
×
111

112
  const handleDeleteNote = useCallback(
×
113
    async (noteId: number) => {
×
UNCOV
114
      try {
×
UNCOV
115
        await deleteNote(noteId);
×
116

117
        // Remove from local state
118
        setNotes((prevNotes: NoteTreeNode[]) =>
×
UNCOV
119
          prevNotes.filter((note: NoteTreeNode) => note.id !== noteId)
×
UNCOV
120
        );
×
121

122
        // If we deleted the currently selected note, clear selection
123
        if (selectedNoteId === noteId) {
×
124
          setSelectedNoteId(null);
×
125
        }
×
126
      } catch (error) {
×
127
        console.error('Failed to delete note:', error);
×
128
      }
×
129
    },
×
UNCOV
130
    [setNotes, selectedNoteId, setSelectedNoteId]
×
131
  );
×
132

133
  const handleRenameNote = useCallback(
×
134
    async (noteId: number, newName: string) => {
×
135
      try {
×
136
        const updatedNote = await updateNote(noteId, { name: newName });
×
137
        updateNoteName(noteId, updatedNote.name);
×
138
      } catch (error) {
×
139
        console.error('Failed to rename note:', error);
×
140
        throw error; // Re-throw to let the component handle the error
×
141
      }
×
142
    },
×
UNCOV
143
    [updateNoteName]
×
144
  );
×
145

146
  const handleLogout = () => {
×
147
    // TODO: Implement logout logic
UNCOV
148
    console.log('Logout clicked');
×
149
  };
×
150

151
  const handleHeaderClick = (line: number) => {
×
152
    // Update URL with line number
153
    if (selectedNoteId) {
×
154
      router.push(`/note/${selectedNoteId}?line=${line}`, { scroll: false });
×
UNCOV
155
    }
×
156

157
    // Scroll to line in CodeMirror editor
158
    if (editorRef.current) {
×
159
      editorRef.current.scrollToLine(line);
×
160
    }
×
161
  };
×
162

163
  // Keyboard shortcuts
164
  useEffect(() => {
×
165
    const handleKeyDown = (e: KeyboardEvent) => {
×
166
      if (e.ctrlKey || e.metaKey) {
×
167
        switch (e.key) {
×
168
          case 's':
×
169
            e.preventDefault();
×
UNCOV
170
            handleSave();
×
171
            break;
×
172
          case 'n':
×
173
            e.preventDefault();
×
174
            handleNewNote();
×
175
            break;
×
176
          case 'r':
×
177
            // Only trigger refresh when focused in editor
178
            if (document.activeElement?.closest('[data-editor]')) {
×
UNCOV
179
              e.preventDefault();
×
180
              handleRefresh();
×
181
            }
×
182
            break;
×
UNCOV
183
        }
×
UNCOV
184
      }
×
185
    };
×
186

187
    window.addEventListener('keydown', handleKeyDown);
×
188
    return () => window.removeEventListener('keydown', handleKeyDown);
×
189
  }, [handleSave, handleNewNote, handleRefresh]);
×
190

191
  // Unsaved changes warning (US-015)
192
  useEffect(() => {
×
UNCOV
193
    const handleBeforeUnload = (e: BeforeUnloadEvent) => {
×
194
      if (hasUnsavedChanges) {
×
195
        e.preventDefault();
×
196
        e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
×
UNCOV
197
        return 'You have unsaved changes. Are you sure you want to leave?';
×
198
      }
×
199
    };
×
200

201
    window.addEventListener('beforeunload', handleBeforeUnload);
×
202
    return () => window.removeEventListener('beforeunload', handleBeforeUnload);
×
203
  }, [hasUnsavedChanges]);
×
204

205
  return (
×
206
    <>
×
207
      <TopNavigationBar hasUnsavedChanges={hasUnsavedChanges} onLogout={handleLogout} />
×
208
      <div className={styles.panels}>
×
209
        <LeftPanel
×
210
          notes={notes}
×
211
          selectedNoteId={selectedNoteId}
×
212
          onNoteSelect={handleNoteSelect}
×
213
          onNewNote={handleNewNote}
×
214
          onDeleteNote={handleDeleteNote}
×
215
          onRenameNote={handleRenameNote}
×
216
        />
×
217
        <MiddlePanel
×
218
          note={selectedNote}
×
219
          content={content}
×
220
          saveStatus={saveStatus}
×
221
          selectedLine={lineNumber}
×
222
          onContentChange={handleContentChange}
×
223
          onSave={handleSave}
×
224
          onEditorReady={(editor) => {
×
UNCOV
225
            editorRef.current = editor;
×
UNCOV
226
          }}
×
227
        />
×
UNCOV
228
        <RightPanel
×
229
          headers={headers}
×
UNCOV
230
          currentLine={lineNumber}
×
UNCOV
231
          onHeaderClick={handleHeaderClick}
×
UNCOV
232
        />
×
UNCOV
233
      </div>
×
UNCOV
234
      <Footer />
×
235

236
      {/* TODO: Add confirmation dialog back when needed */}
UNCOV
237
    </>
×
238
  );
UNCOV
239
}
×
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