Skip to content

Commit 8f6bc82

Browse files
committed
fix(ui): adapt Sentry for TanStack Start and note save states
1 parent 54c4147 commit 8f6bc82

17 files changed

Lines changed: 435 additions & 126 deletions

ui/.env.example

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
# Backend API base URL (required)
22
VITE_API_BASE_URL=http://localhost:8000/api
33

4-
# Environment (optional) - set to 'production' for production builds
4+
# Client-only env flag (optional) - some code checks VITE_ENV === 'production' (e.g. enrichment UI)
55
# VITE_ENV=production
66

7-
# Sentry DSN (optional) - for error tracking
8-
# VITE_SENTRY_DSN=your-sentry-dsn-here
7+
# Sentry (optional)
8+
# - Browser + logger (production): VITE_SENTRY_DSN (import.meta.env)
9+
# - Node server (src/server.ts): SENTRY_DSN, or VITE_SENTRY_DSN if present in process.env
10+
# VITE_SENTRY_DSN=
11+
# SENTRY_DSN=
12+
#
13+
# Build-time: all three are required to enable @sentry/tanstackstart-react/vite (source maps upload).
14+
# Do not use VITE_ prefix for the token. See vite.config.ts loadEnv and Sentry TanStack Start docs.
15+
# SENTRY_AUTH_TOKEN=
16+
# SENTRY_ORG=
17+
# SENTRY_PROJECT=

ui/bun.lock

Lines changed: 229 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
"@react-sigma/minimap": "5.0.6",
8484
"@replit/codemirror-indentation-markers": "^6.5.3",
8585
"@replit/codemirror-vim": "^6.3.0",
86-
"@sentry/react": "^10.47.0",
86+
"@sentry/tanstackstart-react": "^10.47.0",
8787
"@stepperize/react": "^6.1.0",
8888
"@tanstack/react-query": "^5.96.1",
8989
"@tanstack/react-query-devtools": "^5.96.1",

ui/src/components/base/snippet-list/snippet-list.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ const SnippetList = forwardRef<SnippetListRef, SnippetListProps>(
176176
setDeleteDialogOpen(false);
177177
setDeletingSnippet(null);
178178
} catch (error) {
179-
logger.error('Error deleting snippet:', error);
179+
logger.error('Delete snippet failed', error);
180180
}
181181
};
182182

@@ -296,7 +296,7 @@ const SnippetList = forwardRef<SnippetListRef, SnippetListProps>(
296296
};
297297
await createMutation.mutateAsync(snippetData);
298298
} catch (error) {
299-
logger.error('Error creating snippet:', error);
299+
logger.error('Create snippet failed', error);
300300
throw error;
301301
}
302302
}}
@@ -332,7 +332,7 @@ const SnippetList = forwardRef<SnippetListRef, SnippetListProps>(
332332
data: snippetData,
333333
});
334334
} catch (error) {
335-
logger.error('Error updating snippet:', error);
335+
logger.error('Update snippet failed', error);
336336
throw error;
337337
}
338338
}}

ui/src/components/domain/graph/graph-explorer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export default function GraphExplorer({ GraphSearchComponent }: GraphExplorerPro
112112
// Filter valid nodes
113113
const nodesToAdd = nodesToProcess.filter((node) => {
114114
if (!node.id) {
115-
logger.warn('[GraphExplorer] Node missing id', { node });
115+
logger.warn('Skipping graph node without id', { node });
116116
return false;
117117
}
118118
return !nodeIds.has(node.id);
@@ -125,7 +125,7 @@ export default function GraphExplorer({ GraphSearchComponent }: GraphExplorerPro
125125
const edgesToAdd = edgesToProcess.filter((edge) => {
126126
// Validate required properties
127127
if (!edge.id || edge.src == null || edge.dst == null) {
128-
logger.warn('[GraphExplorer] Edge missing required properties', {
128+
logger.warn('Skipping graph edge with missing id, src, or dst', {
129129
edge,
130130
});
131131
return false;
@@ -145,7 +145,7 @@ export default function GraphExplorer({ GraphSearchComponent }: GraphExplorerPro
145145
const dstExists = allNodeIds.has(dstStr);
146146

147147
if (!srcExists || !dstExists) {
148-
logger.warn('[GraphExplorer] Edge references non-existent node(s)', {
148+
logger.warn('Skipping graph edge with unknown endpoint', {
149149
edgeId: edge.id,
150150
src: edge.src,
151151
dst: edge.dst,

ui/src/components/domain/graph/graph.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,8 @@ function GraphContent({
235235
neighborNodes.unshift(node);
236236
newNodes = new Set(neighborNodes);
237237
} catch (e) {
238-
logger.warn('[Graph] Error getting neighbors:', {
239-
error: e,
238+
logger.warn('Sigma neighbors lookup failed', {
239+
cause: e,
240240
});
241241
}
242242
}
@@ -245,7 +245,7 @@ function GraphContent({
245245
onTogglePanel('explorer');
246246
}
247247
} catch (error) {
248-
logger.error('[Graph] Error in clickNode:', error);
248+
logger.error('Graph node click failed', error);
249249
}
250250
},
251251
clickStage: () => {
@@ -265,7 +265,7 @@ function GraphContent({
265265
onTogglePanel('explorer');
266266
}
267267
} catch (error) {
268-
logger.error('[Graph] Error in clickEdge:', error);
268+
logger.error('Graph edge click failed', error);
269269
}
270270
},
271271
downNode: (event) => {
@@ -486,7 +486,7 @@ export default function GraphViewer({
486486
const validNodes = useMemo(() => {
487487
return nodes.filter((node) => {
488488
const isValid = node.id != null && node.id !== '';
489-
if (!isValid) logger.warn('[Graph] Filtered out invalid node:', { node });
489+
if (!isValid) logger.warn('Skipping graph node without id', { node });
490490
return isValid;
491491
});
492492
}, [nodes]);

ui/src/components/domain/graph/knowledge-graph-search.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export default function KnowledgeGraphSearch({
8383
nodes = Array.from(byId.values());
8484
}
8585
} catch (e) {
86-
logger.error('[KnowledgeGraphSearch] Error processing entries:', e);
86+
logger.error('Knowledge graph: parse search entries failed', e);
8787
}
8888

8989
const edges: EdgeRelation[] =

ui/src/components/domain/graph/note-graph-search.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export default function NoteGraphSearch(
9999
if (nodes.length > 0) hasData = true;
100100
}
101101
} catch (e) {
102-
logger.error('[NoteGraphSearch] Error processing entries:', e);
102+
logger.error('Note graph: parse search entries failed', e);
103103
}
104104
}
105105

@@ -132,7 +132,7 @@ export default function NoteGraphSearch(
132132
setAlert({ show: false, message: '', color: 'red' });
133133
}
134134
} catch (e) {
135-
logger.error('[NoteGraphSearch] Error processing graph data:', e);
135+
logger.error('Note graph: apply graph data failed', e);
136136
}
137137
}, [graphData, addBoth, addNodes, addEdges]);
138138

ui/src/components/domain/notes/note-viewer.tsx

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import { cn } from '@/lib/utils';
3434
import { getDisplayMessage, parseAPIError } from '@/utils/api';
3535
import { CradleEditor } from '@/utils/editor/enhancements';
3636
import extractHeaderHierarchy, { HeaderNode } from '@/utils/editor/outline';
37-
import { logger } from '@/utils/logger';
3837
import { Prec } from '@codemirror/state';
3938
import { keymap } from '@codemirror/view';
4039
import { BookOpenIcon, InfoIcon, PencilSimpleIcon } from '@phosphor-icons/react';
@@ -313,7 +312,6 @@ export default function NoteViewer() {
313312
setEnrichmentDialogOpen(true);
314313
}, [editorUtils]);
315314

316-
// Query for note metadata
317315
const {
318316
data: noteData,
319317
isLoading,
@@ -340,18 +338,22 @@ export default function NoteViewer() {
340338
enableEditingRef.current = { enableEditing, router, location, search };
341339
}, [enableEditing, router, location, search]);
342340

343-
// Update state when note data is loaded
341+
const editorDraftRef = useRef({ markdownContent: '', initialMarkdown: '' });
342+
editorDraftRef.current = { markdownContent, initialMarkdown };
343+
344+
const prevNoteIdForDetailRef = useRef<string | null>(null);
345+
344346
useEffect(() => {
345347
if (!noteData) {
346348
setNote(null);
347349
setIsFleeting(false);
348350
setFileData([]);
349351
setMarkdownContent('');
350352
setInitialMarkdown('');
353+
prevNoteIdForDetailRef.current = null;
351354
return;
352355
}
353356

354-
logger.info('NoteViewer - Note loaded successfully', { noteData });
355357
setNote(noteData);
356358
const nextIsFleeting = Boolean(noteData.fleeting);
357359
setIsFleeting(nextIsFleeting);
@@ -363,13 +365,24 @@ export default function NoteViewer() {
363365
replace: true,
364366
});
365367
}
366-
setMarkdownContent(noteData.content);
367-
setInitialMarkdown(noteData.content);
368+
369+
const switchedNote = prevNoteIdForDetailRef.current !== noteId;
370+
prevNoteIdForDetailRef.current = noteId;
371+
372+
const { markdownContent: md, initialMarkdown: init } = editorDraftRef.current;
373+
const applyServerBody =
374+
switchedNote ||
375+
(md === init && noteData.content !== md);
376+
377+
if (applyServerBody) {
378+
setMarkdownContent(noteData.content);
379+
setInitialMarkdown(noteData.content);
380+
setHasUnsavedChanges(false);
381+
}
382+
368383
setFileData(noteData.files || EMPTY_FILES);
369-
setHasUnsavedChanges(false);
370-
}, [noteData]);
384+
}, [noteData, noteId]);
371385

372-
// Mutation for deleting note
373386
const deleteMutation = useMutation({
374387
mutationFn: async () => {
375388
const { error, response } = await fetchClient.DELETE('/notes/{note_id}/', {
@@ -386,14 +399,8 @@ export default function NoteViewer() {
386399
},
387400
});
388401

389-
// Use a ref to store the latest values for the save function
390-
const saveDataRef = useRef({ markdownContent });
391402
const lastSaveFailedRef = useRef(false);
392403

393-
useEffect(() => {
394-
saveDataRef.current = { markdownContent };
395-
}, [markdownContent]);
396-
397404
useEffect(() => {
398405
lastSaveFailedRef.current = false;
399406
}, [noteId]);
@@ -422,7 +429,7 @@ export default function NoteViewer() {
422429
async (showAlert = false) => {
423430
if (!noteId) return;
424431

425-
const { markdownContent: content } = saveDataRef.current;
432+
const content = editorDraftRef.current.markdownContent;
426433

427434
if (!content || content.trim().length === 0) {
428435
toast.error('Cannot save empty note.');

ui/src/components/domain/notes/rich-editor.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ const RichEditor = forwardRef<RichEditorRef, RichEditorProps>(function RichEdito
519519
saveNoteRef.current(true);
520520
} catch (error) {
521521
toast.error('Failed to save note. Please try again with Ctrl-S.');
522-
logger.error('Failed to save note:', error);
522+
logger.error('Save via Vim :w failed', error);
523523
}
524524
return true;
525525
});
@@ -550,7 +550,7 @@ const RichEditor = forwardRef<RichEditorRef, RichEditorProps>(function RichEdito
550550
useEffect(() => {
551551
if (!editorViewRef.current && editorRef.current && extensions.length > 0) {
552552
try {
553-
logger.debug('[RichEditor] initializing editor', {
553+
logger.debug('Editor init', {
554554
noteId: noteid,
555555
initialDocLength: markdownContent.length,
556556
extensionsCount: extensions.length,
@@ -568,7 +568,7 @@ const RichEditor = forwardRef<RichEditorRef, RichEditorProps>(function RichEdito
568568
editorViewRef.current = view;
569569
setEditorReady(true);
570570
} catch (error) {
571-
logger.error('Failed to initialize RichEditor:', error);
571+
logger.error('Editor init failed', error);
572572
toast.error(
573573
`Failed to initialize editor: ${error instanceof Error ? error.message : 'Unknown error'}. Please refresh the page.`,
574574
);

0 commit comments

Comments
 (0)