@dforge-core/dforge-mcp 0.2.38 → 0.2.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ All notable changes to `@dforge-core/dforge-mcp`. This project uses semver-ish
4
4
  `0.1.0-rc.N` pre-release tags; the published version is set at publish time via
5
5
  the release workflow, so committed `package.json` versions are placeholders.
6
6
 
7
+ ## 0.2.39 — 2026-10-13
8
+
9
+ **Field-change reactions are in the entity schema.** `dforge://schema/entity` describes `reactions`
10
+ (rules that suggest a value when the user edits another field — from a formula, or from a
11
+ `logic/functions` function), and `dforge://schema/deps` accepts the `reaction:<code>` and
12
+ `function:<library>` provenance tokens (metadata 0.0.40). A bridge module may declare reactions in an
13
+ extension file on its host's entity.
14
+
15
+ **Storno is in the schema.** A register `L` column's `validation` describes `balanced`,
16
+ `requireBalanced` and `storno` (a red reversal); `requirePositiveAmounts` is marked deprecated.
17
+
18
+ **A reactions reference.** `dforge://reference/reactions` covers formula and server (`call`) rules, how a
19
+ chain runs (each rule once per edit, the edited column never overwritten), a bridge module's reactions
20
+ on its host's entity and the `reaction:<code>` dependency token, dependent references, and the common
21
+ mistakes. The formulas and conventions references point to it.
22
+
23
+ **Storno in the registers reference.** `validation.storno`, the deprecated `requirePositiveAmounts`, and
24
+ that a document with no lines posts nothing to a lines binding and still posts its other registers.
25
+
26
+ **Needs `@dforge-core/dforge-cli` 0.2.30**, which validates, packs and installs the above.
27
+
7
28
  ## 0.2.38 — 2026-10-12
8
29
 
9
30
  **A register column's `map` may name a debit and a credit column.** On an accounting (`L`) column,
package/dist/server.js CHANGED
@@ -31002,7 +31002,7 @@ var import_node_crypto = require("crypto");
31002
31002
  var fs2 = __toESM(require("fs"));
31003
31003
  var path2 = __toESM(require("path"));
31004
31004
 
31005
- // node_modules/.pnpm/@dforge-core+dforge-cli@0.2.29/node_modules/@dforge-core/dforge-cli/dist/lib.mjs
31005
+ // node_modules/.pnpm/@dforge-core+dforge-cli@0.2.30/node_modules/@dforge-core/dforge-cli/dist/lib.mjs
31006
31006
  function buildManifest(opts, moduleId) {
31007
31007
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
31008
31008
  const manifest = {
@@ -31290,7 +31290,7 @@ function buildZedTasks() {
31290
31290
  var fs = __toESM(require("fs"));
31291
31291
  var path = __toESM(require("path"));
31292
31292
 
31293
- // node_modules/.pnpm/@dforge-core+metadata@0.0.39/node_modules/@dforge-core/metadata/dist/index.js
31293
+ // node_modules/.pnpm/@dforge-core+metadata@0.0.40/node_modules/@dforge-core/metadata/dist/index.js
31294
31294
  var AggType = {
31295
31295
  Sum: "sum",
31296
31296
  Count: "count",
@@ -34622,7 +34622,7 @@ var path10 = __toESM(require("path"));
34622
34622
  var fs7 = __toESM(require("fs"));
34623
34623
  var path9 = __toESM(require("path"));
34624
34624
 
34625
- // node_modules/.pnpm/@dforge-core+metadata@0.0.39/node_modules/@dforge-core/metadata/dist/dsl/index.js
34625
+ // node_modules/.pnpm/@dforge-core+metadata@0.0.40/node_modules/@dforge-core/metadata/dist/dsl/index.js
34626
34626
  var PUNCT3 = ["===", "!=="];
34627
34627
  var PUNCT2 = [
34628
34628
  "==",
@@ -38950,6 +38950,7 @@ var resources = [
38950
38950
  read: () => readSkill("scripts/xlsx_to_model.py")
38951
38951
  },
38952
38952
  reference("registers", "Registers: accumulation (balance / turnover / info) and accounting (classic / compound) registers, registers.json, binding a document from its A / L columns (map, per-side debit/credit objects), and the reads. Load before authoring registers.json or an A / L column."),
38953
+ reference("reactions", "Field-change reactions: `reactions` on an entity (formula rules and server `call` rules) that suggest a stored, editable value when the user edits another field; a bridge module's reactions on its host's entity (`reaction:<code>` in deps); dependent references. Load before authoring any reaction."),
38953
38954
  reference("data-migration", "Migrating data from a legacy database."),
38954
38955
  reference("manifest", "manifest.json shape: moduleId UUID, semver, entities map, locale-keyed translations, security block."),
38955
38956
  reference("validation-checklist", "Final pre-pack self-review checklist covering every file type."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dforge-core/dforge-mcp",
3
- "version": "0.2.38",
3
+ "version": "0.2.39",
4
4
  "description": "MCP server for dForge module authoring. Exposes 35 scaffold/patch/validate/pack/install tools and schema resources so AI agents (Claude Code, Cursor, Zed) can create and ship dForge modules.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/iash44/dForge-core",
@@ -26,8 +26,8 @@
26
26
  "node": ">=18"
27
27
  },
28
28
  "dependencies": {
29
- "@dforge-core/dforge-cli": "^0.2.29",
30
- "@dforge-core/metadata": "^0.0.39",
29
+ "@dforge-core/dforge-cli": "^0.2.30",
30
+ "@dforge-core/metadata": "^0.0.40",
31
31
  "@modelcontextprotocol/sdk": "^1.30.0",
32
32
  "zod": "^4.4.3"
33
33
  },
@@ -94,8 +94,8 @@
94
94
  },
95
95
  "provenance": {
96
96
  "type": "string",
97
- "pattern": "^(extends:[^.]+\\.[^.]+|(ref|formula|action|view|report|print|role):.+)$",
98
- "description": "A usage token of the form '<kind>:<symbol>'. Kinds: ref:<entity.column> (a link column joins to the dependency; the entity is qualified <module>.<entity> when it's an extension host), extends:<module.entity> (the consumer extends the provider entity — MUST be fully qualified, e.g. extends:parties.party, never bare extends:party), formula:<entity.column> (a formula nav path reads it), action:<action_cd> (DSL reads/writes it, incl. raw query() SQL), view:<view_cd> (a data view's dataSource targets it), report:<report_cd> (a report queries it), print:<template_cd> (a print template navigates into it), role:<role_cd> (a security role grants rights on the entity). Note: 'display' is intentionally NOT a kind — the label is the provider's concern."
97
+ "pattern": "^(extends:[^.]+\\.[^.]+|(ref|formula|action|function|reaction|view|report|print|role):.+)$",
98
+ "description": "A usage token of the form '<kind>:<symbol>'. Kinds: ref:<entity.column> (a link column joins to the dependency; the entity is qualified <module>.<entity> when it's an extension host), extends:<module.entity> (the consumer extends the provider entity — MUST be fully qualified, e.g. extends:parties.party, never bare extends:party), formula:<entity.column> (a formula nav path reads it), action:<action_cd> (DSL reads/writes it, incl. raw query() SQL), function:<library> (a function in logic/functions/<library>.dsl reads/writes it), reaction:<code> (a field-change reaction the consumer's extension declares on the dependency entity reads or sets it), view:<view_cd> (a data view's dataSource targets it), report:<report_cd> (a report queries it), print:<template_cd> (a print template navigates into it), role:<role_cd> (a security role grants rights on the entity). Note: 'display' is intentionally NOT a kind — the label is the provider's concern."
99
99
  }
100
100
  }
101
101
  }
@@ -33,6 +33,73 @@
33
33
  "type": "boolean",
34
34
  "description": "Records of this entity accept comments \u2014 the composer and thread appear on the record card, beside the audit history. Opt-in per entity; `comments` in the manifest sets a module-wide default that this overrides in both directions."
35
35
  },
36
+ "reactions": {
37
+ "type": "array",
38
+ "description": "Field-change reactions: rules that suggest a stored, editable value when another field is edited (1C's ПриИзменении). Run in the UI only — a form, an editable grid, and bulk update per selected record; a write over the API, an import or a DSL update() runs none. A rule fires on the user's edit of an 'on' column and sets each 'set' column — from a formula evaluated on the client, or from what a server rule's 'call' function returns; a value it sets may fire further rules, each rule at most once per edit, and never over a column the user edited. A column the user may not edit is skipped. Pack and install check every column, formula and function. See docs/ideas/field-change-reactions.md.",
39
+ "items": {
40
+ "type": "object",
41
+ "required": ["code", "on", "set"],
42
+ "additionalProperties": false,
43
+ "properties": {
44
+ "code": {
45
+ "type": "string",
46
+ "pattern": "^[^.]+$",
47
+ "description": "Names the rule in errors; unique within the entity. No '.': a bridge module's rules are stored as <module>.<code>."
48
+ },
49
+ "on": {
50
+ "type": "array",
51
+ "minItems": 1,
52
+ "items": { "type": "string" },
53
+ "description": "The columns whose edit fires the rule — stored or reference columns, not computed ones."
54
+ },
55
+ "call": {
56
+ "type": "string",
57
+ "description": "Makes this a server rule: a top-level function of the module's libraries (logic/functions), or an imported one as namespace.name. It is called with the record as the user is editing it — function f(record) { … } — in a transaction that is always rolled back, and returns an object of suggested values by column. Its info() / warn() / error() reach the user."
58
+ },
59
+ "set": {
60
+ "description": "A formula rule: column → formula. A server rule: the columns its function may set, each a column or { column, ifEmpty } — what it returns beyond them is ignored, and a column it leaves out is unchanged. A target is a stored column, not the primary key, a reference (set its FK column instead) or a computed column. A formula uses the F-column syntax, [ref].[column] reads included.",
61
+ "oneOf": [
62
+ {
63
+ "type": "object",
64
+ "minProperties": 1,
65
+ "additionalProperties": {
66
+ "oneOf": [
67
+ { "type": "string" },
68
+ {
69
+ "type": "object",
70
+ "required": ["formula"],
71
+ "additionalProperties": false,
72
+ "properties": {
73
+ "formula": { "type": "string" },
74
+ "ifEmpty": { "type": "boolean", "description": "Fill only an empty field." }
75
+ }
76
+ }
77
+ ]
78
+ }
79
+ },
80
+ {
81
+ "type": "array",
82
+ "minItems": 1,
83
+ "items": {
84
+ "oneOf": [
85
+ { "type": "string" },
86
+ {
87
+ "type": "object",
88
+ "required": ["column"],
89
+ "additionalProperties": false,
90
+ "properties": {
91
+ "column": { "type": "string" },
92
+ "ifEmpty": { "type": "boolean", "description": "Fill only an empty field." }
93
+ }
94
+ }
95
+ ]
96
+ }
97
+ }
98
+ ]
99
+ }
100
+ }
101
+ }
102
+ },
36
103
  "auditHistory": {
37
104
  "type": "string",
38
105
  "enum": ["basic", "fields", "full"],
@@ -265,6 +332,17 @@
265
332
  "dateField": { "type": "string", "description": "This document's date column, supplying the date the register stamps on each journal row. Required once the register declares a 'journalDate' — one writer that supplied none would make every balance read as of a date quietly short, across all writers. On an accumulation (A) column it may be a one-hop reference path, '<reference column>.<target column>', or an expression over the document ('=...', see 'map')." },
266
333
  "dateTrunc": { "type": "string", "enum": ["day", "month", "year"], "description": "Takes the date 'dateField' supplies back to the start of its day, month or year before the register stamps it — for a document that carries a time while the register's records sit on whole days. The calendar is the value's as stored; no time zone is applied. Needs 'dateField'. Accumulation (A) columns only." },
267
334
  "skipPostingWhenEmpty": { "type": "array", "items": { "type": "string" }, "description": "THE EXCEPTION, not the normal way to model this — read the guide before declaring it. Register dimensions (by the register's names) whose empty value means THIS POSTING records nothing: the line, or the document when the binding posts from the document. One posting is skipped, never the register and never the document. When that is all the document would have recorded it is left unposted to this register, and the flag is per binding, so its other registers post as usual. An empty dimension NOT listed refuses the posting, naming the row and the dimension. It leaves a document fully posted while some of its lines produced no movement, which usually means the document is modelling two kinds of line in one table: since 'lineEntity' is declared per BINDING, prefer giving each register its own line table (goods lines post to stock, service lines do not) so that every line of each table posts. Declare this only when the distinction is not structural but lives in the data — a batch or serial number filled on some rows and not others — so there is no second table to put those rows in. Accumulation (A) columns only. Formerly skipWhenNull: a package using the old name is refused at install, while a tenant's already-stored params keep being read under it." },
335
+ "validation": {
336
+ "type": "object",
337
+ "description": "Per-line and per-entry checks a ledger (L) column runs before it posts. Accounting (L) columns only.",
338
+ "properties": {
339
+ "balanced": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true, "description": "Register resources whose total debit must equal total credit across the entry's lines — a list because an entry may balance its amount but not its quantity. Exclusive with requireBalanced; refused on a compound register, which balances by construction." },
340
+ "requireBalanced": { "type": "boolean", "default": false, "description": "Every resource must balance across the entry's lines. Exclusive with balanced; refused on a compound register." },
341
+ "storno": { "type": "boolean", "default": false, "description": "Red reversal (storno): an amount may be negative, posted on the SAME side as the entry it reverses, so turnovers stay on their side as negatives. The other per-line checks stay, read as non-zero: a classic line still carries one side per resource, and a line must still move something. Wins over requirePositiveAmounts: false." },
342
+ "requirePositiveAmounts": { "type": "boolean", "default": true, "deprecated": true, "description": "Deprecated — use storno. false switches off EVERY per-line check (the sign, both sides on one classic line, a line that moves nothing), not only the sign." }
343
+ },
344
+ "additionalProperties": true
345
+ },
268
346
  "periodEntity": { "type": "string", "description": "Period entity this registry closes against — one of this module's, or 'module.entity' from a dependency." },
269
347
  "periodDateField": { "type": "string", "description": "Header date column that resolves the period." },
270
348
  "periodKeyField": { "type": "string", "default": "period_key" },
@@ -145,7 +145,7 @@ Load these by URI, one per step, at the step that needs them.
145
145
  **Per-element references** — `dforge://reference/<name>`:
146
146
  `field-types`, `flags`, `column-types`, `column-domains`, `formulas`, `traits`,
147
147
  `data-views`, `menus`, `action-dsl`, `filters`, `security`, `reports`,
148
- `settings`, `jobs`, `number-sequences`, `registers`, `document-extraction`,
148
+ `settings`, `jobs`, `number-sequences`, `registers`, `reactions`, `document-extraction`,
149
149
  `print-templates`, `translations`, `queries`, `schema-import`, `excel-import`,
150
150
  `data-migration`, `manifest`, `validation-checklist`, `conventions`.
151
151
 
@@ -84,6 +84,8 @@ When two core modules need to integrate (e.g. CRM and Finance), create a **bridg
84
84
 
85
85
  Extension entities in a bridge module have `"extends": "fin.invoice"` at the top. Physical columns in the extension go into a 1:1 ext table.
86
86
 
87
+ An extension file may also declare `reactions` on its host's entity, calling the bridge's own `logic/functions` library: `crm-pricing` prices a quote line as it is edited. The host columns a rule names go in `deps/<host>.json` as `reaction:<code>`. See `dforge://reference/reactions`.
88
+
87
89
  ## Seed data ordering
88
90
 
89
91
  Seed files run in numeric prefix order. Put entities with FK dependencies **after** their targets:
@@ -374,6 +374,10 @@ deep — `[parent].[name]` reads the parent's plain name.
374
374
  }
375
375
  ```
376
376
 
377
+ ## A formula that suggests an editable value
378
+
379
+ An `F` column is virtual: not stored, not editable. To fill a **stored, editable** column from other fields while the user edits, use a field-change reaction, whose formula rules take this same syntax. See `dforge://reference/reactions`.
380
+
377
381
  ## Common mistakes
378
382
 
379
383
  - Using `column_name` without brackets — **wrong**. Always `[column_name]`.
@@ -0,0 +1,112 @@
1
+ # Field-Change Reactions Reference
2
+
3
+ A reaction suggests a **stored, editable** value the moment the user edits another field — 1C's `ПриИзменении`. Pick a product on an order line and its unit, VAT rate and price are filled; the user may still change the price.
4
+
5
+ ## Reaction, or something else?
6
+
7
+ | Need | Use |
8
+ |---|---|
9
+ | A value shown, never edited, never stored | `F` column |
10
+ | A value the database computes and keeps | `G` column |
11
+ | A value set once, when the record is created | a default |
12
+ | A value that must hold however the record is written | an `async: false` trigger |
13
+ | A value suggested while the user edits | **a reaction** |
14
+
15
+ A reaction is an input aid, not an invariant. It runs in the UI only: a form, an editable grid, and bulk update (per selected record). A write over the API, an import or a DSL `update()` runs none. Never on load, never on a value the server returns after save.
16
+
17
+ ## Declaring one
18
+
19
+ `reactions` sits in the entity file beside `fields`. One rule — "when X changes" — may set several columns.
20
+
21
+ ```jsonc
22
+ "reactions": [
23
+ {
24
+ "code": "product_defaults",
25
+ "on": ["product_id"],
26
+ "set": {
27
+ "unit_id": "[product_id].[unit_id]",
28
+ "vat_rate": "[product_id].[vat_rate]",
29
+ "description": { "formula": "[product_id].[name]", "ifEmpty": true }
30
+ }
31
+ },
32
+ {
33
+ "code": "price_from_list",
34
+ "on": ["product_id", "price_date"],
35
+ "call": "priceOn",
36
+ "set": ["price"]
37
+ }
38
+ ]
39
+ ```
40
+
41
+ - `code` names the rule and is unique within the entity. It may not contain `.`.
42
+ - `on` lists the columns whose **edit** fires the rule: stored or reference columns, not computed ones.
43
+ - A **formula rule** has `set` as `{ column: formula }`. The formula uses the `F`-column syntax, one-hop `[ref].[column]` reads included. It runs in the browser.
44
+ - A **server rule** has `call` (a top-level function in `logic/functions/*.dsl`, or an imported `namespace.fn`) and `set` as the list of columns the function may write.
45
+ - `ifEmpty: true` on a target fills it only when it is empty. The default is to overwrite whenever a source changes.
46
+ - Set a reference through its **FK column** (`customer_id`); the visible reference follows.
47
+ - A target is a stored, writable column: not the primary key, a reference column, `F`, `G`, `S`, or an `A` / `L` register state. Pack and install refuse it, and check every column, formula and function.
48
+
49
+ ## How a chain runs
50
+
51
+ - A rule's `set` may fire further rules. Within one edit each rule runs **at most once**, and a column the user edited is **never** overwritten by a rule. That makes two-way arithmetic work with no special case:
52
+
53
+ ```jsonc
54
+ { "code": "amount", "on": ["quantity", "price"], "set": { "amount": "[quantity] * [price]" } },
55
+ { "code": "price", "on": ["amount"], "set": { "price": "IF([quantity] = 0, [price], [amount] / [quantity])" } }
56
+ ```
57
+
58
+ - A rule that cannot answer at once (a server rule, a formula reading through a reference) replies later, as a pass of its own. An immediate rule that already ran on the old value runs again on the new one, so `amount` follows the price the server suggested.
59
+ - A target the user may not edit (column security, a posted or period lock, `canEdit`) is skipped for that user.
60
+ - Bulk update counts every column it sets as the user's own edit, per record.
61
+ - Cycles are not checked at install; the once-per-edit rule is what stops them.
62
+
63
+ ## Server rules
64
+
65
+ ```js
66
+ // logic/functions/price_suggestions.dsl
67
+ function priceOn(record) {
68
+ var price = /* read a price list, a register slice, the previous waybill … */ 0;
69
+ if (price == 0) { warn('No price in the list'); return {}; }
70
+ return { price: price };
71
+ }
72
+ ```
73
+
74
+ - The function gets `record`, the values as the user has them (unsaved, typed by column; a 64-bit key is an exact BigInt), and returns `{ column: value }`. `{}` suggests nothing. A column it returns that `set` does not list is ignored.
75
+ - `warn()` / `info()` show as toasts. `error()` suggests nothing and shows its text.
76
+ - It runs with the user's rights and settings, in a transaction **that is always rolled back**. `insert()` and `update()` change nothing; queued notifications and e-mails are dropped. A `callApi()` is not undone — keep a reaction free of outside effects.
77
+ - The call needs Select and Insert or Update on the entity, and runs only a rule the entity declares.
78
+ - A stale reply is dropped: if a source changed again while it was out, or the user edited a target meanwhile, it is not applied.
79
+
80
+ ## From a bridge module
81
+
82
+ A bridge module may declare `reactions` in an extension file, on its host's entity, calling its **own** functions. `crm-pricing` prices a `crm.quote_line` from the pricing module as it is edited.
83
+
84
+ ```jsonc
85
+ // crm-pricing/entities/crm.quote_line.json
86
+ {
87
+ "extends": "crm.quote_line",
88
+ "dbObject": "quote_line",
89
+ "reactions": [
90
+ { "code": "line_pricing", "on": ["product_id", "quantity"],
91
+ "call": "quoteLinePricing", "set": ["unit_price", "discount_pct"] }
92
+ ]
93
+ }
94
+ ```
95
+
96
+ - Every host column a rule names (in `on` and `set`) must be in `deps/<host>.json`, with the provenance token `reaction:<code>`. Install checks them against the installed host. Name a reference by its **key column**, since a contract column is a stored one.
97
+ - Stored on the host under `params.extensionReactions[<bridge>]`, codes qualified `<bridge>.<code>`. The host's upgrade keeps them; the bridge's upgrade replaces them and its uninstall removes them.
98
+ - Not yet: exporting a bridge's rules back into its extension file.
99
+
100
+ ## Built in: dependent references
101
+
102
+ Not declared. When the user edits a column that an `@[field]` placeholder in some reference's `refFilter` reads, that reference is re-checked and **cleared** if it no longer passes. Change the customer and the other customer's contract goes. Save waits for the checks.
103
+
104
+ ## Common mistakes
105
+
106
+ - **A reaction as an invariant.** It does not run on the API, import or DSL. Use an `async: false` trigger for a value that must hold.
107
+ - **A target that is a reference column.** Set its FK column.
108
+ - **A dotted `code`.** The dot qualifies a bridge's rules. Rename it.
109
+ - **A host column used by a bridge reaction but missing from `deps/<host>.json`.** Add it with `reaction:<code>`.
110
+ - **Writing in a server rule.** The transaction rolls back; the form may still be cancelled.
111
+ - **Expecting a document header change to recompute every line.** A reaction acts within one record. A line is a record, so product → line price works; header → all lines does not.
112
+ - **Expecting a rule to run when a record is created with values** (New from a parent, a copy). It does not.
@@ -230,6 +230,10 @@ Paths, expressions, `dateTrunc`, `headerMap`, `groupLinesBy` and `skipPostingWhe
230
230
  - `lineEntity` and `lineField` are required. `lockedLineFields` must cover every line column the posting copies, or be `["*"]`.
231
231
  - Every register dimension must be mapped, be a same-name line column, or be the `periodDimField` (a shared dimension).
232
232
  - `validation.balanced: ["amount"]` checks debits equal credits on classic. It is refused on compound, which balances by construction.
233
+ - `validation.storno: true` posts a **red reversal**: the same correspondence on the **same** side with a negative amount, which is how 1C reverses a sale. Classic and compound both accept it. Turnovers stay on their side as negatives, and every read, the totals cache and the account-balances report sum them signed. The other per-line checks stay, read as non-zero: a classic line still carries one side per resource, and an all-zero line is refused. `balanced` is unchanged. Install refuses it on a column that is not an accounting `L` column.
234
+ - `requirePositiveAmounts: false` is **deprecated**: it switches off every per-line check, not only the sign. `storno: true` wins when both are set.
235
+ - An accounting register has no negative-balance control, so a quantity account can be driven below zero. The account-balances report drops an account whose opening is zero and whose in-period turnovers cancel out, such as an entry and its storno in the same period.
236
+ - A binding over lines, or an `L` column, posted from a document with **no lines** records nothing and leaves its state flag off. It asks for no date or open period, and the document's other registers still post. The inline `registries` form of the `L` column still refuses an entry with no lines.
233
237
 
234
238
  ## Reads
235
239
 
@@ -33,6 +33,7 @@ flags, or column patterns.
33
33
  | Add a relation / Set column | `dforge://reference/column-types`, `dforge://example/entities/todo_item.json` | — |
34
34
  | Reuse a type/enum across columns (a `domain`) | `dforge://reference/column-domains` | `dforge://schema/domains` |
35
35
  | Add a formula column | `dforge://reference/formulas` | — |
36
+ | Suggest a value when another field is edited (a reaction), incl. from a bridge module | `dforge://reference/reactions` | `dforge://schema/entity`, `dforge://schema/deps` |
36
37
  | Add a trait | `dforge://reference/traits` | — |
37
38
  | Add a number sequence | `dforge://reference/number-sequences` | — |
38
39
  | Add a grid / list view | `dforge://reference/data-views`, `dforge://example/ui/data_views.json` | — |