Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
66f20d0
feat(scripting): add typed step registry with IStepFactory contract
fuzzzerd Apr 21, 2026
85b1bdc
feat(scripting): migrate Beep, Set Error Capture, If to IStepFactory …
fuzzzerd Apr 21, 2026
98bebf5
refactor(editor): rewire completion provider onto StepRegistry
fuzzzerd Apr 21, 2026
f59bcde
docs: add Advanced Scripting Syntax reference and POCO sweep-phase plan
fuzzzerd Apr 21, 2026
8e9a273
feat(scripting): migrate 42 zero-param steps to IStepFactory POCOs
fuzzzerd Apr 21, 2026
7ad0750
feat(scripting): migrate 21 single-param boolean/enum steps to IStepF…
fuzzzerd Apr 21, 2026
5e65e3b
feat(scripting): migrate 5 multi-param Tier B steps to IStepFactory P…
fuzzzerd Apr 21, 2026
a75fbbb
feat(scripting): migrate Commit Records/Requests and Convert File
fuzzzerd Apr 21, 2026
6a67259
chore(tests): delete transitional-machinery tests slated for end-of-s…
fuzzzerd Apr 21, 2026
d67e5f2
feat(scripting): migrate Tier C text-bearing steps
fuzzzerd Apr 21, 2026
d6bc0c4
feat(scripting): migrate Tier D Bucket A simple calc/namedCalc steps
fuzzzerd Apr 21, 2026
39e5be9
feat(scripting): migrate 13 field-bearing Tier D steps to IStepFactor…
fuzzzerd Apr 21, 2026
be31edc
feat(scripting): migrate control-flow siblings to IStepFactory
fuzzzerd Apr 21, 2026
165d4d5
feat(scripting): migrate 9 file/table/menuset steps to IStepFactory P…
fuzzzerd Apr 21, 2026
cc8db29
feat(scripting): migrate 6 field/selection/AI-log steps to IStepFacto…
fuzzzerd Apr 21, 2026
0027477
feat(scripting): migrate 5 insert/read/write data steps to IStepFacto…
fuzzzerd Apr 21, 2026
b63f365
feat(scripting): migrate Execute FileMaker Data API, Export Field Con…
fuzzzerd Apr 21, 2026
599d968
feat(scripting): migrate Insert Embedding and Insert Embedding in Fou…
fuzzzerd Apr 21, 2026
a48f31f
feat(scripting): migrate 4 complex-attribute steps to IStepFactory POCOs
fuzzzerd Apr 21, 2026
af7393f
refactor(scripting): rewrap pre-existing POCOs onto IStepFactory
fuzzzerd Apr 21, 2026
72f337d
feat(scripting): migrate 4 find-request-bearing steps to IStepFactory…
fuzzzerd Apr 21, 2026
7603ceb
feat(scripting): migrate Perform Find/Replace to IStepFactory POCO
fuzzzerd Apr 21, 2026
79543d1
feat(scripting): migrate Perform Script on Server and its Callback va…
fuzzzerd Apr 21, 2026
bc85c48
feat(scripting): migrate the Go-To navigation family to IStepFactory …
fuzzzerd Apr 21, 2026
9231277
feat(scripting): migrate Print, Print Setup, Sort Records to IStepFac…
fuzzzerd Apr 21, 2026
4e5da20
feat(scripting): migrate Save Records as PDF, Excel, JSONL to IStepFa…
fuzzzerd Apr 21, 2026
be4c348
feat(scripting): migrate Import Records and Export Records to IStepFa…
fuzzzerd Apr 21, 2026
18b1501
feat(scripting): migrate 11 AI/ML steps to IStepFactory POCOs with St…
fuzzzerd Apr 21, 2026
454bf52
feat(scripting): migrate Insert PDF/Picture, Insert from Device, Perf…
fuzzzerd Apr 21, 2026
bdf99ea
test(completion): flip pilot-era regression guards to sweep-complete …
fuzzzerd Apr 21, 2026
5e4ca20
docs(scripting): tidy doc comments across migrated step POCOs
fuzzzerd Apr 21, 2026
8b10144
fix(validator): don't flag field refs as invalid enum values
fuzzzerd Apr 21, 2026
a1a1639
refactor(scripting): consolidate Go to Record POCO; drop empty RawSte…
fuzzzerd Apr 21, 2026
4498e97
docs(plans): mark the POCO sweep status as completed
fuzzzerd Apr 21, 2026
5ee0bf4
refactor(scripting): remove catalog infrastructure now that every ste…
fuzzzerd Apr 21, 2026
69fe485
fix(validator): don't flag flag-style HR tokens; round-trip Insert fr…
fuzzzerd Apr 21, 2026
b61593f
fix(insert-from-url): correct the NoInteract <-> WithDialog inversion
fuzzzerd Apr 21, 2026
ba54dd1
fix(scripting): normalize NoInteract <-> WithDialog semantics across …
fuzzzerd Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
186 changes: 186 additions & 0 deletions docs/advanced-filemaker-scripting-syntax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Advanced FileMaker Scripting Syntax

Status: authoritative reference for all script-step POCOs.

## Purpose

SharpFM renders FileMaker script XML as editable display text. Some XML
state doesn't naturally appear in FileMaker Pro's own display (object
IDs, flags, embedded newlines, etc.). To round-trip that state faithfully
through display-text editing, SharpFM extends FM Pro's display grammar
with a small set of named conventions — collectively, **Advanced
FileMaker Scripting Syntax**.

The extensions are safe because **FileMaker Pro never consumes SharpFM's
display text**. FM Pro reads only the binary `Mac-XMSS` clipboard
payload; `POCO.ToXml()` always emits pure FM Pro XML regardless of
whatever extensions are present in the display. The display text is a
SharpFM-internal surface for user editing, and our display parser only
ever reads our own extended output.

## Core invariant

> `ToDisplayLine()` and `FromDisplayParams()` together are lossless for
> every piece of state the POCO carries. XML state that is dropped is
> dropped because it carries no information, not because the display
> can't express it.

Every POCO's docstring contains a **zero-loss audit** (see below) that
enumerates XML state explicitly and marks each item as either rendered
by FM Pro natively, covered by an advanced-syntax extension, or
intentionally dropped with a written rationale.

## The three extension forms

Pick the form that matches the shape of the hidden state:

| Shape of state | Syntax | Example | Used for |
|---|---|---|---|
| Id annotation on a named reference | `(#id)` suffix after the name | `"Find Customer" (#7)` | Script, Layout, TableOccurrence, Field id round-trip |
| Boolean / enum flag already in FM Pro's grammar | word token inline, matching FM Pro's wording | `Exit after last: On` | Flags FM Pro itself renders; we mirror its wording |
| Bulk invisible state (multi-slot / structured) | trailing `; Kind: [...]` block, word-token values | `; Buttons: ["OK" commit; "Cancel" nocommit]` | Button configurations, input-field metadata, any future bulk state |

### Form 1 — `(#id)` suffix

Appended to a quoted or `Table::Field` name:

- `FieldRef` emits `People::FirstName (#7)` when an id is known; plain
`People::FirstName` otherwise.
- `PerformScriptStep` emits `"Find Customer" (#42)`.
- `GoToLayoutStep` emits `"Invoices Detail" (#11)`.

Omitted when the id is zero or unknown — `(#0)` would be visual noise
for unresolved references.

### Form 2 — inline word tokens

Parsed at a specific named prefix, matching FM Pro's own rendering of
the same flag:

- `Exit after last: On` on found-set iterators.
- `With dialog: Off` on steps that can suppress their confirmation UI.
- `Restore: On|Off` — **reserved**. Not currently emitted by any POCO;
see "What to drop vs. surface" below for the rationale.

### Form 3 — trailing `; Kind: [...]` blocks

Bulk or structured state that doesn't reduce to a single flag:

- `; Buttons: ["OK" commit; "Cancel" nocommit; "" nocommit]` — used by
Show Custom Dialog for its button configuration.
- Future bulk state (Input Field specs, etc.) takes this form.

## Parsing precedence

Named-prefix inline tokens (Form 2) are parsed before trailing
`Kind: [...]` blocks (Form 3). A display line like

```
Go to Record/Request/Page [ Next ; Exit after last: On ; Buttons: [...] ]
```

is tokenized as:
1. Positional `Next` (Form 2 equivalent — fixed-position enum).
2. Named inline `Exit after last: On` (Form 2).
3. Named block `Buttons: [...]` (Form 3).

Form 1 (`(#id)`) is applied inside each name-bearing token — e.g. inside
the bracketed name-and-id of a named ref — and does not collide with
Form 2 or Form 3 separators.

## Zero-loss audit requirement

Every POCO author must complete this audit in the class XML doc
comment. Template:

```
/// <summary>
/// Zero-loss audit for StepName:
/// <list type="bullet">
/// <item>&lt;Step&gt; attributes (enable/id/name) — round-tripped.</item>
/// <item>&lt;Calculation&gt; CDATA — round-tripped via Calculation.</item>
/// <item>&lt;SomeElement state="..."/&gt; — Form 2 token "Some: On|Off".</item>
/// <item>&lt;Dropped/&gt; — intentionally dropped; rationale: ...</item>
/// </list>
/// </summary>
```

Items fall into exactly one of these buckets:

1. **Rendered natively by FM Pro** — FM Pro's display grammar already
covers it; SharpFM mirrors the wording.
2. **Covered by an extension form** — one of the three above.
3. **Intentionally dropped** — rationale required. See next section for
how to judge.

Omitting the audit is a review blocker.

## What to drop vs. surface

Hidden state is surfaced only when a user could meaningfully change it.
Some state is structurally present in XML but semantically fixed — FM
Pro never alters it, never emits it in clipboard output, and no user
workflow produces a different value. Round-tripping such state adds
visual noise for zero information.

### Canonical drop: `<Restore state="False"/>` on `If`

Upstream `agentic-fm` snippets include the element; FM Pro's own
clipboard output never does; no FM Pro user interaction produces
`state="True"`. `IfStep` drops it on both read and write. The audit
entry documents the drop:

> &lt;Restore state="False"/&gt; — intentionally dropped. FM Pro never
> changes the value and never emits the element in clipboard output; it
> carries no information worth round-tripping.

### Canonical surface: field `id` via `(#id)` suffix

`<Field table="T" id="12" name="F"/>` is a real identity — the id
selects which field is referenced, and two fields named `F` in different
tables are not interchangeable. Dropping the id would change semantics.
`FieldRef` always emits `(#12)` when an id is available.

### The heuristic

- If two valid FM Pro script states would be visually identical under
the display grammar without the extension, **surface** the state.
- If the state has a fixed value that no user can change, **drop** it.
- If you're not sure which, **surface**. Reversing a surface → drop
later is non-breaking; reversing a drop → surface may break tests
users wrote against the earlier display.

## Adjacent convention: `//` disabled-step prefix

Disabled steps are prefixed with `//` in display text:

```
// Set Error Capture [ On ]
```

Parsing strips the `//` and sets `ScriptStep.Enabled = false`. This is
a document-level convention (applied to any step line) rather than a
per-step extension. Covered here for completeness.

## Implementation touch points

- `FieldRef.ToDisplayString` / `FieldRef.FromDisplayToken`
(`src/SharpFM.Model/Scripting/Values/FieldRef.cs`) — Form 1 reference
implementation.
- `CommentStep.ReturnGlyph`
(`src/SharpFM.Model/Scripting/Steps/CommentStep.cs`) — the `⏎`
(U+23CE) glyph for single-line rendering of multi-line comment text.
An idiom adjacent to the three forms but specific to Comment.
- `ScriptLineParser.ParseLine`
(`src/SharpFM.Model/Scripting/ScriptLineParser.cs`) — disabled-step
prefix and bracket tokenization.
- `PerformScriptStep.FromDisplayParams`,
`GoToLayoutStep.FromDisplayParams` — Form 1 regex parsers for named
refs with `(#id)` suffixes.

## Change log

- **2026-04** — Extracted from `docs/step-definitions.md:44-69` into
its own document as part of the POCO big-bang migration. Rationale
section ("what to drop vs. surface") added with `Restore` on `If` as
the canonical drop example.
Loading
Loading