Audience: Contributor
Short goal → verify → failure recipes for page JSON and schema changes. Details live on the linked contract pages.
See also: Page structure · Catalog · Operations
Each recipe: goal → files → before/after → commands → verify → failure mode. Start from catalogElementToInstance semantics and the catalog’s children / insert-inside columns.
| MUST | MUST NOT |
|---|---|
Use exact type strings from the Catalog |
Invent tags or camelCase variants |
| Nest only what the parent’s schema children allow | Put string/HTML as a child (use text) |
| Omit empty props/actions/validators/children | Persist id, lkt_id, or builder version |
Prefer raw or expr on a value |
Serialize expanded children on lkt-block |
Run schemas:page after adding element folders |
Rely on warn-only AJV to catch mistakes |
| Restart API after schema changes so DbInit refreshes | Assume allowed_child_elements DB column is authoritative |
Run schemas:wiki-catalog after type inventory changes |
Retype a node in place when props/children no longer fit |
Goal: Add a default instance as the builder would.
How: Copy the element’s children.json as the starting children of a node with the correct type, or use { type } only when there is no children.json.
After (button): use the tree in Page structure.
Verify: type ∈ catalog; insert-inside parents have catalog children defined.
Failure: Empty prop values from the catalog must be omitted (catalogElementToInstance drops them).
Goal: Place text under ion-button.
Check catalog: ion-button schema children includes text.
Failure: Nesting ion-grid under ion-button fails schema children (restricted list).
html raw value{ "name": "html", "value": { "raw": "New label" } }
Do not replace the whole text node with a string.
{ "name": "fill", "type": "attr", "value": { "raw": "outline" } }{ "name": "justify-content", "type": "css", "value": { "raw": "center" } }Prefer shared layout $ref props over one-off style bags.
expr instead of raw{ "name": "html", "value": { "expr": "record.name" } }
Do not leave a stale raw alongside and expect a merge.
Add an actions entry using a real shape from action.model.ts (e.g. navigate-to-page with event: "click"). Do not invent event names.
On a css prop:
{
"name": "padding",
"type": "css",
"value": { "raw": "8px" },
"responsive": {
"tablet": { "value": { "raw": "16px" } },
"desktop": { "value": { "raw": "24px" } }
}
}
Parent must allow the grid host; grid host must allow row/col per catalog. Move prior siblings under the new wrapper — do not leave duplicates.
lkt-for-eachWrap the repeated item as its child per catalog / children.json. Keep data-source props on the for-each node, not only on descendants.
lkt-block instance{
"type": "lkt-block",
"props": [
{ "name": "blockId", "type": "none", "value": { "raw": 42 } }
]
}
MUST NOT embed the master’s expanded children.
Changing "type": "ion-button" → "ion-item" in place almost always leaves illegal props/children. Delete + insert instead.
{ "$ref": "https://lokakrt.com/schema/.../schema.json" } to the parent’s properties.children.items.anyOfcd server/main && npm run schemas:pagenpm run schemas:wiki-catalogion-button$defs.name.const and rawType enum (or $ref)&def.selectOptions / let DbInit infer from enumSet "&def": { "palette": false } (keep nesting). Restart API.
"&def": {
"render": { "kind": "component", "tag": "lkt-my-widget", "children": true }
}
Then npm run schemas:page, restart, npm run generate:templates from repo root, schemas:wiki-catalog.