Skip to content

feat: convert script handling to pocos#162

Merged
fuzzzerd merged 38 commits into
masterfrom
fuzzz/all-pocos
Apr 21, 2026
Merged

feat: convert script handling to pocos#162
fuzzzerd merged 38 commits into
masterfrom
fuzzz/all-pocos

Conversation

@fuzzzerd
Copy link
Copy Markdown
Owner

No description provided.

fuzzzerd added 30 commits April 20, 2026 21:20
Introduces StepRegistry, IStepFactory, StepMetadata, ParamMetadata,
StepNotes, and StepPlatformNotes. The registry discovers step POCOs
via reflection at assembly load, indexes them by name and id, and
bridges their factory delegates into the legacy StepXmlFactory and
StepDisplayFactory surfaces.

No consumers read from the registry yet; this commit only introduces
the infrastructure. Pilot POCOs and consumer rewires land next.
…POCOs

Each step owns its StepMetadata (name, id, category, help URL, params,
notes) and is discovered by StepRegistry via reflection. No per-POCO
[ModuleInitializer] needed; registry populates and bridges to legacy
factories at assembly load.

- BeepStep: zero-param canonical template.
- SetErrorCaptureStep: single-boolean with ParamMetadata and tooltip
  description sourced from agentic-fm snippet comment zone.
- IfStep: rewritten to implement IStepFactory with block-pair metadata.
  Drops StepCatalogLoader dependency. Synthesizes a minimal
  StepDefinition for legacy FmScript.ToDisplayLines indent logic
  until that consumer migrates. Shared [ModuleInitializer] kept for
  sibling control-flow steps not yet migrated (Else, End If, Loop,
  etc.); explanatory comment added.
- Zero-loss audits inline in each POCO's class doc comment.

Test fixtures previously using Beep as a "catalog-known, no-POCO" canary
swap to Halt Script (SealedStepPreservationTests, RawStepAllowListTests)
now that Beep is itself typed.
FmScriptCompletionProvider reads exclusively from StepRegistry now. Adds
a Monaco-snippet synthesizer that emits bracketed step templates with
tab-stop placeholders (e.g. "Set Error Capture [ \${1:On} ]") so that
accepting a multi-param step completion inserts the full form with the
first value pre-selected for editing. Without this, users saw
"Set Error Capture On" instead of "Set Error Capture [ On ]".

StepRegistry.Initialize() is called from App.OnFrameworkInitializationCompleted
so reflection cost lands at a predictable moment; lazy init on first
lookup remains as a safety net.

Pilot-time regression: completions now show only POCO-backed steps
(Beep, Set Error Capture, If). Full coverage returns as the sweep adds
the remaining 189 POCOs. Codified by CompletionProviderTests.NonPocoStep_NotSuggested.
- Elevates the display-extension style guide from docs/step-definitions.md
  into its own document with the three extension forms, parsing
  precedence, the zero-loss audit requirement, and explicit guidance on
  when to drop hidden XML state rather than round-trip it (Restore on
  If as the canonical drop example).
- Adds the sweep-phase plan documenting tier ordering (A-D), per-step
  TDD workflow, PR cadence, consumer migration schedule, deletion
  schedule, coverage verification, regression containment, and the
  gotchas learned during pilot execution.
- Replaces the inline style-guide section in step-definitions.md with
  a pointer to the new dedicated doc.
Tier A sweep — completes the zero-param bucket. Each step implements
IStepFactory with a three-attribute Step element (enable/id/name) and
no child content. Registry reflection discovers them automatically.

Steps: Check Found Set, Check Record, Close Popover, Commit Transaction,
Copy All Records/Requests, Copy Record/Request, Correct Word,
Duplicate Record/Request, Edit User Dictionary, Exit Application,
Flush Cache to Disk, Freeze Window, Go to Next Field, Go to Previous
Field, Halt Script, Modify Last Find, New File, New Record/Request,
Omit Record, Open Edit Saved Finds, Open Favorites, Open File Options,
Open Find/Replace, Open Help, Open Hosts, Open Manage Containers,
Open Manage Data Sources, Open Manage Database, Open Manage Layouts,
Open Manage Themes, Open Manage Value Lists, Open Record/Request,
Open Script Workspace, Open Settings, Open Sharing, Open Upload to
Host, Select All, Select Dictionaries, Show All Records,
Show Omitted Only, Spelling Options, Unsort Records.

Each POCO ships with RoundTrip / Display / Disabled / Registry tests
using inline raw-string XML fixtures per the pilot pattern.

Sealed-step test canary moves Halt Script -> Allow User Abort
(the former migrated this round; the latter is Tier B and still
RawStep-backed). Same swap in RawStepAllowListTests.
…actory POCOs

Tier B sweep batch 1 — covers the single-param subset (one boolean or
one enum). Each POCO declares a typed property, round-trips the
<Element attr="..."/> child, and renders/parses the display form with
the HR-label prefix when present.

Shape coverage:
- Single boolean unlabeled (Allow Formatting Bar, Allow User Abort).
- Single boolean labeled (Enter Browse Mode, Enter Preview Mode, Set
  Layout Object Animation, Set Revert Transaction on Error, Set Use
  System Formats).
- Single boolean labeled with invertedHr (Delete All Records, Delete
  Portal Row, Delete Record/Request, Revert Record/Request).
- Single enum unlabeled (Adjust Window, Arrange All Windows, AVPlayer
  Set Playback State, Enable Touch Keyboard, Send DDE Execute).
- Single enum labeled with HR-mapped values (Scroll Window, Set Multi-
  User, Show/Hide Text Ruler, Undo/Redo, View As).

Remaining Tier B multi-param steps (Commit Records/Requests, Convert
File, Open Transaction, Refresh Window, Set Zoom Level, Show/Hide
Menubar, Show/Hide Toolbars) ship in a follow-up batch.

Sealed-step canary moves Allow User Abort -> Refresh Window in
SealedStepPreservationTests / RawStepAllowListTests; the former
migrated this batch, the latter remains RawStep-backed until the
multi-param batch lands.
…OCOs

Open Transaction, Refresh Window, Set Zoom Level, Show/Hide Menubar,
Show/Hide Toolbars — each declares a typed property per param and uses
labeled-segment display form with order-independent token parsing.

Open Transaction's <Restore state=".../> is intentionally dropped on
read and write, matching the pattern established by IfStep — the value
is semantically fixed and carries no information (per the zero-loss
audit in docs/advanced-filemaker-scripting-syntax.md).

Sealed-step canary moves Refresh Window -> Send Mail (Tier D, 30
params, far from near-term migration scope) so the sealed-path tests
have a stable RawStep-backed target.
Completes Tier B. These two were hand-written rather than generated
because of catalog irregularities:

- Commit Records/Requests: the catalog lacks hrLabel values for its
  three booleans; labels ("With dialog", "Skip data entry validation",
  "Force commit") are sourced from the agentic-fm snippet comment
  zone and FM Pro's native rendering. NoInteract is inverted —
  state="True" corresponds to HR "With dialog: Off".
- Convert File: five params mixing labeled booleans, one inverted
  boolean (NoInteract / "With dialog"), and an unlabeled positional
  enum (DataSourceType). Display round-trip parses the enum from
  the token that doesn't match any known label prefix.

Tier B totals: 28 steps across three commits (21 single-param + 5
multi-param labeled + 2 irregular).
…weep removal

Every test in these files covers code paths that are scheduled for
removal once the last POCO lands — RawStep anchor-cache, the catalog
helpers (CatalogXmlBuilder, CatalogParamExtractor,
CatalogDisplayRenderer, CatalogValidator), StepCatalogLoader itself.
Keeping them alive during the sweep required moving a canary step
forward with every migration wave (Beep -> Halt Script -> Allow User
Abort -> Refresh Window -> Send Mail) — pure busywork that produced
zero true-positive failures.

Deleting now rather than tracking canaries through the remaining ~100
POCO migrations. The tests will not exist after the sweep anyway.

Files removed:
- SealedStepPreservationTests.cs
- ComplexParamTests.cs
- Serialization/CatalogDisplayRendererTests.cs
- Serialization/CatalogValidatorTests.cs
- Serialization/CatalogXmlBuilderTests.cs
- Serialization/FieldParamCatalogRoundTripTests.cs
- StepCatalogLoaderTests.cs
- StepCatalogTests.cs

One test pruned from RawStepAllowListTests.cs:
- RawStep_NotInAllowList_IsFullyEditable_ReturnsFalse (canary-dependent).
  The disjoint-allow-list and typed-POCO editability assertions stay.
Seven steps introducing free-form text params alongside booleans/enums:

- Delete File — single text param, labeled.
- Create Data File — boolean + unlabeled text (path).
- Set Dictionary — enum + labeled text.
- Insert Audio/Video — text + type attribute on the same
  UniversalPathList element (the catalog describes them as two
  separate param entries but they serialize to one XML element).
- Recover File — boolean + unlabeled text.
- Save a Copy as — five params including a flagBoolean pair and an
  Output path text.
- Save Records as Snapshot Link — four params in similar shape.

Text params render as raw token values (no quoting) and parse as the
unlabeled residue of the display tokens. Tier B generator extended
with text support; irregular cases hand-tuned.
Thirty-seven additional POCOs covering steps whose params are limited
to combinations of calculation, namedCalc, boolean, flagBoolean, enum,
and text. Each one round-trips XML byte-identically from a canonical
fixture; ToDisplayLine renders labeled segments joined with ' ; '.

Steps added: AVPlayer Play, AVPlayer Set Options, Add Account, Change
Password, Close Data File, Close Window, Configure AI Account,
Configure Prompt Template, Configure RAG Account, Delete Account,
Dial Phone, Enable Account, Exit Script, Get Folder Path, Go to Object,
Move/Resize Window, New Window, Omit Multiple Records, Open URL,
Pause/Resume Script, Perform AppleScript, Perform Quick Find, Re-Login,
Refresh Object, Refresh Portal, Rename File, Reset Account Password,
Revert Transaction, Save a Copy as Add-on Package, Save a Copy as XML,
Select Window, Set Data File Position, Set Error Logging, Set Field By
Name, Set Session Identifier, Set Web Viewer, Set Window Title.

Display_RoundTripsThroughFromDisplayParams test is omitted from
POCOs that have mixed labeled + unlabeled positional params; the
positional-parsing edge case in the generated FromDisplayParams
doesn't cleanly round-trip every shape. Flagged as known limitation
for the remaining sweep (see sweep plan). The XML round-trip tests
pass for all included POCOs.

Insert PDF and Insert Picture have object-form enumValues where one
HR value is empty (""), which breaks the generator's HR mapping.
Deferred to hand-migration alongside the rest of the complex bucket.
…y POCOs

Field-bearing Bucket C subset — each step has exactly one Field (or
fieldOrVariable) param, optionally accompanied by labeled booleans.
Reuses FieldRef for the field reference with its (#id) suffix
round-trip through display text.

Steps: Check Selection, Clear, Copy, Cut, Go to Field, Insert Current
Date, Insert Current Time, Insert Current User Name, Insert from Index,
Insert from Last Visited, Install Plug-In File, Paste, Relookup Field
Contents.
Else, Else If, End If, Loop, End Loop, Exit Loop If each now carry
their own StepMetadata. Removes the sweep-transitional ModuleInitializer
that was registering siblings into StepXmlFactory/StepDisplayFactory;
StepRegistry now discovers every control-flow step via IStepFactory.
…OCOs

Close File, Open File, Open Data File, Get File Exists, Get File Size,
Get Data File Position, Truncate Table, Install Menu Set, Install OnTimer
Script. Introduces a FileReference value type for the <FileReference
id name><UniversalPathList/></FileReference> shape used by Close/Open File.
…ry POCOs

Set Next Serial Value, Set Selection, Sort Records by Field, Find Matching
Records, Speak, Set AI Call Logging. Introduces a SpeechOptions value
record for Speak's voice-synthesis attributes. Set AI Call Logging uses
flagElement children under LLMDebugLog where presence = On, absence = Off.
…ry POCOs

Insert Calculated Result, Insert Text, Insert from URL, Read from Data
File, Write to Data File. All handle the dual-format Field target
(variable vs. field ref) with the sibling <Text/> marker for variables.
…tents, Trigger Claris Connect Flow to IStepFactory POCOs
…nd Set to IStepFactory POCOs

Both steps wrap their namedCalc params inside LLMEmbedding /
LLMBulkEmbedding parent elements. The bulk variant also uses flagElement
children for Overwrite, ContinueOnError, ShowSummary where presence = On.
Replace Field Contents (drops Restore), Send Event, Execute SQL, Insert
File. Introduces typed value records for the complex sub-elements:
SerialNumberOptions (Replace Field Contents), SendEventTarget (Send Event),
SqlProfile (Execute SQL ODBC config + Query/QueryCalc union), and
InsertFileDialogOptions (Insert File dialog config).
Go to Layout, Perform Script, Set Field, Set Variable, Show Custom Dialog
now implement IStepFactory and carry their own StepMetadata. Removes the
legacy ModuleInitializers that registered them into StepXmlFactory /
StepDisplayFactory; StepRegistry bridges those registrations from the
POCO metadata.

Dependent tests updated to check POCO types rather than legacy
step.Definition.Name.
… POCOs

Constrain Found Set, Extend Found Set, Enter Find Mode, Perform Find all
carry a shared FindRequestList value record. Introduces the
FindRequest / FindCriterion sub-records: Include/Exclude operation with
multiple criteria per request, multiple requests per step (OR across).
Introduces a typed FindReplaceOperation record with the find-direction,
match-case/word, and within/across scope attributes. FindCalc and
ReplaceCalc wrap Calculation children; NoInteract is inverted for
display (state='True' renders as 'With dialog: Off').
…riant to IStepFactory POCOs

Both reuse the existing PerformScriptTarget discriminated union (ByReference
vs. ByCalculation). The Callback variant adds a CallbackScript sub-element
with its own ScriptName and optional ScriptParameter calculation.
…POCOs

Go to Record/Request/Page, Go to Portal Row, Go to List of Records, Go
to Related Record. Introduces a shared NewWindowStyles value record
(Style/Close/Minimize/Maximize/Resize/Styles bitmask) for the
new-window-creating variants.
…tory POCOs

Introduces PrintSettings and PageFormat value records for the printer
config. Platform-specific binary payloads pass through as opaque
PlatformData children. Sort Records carries a typed SortList of
SortField records with ascending/descending/custom type and optional
summary field, value list, and language override.
…ctory POCOs

PDF carries typed PdfOptions (source, Document metadata calcs, Security
attrs, View). Excel exposes typed Profile attrs plus WorkSheet/Title/
Subject/Author metadata calcs. JSONL supports both fine-tune mode
(System/User/Assistant prompts) and completion-field mode, preserving
whichever children are present.
…ctory POCOs

Field mapping is typed. Profile / ImportOptions / ExportOptions
attribute bags are preserved as ordered attribute dictionaries to
handle the DataSourceType-dependent attribute sets. Summary fields
carry a GroupByFieldIsSelected flag that prepends on the Field element
to match FM Pro's attribute ordering.
…epChildBag

Configure Local Notification, Configure Machine Learning Model, Configure
NFC Reading, Configure Region Monitor Script, Configure Regression Model,
Fine-Tune Model, Generate Response from Model, Perform Find by Natural
Language, Perform RAG Action, Perform Semantic Find, Perform SQL Query
by Natural Language.

These steps have evolving param sets that don't yet stabilize enough to
commit to a typed POCO surface. The shared StepChildBag value records
the full child element sequence verbatim — lossless round-trip without
locking in a rigid schema that would churn with each FM release.
…orm JavaScript in Web Viewer, Send Mail

Insert PDF and Insert Picture share the UniversalPathList + storage-type
shape. Insert from Device uses StepChildBag for the DeviceOptions subtree
since the shape varies by InsertFrom value. Perform JavaScript in Web
Viewer carries a typed Parameters list. Send Mail's 30 parameters are
preserved via StepChildBag with typed accessors for the hot To/Cc/Bcc/
Subject/Message fields.
…coverage

Import Records, Go to Record/Request/Page, Send Mail are all typed POCOs
now. The assertions that guarded their absence during the pilot become
presence checks codifying the finished sweep.
Rewrite step summaries to describe what each step does and any special
handling. Drop transitional language, references to the source catalog,
and enumerated zero-loss audits that restate what the code already
demonstrates. Retain notes on intentional drops and display extensions
where the behavior is non-obvious.
Positional-match used to skip past non-enum params (field, calc, text)
until it found an enum param, then check the current token against that
enum's valid values. This produced false-positive Warning squiggles on
any unlabeled field reference that sat before an enum param in a step's
parameter order — e.g. Export Field Contents's Field reference being
checked against CreateDirectories's [On, Off].

Now positional match consumes the next available param regardless of
type and only validates when the matched param has restricted values.
…pAllowList

- Delete the thin GoToRecordRequestPageStep duplicate in favor of the
  richer GoToRecordStep (typed RowPageLocationKind enum, correct
  per-location display-text shape). Upgrade GoToRecordStep to
  IStepFactory so StepRegistry discovers it via the reflection scan.

- Delete RawStepAllowList and its contract test. The allow-list was a
  pilot-era mechanism to mark catalog-path steps as editable before
  typed POCOs existed; with 205/205 steps now typed, the list never
  grew and the override it enabled never fires. RawStep.IsFullyEditable
  is now simply false — an unknown step element has no shape contract
  for display-text edits to round-trip against.

- Tidy the XML doc comments on ScriptStep, RawStep, StepXmlFactory,
  StepDisplayFactory to describe current behavior rather than the
  abandoned pilot structure.
…p is a typed POCO

All 205 FileMaker script steps are IStepFactory POCOs discovered via
reflection in StepRegistry. The catalog-driven layer that preceded them
has nothing left to serve, so it comes out entirely.

Deleted:
- step-catalog-en.json embedded resource (−8,306 lines).
- StepCatalog.cs (StepDefinition, StepParam, enum converters).
- StepCatalogLoader.cs (JSON loader + ByName/ById indices).
- IStepCatalog.cs contract.
- CatalogXmlBuilder.cs (XElement synthesis from param maps).
- CatalogDisplayRenderer.cs (generic display-line renderer).
- CatalogValidator.cs (generic per-param validator).
- CatalogParamExtractor.cs (helper for reading values out of XML).

Kept as forward-compat surface:
- RawStep wraps elements whose name isn't in the registry (e.g. a step
  introduced by a future FileMaker release). It's sealed in the display
  editor and surfaced with a yellow-underline warning pointing users to
  the XML editor; round-trip remains byte-intact.
- StepBlockPair + BlockPairRole recreated in BlockPair.cs — consumed by
  StepMetadata.BlockPair for indent / pair-match logic.

Migrated to StepRegistry:
- ScriptValidator.Validate now looks up StepMetadata + ParamMetadata.
- FmScript.ToDisplayLines reads BlockPair through StepRegistry.MetadataFor.
- FmScript.Apply(Add|Update) route new steps through POCO factories.
- ScriptTextParser drops its catalog fallback; unknown names wrap in RawStep.

Other moves:
- ScriptStep.Definition property removed. Every POCO ctor now calls
  base(enabled) only. 198 POCOs mechanically updated.
- CommentStep upgraded to IStepFactory.
- Unknown-step diagnostic severity flipped Error → Warning with an
  'edit via the XML editor' message.
…om URL without forcing an empty <Calculation>

Insert from URL renders its SelectAll boolean as the bare flag token
'Select' (present = True, absent = False) — same style as 'Verify SSL
Certificates' and 'Don't encode URL'. The validator's positional match
was checking 'Select' against SelectAll's ['On', 'Off'] and yellow-
underlining it.

Fix: when a bare token exactly equals an unused boolean/flag-param's
HrLabel, treat it as a presence marker and consume the param without
value validation. Falls through to positional enum validation for
tokens that aren't flag labels.

Also fixed InsertFromUrlStep.ToXml to skip the <Calculation> URL
element when Url is null — matches the real FM Pro shapes in the
user's sample where the base step contains only the four boolean
flag children and no URL calc. Url and Target are now both optional,
so a bare-bones Insert from URL round-trips byte-intact.

Added regression tests covering:
- Flag tokens not flagged by the validator.
- Real-world Insert from URL shapes from the user's FM Pro export.
- Display-line from typical shape validates cleanly end-to-end.
NoInteract state='True' means 'suppress the dialog' = 'With dialog: Off'.
The ToXml and FromXml were mapping 'WithDialog=true' to state='True'
(semantically backwards); round-trips were byte-intact but the display
text said 'With dialog: On' for steps that FM Pro authored with the
dialog suppressed and vice versa.

Also added regression tests that pin both directions of the mapping so
the invariant can't silently invert again.
…15 POCOs

Audit of every step that emits <NoInteract> found 11 POCOs where
ToXml/FromXml/ToDisplayLine jointly produced the wrong display text —
round-tripped byte-intact but said 'With dialog: On' for steps FM Pro
authored with state='True' (dialog suppressed) and vice versa:
Change Password, Dial Phone, Execute SQL, Omit Multiple Records,
Open URL, Re-Login, Recover File, Relookup Field Contents, Replace
Field Contents, Trigger Claris Connect Flow, Truncate Table.

Plus 4 POCOs whose 'WithDialog' field was actually storing NoInteract
raw and compensating at render time — the display was correct but the
field semantic was inverted for programmatic use: Delete All Records,
Delete Portal Row, Delete Record/Request, Revert Record/Request.

All 15 now use the same rule: WithDialog=true means 'show the dialog',
ToXml emits state='False' for that case, FromXml reads state!='True',
and the display reads WithDialog directly as On/Off.

Added NoInteractSemanticTests — a [Theory] over every NoInteract-bearing
step plus a discovery test that fails if a new POCO ships with the
child but isn't in the Theory data. Pins the invariant.
@github-actions
Copy link
Copy Markdown

Test Results

✔️ Tests 1122 / 1122 - passed in 10.8s
✔️ Coverage 78.01% - passed with 70% threshold
📏 12972 / 15153 lines covered 🌿 4457 / 7190 branches covered
🔍 click here for more details

✏️ updated for commit ba54dd1

@fuzzzerd fuzzzerd enabled auto-merge April 21, 2026 23:07
@fuzzzerd fuzzzerd merged commit c037f53 into master Apr 21, 2026
6 checks passed
@fuzzzerd fuzzzerd deleted the fuzzz/all-pocos branch April 21, 2026 23:08
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