Skip to content

fix: review sweep of the analysis path (runs, model memory, vision, Whisper, watcher, dates) - #109

Merged
iLevyTate merged 2 commits into
mainfrom
fix/review-sweep
Nov 10, 2026
Merged

iLevyTate merged 2 commits into
mainfrom
fix/review-sweep

Conversation

@iLevyTate

@iLevyTate iLevyTate commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

A review sweep over the analysis path after #107 and #108: the Discover run in the renderer, the batch handler, the model memory manager, the vision runtime, Whisper, the smart-folder watcher, and dates. Each fix has a regression test that was run against the code before the fix and failed there for the reason the fix addresses. CHANGELOG.md ([Unreleased] → Fixed) has the user-facing wording for all of it.

What changes for the user

Discover runs

  • A run with a long recording is no longer cut off at 30 minutes or taken for stuck after five minutes without a finished file. Audio and video always go through the batch, the batch keeps reporting during one long file, and the run is bounded by its own checks (no progress; six hours at most).
  • Stop leaves nothing behind: no progress after the cancel, no "failed" document, no filename-only "Limited analysis" image.
  • "Analysis paused" no longer appears after Stop. A progress update held back by the 80 ms throttle was written after the analysis had ended.
  • Leaving Discover no longer says "Analysis cancelled by user", and a run left behind no longer writes over the progress of the run that replaced it.
  • The Dashboard waits for a running analysis instead of ending it silently (the way back to Guided stays open).
  • Progress from Organize batches, model downloads and Reanalyze all no longer shows as analysis progress, and another sender can no longer take over the running batch.
  • A file that finishes right after another appears at once instead of with the next file.
  • A run that waited for an earlier batch counts the whole run, in the progress ("31 of 40", not "1 of 10") and in the completion notice.
  • A second Analyze right after the first is queued behind it. Until a run wrote its first progress it read as a stale lock, and the second request reset it.
  • A file's status is no longer put back by a later change: setFileStates(prev => …) got the list the hook last rendered with, not the one in the store.
  • Development builds (StrictMode): the run aborted while starting stops there, and no longer switches off the heartbeat of the run that replaced it.

Models and memory

  • Memory counts from the moment a load begins, a second caller for the same model shares the load, and background work does not start on memory that is spoken for. On a 6 GB card the text model and the vision runtime could both start "on paper".
  • The vision runtime is no longer shut down under an image (idle timer during another image, a text model taking its memory between start and request, a start that failed without reporting it), a timed-out request no longer pins it for the session, and a crash part way through an answer no longer leaves the image waiting.
  • Cancelling one analysis no longer cancels another that shares its transcription.
  • A recording longer than the transcription limit is recorded once as "not transcribed" instead of being decoded and failed on every attempt.

Smart-folder watcher

  • It asks the history again when a file's turn comes, records the file as it was when the analysis started, pauses an image batch when a Discover run starts, and keeps a reanalysis request when the same file changes.

Dates

  • Suggested names, the file date given to the model, dates read from the content and the settings export name use the local calendar day, not the UTC day. A date-range search of the history no longer skips the first or last month of the range away from Greenwich.

Logs

  • A model load refused because another model is in use is not logged as an unclassified failure; waiting on a model in use warns once.

Tests

New: ModelMemoryManager.pendingLoads, calendarDayLocalTime (each zone in a child process, since CI runs in UTC and Jest copies process.env), fileStatesUpdaterLiveStore, imageAnalysis.cancellation, ipcMiddleware.progressAfterStop, ipcMiddleware.runBase; additions to the existing suites of the files touched.

The child process of calendarDayLocalTime is plain Node, so it does not have Jest's module mapping. One of the modules it loads requires electron, whose entry point downloads the Electron binary when it is not on disk and prints "Downloading Electron binary..." to stdout. That failed the first zone on a fresh Ubuntu runner (first push of this PR). The probe now puts an empty module in place of electron, as the mapping does (second commit).

test/useAnalysis.test.js now clears the module-level "resume after unmount" flag after each test. A test that unmounted in the middle of a run started a resume in the next test; the stale-lock takeover fixed here used to hide that.

Local: typecheck, typecheck:js, format:check, verify:checkjs-ratchet (493/493), eslint on the changed files, and the full Jest suite.

Looked at and left

  • A run that was aborted but not replaced still continues past its start-up pause to the next abort check; its finally handles it and it sends nothing (fix(discover): an aborted run sends no batch; the watcher yields to Discover runs #108).
  • The watcher retries an embedding failure without backoff, bounded by MAX_ANALYSIS_RETRIES.
  • sendOperationProgress throttles through one pending slot shared by all senders, so one sender's held update can replace another's. Two senders at once would take an Organize batch during a Discover batch, which navigation blocks.
  • ModelMemoryManager.pendingLoads.test.js uses real timers with 20–70 ms waits inside a 500 ms window.
  • The Dashboard gate is a UX choice and easy to reverse: NavigationBar only.

Not verified here: behaviour on real hardware beyond the dev build on one 6 GB laptop GPU, and the open CodeQL alerts.

…hisper, watcher, dates)

Discover runs
- A run with audio or video always goes through the batch; the batch keeps
  reporting during one long file and is bounded by its own checks instead of
  a fixed 30-minute request timeout and a 5-minute "stuck" check.
- A cancelled batch reports nothing more; a cancelled file is neither failed
  nor recorded. A progress update held by the renderer's throttle is dropped
  when its analysis ends ("Analysis paused" after Stop).
- Only a Discover analysis writes analysis progress, and only the running
  batch's own events are accepted.
- A run that waited for an earlier batch shows the counts of the whole run.
- A run that holds the lock and was not aborted counts as active from the
  moment it took the lock: a second call during its start-up is queued
  instead of resetting it as stale.
- A run replaced during its start-up pause stops there, and a heartbeat
  clears only its own interval (StrictMode resume lost its heartbeat).
- setFileStates(prev => next) reads prev from the store, not from the last
  render, in Discover and Organize.
- The Dashboard waits for a running analysis.

Models
- Memory counts from the start of a load; a second caller shares the load;
  external consumers reserve before they start.
- The vision runtime is not shut down under a running image, a failed start
  is reported, a timed-out request releases its pin, and a connection closed
  mid-answer settles the request.
- Cancelling one analysis does not cancel another that shares its
  transcription. A recording past the transcription limit is recorded once.

Watcher
- Checks the history again before analyzing, records the file as it was when
  the analysis started, pauses an image batch for a Discover run, and keeps
  a reanalysis request across a change to the same file.

Dates
- Suggested names, the file date given to the model, dates read from content
  and the settings export name use the local calendar day. The history
  date-range walk steps through UTC months, as the index is keyed.

Tests
- New suites for pending loads, per-zone dates (child process per zone),
  the live-store updater, image cancellation and the progress middleware.
- useAnalysis tests clear the module-level resume flag after each test; a
  test that unmounted mid-run started a resume in the next one.
The probe runs in a plain Node child process, without the module mapping of
jest.config.js. One of the modules it loads requires 'electron', and the
entry point of that package downloads the Electron binary when it is not on
disk yet and prints "Downloading Electron binary..." to stdout. On a fresh
CI runner the first zone's output was that line followed by the JSON, and
JSON.parse failed (ci, ubuntu-latest). The later zones found the binary on
disk and passed.

The probe now puts an empty module in place of 'electron' before it loads
anything, as the mapping does for the tests themselves. The output is still
parsed as a whole, so anything else written to stdout fails the test.
@iLevyTate
iLevyTate merged commit f37a826 into main Nov 10, 2026
10 checks passed
@iLevyTate
iLevyTate deleted the fix/review-sweep branch November 10, 2026 05:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant