Feature Specification: Difference from a Baseline
Created: 2026-09-22
Status: Implemented
Input: Reconstructed from the code at d225ebe (src/diff.ts, src/diff-highlight.ts, src/index.ts: the baseline, onDiffChange and recomputeDiffAgainst parts; src/diff.test.ts, src/diff-highlight.dom-test.js), docs/usage.md, demo/value-diff/, the history of the former panel/forms-island directory in the fediversity monorepo, and the Claude Code logs of the maintainers.
User Scenarios & Testing (mandatory)
User Story 1 - See which fields differ from the applied value (Priority: P1)
The embedder gives the widget a baseline: the value that is now in effect (for example the value of the last deployment). While the user edits, each field that differs from the baseline gets a colored border: green when the user set a field that was unset, yellow when the user changed a set field, red when the user set a field back to unset.
Why this priority: in a form with many options, the user must see what they are about to change before they apply it.
Independent Test: open the value-diff page of the demo, which mounts a value with a baseline, and check the three colors.
Acceptance Scenarios:
- Given a baseline where a field is unset, When the user enters a value, Then the field gets the "added" class.
- Given a baseline where a field has a value, When the user changes it, Then the field gets the "changed" class.
- Given a baseline where a field has a value, When the user clears it, Then the field gets the "removed" class.
- Given a field with a changed value, When the user enters the baseline value again, Then the field has no class.
User Story 2 - Know if there is anything to apply (Priority: P1)
The embedder has a button that applies the value. It enables the button only when the value differs from the baseline.
Why this priority: an apply without a change costs time and can hide mistakes.
Independent Test: mount with onDiffChange, edit a field, and set it back; check that the callback reports true and then false.
Acceptance Scenarios:
- Given a value equal to the baseline, When the form mounts, Then
onDiffChangereceivesfalse. - Given a mounted form, When the user changes a field, Then
onDiffChangereceivestrue. - Given a value that a script wrote outside the form, When the embedder calls
recomputeDiffAgainst(value), ThenonDiffChangereceives the result for that value.
User Story 3 - A default is not a change (Priority: P2)
A section that the user turns on shows its fields at their defaults. These fields do not show as changed.
Why this priority: markers on fields that the user did not touch hide the real changes.
Independent Test: the unit tests of changeKind with a schema default.
Acceptance Scenarios:
- Given a field that is absent in the baseline, When it appears with a value equal to its default, Then it has no class.
- Given a field at its default, When the user changes it to another value, Then it gets the "added" class.
Edge Cases
- No baseline is given: the baseline is the empty value
{}, so each field that the value sets shows as added. null,false,""and an absent key all count as unset.0is a value.- A list changes: the widget compares the list as one value and does not mark single items.
- A container (object, map or list) changes: only the scalar leaves inside it get a class, not the container.
- A nullable leaf counts as scalar when its inner schema is scalar.
- The border shows only that a field differs, not the old value. This keeps secret values out of the page.
Requirements (mandatory)
Functional Requirements
- FR-001: The widget MUST accept an optional
baselinevalue at mount. Without it, the baseline MUST be{}. - FR-002: The widget MUST NOT change the baseline during the life of the form.
- FR-003: For each scalar leaf, the widget MUST classify the difference between the baseline and the current value as added, changed, removed or none, and MUST put the matching class (
nmf-diff-added,nmf-diff-changed,nmf-diff-removed) on the control. - FR-004: The widget MUST treat an absent key,
null,false,""and a value equal to the schema default of the field as unset. - FR-005: The widget MUST NOT mark containers, and MUST compare lists as whole values.
- FR-006: The widget MUST call
onDiffChangeat mount and after each edit withtruewhen the value differs from the baseline, andfalsewhen not. Synthetic fields do not count. - FR-007: The handle MUST offer
recomputeDiffAgainst(value)for a value that changed outside the form. - FR-008: The difference markers MUST NOT show the baseline values.
Key Entities
- Baseline: the value that is now in effect, given by the embedder. It is the reference for all difference markers.
- Change kind: added, changed, removed or none, for one field.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: A form mounted with a value equal to its baseline shows no difference marker and reports
false. - SC-002: Each edit of a scalar leaf changes the marker of that leaf only.
- SC-003: The unit tests of the classification and the DOM tests of the markers pass in CI.
Assumptions
- The embedder knows which value is in effect. The widget does not store or fetch it.
- The embedder gives the baseline in the current schema. When options were renamed, it migrates the baseline first (see
004-schema-changes-and-migration).
Known Deviations
None.