Skip to content

Fix #13885: coalesce per-line OCR (and auto-translate) UI updates so input stays responsive - #13887

Merged
niksedk merged 6 commits into
mainfrom
claude/subtitleedit-issue-13885-3a7a95
Aug 19, 2026
Merged

Fix #13885: coalesce per-line OCR (and auto-translate) UI updates so input stays responsive#13887
niksedk merged 6 commits into
mainfrom
claude/subtitleedit-issue-13885-3a7a95

Conversation

@niksedk

@niksedk niksedk commented Aug 19, 2026

Copy link
Copy Markdown
Member

Fixes issue 1 of #13885 (buttons dead during OCR) and the "minimize only happens after the run completes" half of issue 2.

Why the UI froze

Fast OCR engines (binary image compare, nOCR) finish a line in milliseconds, and every line posted selection, scroll, text and progress updates at Normal dispatcher priority — which outranks input processing in Avalonia. During a run the dispatcher queue never drained, so Pause/OK/Cancel clicks and even the window's minimize were starved until OCR completed.

The post-restore wedge in issues 2/3 (window hangs / lines unclickable after minimize+restore) is the #13865 minimize-mirror bug, already fixed in #13877 — which merged a few hours after beta 18 was cut, so the reporter can't have it yet. That part should be retested on the next beta.

The fix

New CoalescedUiUpdateQueue (src/ui/Logic): per-line UI feedback is queued and applied in batched dispatcher jobs at Background priority, below input, so a flood of updates can never starve clicks or window messages.

The flush is leading-edge throttled: an update arriving after a quiet period is applied immediately (so slower engines still feel live), and only while updates keep streaming are they batched to at most one flush per 100 ms.

  • Ordered updates (line text, fix results, unknown-word/fix/guess rows) are all applied, in order — nothing is dropped.
  • Selection+scroll and progress are latest-wins: a newly processed line replaces any still-pending selection, so the grid follows the run at ~10 Hz instead of per line.

OCR

All the per-line engine loops now go through the queue. Side effect fixed for free: the loops previously mutated the bound UnknownWords/AllFixes/AllGuesses ObservableCollections from their worker threads; those adds now ride in the coalesced update on the UI thread.

Ordering safety: the queue is flushed synchronously before any modal that reads line text (unknown-word prompt, nOCR/binary add-character windows) and at the top of OK, so dialogs and the final subtitle never see a half-flushed queue. User-driven navigation (inspect, go-to-line, pre-processing) keeps the immediate SelectAndScrollToRow path.

Auto-translate

Same treatment for the per-batch progress and follow-along selection, replacing a synchronous Dispatcher.Invoke per batch at Normal priority.

Deliberately narrower than OCR: the row TranslatedText writes stay immediate, because AdvancedTranslatorBase.CollectHistory reads them back to build the next batch's rolling context and MergeAndSplitHelper reads existing translations when re-applying formatting — deferring those would degrade the translation itself, not just the display. The finally block flushes before applying the final progress/selection so a stale queued update cannot override it.

Tests

New CoalescedUiUpdateQueueTests covers the leading-edge apply, batching while streaming, in-order data updates with latest-wins selection/progress, no replay after an explicit flush, and worker-thread enqueue applying on the UI thread.

Full UI suite: 3242 passed, 0 failed.

The last commit fixes BatchConvertStatusColor_StillFollowsTheErrorTextAfterItChanges, which is red on main: #13884 changed the converter's non-error default from UnsetValue to the theme text brush, and the test still asserted the old null. It was the only red test on this PR's CI, so it is fixed here rather than in a separate PR.

🤖 Generated with Claude Code

niksedk and others added 4 commits August 19, 2026 21:13
Fast OCR engines finish a line in milliseconds, and every line posted
selection, scroll, text and progress updates at Normal dispatcher
priority - which outranks input processing. During a run the queue never
drained, so Pause/Cancel clicks and even the window's minimize were
starved until OCR completed (issue 1 and the delayed-minimize half of
issue 2; the post-restore wedge of issues 2/3 is the #13865 minimize
mirror bug already fixed by #13877 after beta 18 was cut).

Per-line UI feedback now goes through a coalescing queue flushed by a
one-shot Background-priority timer at most every 200 ms: data updates
(line text, fix results, unknown-word/fix/guess rows) are applied in
order, while selection+scroll and progress are latest-wins - a newly
OCR'ed line replaces any still-pending selection. This also moves the
UnknownWords/AllFixes/AllGuesses ObservableCollection mutations onto the
UI thread; the loop engines previously added to these bound collections
from their worker threads.

The queue is flushed synchronously before any modal that reads line
text (unknown-word prompt, nOCR/binary add-character windows) and in OK,
so dialogs and the final subtitle never see a half-flushed queue.

OCR UI tests: 665 passed. Full UI suite: 3234 passed, 1 pre-existing
failure (BatchConvertStatusColor test stale since #13884, unrelated).

Co-Authored-By: Claude Fable 5 <[email protected]>
The pure trailing 200 ms delay made the grid visibly lag behind the
run. An update arriving after a quiet period now flushes immediately;
only while updates keep streaming are they batched, at most one flush
per 100 ms. Flushes stay at Background priority, so input still wins.

Co-Authored-By: Claude Fable 5 <[email protected]>
The OCR throttle is now CoalescedUiUpdateQueue in Logic, so auto-translate
gets the same treatment: per-batch progress and the follow-along selection
are queued and applied leading-edge throttled at Background priority,
instead of a synchronous Dispatcher.Invoke per batch at Normal priority.

Only the pure UI feedback is queued for translate. The row TranslatedText
writes deliberately stay immediate: AdvancedTranslatorBase.CollectHistory
reads them back to build the next batch's rolling context, and
MergeAndSplitHelper reads existing translations when re-applying
formatting, so deferring those writes would degrade the translation
itself, not just the display.

The finally block flushes before applying the final progress/selection so
a stale queued update cannot override it.

Adds unit tests for the queue: leading-edge apply, batching while
streaming, in-order data updates with latest-wins selection/progress,
no replay after an explicit flush, and worker-thread enqueue applying on
the UI thread.

Co-Authored-By: Claude Fable 5 <[email protected]>
The converter's non-error default changed from AvaloniaProperty.UnsetValue
to the theme text brush in #13884 (so in-progress statuses stop rendering
black on the dark theme), which left this test asserting a null that can
no longer happen - it fails on main. Assert against the default brush
instead: after the language swap the old prefix gets the default brush and
only the new prefix gets the error brush, which is what the test is
actually about.

Fixing it here because it is the only red test on this PR's CI run.

Co-Authored-By: Claude Fable 5 <[email protected]>
@niksedk niksedk changed the title Fix #13885: coalesce per-line OCR UI updates so input stays responsive Fix #13885: coalesce per-line OCR (and auto-translate) UI updates so input stays responsive Aug 19, 2026
niksedk and others added 2 commits August 19, 2026 21:40
Both view models reached the queue through a lazy `??=` property, and
both are touched from a worker thread (the OCR/translate loop) and the UI
thread (OK, the modal flushes). A simultaneous first access could build
two queues and drop whatever was pending in the loser. Constructing them
up front removes the race; the field initializer cannot do it because the
callbacks are instance methods.

Co-Authored-By: Claude Fable 5 <[email protected]>
…ssue-13885-3a7a95

# Conflicts:
#	tests/UI/Logic/ValueConverters/ValueConverterTests.cs
@niksedk
niksedk merged commit 57ffd9e into main Aug 19, 2026
1 check passed
@niksedk
niksedk deleted the claude/subtitleedit-issue-13885-3a7a95 branch August 19, 2026 19:51
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