refactor(mqtt): accept a blank jsonPath; guide with a seeded "$" instead

Review follow-up. Dropped the hard Validate rejection of a blank jsonPath under
a Json payload: the runtime defaults it to the document root, which is the real
"publisher puts a bare JSON scalar on the topic" case, so rejecting it made the
editor refuse what the driver accepts — inverting "authoring surface accepts <=>
publish accepts" — and blocked whole CSV-import batches, since this validator
also gates RawManualTagEntryModal's review grid.

Replaced with guidance: an UNAUTHORED tag (no topic AND no jsonPath) seeds the
field with "$", so a fresh tag emits an explicit "jsonPath":"$" while an
existing path-less tag keeps the key ABSENT and the driver defaults it. The
wildcard-topic rule stays strict — a wildcard has no legitimate single-Tag
interpretation.

Also: omit a blank "topic" rather than writing "topic":"", and record why the
_lastConfigJson guard diverges from the Modbus template — the landmine is a
DERIVED, NON-PERSISTED UI FIELD INFERRED FROM BLOB CONTENT (Mode), which Task
24's Sparkplug field group will be tempted to add more of. Named the host-side
dependency (RawTagModal only mutates through this callback) that keeps the
staleness trade benign today.

Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
Joseph Doherty
2026-07-24 17:37:13 -04:00
parent a13ae926a8
commit ff62b62a55
3 changed files with 109 additions and 26 deletions
@@ -133,8 +133,22 @@
apply();
_validationError = _m.Validate();
var json = _m.ToJson();
// Keep the guard in OnParametersSet in step with what we just emitted, so the host echoing the
// new JSON back as a parameter cannot re-parse and clobber an in-progress edit.
// Keep the OnParametersSet guard in step with what we just emitted, so the host echoing the new
// JSON straight back as a parameter cannot re-parse and clobber an in-progress edit.
//
// WHY THIS DIVERGES FROM THE MODBUS TEMPLATE (which does not do this): the general landmine is a
// DERIVED, NON-PERSISTED UI FIELD INFERRED FROM BLOB CONTENT. Mode is exactly that — it is
// re-inferred by FromJson from the Sparkplug keys and never serialised, so a plain re-parse of
// our own output would silently reset the operator's shape selection to Plain on the very next
// render. Modbus has no such field, which is why it needs no guard. Task 24's Sparkplug field
// group will be tempted to add more of them; each one needs this guard to hold.
//
// TRADE (does not manifest today): the editor will not pick up a change the HOST makes to the
// JSON while it is open. That is safe only because RawTagModal/RawManualTagEntryModal mutate
// _form.TagConfig exclusively through this callback. A host-side feature that rewrites the blob
// out-of-band — a "reformat JSON" action, a bulk address re-pick — would reintroduce staleness
// here and must re-key the guard (e.g. compare against a host-supplied revision, not the text).
_lastConfigJson = json;
await ConfigJsonChanged.InvokeAsync(json);
}