Drives the spec 047 §8 / §8.1 decision (can the change-echo suppressor be
eliminated entirely?) and the controlled/uncontrolled/initial classification
in §6.1. The raw data is in begin-suppress-audit.csv.
Every call to ChangeEchoSuppressor.BeginSuppress reachable from production
Reactor.dll code, excluding the definition and doc-comment references inside
ChangeEchoSuppressor.cs itself. Total: 24 call sites across
Reconciler.Mount.cs (3) and Reconciler.Update.cs (21).
| Category | Count | Affected controls |
|---|---|---|
eliminable-tight-diff |
14 | TextBox ×3, PasswordBox, ToggleSwitch ×2, ToggleSplitButton, CheckBox, RadioButton, CalendarDatePicker, DatePicker, TimePicker, NumberBox-immediate-sync |
coercion |
4 | NumberBox (Min, Max), Slider (Min, Max) |
float-precision |
4 | NumberBox.Value, Slider.Value, RatingControl.Value, NumberBox-immediate-sync (also tight-diff) |
items-coercion |
2 | CalendarView.SelectedDates (Add, Remove) |
user-state-races-render |
1 | ColorPicker.Color |
defensive-redundant |
1 | AutoSuggestBox.Text (added category — see below) |
focus-prop |
0 | — |
reference-equality |
0 | — |
animation-tick |
0 | — |
Total: 24 (the immediate-sync NumberBox site at Mount.cs:670 is double-counted
once as eliminable-tight-diff and once as float-precision; the table counts
it under float-precision since the precision concern is load-bearing).
The original schema didn't anticipate a site whose own code comment declares it
unnecessary. AutoSuggestBox.Text (Reconciler.Update.cs:951) is documented
as "suppress anyway for consistency" — the underlying control already filters
TextChanged to UserInput only, so the programmatic Text= write cannot
echo to the user handler. Recommend deleting this site as part of any §8.x
follow-up rather than carrying it through a redesign.
The dominant category by a wide margin is eliminable-tight-diff (14/24).
These sites are all simple programmatic writes already gated by
if (control.X != element.X). The post-write state guarantees
control.X == element.X, so a handler-side check —
lastFired != GetElementTag(control).X — would suffice. Per §8 this means
~58% of suppression sites can be eliminated by moving the de-duplication
into the per-control event handler, without any new mostRecentEventCount
plumbing.
The remaining 10 sites fall into three groups, each requiring a different treatment:
-
coercion(4) —float-precision(4): the change-event fires with a value the engine did not directly write. A naïve handler-sidelastFired != tag.Xwould either miss the echo (coerced value happens to match) or fail the float comparison spuriously. These sites need either:- a per-control tolerance-aware comparison stored alongside the handler
(matches today's
AreNumberBoxValuesEquivalentdiscipline), or - the §8 escape hatch where the engine records "I wrote X expecting Y; suppress one echo for Y±tolerance." The numbers are small enough (8 sites, 4 controls) that descriptor-level tolerance metadata is plausible.
- a per-control tolerance-aware comparison stored alongside the handler
(matches today's
-
items-coercion(2):CalendarView.SelectedDatesis mutated as a diff. The clean fix is "batch then assign" or "compute desired set, suppress one token per applied mutation" (today's choice). Not generalizable to a descriptor field — keep as a per-control imperative shim. -
user-state-races-render(1) — ColorPicker only: the spec's §8.1 case.ColorChangedechoes through a re-rendered control whose tag has already moved to the next element. This is the only site that requires §8.1'smostRecentEventCountround-trip or its equivalent. One site is small enough that it can be addressed without protocol-level changes — e.g., the imperative shim can capture the expected color into the handler closure and reject mismatches.
- The §8 "eliminate
BeginSuppressentirely" direction is viable for the 14 tight-diff sites with no spec changes. - The 8 coercion/float-precision sites are tractable with per-control tolerance metadata; not all-or-nothing.
- The 1 ColorPicker site is the only one that demands the heavier §8.1
machinery. Building §8.1 just for one site is over-engineered; an imperative
fix specific to ColorPicker (per-handler
expectedColorplus tolerance) is likely the right shape.
Every site that suppresses is a value-bearing DP where Reactor exposes an
OnXChanged callback. The audit confirms the §6.1 split:
- Controlled props are exactly the ones in this audit (writes that need echo protection because they may otherwise re-trigger user callbacks).
- Uncontrolled props are written without
BeginSuppress(e.g.,Header,PlaceholderText,OnContent/OffContent,IsThreeState,SnapsTo) — they are write-only from the engine's perspective and have no echo path back to user code.
No call site in the audit fits the focus-prop, reference-equality, or
animation-tick categories. Spec §6.1 can drop those rows or fold them into
"reserved for future controls."
- Delete
Reconciler.Update.cs:951(thedefensive-redundantAutoSuggestBox.Textsuppress) as a standalone correctness-no-op. - Decide whether
coercionsites move to descriptor-level metadata ({ property: Value, coercedBy: [Minimum, Maximum] }) or stay as imperative per-control logic. This is §13 Q3 territory. - The single
user-state-races-renderColorPicker site is the smallest evidence base in the codebase for §8.1. Consider whether the §8.1 design is load-bearing across the whole protocol or whether a one-control shim is the right scope. Captured indecision-criteria.mdQ3.
Spec §8 should cite this audit. Suggested footnote at §8 first paragraph:
See
docs/specs/047/audits/begin-suppress-audit.csvfor the per-call-site classification. 14 / 24 sites areeliminable-tight-diff, 8 / 24 fall intocoercion+float-precision(tractable with per-control tolerance metadata), 1 / 24 (ColorPicker.Color) is the only site that requires §8.1'smostRecentEventCountround-trip, and 1 / 24 is documented as already redundant.