What the user sees
Pick a file in Library → + → Upload file, and if the import does not succeed, nothing happens at all: no error, no row, no change. The panel keeps showing its empty state, so the reasonable conclusion is that the upload worked and the app is not showing it.
This is not hypothetical. #37 was closed with exactly this report — "选择新建笔记,上传文档,但是不知道为啥我就上传失败了" — and nothing in the code has changed since that could have told the user why.
Root cause, in two halves
Both are needed for the symptom, and both are verified in the current tree.
1. The failure is discarded before it can be shown.
SourcePanel.handleFileUpload calls the store and ignores what it returns:
const files = await selectFiles()
for (const filePath of files) {
await addDocumentFromFile(notebookId, filePath) // { success, error } discarded
}
The IPC layer does report failures — knowledge:add-document-from-file catches and returns { success: false, error: message } — and the store returns that object to its caller. Nobody looks at it. handleUrlImport, handleTextPaste and handleNoteImport in the same component discard theirs the same way.
2. The list is only refreshed when the import succeeded.
knowledgeStore reloads documents and stats inside if (result.success). So when an import fails after the source row exists, the row is there but invisible:
KnowledgeService.addDocumentFromFile
1. copy the file
2. parse it ← failure here: no row exists yet
3. insert the documents row ← status 'processing'
4. indexDocument() ← failure here: row is marked 'failed'
For a failure at step 4 the row is status: 'failed', and DocumentList already has the feedback for it — an alert saying the document failed to embed — but the list is never re-read, so the row never renders and the alert never fires. For a failure at step 2 (Unsupported file type: exe, a corrupt or scanned PDF that extracts nothing) there is no row at all, so the only possible feedback is an error message, and there is none.
What the fix is
- The store refreshes documents and stats after every import attempt, so a
failed row becomes visible and retryable, and records the failure in its error state.
- Every import entry point in
SourcePanel reports a failed result instead of discarding it.
Not in this issue
What the user sees
Pick a file in Library → + → Upload file, and if the import does not succeed, nothing happens at all: no error, no row, no change. The panel keeps showing its empty state, so the reasonable conclusion is that the upload worked and the app is not showing it.
This is not hypothetical. #37 was closed with exactly this report — "选择新建笔记,上传文档,但是不知道为啥我就上传失败了" — and nothing in the code has changed since that could have told the user why.
Root cause, in two halves
Both are needed for the symptom, and both are verified in the current tree.
1. The failure is discarded before it can be shown.
SourcePanel.handleFileUploadcalls the store and ignores what it returns:The IPC layer does report failures —
knowledge:add-document-from-filecatches and returns{ success: false, error: message }— and the store returns that object to its caller. Nobody looks at it.handleUrlImport,handleTextPasteandhandleNoteImportin the same component discard theirs the same way.2. The list is only refreshed when the import succeeded.
knowledgeStorereloads documents and stats insideif (result.success). So when an import fails after the source row exists, the row is there but invisible:For a failure at step 4 the row is
status: 'failed', andDocumentListalready has the feedback for it — an alert saying the document failed to embed — but the list is never re-read, so the row never renders and the alert never fires. For a failure at step 2 (Unsupported file type: exe, a corrupt or scanned PDF that extracts nothing) there is no row at all, so the only possible feedback is an error message, and there is none.What the fix is
failedrow becomes visible and retryable, and records the failure in itserrorstate.SourcePanelreports a failed result instead of discarding it.Not in this issue
Document ingestion status, retry and re-index).hasEmbeddingModelgate that disables+before an embedding model is available — unrelated, and after fix(embedding): enable RAG with the built-in local model and batch local embeddings #134 the built-in local backend makes it effectively always open.