@dforge-core/dforge-mcp 0.2.43 → 0.2.45

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +1 -1
  3. package/dist/server.js +130 -86
  4. package/package.json +3 -3
  5. package/resources/docs/conventions.md +17 -14
  6. package/resources/docs/dsl-reference.md +34 -6
  7. package/resources/schemas/entity.schema.json +14 -4
  8. package/resources/schemas/registers.schema.json +8 -3
  9. package/resources/schemas/reports.schema.json +6 -2
  10. package/resources/schemas/translations.schema.json +21 -0
  11. package/skills/dforge-mcp-author/SKILL.md +3 -2
  12. package/skills/dforge-mcp-author/examples/column-security/entities/product.json +1 -1
  13. package/skills/dforge-mcp-author/examples/column-security/manifest.json +0 -1
  14. package/skills/dforge-mcp-author/examples/column-security/security/roles.json +2 -2
  15. package/skills/dforge-mcp-author/references/action-dsl.md +38 -8
  16. package/skills/dforge-mcp-author/references/conventions.md +7 -1
  17. package/skills/dforge-mcp-author/references/field-types.md +3 -2
  18. package/skills/dforge-mcp-author/references/filters.md +2 -0
  19. package/skills/dforge-mcp-author/references/formulas.md +5 -1
  20. package/skills/dforge-mcp-author/references/print-templates.md +13 -2
  21. package/skills/dforge-mcp-author/references/queries.md +1 -1
  22. package/skills/dforge-mcp-author/references/registers.md +18 -3
  23. package/skills/dforge-mcp-author/references/reports.md +8 -2
  24. package/skills/dforge-mcp-author/references/schema-import.md +1 -1
  25. package/skills/dforge-mcp-author/references/security.md +1 -1
  26. package/skills/dforge-mcp-author/references/translations.md +44 -16
  27. package/skills/dforge-mcp-author/references/validation-checklist.md +2 -3
  28. package/skills/dforge-module-build/SKILL.md +10 -1
  29. package/skills/dforge-module-ship/SKILL.md +31 -3
  30. package/skills/dforge-mcp-author/examples/column-security/translations/en-US.json +0 -35
@@ -78,7 +78,7 @@ A report has a `label`, a `description`, a `datasets` map, a `layout` object (`{
78
78
  - `query.columns` — column codes; supports dot navigation (`customer.name`).
79
79
  - `query.filter` — canonical filter (`{c,o,v}` or `{g,i:[...]}`); use `@param_code` to reference a parameter.
80
80
  - `query.sort` — `[{ c, d }]` where `d` ∈ `asc | desc`.
81
- - `columnsDef` — optional per-column display overrides (`{ label, visible, width }`).
81
+ - `columnsDef` — optional per-column display overrides (`{ label, fieldTypeCd, format, width }`; each key replaces the projected value). `label` is a literal and wins over a translated header. `visible` is accepted but read by nothing. `baseDatatypeCd` is read only where the dataset projects no columns of its own.
82
82
 
83
83
  ### Stored procedure dataset (developer path)
84
84
 
@@ -110,6 +110,9 @@ Key differences from entity datasets:
110
110
  **override** — `report.run` projects `dForge.sp_column`, and `columnsDef` only restyles it.
111
111
  A function returning an unnamed `SETOF <composite>` or a bare scalar declares no column
112
112
  names, so that one needs an explicit `columns` block in `logic/stored_procedures.json`.
113
+ Leave `label` out of `columnsDef` to keep the column's translated header: result-column headers
114
+ translate under the procedure, `storedProcedures.<sp_cd>.columns.<column_cd>.label`
115
+ (see `references/translations.md`), and every dataset bound to the procedure shows it.
113
116
  - Params work exactly as they do for an entity dataset, and `datasetType` makes no difference: declare them at report level (`parameters`) or on the dataset (`params`). Report level wins on a code collision.
114
117
  - **The parameter code must match the procedure's declared `paramCd` exactly.** `report.run`
115
118
  resolves each argument by that code alone — no prefix is added or stripped — so a report
@@ -159,8 +162,10 @@ $$;
159
162
  - `paramCd` is capped at 50 characters, and a declared `fieldTypeCd` must be a real one —
160
163
  both are checked at install, because binding the SP in the report editor copies them
161
164
  into report params.
162
- - `RETURNS TABLE (...)`.
165
+ - `RETURNS TABLE (...)`. In `LANGUAGE plpgsql` with `RETURN QUERY`, each selected column must match its declared type exactly: a `varchar` column returned as `text` fails with "structure of query does not match function result type". Cast in the select (`c.account_name::text`).
163
166
  - Use the module's schema prefix (`crm.rpt_*`) and `STABLE` volatility.
167
+ - **A function that returns one row: declare `ROWS 1`.** Install adds `SET search_path` to the function, which stops Postgres from inlining it, so the planner falls back to its default estimate of 1000 rows and a join on the function can turn a 5-second report into a 20-minute one. `ROWS 1` (or a realistic estimate) on each table function the report calls restores the plan.
168
+ - **Every `logic/reports/*.sql` runs at every install**, as the install's database user. Don't create or replace the function by hand under another user while experimenting: the next install fails with "must be owner of function". Try SQL in a transaction you roll back, and let install own the functions.
164
169
  - **Security is your responsibility, and the function has no context to work from**: it is not
165
170
  told the folder or the user, so an SP dataset cannot be row-level scoped the way a `"Q"`
166
171
  dataset is. Use `"Q"` for anything that must be filtered per user, and keep `"S"` for
@@ -194,6 +199,7 @@ granted on. Keyed by stored-procedure code, which may not contain a dot.
194
199
  - **`params`** is the call signature, in order — omit it for a zero-argument function.
195
200
  - **`columns`** is optional; without it install derives the result columns from the
196
201
  function's `RETURNS TABLE (…)`.
202
+ - A result column's `baseDatatypeCd` is a platform base type code: `string`, `number`, `bool`, `date`, `timestamp`, `time`, `guid`, `json`, `link`, `set`, `binary`, `xml`, `culture`. Not `datetime`, `boolean`, `integer` or `text`: an unknown code fails install with a bare foreign-key error on `sp_column`, which validate does not catch yet.
197
203
  - A result column may name a **`domain`** (`domain_cd` or `module_cd.domain_cd`) instead of
198
204
  `fieldTypeCd` / `baseDatatypeCd` / `params`: a status the function returns then shows its
199
205
  labels without restating the entity's option list. Naming a domain and restating any of
@@ -111,7 +111,7 @@ Using the converted entities, generate the full module package:
111
111
  4. **`ui/menus.json`** — grouped logically (group related entities under section nodes)
112
112
  5. **`ui/folders.json`** — root folder with all entities listed
113
113
  6. **`security/roles.json`** — at least `module.admin` (SIUDC on everything) and `module.user` (SIU on main entities)
114
- 7. **`translations/en-US.json`** — labels for all entities, fields, views, menus, actions, roles, folders
114
+ 7. **English labels in the definitions** — a short `description` on every entity, field and role, a `label` on every view, menu and action. No `translations/en-US.json`: install skips it. Translation files only for the locales in `supportedLocales` (`dforge_translation_sync`)
115
115
  8. **`seed-data/`** — if the source had enum/lookup tables converted to entities, generate seed data for them
116
116
 
117
117
  ### Step 8: Generate IMPORT_NOTES.md
@@ -16,7 +16,7 @@ Lives in: `security/roles.json`
16
16
 
17
17
  Role codes use a `module.role` naming pattern (e.g. `crm.admin`, `crm.sales-rep`).
18
18
 
19
- > **Role display name & translations.** Each role has exactly two keys: `description` and `rights` (`roles.schema.json` is `additionalProperties: false` — **do not** add a `label` here). `description` is the English fallback display name. The **localized** display name lives in the translation files as `roles.<code>.label`, keyed by the module-qualified role code, and is **completeness-enforced**: every role must have a `roles.<code>.label` in every `translations/<locale>.json` (including the `en-US` base) or install fails. See `translations.md`.
19
+ > **Role display name & translations.** Each role has exactly two keys: `description` and `rights` (`roles.schema.json` is `additionalProperties: false` — **do not** add a `label` here). `description` is the English display name — keep it a short label ("Storekeeper"), not an explanation, since it is what the English UI shows. The **localized** display name lives in the translation files as `roles.<code>.label`, keyed by the module-qualified role code, and is **completeness-enforced**: every role must have a `roles.<code>.label` in every non-English `translations/<locale>.json` or install fails. There is no English translation file. See `translations.md`.
20
20
 
21
21
  Example:
22
22
 
@@ -2,9 +2,33 @@
2
2
 
3
3
  dForge modules support multi-language translations for user-facing labels — entity names, field labels, view titles, folder names, menu items, action labels and params, and report params/dataset captions.
4
4
 
5
- Lives in: `translations/<locale>.json` (e.g. `translations/en-US.json`, `translations/de-DE.json`)
5
+ Lives in: `translations/<locale>.json` for each **non-English** locale (e.g. `translations/de-DE.json`, `translations/uk-UA.json`)
6
6
 
7
- Locale codes use the `ll-CC` format (language-country): `en-US`, `de-DE`, `fr-FR`, `es-ES`, etc.
7
+ Locale codes use the `ll-CC` format (language-country): `de-DE`, `uk-UA`, `fr-FR`, `es-ES`, etc.
8
+
9
+ ## English is the base — and it lives in the definitions
10
+
11
+ Install **never reads** `translations/en.json` or `translations/en-*.json` (`ModulePackage.ReadTranslationFiles` skips them). The English UI shows the text written in the definitions themselves, so an English-only module ships **no** `translations/` folder, and a translated module ships one file per locale in `supportedLocales` and nothing for English. An `en-US.json` is dead weight: its labels are never applied, and the English UI keeps showing whatever the definitions say — which is how a module ends up displaying long service descriptions as labels. `dforge_module_validate` warns about such a file; `dforge_translation_sync` refuses to write one.
12
+
13
+ Where each object's English text lives (what install shows, `label ?? description ?? code` where both exist):
14
+
15
+ | Object | English text | Notes |
16
+ |---|---|---|
17
+ | Entity | `description` in `entities/<e>.json` | |
18
+ | Field | `description` on the field | trait columns take theirs from the trait |
19
+ | Data view | `label`, else `description` | with both, `description` is the tooltip |
20
+ | Folder | `label`, else `description` | |
21
+ | Menu / menu item | `label`, else `description` | |
22
+ | Action | `label`, else `description` | an action param: its `description` |
23
+ | Report | `label`, else `description` | |
24
+ | Stored procedure | `description`; a result column: its `label` | |
25
+ | Print template | `description` | `label` is rejected by the schema |
26
+ | Setting | `description` | |
27
+ | Role | `description` | `label` is rejected by the schema |
28
+ | Column domain | `description` | |
29
+ | Register | `label` | |
30
+
31
+ Keep these short **labels** — "Product", "Storekeeper" — not explanations. Notes such as a legacy table name or "(in Ukrainian)" belong in docs or comments: whatever the field holds is the UI text.
8
32
 
9
33
  ## Structure
10
34
 
@@ -83,7 +107,8 @@ Translation files mirror the module's content structure. Each translatable objec
83
107
  | `views` | Data view labels and descriptions (`label`, `desc`) | View codes (keys from `ui/data_views.json`) → `{ label, desc }` |
84
108
  | `menus` | Menu node labels (root + nested items) | Menu root key → `{ label, items: { item_code: { label } } }` matching `ui/menus.json` structure |
85
109
  | `actions` | Action labels, descriptions, param labels **and param dropdown option labels** | Action codes (keys from `ui/actions.json`) → `{ label, desc, params: { param_cd: { label, options: { value: … } } } }` |
86
- | `reports` | Report dataset captions, param labels **and param dropdown option labels** | Report codes → `{ datasets: { ds_cd: { caption } }, params: { param_cd: { label, options: { value: … } } } }` |
110
+ | `reports` | Report `label`, `desc`, dataset captions, param labels **and param dropdown option labels** | Report codes → `{ datasets: { ds_cd: { caption } }, params: { param_cd: { label, options: { value: … } } } }` |
111
+ | `storedProcedures` | Result-column headers of a stored procedure (`logic/stored_procedures.json`), shared by every report bound to it | Procedure codes → `{ columns: { column_cd: { label } } }` |
87
112
  | `entities.<e>.constraints` | Check/unique constraint violation messages (opt-in — warned, not enforced) | Constraint codes → `{ message }` under the owning entity |
88
113
  | `messages` | DSL `info` / `warn` / `error` / `exit` texts (opt-in — warned, not enforced) | Exact English text → translated text |
89
114
  | `entities.<e>.fields.<f>.options` | Dropdown/radio/flags **option labels** (opt-in) | Option `value` → localized label (string) or `{ label, icon, color }` |
@@ -110,7 +135,7 @@ A check/unique constraint declares a user-facing `message` in the entity JSON (`
110
135
 
111
136
  ### Dropdown option labels ARE translatable (opt-in)
112
137
 
113
- The `options` on a `dropdown`/`radio`/`flags` column are the **base (fallback) labels**. To translate the labels a user actually sees, add an `options` map next to the field's `label`, keyed by the option's **value** (the stored code):
138
+ The `options` on a `dropdown`/`radio`/`flags` column are the **base (fallback) labels**, and they are the English UI — write plain English there (`"Active"`), never a bilingual `"Active (Актив)"`. To translate the labels a user actually sees, add an `options` map next to the field's `label`, keyed by the option's **value** (the stored code). Prefer the **string** form (`"active": "Активний"`): the `{ label, … }` form is valid, but the VS Code extension's translation panel counts it as missing, and `module validate` does not check option translations at all. An option list several columns share belongs in a column domain, translated once under `domains.<code>.options`.
114
139
 
115
140
  ```json
116
141
  "entities": {
@@ -203,15 +228,14 @@ Role labels are **required and localized** (this changed — older docs said rol
203
228
  ```
204
229
 
205
230
  - **Keys are the role codes** from `security/roles.json` — already module-qualified (e.g. `crm.admin`, `crm.sales-rep`). They match `module_role.name`.
206
- - **Value is `{ "label": ... }`** — the localized display name. It supersedes the `description` from `roles.json` at runtime; `description` remains the English fallback if a role has no translation entry.
207
- - **Completeness is enforced.** `TranslationCompletenessValidator` requires `roles.<code>.label` for **every** role in `security/roles.json`, in **every** translation file — including the `en-US` base. A missing role label fails install with `Label for role '<code>'.` So ship a `roles` block with a label for each role in **each** `translations/<locale>.json` (en-US + every locale in `supportedLocales`).
231
+ - **Value is `{ "label": ... }`** — the localized display name. It supersedes the `description` from `roles.json` in that locale; `description` is the English text.
232
+ - **Completeness is enforced.** `TranslationCompletenessValidator` requires `roles.<code>.label` for **every** role in `security/roles.json`, in **every** translation file install reads — each locale in `supportedLocales`. A missing role label fails install with `Label for role '<code>'.` English needs nothing here: it is the role's `description`.
208
233
 
209
234
  ### Sections that are NOT translated (write-time only)
210
235
 
211
- - `settings` — completeness *is* checked by the validator when listed in `manifest.supportedLocales` (so you'll be forced to ship `settings.<cd>.label` for each declared locale), but the registrar doesn't display those translated labels at runtime. The English `label` in `settings.json` is what users see today.
212
- - `print_templates` — not currently consumed by the registrar. Template labels come from `ui/print_templates.json`.
236
+ - `print_templates` — not consumed by the registrar: a template row has no translatable resource. Its name is the `description` in `ui/print_templates.json` in every language. If you include the section, install succeeds and ignores it.
213
237
 
214
- If you include these sections, the install succeeds (they're silently ignored) — but you won't see localized output for them, so don't expect it.
238
+ `settings` **is** translated: completeness is checked for each declared locale (ship `settings.<cd>.label`), and the setting's English text is its `description` in `settings.json` — the registrar never reads a `label` there.
215
239
 
216
240
  ## German example (de-DE)
217
241
 
@@ -261,7 +285,7 @@ If you include these sections, the install succeeds (they're silently ignored)
261
285
 
262
286
  ## Manifest declaration
263
287
 
264
- Non-English locales are declared in `supportedLocales` (an array of `ll-CC` tags) — **not** a `translations` object. The manifest schema has no `translations` key and rejects it (`additionalProperties: false`). Translation files are auto-discovered at `translations/<locale>.json`. Ship `translations/en-US.json` as the English base, but do **not** list `en`/`en-US` in `supportedLocales` — it covers the non-English locales only (the schema rejects `en`/`en-US` entries).
288
+ Non-English locales are declared in `supportedLocales` (an array of `ll-CC` tags) — **not** a `translations` object. The manifest schema has no `translations` key and rejects it (`additionalProperties: false`). Translation files are auto-discovered at `translations/<locale>.json`. Do **not** list `en`/`en-US` in `supportedLocales` and do not ship an English file — English is the definitions' own text (see above); the schema rejects `en`/`en-US` entries.
265
289
 
266
290
  ```json
267
291
  "supportedLocales": ["de-DE", "fr-FR"]
@@ -269,20 +293,24 @@ Non-English locales are declared in `supportedLocales` (an array of `ll-CC` tags
269
293
 
270
294
  Every listed locale MUST have a matching `translations/<locale>.json` with a label for each translatable resource, or install fails completeness validation.
271
295
 
296
+ ### Stored-procedure column headers
297
+
298
+ A result column's header belongs to the procedure, not to a report. `module pack` / `module validate` require one `storedProcedures.<sp_cd>.columns.<column_cd>.label` per declared column for each non-English supported locale; install only warns (also when a translation names a column the module lacks). A dataset's `columnsDef` label is a literal and overrides it, so leave the label out to keep the column translated.
299
+
272
300
  ## Rules
273
301
 
274
- 1. **Always provide `en-US`** as the base language. Other languages fall back to it for missing keys.
275
- 2. **Locale format is `ll-CC`** (e.g. `en-US`, `de-DE`, `fr-FR`) — language code + country code, hyphen-separated.
276
- 3. **Translate everything user-visible**: entity labels, every field label (including trait-provided fields like `created_date`, `last_updated`), view names, menu items, action labels, **role labels (required — completeness-enforced, `roles.<code>.label` for every role in every locale incl. en-US)**, setting labels, folder names.
302
+ 1. **English is the definitions' text** — no `en-US.json`. Write a short English label into each definition (see the table above); other languages fall back to it for missing keys.
303
+ 2. **Locale format is `ll-CC`** (e.g. `de-DE`, `uk-UA`, `fr-FR`) — language code + country code, hyphen-separated.
304
+ 3. **Translate everything user-visible**: entity labels, every field label (including trait-provided fields like `created_date`, `last_updated`), view names, menu items, action labels, **role labels (required — completeness-enforced, `roles.<code>.label` for every role in every declared locale)**, setting labels, folder names.
277
305
  4. **Include virtual columns** (references, sets, formulas) in field translations — they appear in the UI just like physical columns.
278
306
  5. **Menu translations mirror the `ui/menus.json` structure** — root key → `items` → nested items.
279
- 6. **Missing keys fall back** to the `en-US` value, then to the raw code name. So it's safe to ship partial translations — untranslated items show in English rather than breaking.
307
+ 6. **Missing keys fall back** to the definition's English text, then to the raw code name. So it's safe to ship partial translations — untranslated items show in English rather than breaking.
280
308
  7. **Don't translate column codes** — only the `label` values. Codes stay as-is in all languages.
281
309
  8. **File naming**: `translations/<locale>.json` (e.g. `de-DE.json`) — each non-English file must match a locale listed in `supportedLocales`.
282
310
  9. **Constraint messages are opt-in** — localize them under `entities.<e>.constraints.<c>.message` when you want a per-locale violation message; otherwise the base `message` from the entity JSON is used. Missing overrides warn (non-fatal), they don't fail install. See "Constraint violation messages ARE translatable" above.
283
311
 
284
312
  ## When to create translations
285
313
 
286
- - **Always** create `translations/en-US.json` — even for English-only modules. It ensures all labels are explicitly declared rather than derived from column codes.
287
- - **On request** create additional languages. The LLM can generate translations from the `en-US` file — the structure is identical, only `label` and `desc` values change.
314
+ - **Never** create `translations/en-US.json` — install skips it. Give every definition a short English label instead, so nothing falls back to a column code.
315
+ - **On request** create additional languages: declare them in `supportedLocales` and run `dforge_translation_sync`, which seeds each file from the definitions' English text for you to translate in place.
288
316
  - **Include all fields** in each language file, including trait-provided fields (`created_date`, `last_updated`, `created_by`, etc.) — users see these columns and they need labels.
@@ -162,8 +162,8 @@ For each action:
162
162
 
163
163
  ## Translations
164
164
 
165
- - [ ] `translations/en-US.json` exists
166
- - [ ] Has `entities` section with `label`, `desc`, and `fields` for every entity
165
+ - [ ] **No** `translations/en.json` / `en-*.json` — install skips them. English is each definition's own short label (entity/field/role/setting `description`, view/menu/action/report `label`)
166
+ - [ ] Each `translations/<locale>.json` (one per `supportedLocales` entry) has an `entities` section with `label`, `desc`, and `fields` for every entity
167
167
  - [ ] Every field (including trait-provided: `created_date`, `last_updated`, etc.) has a `label`
168
168
  - [ ] Has `views` section with labels for every data view
169
169
  - [ ] Has `menus` section matching the `ui/menus.json` structure
@@ -172,7 +172,6 @@ For each action:
172
172
  - [ ] (Optional) Constraint messages localized under `entities.<e>.constraints.<c>.message` — opt-in; a missing override for a declared `supportedLocales` entry warns (surfaced by `dforge_module_validate` and at install) but does not fail install
173
173
  - [ ] Has `settings` section with labels for every setting (if settings exist)
174
174
  - [ ] Has `folders` section with label for the root folder
175
- - [ ] Additional language files (if any) have the same structure as `en-US.json`
176
175
  - [ ] Manifest `supportedLocales` lists every non-English locale that has a `translations/<locale>.json` file (English is not listed)
177
176
 
178
177
  ## Versioning
@@ -36,12 +36,15 @@ flags, or column patterns.
36
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` |
37
37
  | Add a trait | `dforge://reference/traits` | — |
38
38
  | Add a number sequence | `dforge://reference/number-sequences` | — |
39
+ | Add an accumulation register (`A` column, `registers.json`) | `dforge://reference/registers` | `dforge://schema/registers` |
40
+ | Edit the manifest (version, `supportedLocales`, dependencies, `entities` map) | `dforge://reference/manifest` | `dforge://schema/manifest` |
39
41
  | Add a grid / list view | `dforge://reference/data-views`, `dforge://example/ui/data_views.json` | — |
40
42
  | Add a matrix (pivot) view | `dforge://reference/data-views` (§Matrix), `dforge://example/matrix-budget/ui/data_views.json`, `dforge://example/matrix-budget/entities/budget_line.json` | — |
41
43
  | Add kanban / calendar / tree-grid / master-detail | `dforge://reference/data-views` | `dforge://schema/data-views` |
42
44
  | Hide columns from a user type (column-level security) | `dforge://reference/security` (§Column-level security), `dforge://example/column-security/entities/product.json`, `dforge://example/column-security/ui/folders.json` | — |
43
45
  | Add a menu | `dforge://reference/menus`, `dforge://example/ui/menus.json` | — |
44
46
  | Add an action | `dforge://reference/action-dsl`, `dforge://example/logic/actions/mark_done.dsl`, `dforge://example/ui/actions.json` | `dforge://docs/dsl` |
47
+ | Email a file the script builds, or ship saved values / a PDF preview for an action's dialog | `dforge://reference/action-dsl` (§Email and §PDF and file output: `sendEmail` attachments, `attachText`, `attachBase64`; §Registering the action: `paramSets`, `previewTemplate`) | `dforge://docs/dsl` |
45
48
  | Share helpers between actions, or call another module's functions | `dforge://reference/action-dsl` (§Function libraries): `logic/functions/*.dsl`, imports in `deps/<module>.json` | `dforge://schema/deps` |
46
49
  | Add a trigger | — | `dforge://schema/triggers`; condition syntax in `dforge://docs/dsl` |
47
50
  | Add a scheduled job | `dforge://reference/jobs` | `dforge://schema/jobs` |
@@ -115,6 +118,10 @@ Enough to author inline; load the linked reference for the full detail.
115
118
  - **Manifest:** non-English locales go in `supportedLocales` (`ll-CC`, never
116
119
  `en`/`en-US`); there is no `translations` manifest key. `security` has both
117
120
  `roles` and `folders`. → `manifest`
121
+ - **English is the definitions' text:** install skips `translations/en*.json`, so
122
+ never create one. Give every entity, field, role and setting a short
123
+ `description` and every view, menu, action and report a short `label` — that
124
+ is what the English UI shows. → `translations`
118
125
  - **Seed data:** explicit numeric PKs under `{entity}_id`; parents before
119
126
  children via the `NN-` prefix. Use `dforge_seed_add`.
120
127
 
@@ -160,6 +167,7 @@ approval, then patch. Reach for the **composite tools first**:
160
167
  - roll-up total → `dforge_entity_rollup_add`
161
168
  - status column → `dforge_entity_status_add`
162
169
  - plain scalar → `dforge_entity_field_add`
170
+ - change an existing field (label, flags, type, …) → `dforge_entity_field_modify`
163
171
 
164
172
  **Field batching rule** — the only Phase 1 exception to one-at-a-time. A field
165
173
  is batchable only if ALL of: scalar primitive (string/integer/decimal/boolean/
@@ -285,7 +293,8 @@ stated trigger; every report's params sit on a dataset.
285
293
  ## Phase 4 — Polish (mostly optional)
286
294
 
287
295
  - **Settings** — `dforge_setting_add` per configurable value the user asked for.
288
- - **Translations** — run **`dforge_translation_sync`**. It generates every
296
+ - **Translations** — only for the locales in `supportedLocales`; English needs
297
+ no file. Run **`dforge_translation_sync`**. It generates every
289
298
  required key from the module's own contents, never overwrites existing
290
299
  translated text, and fills the **role labels that are completeness-enforced at
291
300
  install**. Run it after any entity/view/role change, and always before Phase 6.
@@ -78,9 +78,9 @@ formula column shape; flags letters (`V I E M H` only); every entity has a
78
78
  default grid and `dataSources` at root with `"order"` as a string array; menu
79
79
  leaves use `dataViewCode` and bare icon names; every entity in at least one
80
80
  role's rights; action `script` values are bare filenames; seed PKs numeric with
81
- parents first; a `translations/<locale>.json` for every `supportedLocales` entry
82
- **plus** the `en-US` base, each carrying a label for every trait-provided field
83
- and every role.
81
+ parents first; a `translations/<locale>.json` for every `supportedLocales` entry,
82
+ each carrying a label for every trait-provided field and every role. No English
83
+ file — install skips `en*.json`; English is the definitions' own short labels.
84
84
 
85
85
  If translations are thin, run **`dforge_translation_sync`** now rather than
86
86
  hand-patching — it fills exactly the keys install requires.
@@ -156,6 +156,34 @@ package — fix the referenced files, report what changed, and immediately re-ru
156
156
  validate → pack → install. Keep sign-off for product/design choices or genuinely
157
157
  ambiguous fixes.
158
158
 
159
+ **If install hangs, or fails with a bare HTTP 500 or 400 "deadlock":** look for
160
+ a lock before touching the module. An install's `ALTER TABLE` waits behind any
161
+ open transaction on the module's tables — an idle `psql` session, an ad hoc
162
+ read left open, a script that never commits — and waits without a message,
163
+ for an hour or more. Check `pg_stat_activity` for a blocked `ALTER` and the
164
+ session holding it. Never install while scripts hold transactions on the
165
+ module's tables; run ad hoc scripts in autocommit.
166
+
167
+ **If install times out on the client while rebuilding a register cache:** the
168
+ server goes on and finishes (an analytic cache over 1.5 million postings took
169
+ about 18 minutes). Check the server log or the cache before installing again.
170
+
171
+ **Same version installed "successfully" but nothing changed:** a re-install of
172
+ the same `version` is skipped (`isEmpty: true`). Bump the version, or pass
173
+ `--force` on a dev tenant.
174
+
175
+ **Local dev stack pitfalls:**
176
+ - After installing into a local docker setup, restart `dforge-api` if the UI
177
+ still shows the old metadata; its cache can stay stale.
178
+ - The tenant URL is `http://<tenant>._.localhost:5179`. `<tenant>.localhost`
179
+ and `<tenant>_.localhost` sign into **another** tenant.
180
+ - "no token for tenant X": the CLI session expired (a token lasts about an
181
+ hour). Run `dforge-cli auth login --url <tenant url>`.
182
+ - "tenant at db_schema_version … is ahead of this binary" after rebuilding the
183
+ platform: install through the server with `--url`.
184
+ - Don't keep scaffold copies of a module (same `code`, another `moduleId`)
185
+ anywhere a folder publish can pick them up.
186
+
159
187
  **If install fails on auth (401/403) or connectivity (refused):** that is
160
188
  credentials/connectivity, NOT a module defect. Tell the user to verify
161
189
  `DFORGE_URL` and `DFORGE_TOKEN`. **Do not backtrack** to earlier phases.
@@ -1,35 +0,0 @@
1
- {
2
- "entities": {
3
- "product": {
4
- "label": "Product",
5
- "fields": {
6
- "name": { "label": "Product Name" },
7
- "sku": { "label": "SKU" },
8
- "quantity": { "label": "Quantity On Hand" },
9
- "cost_price": { "label": "Cost Price" },
10
- "sale_price": { "label": "Sale Price" },
11
- "margin": { "label": "Margin" }
12
- }
13
- }
14
- },
15
- "folders": {
16
- "warehouse": { "label": "Warehouse" },
17
- "finance": { "label": "Finance" }
18
- },
19
- "roles": {
20
- "column_security.storekeeper": { "label": "Storekeeper" },
21
- "column_security.accountant": { "label": "Accountant" }
22
- },
23
- "views": {
24
- "product_grid": { "label": "Products" }
25
- },
26
- "menus": {
27
- "product_menu": {
28
- "label": "Products",
29
- "items": {
30
- "catalog": { "label": "Catalog" },
31
- "all_products": { "label": "Products" }
32
- }
33
- }
34
- }
35
- }