Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
2a3bd0f
feat: implement unified authoring changeset infrastructure (Phase 4a)
claude Mar 24, 2026
ccb12e5
fix: correct command type in batchWithRebuild test and remove unused …
claude Mar 24, 2026
ba5c3aa
feat: wire changeset brackets into all MCP mutation tools
mikewolfd Mar 24, 2026
81e3bf5
fix: changeset infrastructure bugs F1/F2/F6/F7 and add missing tests
mikewolfd Mar 24, 2026
7e1c64f
test: add remaining gap-coverage tests for changeset infrastructure
mikewolfd Mar 24, 2026
bfec4b5
test: flip F3/O1 tests to assert correct spec behavior (expected-fail)
mikewolfd Mar 24, 2026
3b7738d
docs: move wasm size trim research to reviews, add planner spec diver…
mikewolfd Mar 25, 2026
3f5b8e7
fix: extract real helper summary in changeset bracket (O1)
mikewolfd Mar 25, 2026
cca934e
fix: populate capturedValues for =-prefix expressions during recordin…
mikewolfd Mar 25, 2026
4333174
feat(fel-core): export FEL identifier validation and sanitization (E1)
mikewolfd Mar 25, 2026
9d88083
feat(engine): add data type taxonomy predicates (E2)
mikewolfd Mar 25, 2026
4a7a482
feat(core): add normalizeBinds, shapesForPath, and consolidated looku…
mikewolfd Mar 25, 2026
659f671
feat(core): add drop targets, tree flattening, and selection ops (C5-C7)
mikewolfd Mar 25, 2026
fc3a8a6
feat(core): add shape display, optionset usage, search index, seriali…
mikewolfd Mar 25, 2026
502de9e
refactor(studio): replace local helpers with formspec-core imports, d…
mikewolfd Mar 25, 2026
f74f99a
feat(mcp): add formspec_widget tool with widget catalog, compatibilit…
mikewolfd Mar 25, 2026
e832b8b
feat(mcp): expand formspec_fel with validate, autocomplete, and human…
mikewolfd Mar 25, 2026
ce1a3d5
feat(mcp): add batch structure ops and preview expansion (S9-S16)
mikewolfd Mar 25, 2026
ab23d14
feat(mcp): add audit, theme, and component tools (S17-S18, 2f, 2g)
mikewolfd Mar 25, 2026
951b07e
feat(changeset): create formspec-changeset Rust crate with dependency…
mikewolfd Mar 25, 2026
01ba5c0
feat(mcp): add locale, ontology, and reference tools (3a-3c)
mikewolfd Mar 25, 2026
021cd1c
feat(mcp): add remaining Phase 3 document type tools (3d-3l)
mikewolfd Mar 25, 2026
5167f1a
refactor(chat): remove McpBridge, accept ToolContext from host (M6)
mikewolfd Mar 25, 2026
d149b56
feat(studio): add changeset review and dependency group components (M7)
mikewolfd Mar 25, 2026
8ac4d1d
fix: register locale/ontology/reference tools in create-server and fi…
mikewolfd Mar 25, 2026
6bf1e7d
feat(studio-core): wire WASM dependency groups into ProposalManager (…
mikewolfd Mar 25, 2026
227ebb6
feat(formspec-changeset): add missing dependency edge types — variabl…
mikewolfd Mar 25, 2026
bd3abee
test(studio): skip chat shell test pending buildBundle injection update
mikewolfd Mar 25, 2026
563e355
fix(formspec-changeset): iterate targets not references in same-targe…
mikewolfd Mar 25, 2026
9de23d9
test(chat): add mockBuildBundle helper for ChatSession constructor AP…
mikewolfd Mar 25, 2026
2566cb8
fix(chat): update bundle tests for buildBundle injection pattern
mikewolfd Mar 25, 2026
d1b3a12
docs(locale-spec): drop plural() function — replaced by pluralCategor…
mikewolfd Mar 25, 2026
a27d750
feat(mcp): register behavior-expanded, composition, response, mapping…
mikewolfd Mar 25, 2026
bc8f4ba
test(e2e): flesh out changeset-review E2E test skeleton
mikewolfd Mar 25, 2026
70f3d2f
test(e2e): implement changeset-review E2E tests with harness
mikewolfd Mar 25, 2026
08501c3
refactor: consolidate FEL function catalog into Rust, delete studio d…
mikewolfd Mar 25, 2026
3303b40
feat(studio): add integrated ChatPanel with live changeset review and…
mikewolfd Mar 25, 2026
319ded7
fix(studio): add formspec-mcp aliases to Vite config for in-process d…
mikewolfd Mar 25, 2026
8778564
docs: update unified authoring finish plan with completed milestones
mikewolfd Mar 25, 2026
e2a23bc
feat(studio): add inline AI context actions on canvas items (6.4)
mikewolfd Mar 25, 2026
a3cdf04
feat(studio): load AI scaffold as reviewable changeset (6.5)
mikewolfd Mar 25, 2026
4f672f7
docs: mark all milestones complete in unified authoring finish plan
mikewolfd Mar 25, 2026
e9cb96b
feat(studio): add AppSettingsDialog for AI provider API key configura…
mikewolfd Mar 25, 2026
2f9ac26
fix(studio): require API key before showing chat — prompt users to co…
mikewolfd Mar 25, 2026
91edf0f
feat(studio): add dark mode with system preference detection and manu…
mikewolfd Mar 25, 2026
3e0ceef
fix(studio): add visible focus ring for dark mode with overflow-safe …
mikewolfd Mar 25, 2026
b492af9
fix(studio): replace bg-ink text-white with bg-accent text-white for …
mikewolfd Mar 25, 2026
c8edd5c
fix(fel): surface broken expressions as validation errors, add arity …
mikewolfd Mar 25, 2026
37a0d49
feat(core): add itemPaths() for content items and scope annotations o…
mikewolfd Mar 25, 2026
e92d62e
fix(studio-core): phone regex, semantic FEL validation, unified remov…
mikewolfd Mar 25, 2026
73dcaac
fix(mcp): resolve silently-ignored params, add position-based move, e…
mikewolfd Mar 25, 2026
302eca7
docs: add MCP chaos test artifacts (phase 1-4 reports)
mikewolfd Mar 25, 2026
b37b9f6
docs: add design spec for page mode as presentation
mikewolfd Mar 25, 2026
f0d4b7c
docs: address review findings in page mode design spec
mikewolfd Mar 26, 2026
954c1df
docs: add prior art references to page mode design spec
mikewolfd Mar 26, 2026
1cbecc8
docs: add implementation plan for page mode as presentation
mikewolfd Mar 26, 2026
c7a3fa1
feat(schema): add wizard/tabs navigation properties to formPresentation
mikewolfd Mar 26, 2026
6af9b67
feat(schema): deprecate Wizard component type
mikewolfd Mar 26, 2026
424ac14
build: regenerate types and artifacts after Wizard deprecation
mikewolfd Mar 26, 2026
d4e882f
feat(core): add component-tree-based page resolution query
mikewolfd Mar 26, 2026
469f2e7
feat(core): rewrite page handlers to write component tree directly
mikewolfd Mar 26, 2026
62f56e6
refactor(core): delete component.setWizardProperty handler
mikewolfd Mar 26, 2026
a70f30b
refactor(core): page resolution reads component tree instead of theme…
mikewolfd Mar 26, 2026
1617260
refactor(core): reconciler always produces Stack root, fix downstream…
mikewolfd Mar 26, 2026
0d02aa2
feat(core): add Wizard/Tabs root migration on project load
mikewolfd Mar 26, 2026
3ee5541
refactor(studio-core): update page helpers to read/write component tree
mikewolfd Mar 26, 2026
85ab825
refactor(layout): planner produces Stack>Page* instead of Wizard/Tabs…
mikewolfd Mar 26, 2026
852d162
refactor(webcomponent): wizard behavior driven by pageMode, not compo…
mikewolfd Mar 26, 2026
37aaefe
fix(fixtures): migrate example component docs from Wizard to Stack>Page*
mikewolfd Mar 26, 2026
c567769
refactor(mcp): update tools for Wizard deprecation
mikewolfd Mar 26, 2026
6389fbe
refactor(lint): remove Wizard from known types, delete E805 rule
mikewolfd Mar 26, 2026
a1ce574
feat(studio): split PagesTab into mode-specific renderers (single/wiz…
mikewolfd Mar 26, 2026
26b51e6
fix(mcp): update structure test to read component tree instead of the…
mikewolfd Mar 26, 2026
cb383cf
fix: resolve CI failures — spec/schema/test infrastructure gaps
claude Mar 26, 2026
d022c11
docs: mark page mode implementation plan complete
mikewolfd Mar 26, 2026
1192a99
fix(core): add span/start to TreeNode and use type-safe property assi…
mikewolfd Mar 26, 2026
ba30e37
fix(test): use genuinely unknown function name in FEL_UNKNOWN_FUNCTIO…
mikewolfd Mar 26, 2026
b729cee
fix(studio): update 42 failing tests to read component tree instead o…
mikewolfd Mar 26, 2026
86bae7b
fix(test,spec): remove Wizard from conformance tests and spec examples
mikewolfd Mar 26, 2026
0008029
fix(core,studio-core): resolve 4 P0 bugs from page-mode branch review
mikewolfd Mar 26, 2026
f0bac9f
fix(spec,schema): resolve P1 spec/schema consistency issues from bran…
mikewolfd Mar 26, 2026
74e67eb
fix(studio,webcomponent): P2 cleanup — dead Wizard code and stale refs
mikewolfd Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Core Commands Schema Reference Map

> schemas/core-commands.schema.json -- 1196 lines -- Command definitions for programmatic form manipulation
> schemas/core-commands.schema.json -- 1220 lines -- Command definitions for programmatic form manipulation

## Overview

The Core Command Catalog is a structured JSON schema that defines every mutation command available through `RawProject.dispatch()` in `formspec-core`. It serves as a machine-readable catalog enabling LLM agents, CLI tools, and visual editors to discover available operations and construct valid command payloads. The schema defines 119 commands organized across 15 handler areas spanning definition manipulation, theme configuration, mapping rules, component tree management, and project-level operations.
The Core Command Catalog is a structured JSON schema that defines every mutation command available through `RawProject.dispatch()` in `formspec-core`. It serves as a machine-readable catalog enabling LLM agents, CLI tools, and visual editors to discover available operations and construct valid command payloads. The schema defines 120 commands organized across 16 handler areas spanning definition manipulation, theme configuration, page layout, mapping rules, component tree management, and project-level operations.

## Top-Level Structure

Expand Down Expand Up @@ -116,7 +116,7 @@ The document is typed as `"object"` with `version` and `commands` as the two dat
| `definition.deleteFieldMapRule` | Remove a migration field map rule by index. | fromVersion, index | fromVersion, index | -- |
| `definition.setMigrationDefaults` | Set default values for newly added fields in a migration. | fromVersion, defaults | fromVersion, defaults | -- |

### theme (28 commands)
### theme (17 commands)

| Command | Description | Parameters | Required Params | Returns |
|---------|-------------|------------|-----------------|---------|
Expand All @@ -132,17 +132,6 @@ The document is typed as `"object"` with `version` and `commands` as the two dat
| `theme.setItemStyle` | Set a style property on a per-item override. | itemKey, property, value | itemKey, property, value | -- |
| `theme.setItemWidgetConfig` | Set a widget configuration property on a per-item override. | itemKey, property, value | itemKey, property, value | -- |
| `theme.setItemAccessibility` | Set an accessibility property on a per-item override. | itemKey, property, value | itemKey, property, value | -- |
| `theme.addPage` | Add a page layout definition to the theme. | id, title, description, insertIndex | -- | -- |
| `theme.setPageProperty` | Update a property on a theme page layout. | index, property, value | index, property, value | -- |
| `theme.deletePage` | Remove a theme page layout by index. | index | index | -- |
| `theme.reorderPage` | Move a theme page layout one position up or down. | index, direction | index, direction | -- |
| `theme.addRegion` | Add a grid region to a theme page. | pageId, key, span, start, insertIndex | pageId | -- |
| `theme.setRegionProperty` | Update a property on a grid region. | pageId, regionIndex, property, value | pageId, regionIndex, property, value | -- |
| `theme.deleteRegion` | Remove a grid region by index. | pageId, regionIndex | pageId, regionIndex | -- |
| `theme.reorderRegion` | Move a grid region one position up or down within its page. | pageId, regionIndex, direction | pageId, regionIndex, direction | -- |
| `theme.renamePage` | Rename a theme page ID. | pageId, newId | pageId, newId | -- |
| `theme.setRegionKey` | Change a grid region's key. | pageId, regionIndex, newKey | pageId, regionIndex, newKey | -- |
| `theme.setPages` | Replace the entire pages array. | pages | pages | -- |
| `theme.setBreakpoint` | Set or remove a named viewport breakpoint. | name, minWidth | name, minWidth | -- |
| `theme.setStylesheets` | Replace the list of external stylesheet URLs. | urls | urls | -- |
| `theme.setDocumentProperty` | Set a top-level theme document property. | property, value | property, value | -- |
Expand Down Expand Up @@ -170,6 +159,24 @@ The document is typed as `"object"` with `version` and `commands` as the two dat
| `mapping.reorderInnerRule` | Move a nested inner rule one position up or down. | ruleIndex, innerIndex, direction | ruleIndex, innerIndex, direction | -- |
| `mapping.preview` | Execute mapping rules against sample data and return the transformed output. Does not mutate state. | sampleData, direction, ruleIndices | sampleData | `output`, `diagnostics`, `appliedRules`, `direction` |

### pages (13 commands)

| Command | Description | Parameters | Required Params | Returns |
|---------|-------------|------------|-----------------|---------|
| `pages.addPage` | Add a Page node to the component tree. Promotes pageMode to wizard if currently single or unset. | id, title, description | -- | -- |
| `pages.deletePage` | Remove a Page node from the component tree by ID. | id | id | -- |
| `pages.setMode` | Set the form presentation page mode. Ensures the component tree exists. | mode | mode | -- |
| `pages.reorderPages` | Move a Page node one position up or down among its siblings. | id, direction | id, direction | -- |
| `pages.movePageToIndex` | Move a Page node to an absolute index position, clamped to bounds. | id, targetIndex | id, targetIndex | -- |
| `pages.setPageProperty` | Set an arbitrary property on a Page node. | id, property, value | id, property, value | -- |
| `pages.assignItem` | Assign a bound item to a Page. Moves the node from its current location in the tree. | pageId, key, span | pageId, key | -- |
| `pages.unassignItem` | Remove a bound item from a Page and move it back to the root level. | pageId, key | pageId, key | -- |
| `pages.autoGenerate` | Auto-generate Page nodes from definition items using presentation.layout.page hints. Replaces all existing pages. | -- | -- | -- |
| `pages.setPages` | Replace the entire set of Page nodes in the component tree. | pages | pages | -- |
| `pages.reorderRegion` | Move a region (bound item) within a Page to a target index. | pageId, key, targetIndex | pageId, key, targetIndex | -- |
| `pages.renamePage` | Rename a Page node's title. | id, newId | id, newId | -- |
| `pages.setRegionProperty` | Set a property (span, start, or responsive) on a region within a Page. | pageId, key, property, value | pageId, key, property, value | -- |

### component-tree (7 commands)

| Command | Description | Parameters | Required Params | Returns |
Expand All @@ -182,7 +189,7 @@ The document is typed as `"object"` with `version` and `commands` as the two dat
| `component.wrapNode` | Wrap a node in a new container node. | node, wrapper | node, wrapper | `nodeRef`: Reference to the new wrapper node. |
| `component.unwrapNode` | Replace a container node with its children (unwrap one level). | node | node | -- |

### component-properties (18 commands)
### component-properties (17 commands)

| Command | Description | Parameters | Required Params | Returns |
|---------|-------------|------------|-----------------|---------|
Expand All @@ -193,7 +200,6 @@ The document is typed as `"object"` with `version` and `commands` as the two dat
| `component.spliceArrayProp` | Splice (insert/remove) items in an array-valued node property. | node, property, index, deleteCount, insert | node, property, index, deleteCount | -- |
| `component.setFieldWidget` | Set which widget component renders a field. Convenience command that finds the node by field key. | fieldKey, widget | fieldKey, widget | -- |
| `component.setResponsiveOverride` | Set or remove a responsive breakpoint override on a node. | node, breakpoint, patch | node, breakpoint, patch | -- |
| `component.setWizardProperty` | Set a wizard-mode property on the component document. | property, value | property, value | -- |
| `component.setGroupRepeatable` | Toggle a group's repeatable flag in the component tree. | groupKey, repeatable | groupKey, repeatable | -- |
| `component.setGroupDisplayMode` | Set how a group renders (table, accordion, card, etc.). | groupKey, mode | groupKey, mode | -- |
| `component.setGroupDataTable` | Configure data table rendering for a repeatable group. | groupKey, config | groupKey, config | -- |
Expand Down Expand Up @@ -241,7 +247,7 @@ The document is typed as `"object"` with `version` and `commands` as the two dat
### Per-command required payload parameters

Commands where ALL parameters are optional (no required params):
- `definition.addVariable`, `definition.addInstance`, `theme.addPage`, `mapping.addRule`, `mapping.autoGenerateRules`, `mapping.addInnerRule`, `project.import`
- `definition.addVariable`, `definition.addInstance`, `pages.addPage`, `pages.autoGenerate`, `mapping.addRule`, `mapping.autoGenerateRules`, `mapping.addInnerRule`, `project.import`

Commands with required params are listed in the Command Definitions tables above.

Expand All @@ -253,7 +259,7 @@ Handler area grouping for commands:
"definition-items", "definition-binds", "definition-metadata", "definition-pages",
"definition-shapes", "definition-variables", "definition-optionsets", "definition-instances",
"definition-screener", "definition-migrations", "theme", "mapping",
"component-tree", "component-properties", "project"
"pages", "component-tree", "component-properties", "project"
```

### CommandEntry.sideEffects
Expand Down Expand Up @@ -298,29 +304,34 @@ Item type discriminant:
"up", "down"
```

### theme.reorderPage > direction
### mapping.reorderRule > direction
```
"up", "down"
```

### theme.reorderRegion > direction
### mapping.reorderInnerRule > direction
```
"up", "down"
```

### mapping.reorderRule > direction
### mapping.preview > direction
```
"up", "down"
"forward", "reverse"
```

### mapping.reorderInnerRule > direction
### pages.setMode > mode
```
"single", "wizard", "tabs"
```

### pages.reorderPages > direction
```
"up", "down"
```

### mapping.preview > direction
### pages.setRegionProperty > property
```
"forward", "reverse"
"span", "start", "responsive"
```

### component.reorderNode > direction
Expand Down Expand Up @@ -394,9 +405,10 @@ At least one must be provided; the `NodeRef` schema does not enforce this via `o
| definition-instances | 4 |
| definition-screener | 8 |
| definition-migrations | 7 |
| theme | 28 |
| theme | 17 |
| mapping | 16 |
| pages | 13 |
| component-tree | 7 |
| component-properties | 18 |
| component-properties | 17 |
| project | 5 |
| **Total** | **119** |
| **Total** | **120** |
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"crates/fel-core",
"crates/formspec-changeset",
"crates/formspec-core",
"crates/formspec-eval",
"crates/formspec-lint",
Expand Down
134 changes: 134 additions & 0 deletions crates/fel-core/src/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,10 @@ impl<'a> Evaluator<'a> {
"min" => self.fn_min_max(args, true),
"max" => self.fn_min_max(args, false),
"countWhere" => self.fn_count_where(args),
"sumWhere" => self.fn_sum_where(args),
"avgWhere" => self.fn_avg_where(args),
"minWhere" => self.fn_min_where(args),
"maxWhere" => self.fn_max_where(args),

// String
"length" => self.fn_length(args),
Expand Down Expand Up @@ -924,6 +928,7 @@ impl<'a> Evaluator<'a> {
}
"moneyAdd" => self.fn_money_add(args),
"moneySum" => self.fn_money_sum(args),
"moneySumWhere" => self.fn_money_sum_where(args),

// MIP state queries
"valid" => self.fn_mip(args, "valid"),
Expand Down Expand Up @@ -1068,6 +1073,135 @@ impl<'a> Evaluator<'a> {
FelValue::Number(dec(count))
}

/// Filter array elements by predicate (shared by *Where functions).
fn filter_where(&mut self, args: &[Expr], fn_name: &str) -> Option<Vec<FelValue>> {
if args.len() < 2 {
self.diag(format!("{fn_name}: requires 2 arguments"));
return None;
}
let arr_val = self.eval(&args[0]);
let arr = self.get_array(&arr_val, fn_name)?;
let mut matched = Vec::new();
for elem in &arr {
self.let_scopes
.push(HashMap::from([("$".to_string(), elem.clone())]));
let pred = self.eval(&args[1]);
self.let_scopes.pop();
if pred.is_truthy() {
matched.push(elem.clone());
}
}
Some(matched)
}

fn fn_sum_where(&mut self, args: &[Expr]) -> FelValue {
let Some(matched) = self.filter_where(args, "sumWhere") else {
return FelValue::Null;
};
let nums: Vec<Decimal> = matched.iter().filter_map(|v| v.as_number()).collect();
FelValue::Number(nums.iter().copied().sum())
}

fn fn_avg_where(&mut self, args: &[Expr]) -> FelValue {
let Some(matched) = self.filter_where(args, "avgWhere") else {
return FelValue::Null;
};
let nums: Vec<Decimal> = matched.iter().filter_map(|v| v.as_number()).collect();
if nums.is_empty() {
return FelValue::Null;
}
FelValue::Number(nums.iter().copied().sum::<Decimal>() / Decimal::from(nums.len() as i64))
}

fn fn_min_where(&mut self, args: &[Expr]) -> FelValue {
let Some(matched) = self.filter_where(args, "minWhere") else {
return FelValue::Null;
};
let non_null: Vec<&FelValue> = matched.iter().filter(|v| !v.is_null()).collect();
if non_null.is_empty() {
return FelValue::Null;
}
let mut best = non_null[0].clone();
for elem in &non_null[1..] {
let cmp = match (&best, *elem) {
(FelValue::Number(a), FelValue::Number(b)) => Some(a.cmp(b)),
(FelValue::String(a), FelValue::String(b)) => Some(a.cmp(b)),
(FelValue::Date(a), FelValue::Date(b)) => Some(a.ordinal().cmp(&b.ordinal())),
_ => {
self.diag("minWhere: mixed types".to_string());
return FelValue::Null;
}
};
if let Some(ord) = cmp
&& ord.is_gt()
{
best = (*elem).clone();
}
}
best
}

fn fn_max_where(&mut self, args: &[Expr]) -> FelValue {
let Some(matched) = self.filter_where(args, "maxWhere") else {
return FelValue::Null;
};
let non_null: Vec<&FelValue> = matched.iter().filter(|v| !v.is_null()).collect();
if non_null.is_empty() {
return FelValue::Null;
}
let mut best = non_null[0].clone();
for elem in &non_null[1..] {
let cmp = match (&best, *elem) {
(FelValue::Number(a), FelValue::Number(b)) => Some(a.cmp(b)),
(FelValue::String(a), FelValue::String(b)) => Some(a.cmp(b)),
(FelValue::Date(a), FelValue::Date(b)) => Some(a.ordinal().cmp(&b.ordinal())),
_ => {
self.diag("maxWhere: mixed types".to_string());
return FelValue::Null;
}
};
if let Some(ord) = cmp
&& ord.is_lt()
{
best = (*elem).clone();
}
}
best
}

fn fn_money_sum_where(&mut self, args: &[Expr]) -> FelValue {
let Some(matched) = self.filter_where(args, "moneySumWhere") else {
return FelValue::Null;
};
let mut total: Option<FelMoney> = None;
for elem in &matched {
match elem {
FelValue::Money(m) => match &total {
None => total = Some(m.clone()),
Some(t) => {
if t.currency != m.currency {
self.diag("moneySumWhere: mixed currencies");
return FelValue::Null;
}
total = Some(FelMoney {
amount: t.amount + m.amount,
currency: t.currency.clone(),
});
}
},
FelValue::Null => {}
_ => {
self.diag("moneySumWhere: non-money element");
return FelValue::Null;
}
}
}
match total {
Some(t) => FelValue::Money(t),
None => FelValue::Null,
}
}

// ── String helpers ──────────────────────────────────────────

fn fn_str1(&mut self, args: &[Expr], f: fn(&str) -> FelValue) -> FelValue {
Expand Down
30 changes: 30 additions & 0 deletions crates/fel-core/src/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,36 @@ const BUILTIN_FUNCTIONS: &[BuiltinFunctionCatalogEntry] = &[
signature: "countWhere(array<any>, predicate) -> number",
description: "Counts elements whose predicate evaluates to true.",
},
BuiltinFunctionCatalogEntry {
name: "sumWhere",
category: "aggregate",
signature: "sumWhere(array<number>, predicate) -> number",
description: "Sums numeric elements whose predicate evaluates to true.",
},
BuiltinFunctionCatalogEntry {
name: "avgWhere",
category: "aggregate",
signature: "avgWhere(array<number>, predicate) -> number",
description: "Returns the mean of numeric elements whose predicate evaluates to true.",
},
BuiltinFunctionCatalogEntry {
name: "minWhere",
category: "aggregate",
signature: "minWhere(array<any>, predicate) -> any",
description: "Returns the smallest element whose predicate evaluates to true.",
},
BuiltinFunctionCatalogEntry {
name: "maxWhere",
category: "aggregate",
signature: "maxWhere(array<any>, predicate) -> any",
description: "Returns the largest element whose predicate evaluates to true.",
},
BuiltinFunctionCatalogEntry {
name: "moneySumWhere",
category: "money",
signature: "moneySumWhere(array<money>, predicate) -> money",
description: "Sums money elements whose predicate evaluates to true.",
},
BuiltinFunctionCatalogEntry {
name: "length",
category: "string",
Expand Down
Loading
Loading