* fix(#4294): reserve a full progress bar for 100% and give the render half one owner Six call sites each carried `Math.round((percent / 100) * width)` inline, and every copy rounded to a full bar before the percent reached 100: from 95 up at width 10, from 98 up at width 20. A project at 19/20 plans drew the same bar as a shipped one beside a number that said otherwise, and an out-of-range percent threw `RangeError` from the unguarded `'░'.repeat`. ADR-3180 Decision 7 gave the completion-RATIO derivation one owner (`clampPercentFromFraction`). This gives the RENDER half the same: `progressBarFilledCells` / `renderProgressBar` in phase-lifecycle.cts, with the `progress` table and bar renderers, the stats renderer, the gsd2 import writer, and #4231's `formatProgressMachineSegment` (which now serves both STATE.md writers) all drawing through it. Contract: below 100 the fill is held one cell short of the width, so only the saturating percents move (95-99 at width 10, 98-99 at width 20) and every other value in 0-100 renders as before — pinned by an exhaustive comparison against the legacy formula at both widths. Null / non-finite renders an empty bar; out-of-range is clamped, never thrown. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012hbFn24VWaxJBw8DUWjAmU * chore(#4294): set changeset fragment pr to 4473 * docs(#4294): correct the pre-fix inline call-site count to five The kernel's doc comment said SIX call sites carried their own `Math.round((percent / 100) * width)`. The base tree has five: three in `commands.cts` plus one each in `gsd2-import.cts` and `formatProgressMachineSegment`, the latter two using `/ 10` with the width already substituted (`pct` and `clamped` respectively). The six is #4294's count of consumers -- it counts `cmdStateUpdateProgress` and `syncCore` separately, but #4231 had already routed both through `formatProgressMachineSegment` (as it does `applyPostSyncPreservation`), so by this branch's base they share one copy. The comment now states the tree's count and records where the six comes from, so neither number reads as an error later. Comment-only; no behaviour change, and no change to compiled output. --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
@@ -20,7 +20,7 @@ import { stateReplaceField, stateExtractField, stateReplaceFieldIfTemplate, stat
|
||||
import { KNOWN_TEMPLATE_DEFAULTS, toFiniteNumber, computeProgressPercent } from './state-document.cjs';
|
||||
import { tokenizeHeadings } from './markdown-sectionizer.cjs';
|
||||
import type { HeadingToken } from './markdown-sectionizer.cjs';
|
||||
import { deriveProgressFromRoadmap, clampPercent, clampPercentFromFraction } from './phase-lifecycle.cjs';
|
||||
import { deriveProgressFromRoadmap, clampPercent, clampPercentFromFraction, renderProgressBar } from './phase-lifecycle.cjs';
|
||||
import { escapeRegex } from './pattern.cjs';
|
||||
// #4129: the completion-ratio kernel for the resync-arm ratchet's percent
|
||||
// (planning-scope's SCOPE — state-document's own dependency, no cycle here:
|
||||
@@ -38,12 +38,13 @@ export function formatProgressMachineSegment(percent: number): string {
|
||||
// ADR-3180 Decision 7: rounding and the 100 ceiling belong to the
|
||||
// completion-ratio kernel. The floor is added here because this helper is
|
||||
// also fed persisted frontmatter values (hand-editable, unlike the
|
||||
// count-shaped entries into that kernel), and `'░'.repeat` throws on a
|
||||
// negative count. Bar and printed percent use the clamped value so the two
|
||||
// halves of the segment can never disagree.
|
||||
// count-shaped entries into that kernel). Bar and printed percent use the
|
||||
// clamped value so the two halves of the segment can never disagree.
|
||||
// #4294: the CELL count is the render kernel's — `renderProgressBar` holds a
|
||||
// sub-100 percent one cell short of full, so `[██████████]` beside `95%`
|
||||
// cannot recur here as a seventh inline copy of the rounding.
|
||||
const clamped = Math.max(0, clampPercentFromFraction(percent / 100));
|
||||
const filled = Math.round(clamped / 10);
|
||||
return `[${'█'.repeat(filled)}${'░'.repeat(10 - filled)}] ${clamped}%`;
|
||||
return `[${renderProgressBar(clamped, 10)}] ${clamped}%`;
|
||||
}
|
||||
|
||||
// Consumers (a future STATE.md writer that bypasses all three reintroduces the
|
||||
|
||||
Reference in New Issue
Block a user