Skip to content

fix(library): a failed import is reported, and the library is re-read either way - #147

Merged
mrsibe merged 1 commit into
mainfrom
fix/upload-failure-refresh
Sep 27, 2026
Merged

mrsibe merged 1 commit into
mainfrom
fix/upload-failure-refresh

Conversation

@mrsibe

@mrsibe mrsibe commented Sep 27, 2026

Copy link
Copy Markdown
Owner

Fixes #146. A failed import used to produce nothing at all: no error, no row, and a panel that still showed its empty state — which reads as "the upload worked and the app is not showing it".

Two halves, both needed

The failure was discarded before it could be shown. SourcePanel calls the store and ignores what it returns:

for (const filePath of files) {
  await addDocumentFromFile(notebookId, filePath)   // { success, error } discarded
}

The store reports failure by returning { success: false, error } — the IPC layer already catches and describes it — so an unchecked return value makes a failure indistinguishable from a success. handleFileUpload, handleUrlImport, handleTextPaste and handleNoteImport all did this.

The list was only re-read when the import succeeded. KnowledgeService.addDocumentFromFile inserts the source row and then indexes it:

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'

A failure at step 4 leaves a status: 'failed' row, and DocumentList already has the feedback for it — an alert saying the document failed to embed — but the list was never re-read, so the row never rendered and the alert never fired. A failure at step 2 (unsupported type, a corrupt or scanned file that extracts nothing) has no row at all and needs the message to say so.

What changes

  • One rule for every entry point: an import attempt always ends by re-reading the library and recording the reason, and the reason reaches the reader as a toast (importFailed, en + zh).
  • The rejection path re-reads too, deliberately: an IPC call can fail after the main process already wrote the source row, so the failure path cannot assume that nothing changed.
  • The store's error state is now set from a failed result as well as from a thrown error — both are failures, and only the second one used to be recorded.

Not in this PR

Behaviour note

The empty-note case used to alert() from a branch that could not be reached: the store returns { success: false } rather than throwing, so the catch never ran. It now shows the same message as a toast like every other import failure, and names the note instead of printing Note note_123 not found.

Verification

npm test 303 pass, 0 fail — 6 new in test/knowledgeStore.test.ts
npm run typecheck clean (node, web, test)
npm run check:design no violations
npx eslint <changed files> 0 problems
npm run build bundles (renderer included)

The new tests drive all four entry points (pasted text, a file, a URL, a note) through the real store with a stubbed window.api, and assert three things per entry point: the failure is reported, the library is re-read (so a status: 'failed' row can appear), and the reason is recorded. Two more cover the success path clearing an earlier failure, and a rejected IPC call.

tsconfig.test.json now also includes src/preload/index.d.ts. Importing renderer code means typechecking it, and that file is where window.api is declared; without it a test cannot import a store that legitimately uses the preload global. The test file's header records why.

Not run: build:unpack + smoke:packaged — neither main nor preload changed (CI runs them for this PR, and they were run in #145 for the last main-process change). Not hand-verified: the toast itself, which needs the GUI. To check it by hand: Library → + → Upload file → pick a .zip or a scanned PDF → a red toast naming the file, instead of nothing happening.

… either way

Fixes #146. A failed import used to produce nothing at all — no error, no row, and
a panel that still showed its empty state, which reads as "the upload worked and
the app is not showing it". #37 was closed with exactly that report and nothing in
the code could have told that user what went wrong.

Two halves, both needed:

**The failure was discarded before it could be shown.** `SourcePanel` calls the
store and ignores what it returns. The store reports failure by *returning*
`{ success: false, error }` — the IPC layer already catches and describes it — so
an unchecked return value makes a failure indistinguishable from a success.
`handleFileUpload`, `handleUrlImport`, `handleTextPaste` and `handleNoteImport`
all did this.

**The list was only re-read when the import succeeded.** `KnowledgeService` inserts
the source row and *then* indexes it, so a failure at the indexing step leaves a row
with `status: 'failed'` — and `DocumentList` already has the feedback for it, an
alert saying the document failed to embed. It never fired, because the list was
never re-read. A failure *before* the row exists (an unsupported type, a corrupt or
scanned file that extracts nothing) has no row and so needs the message to say so.

Now one rule covers every entry point: an import attempt always ends by re-reading
the library and recording the reason, and the reason reaches the reader as a toast.
The rejection path re-reads too, deliberately: an IPC call can fail after the main
process already wrote the source row, so the failure path cannot assume that
nothing changed.

Not in this PR: recording an attempt that failed before its source row existed
(there is still no row for an unparseable file — that is #95's ingestion record),
and any retry affordance (#95). The `hasEmbeddingModel` gate that disables `+` is
untouched; after #134 the built-in local backend makes it effectively always open.

One behaviour note: the empty-note case used to `alert()` from a branch that could
not be reached — the store returns `{ success: false }` rather than throwing, so the
`catch` never ran. It now shows the same message as a toast, like every other
import failure, and names the note instead of printing `Note note_123 not found`.

Verified: `npm test` 303 pass (6 new in `test/knowledgeStore.test.ts`, which drive all
four entry points through the store with a stubbed `window.api`), `npm run typecheck`,
`npm run check:design`, `npx eslint` on the changed files (0 problems), `npm run build`.

`tsconfig.test.json` now also includes `src/preload/index.d.ts`: importing renderer
code means typechecking it, and that file is where `window.api` is declared — without
it a test cannot import a store that legitimately uses the preload global.

Not run: `build:unpack` + `smoke:packaged`. Neither main nor preload changed; the
packaged checks are in CI for this PR and were run in #145 for the last main-process
change. Not hand-verified: the toast itself, which needs the GUI.
@github-actions github-actions Bot added the bug Something isn't working label Sep 27, 2026
@mrsibe
mrsibe merged commit 49cbd78 into main Sep 27, 2026
4 checks passed
@mrsibe
mrsibe deleted the fix/upload-failure-refresh branch September 27, 2026 05:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] A failed import is silent: no error is shown, and the Library never refreshes

1 participant