⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
progressChanged in packages/loopover-engine/src/loop-progress.ts (~line 79) decides whether a
new ProgressSnapshot differs from the previous one "in a way worth pushing to the customer" —
its own doc comment says it "Compares the displayed axes: phase, status, iteration, and the
activity tail's contents," per #4800's on-change-not-polling design.
ProgressSnapshot (defined ~line 28) has two more displayed fields the function never compares:
maxIterations and percentComplete (computed from iteration / maxIterations, ~lines 45-47).
Concretely: if a caller's maxIterations changes between two snapshots while iteration,
phase, status, and recentActivity all stay the same (e.g. an operator raises the iteration
budget mid-run), percentComplete silently changes value but progressChanged still returns
false — so the customer-facing streaming surface that consumes this (buildProgressSnapshot's
caller in src/api/routes.ts) never pushes the update, and the progress bar shown to the customer
goes stale even though a real, displayed number changed.
test/unit/loop-progress.test.ts has no test exercising a maxIterations-only change between two
snapshots.
Requirements
progressChanged must also return true when prev.maxIterations !== next.maxIterations or
prev.percentComplete !== next.percentComplete, added to its existing OR-chain of comparisons
alongside phase/status/iteration/activityChanged(...).
- Do not change
activityChanged or the other existing comparisons.
- Do not change how
percentComplete/maxIterations are computed in buildProgressSnapshot —
only the diffing function.
Deliverables
All three deliverables are required in this single PR.
Test Coverage Requirements
This repo enforces 99%+ Codecov patch coverage, branch-counted, on every changed line/branch in
src/**/workers/**. (Note: packages/loopover-engine/src/** is graded under this same gate —
confirm via the repo's Codecov config if adding a new branch here; the two new test cases above
must exercise both the true and false paths of the added condition.)
Expected Outcome
A maxIterations change between two progress snapshots (and the resulting percentComplete
change) is now correctly detected by progressChanged, so the customer-facing streaming surface
pushes the update instead of leaving a stale progress bar.
Links & Resources
packages/loopover-engine/src/loop-progress.ts (progressChanged, ~line 79; ProgressSnapshot
type, ~line 28)
src/api/routes.ts (consumer of buildProgressSnapshot/progressChanged, for context)
test/unit/loop-progress.test.ts (existing test file to extend)
Context
progressChangedinpackages/loopover-engine/src/loop-progress.ts(~line 79) decides whether anew
ProgressSnapshotdiffers from the previous one "in a way worth pushing to the customer" —its own doc comment says it "Compares the displayed axes: phase, status, iteration, and the
activity tail's contents," per #4800's on-change-not-polling design.
ProgressSnapshot(defined ~line 28) has two more displayed fields the function never compares:maxIterationsandpercentComplete(computed fromiteration / maxIterations, ~lines 45-47).Concretely: if a caller's
maxIterationschanges between two snapshots whileiteration,phase,status, andrecentActivityall stay the same (e.g. an operator raises the iterationbudget mid-run),
percentCompletesilently changes value butprogressChangedstill returnsfalse— so the customer-facing streaming surface that consumes this (buildProgressSnapshot'scaller in
src/api/routes.ts) never pushes the update, and the progress bar shown to the customergoes stale even though a real, displayed number changed.
test/unit/loop-progress.test.tshas no test exercising amaxIterations-only change between twosnapshots.
Requirements
progressChangedmust also returntruewhenprev.maxIterations !== next.maxIterationsorprev.percentComplete !== next.percentComplete, added to its existing OR-chain of comparisonsalongside
phase/status/iteration/activityChanged(...).activityChangedor the other existing comparisons.percentComplete/maxIterationsare computed inbuildProgressSnapshot—only the diffing function.
Deliverables
progressChangedinpackages/loopover-engine/src/loop-progress.tscomparesmaxIterationsandpercentCompletein addition to its existing four axes.test/unit/loop-progress.test.tsassertingprogressChangedreturnstruewhen onlymaxIterations(and thereforepercentComplete) differs between twosnapshots, with
phase/status/iteration/recentActivityheld identical.progressChangedstill returnsfalsewhen all six axes(including the two new ones) are identical between two snapshots — a regression guard
against over-triggering.
All three deliverables are required in this single PR.
Test Coverage Requirements
This repo enforces 99%+ Codecov patch coverage, branch-counted, on every changed line/branch in
src/**/workers/**. (Note:packages/loopover-engine/src/**is graded under this same gate —confirm via the repo's Codecov config if adding a new branch here; the two new test cases above
must exercise both the true and false paths of the added condition.)
Expected Outcome
A
maxIterationschange between two progress snapshots (and the resultingpercentCompletechange) is now correctly detected by
progressChanged, so the customer-facing streaming surfacepushes the update instead of leaving a stale progress bar.
Links & Resources
packages/loopover-engine/src/loop-progress.ts(progressChanged, ~line 79;ProgressSnapshottype, ~line 28)
src/api/routes.ts(consumer ofbuildProgressSnapshot/progressChanged, for context)test/unit/loop-progress.test.ts(existing test file to extend)