* test(#3945): 0-of-0 milestone counters must not render complete Regression coverage for the string-truthiness vacuous equality in both statusline renderers: '0' counters, boundary 1/1 vs 0/1 vs 1/0, compact parity. * fix(#3945): require a non-empty denominator for milestone-complete Both statusline renderers compared the regex-captured string counters directly, so '0' && '0' && '0' === '0' fired Scene 3 on the empty set. Numeric coercion plus total > 0 restores the guard's intent; all non-zero shapes and percent=100 render byte-identically. * chore(#3945): add changeset fragment * chore(#3945): backfill changeset pr number --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -389,8 +389,12 @@ function formatGsdState(s) {
|
||||
// Scene 2: idle + a recommended next command is visible to the user.
|
||||
// Surfaces "what to run next" without the user opening STATE.md.
|
||||
parts.push(`next ${s.nextAction} ${phasesStr}`);
|
||||
} else if (Number(s.percent) === 100 || (s.completedPhases && s.totalPhases && s.completedPhases === s.totalPhases)) {
|
||||
// Scene 3: milestone complete (every phase done).
|
||||
} else if (Number(s.percent) === 100 || (Number(s.totalPhases) > 0 && Number(s.completedPhases) === Number(s.totalPhases))) {
|
||||
// Scene 3: milestone complete (every phase done). #3945: the counters are
|
||||
// regex-captured STRINGS, so the old `cp && tp && cp === tp` guard fired on
|
||||
// the empty set ('0' is truthy, '0' === '0') — "0% · milestone complete".
|
||||
// Numeric coercion + a non-empty denominator makes "nothing to measure"
|
||||
// stop meaning "everything is done".
|
||||
parts.push('milestone complete');
|
||||
} else {
|
||||
// Backward-compatible default — preserved EXACTLY for STATE.md files that
|
||||
@@ -500,7 +504,7 @@ function formatGsdStateCompact(s) {
|
||||
// still completes) wins over milestone-complete (Scene 3), even if a
|
||||
// non-atomic STATE.md edit leaves percent=100 alongside a lifecycle phase.
|
||||
const done = !s.activePhase && (Number(s.percent) === 100 ||
|
||||
(s.completedPhases && s.totalPhases && s.completedPhases === s.totalPhases));
|
||||
(Number(s.totalPhases) > 0 && Number(s.completedPhases) === Number(s.totalPhases)));
|
||||
|
||||
if (done) {
|
||||
parts.push('complete');
|
||||
|
||||
Reference in New Issue
Block a user