Skip to content

fix(scripting): apply step params in place instead of rebuilding from display text#251

Merged
fuzzzerd merged 3 commits into
masterfrom
fuzzz/apply-params-in-place
Jul 17, 2026
Merged

fix(scripting): apply step params in place instead of rebuilding from display text#251
fuzzzerd merged 3 commits into
masterfrom
fuzzz/apply-params-in-place

Conversation

@fuzzzerd

@fuzzzerd fuzzzerd commented Jul 16, 2026

Copy link
Copy Markdown
Owner

What

FmScript.Apply routed param maps through display-text synthesis (SynthesizeHrParams) and rebuilt the step with StepDisplayFactory.TryCreate, replacing the instance. That made every update destructive: params not named in the map reset to constructor defaults, state the display line can't carry (dialog geometry, button configuration, passthrough XML) was wiped, and HrOnly / variant / value-type slots were dropped with no error. A Value-only update on Set Variable [ $x ; Value: 1 ] produced Set Variable [ Value: 2 ; Value: ] — the synthesized token mis-bound positionally into the Name slot.

How

  • Add ScriptStep.ApplyParam(name, value): sets one shape-bound public property in place and returns an error message or null. The ScriptStep<TSelf> default dispatches to the new StepParamApplier, the validating counterpart of StepDisplayParser — unknown names, non-On/Off booleans, and out-of-range enum values return errors instead of silently dropping.
  • Slots the typed conversion cannot set — HrOnly, variant / value-type / list slots, emit-only wire projections (e.g. Perform Script's ParameterBeforeScript), and enums whose DisplayValues have no paired ValidValues — route through the step's own display-token grammar generically, with no per-step wiring: the synthesized token is validated against a blank probe (it must produce a wire-level change, so unrecognized values fail loudly), then re-parsed onto the live instance together with its current display tokens. Same-label current tokens are dropped and both token positions are tried, so first-match and last-match parser styles both take the new value; a token already present is an idempotent success.
  • Structured slots additionally accept their wire element as an XML fragment value (<SortList>…</SortList>, <Query>…</Query>): the fragment replaces the step element's same-named children and the merged element is re-read in place through PopulateFromXml — so typed readers normalize it to canonical wire form. The fragment root must match the slot's wire element and the re-emitted step must retain it, otherwise the original state is restored and an error returned.
  • ApplyUpdate mutates Steps[i] in place; properties the map does not name keep their values. ApplyAdd constructs a blank POCO through the registry factory, applies params the same way, and no longer inserts when a param errors. SynthesizeHrParams is deleted.
  • ShowCustomDialogStep's InputFields slot gains HrLabel = "Inputs" so label-addressed lookups resolve to the token form its parser recognizes.

Catalog audit

A probe swept all 438 (step, display slot) pairs in the catalog, attempting to set each param through Apply with kind-appropriate values (On/Off for booleans, declared enum values, calc text, and a wire-element XML fragment for structured slots), verifying against the emitted XML. On the old path, 68 of the 438 were accepted and silently dropped or mangled; none returned an error.

With this PR:

outcome pairs
apply correctly 423
rejected with an actionable error 14
quiet edge case 1
The 14 rejected pairs and the 1 quiet case

Real gap — wire lives nested inside a wrapper element the fragment graft cannot address:

  • Configure Prompt Template — TemplateName, SQLPrompt, NaturalLanguagePrompt, FindRequestPrompt, RAGPrompt

Internal wire-alias names — duplicates of params that work under their friendly name, or state with no addressable display form:

  • Dial Phone — UseDialPreferencesValue
  • Go to Layout / Go to Related Record — LayoutDestination (the target is settable via Layout)
  • Go to Record/Request/Page — ExitWire, LocationCalcWire
  • Perform Script on Server with Callback — ScriptWire, CalculatedWire

Audit artifacts — the params behave correctly; the probe's synthetic values can't show it:

  • Create PDF — CreatePDFFile accepts real fragments; the probe's placeholder fragment normalizes to the step's defaults, so the diff registers as unchanged
  • Send DDE Execute — ContentType correctly rejects a fragment aimed at a typed enum whose only valid value is the default

Quiet case: Go to Portal Row's CalculationWire write lands on the property but only emits once RowPageLocation is ByCalculation.

Test plan

  • 29 FmScriptApplyTests cover in-place instance identity, preservation of unmentioned params and XML-only state, loud errors (unknown param, invalid boolean, null value, unaddressable slot, wrong fragment root, malformed fragment), Perform Script targets and parameter, Show Custom Dialog buttons/inputs, Go to Layout targets, first-match parser ordering, idempotent re-application, and XML-fragment set/replace for SortList and Query
  • dotnet test SharpFM.sln — 2343 passed, including the display round-trip contract suites

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

Test Results

✔️ Tests 2343 / 2343 - passed in 15.6s
✔️ Coverage 80.31% - passed with 70% threshold
📏 11693 / 13732 lines covered 🌿 3676 / 5406 branches covered
🔍 click here for more details

✏️ updated for commit b5f8150

@fuzzzerd
fuzzzerd merged commit 8387f4e into master Jul 17, 2026
6 checks passed
@fuzzzerd
fuzzzerd deleted the fuzzz/apply-params-in-place branch July 17, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant