@sparkerp/plugin-sdk 0.1.0

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 (142) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/bundle/blocks.json +12229 -0
  4. package/bundle/catalog.json +32475 -0
  5. package/bundle/docs/applications/hcm/employee-profile.md +144 -0
  6. package/bundle/docs/concepts/artifact-types.md +80 -0
  7. package/bundle/docs/concepts/glossary.md +124 -0
  8. package/bundle/docs/concepts/overview.md +88 -0
  9. package/bundle/docs/concepts/page-data-flow.md +84 -0
  10. package/bundle/docs/concepts/plugin-lifecycle.md +81 -0
  11. package/bundle/docs/concepts/sdk-modes.md +71 -0
  12. package/bundle/docs/docs.json +109 -0
  13. package/bundle/docs/guides/add-a-cadence-job.md +134 -0
  14. package/bundle/docs/guides/add-a-compliance-job.md +139 -0
  15. package/bundle/docs/guides/add-a-create-edit-form.md +167 -0
  16. package/bundle/docs/guides/add-a-custom-block.md +225 -0
  17. package/bundle/docs/guides/add-a-data-provider.md +216 -0
  18. package/bundle/docs/guides/add-a-kpi.md +154 -0
  19. package/bundle/docs/guides/add-a-reminder-job.md +181 -0
  20. package/bundle/docs/guides/add-an-aggregation-job.md +153 -0
  21. package/bundle/docs/guides/add-an-approval-workflow.md +222 -0
  22. package/bundle/docs/guides/add-an-entity.md +170 -0
  23. package/bundle/docs/guides/add-business-rules.md +147 -0
  24. package/bundle/docs/guides/add-i18n.md +121 -0
  25. package/bundle/docs/guides/add-menus.md +149 -0
  26. package/bundle/docs/guides/build-a-code-plugin.md +141 -0
  27. package/bundle/docs/guides/build-a-page.md +213 -0
  28. package/bundle/docs/guides/build-a-polyglot-service-plugin.md +201 -0
  29. package/bundle/docs/guides/build-a-tenant-extension-service.md +149 -0
  30. package/bundle/docs/guides/create-a-plugin.md +172 -0
  31. package/bundle/docs/guides/expose-a-plugin-operation-as-an-ai-tool.md +114 -0
  32. package/bundle/docs/guides/index.md +53 -0
  33. package/bundle/docs/guides/publish-and-upgrade.md +152 -0
  34. package/bundle/docs/guides/responsive-plugin.md +117 -0
  35. package/bundle/docs/guides/seed-data-on-install.md +127 -0
  36. package/bundle/docs/guides/set-up-the-sdk.md +120 -0
  37. package/bundle/docs/guides/use-the-mcp-server.md +104 -0
  38. package/bundle/docs/guides/validate-and-test.md +119 -0
  39. package/bundle/docs/guides/wire-a-pages-data.md +153 -0
  40. package/bundle/docs/quickstart/index.md +118 -0
  41. package/bundle/docs/recipes/condition-based-compliance-flag.md +70 -0
  42. package/bundle/docs/recipes/cross-plugin-offboarding-fanout.md +68 -0
  43. package/bundle/docs/recipes/cross-row-aggregation.md +67 -0
  44. package/bundle/docs/recipes/entity-with-approval-workflow.md +72 -0
  45. package/bundle/docs/recipes/index.md +40 -0
  46. package/bundle/docs/recipes/install-lifecycle-data-seeding.md +76 -0
  47. package/bundle/docs/recipes/kpi-dashboard-page.md +92 -0
  48. package/bundle/docs/recipes/master-detail-page.md +84 -0
  49. package/bundle/docs/recipes/n-days-before-reminder.md +72 -0
  50. package/bundle/docs/recipes/per-row-document-generator.md +65 -0
  51. package/bundle/docs/recipes/related-record-lookup-field.md +74 -0
  52. package/bundle/docs/recipes/resolved-reference-grid-column.md +63 -0
  53. package/bundle/docs/recipes/scheduled-record-generation.md +68 -0
  54. package/bundle/docs/reference/action.md +18 -0
  55. package/bundle/docs/reference/block-definition.md +35 -0
  56. package/bundle/docs/reference/block-instance.md +33 -0
  57. package/bundle/docs/reference/composite-pack-manifest.md +33 -0
  58. package/bundle/docs/reference/connector-definition.md +36 -0
  59. package/bundle/docs/reference/data-service-definition.md +22 -0
  60. package/bundle/docs/reference/data-view-definition.md +26 -0
  61. package/bundle/docs/reference/entity-aggregation-config.md +38 -0
  62. package/bundle/docs/reference/entity-cadence-config.md +33 -0
  63. package/bundle/docs/reference/entity-compliance-config.md +37 -0
  64. package/bundle/docs/reference/entity-cross-plugin-action-config.md +37 -0
  65. package/bundle/docs/reference/entity-definition.md +18 -0
  66. package/bundle/docs/reference/entity-document-generator-config.md +35 -0
  67. package/bundle/docs/reference/entity-rule-definition.md +29 -0
  68. package/bundle/docs/reference/entity-status-date-sweep-config.md +27 -0
  69. package/bundle/docs/reference/form.md +35 -0
  70. package/bundle/docs/reference/index.md +37 -0
  71. package/bundle/docs/reference/menu.md +26 -0
  72. package/bundle/docs/reference/mobile-nav.md +22 -0
  73. package/bundle/docs/reference/page-template.md +29 -0
  74. package/bundle/docs/reference/page.md +44 -0
  75. package/bundle/docs/reference/plugin-manifest.md +54 -0
  76. package/bundle/docs/reference/plugin-seed-data.md +21 -0
  77. package/bundle/docs/reference/print-template-definition.md +22 -0
  78. package/bundle/docs/reference/provider-definition.md +22 -0
  79. package/bundle/docs/reference/report-definition.md +22 -0
  80. package/bundle/docs/reference/rule.md +30 -0
  81. package/bundle/docs/reference/workflow-definition.md +28 -0
  82. package/bundle/docs/troubleshooting/grid-shows-zero-rows.md +82 -0
  83. package/bundle/docs/troubleshooting/job-never-runs.md +89 -0
  84. package/bundle/docs/troubleshooting/known-gotchas.md +193 -0
  85. package/bundle/docs/troubleshooting/publishing-errors.md +119 -0
  86. package/bundle/docs/tutorial/01-create-the-plugin.md +157 -0
  87. package/bundle/docs/tutorial/02-category-entity-and-seed.md +94 -0
  88. package/bundle/docs/tutorial/03-equipment-entity.md +121 -0
  89. package/bundle/docs/tutorial/04-equipment-register-page.md +153 -0
  90. package/bundle/docs/tutorial/05-checkout-entity-and-page.md +116 -0
  91. package/bundle/docs/tutorial/06-checkout-approval-workflow.md +139 -0
  92. package/bundle/docs/tutorial/07-return-due-reminder-job.md +129 -0
  93. package/bundle/docs/tutorial/08-menus-i18n-publish.md +123 -0
  94. package/bundle/docs/tutorial/index.md +72 -0
  95. package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-aggregation-configs.json +18 -0
  96. package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-cadence-configs.json +9 -0
  97. package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-compliance-configs.json +74 -0
  98. package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-cross-plugin-configs.json +9 -0
  99. package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-document-configs.json +9 -0
  100. package/bundle/examples/backend/modules/hcm-assets/spk-assembly/metadata/seed-data/hcm-assets-sweep-configs.json +260 -0
  101. package/bundle/examples/backend/modules/hcm-compensation/spk-assembly/metadata/entities/hcm_salary_planning_entry.json +41 -0
  102. package/bundle/examples/backend/modules/hcm-compensation/spk-assembly/metadata/page/salary-planning.json +1 -0
  103. package/bundle/examples/backend/modules/hcm-employee/spk-assembly/metadata/page/promotions.json +10944 -0
  104. package/bundle/examples/backend/modules/hcm-employee/spk-assembly/metadata/workflow/employee.movement.json +44 -0
  105. package/bundle/examples/backend/modules/hcm-employee/spk-assembly/metadata/workflow/employee.promotion-acknowledge.json +19 -0
  106. package/bundle/examples/backend/modules/hcm-employee/spk-assembly/metadata/workflow/employee.resignation.json +29 -0
  107. package/bundle/examples/example-patterns.mjs +94 -0
  108. package/bundle/manifest.json +14 -0
  109. package/bundle/schemas/action.schema.json +102 -0
  110. package/bundle/schemas/block-definition.schema.json +222 -0
  111. package/bundle/schemas/block-instance.schema.json +139 -0
  112. package/bundle/schemas/composite-pack-manifest.schema.json +160 -0
  113. package/bundle/schemas/connector-definition.schema.json +29 -0
  114. package/bundle/schemas/data-service-definition.schema.json +64 -0
  115. package/bundle/schemas/data-view-definition.schema.json +148 -0
  116. package/bundle/schemas/entity-aggregation-config.schema.json +32 -0
  117. package/bundle/schemas/entity-cadence-config.schema.json +27 -0
  118. package/bundle/schemas/entity-compliance-config.schema.json +31 -0
  119. package/bundle/schemas/entity-cross-plugin-action-config.schema.json +31 -0
  120. package/bundle/schemas/entity-definition.schema.json +126 -0
  121. package/bundle/schemas/entity-document-generator-config.schema.json +29 -0
  122. package/bundle/schemas/entity-rule-definition.schema.json +75 -0
  123. package/bundle/schemas/entity-status-date-sweep-config.schema.json +21 -0
  124. package/bundle/schemas/form.schema.json +281 -0
  125. package/bundle/schemas/menu.schema.json +106 -0
  126. package/bundle/schemas/mobile-nav.schema.json +53 -0
  127. package/bundle/schemas/page-template.schema.json +46 -0
  128. package/bundle/schemas/page.schema.json +292 -0
  129. package/bundle/schemas/plugin-manifest.schema.json +139 -0
  130. package/bundle/schemas/plugin-seed-data.schema.json +24 -0
  131. package/bundle/schemas/print-template-definition.schema.json +90 -0
  132. package/bundle/schemas/provider-definition.schema.json +27 -0
  133. package/bundle/schemas/report-definition.schema.json +96 -0
  134. package/bundle/schemas/rule.schema.json +82 -0
  135. package/bundle/schemas/workflow-definition.schema.json +42 -0
  136. package/bundle/validators/block-engine.mjs +23222 -0
  137. package/bundle/validators/page-engine.mjs +21645 -0
  138. package/erp-cli/authoring-root.mjs +344 -0
  139. package/erp-cli/erp.mjs +4065 -0
  140. package/erp-cli/example-patterns.mjs +94 -0
  141. package/erp-mcp-server/index.mjs +818 -0
  142. package/package.json +27 -0
@@ -0,0 +1,106 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://erp.internal/schema/menu-definition.schema.json",
4
+ "title": "MenuDefinitionArtifact",
5
+ "description": "The real on-disk authoring shape for a menu JSON artifact (spk-assembly/metadata/menu/*.json) — confirmed against a live shipped file (crm-foundation-main-menu.json) and grounded in frontend/packages/menu-engine/src/types.ts's MenuDefinition/MenuNode. Top-level wrapper (name/description/definition/metadata/modules) matches every other JsonArtifactAuthoringController-backed artifact's persisted shape; `definition` is MenuDefinition verbatim (a real nested object, unlike workflow's string-encoded *Json columns).",
6
+ "type": "object",
7
+ "required": ["name", "definition"],
8
+ "properties": {
9
+ "name": { "type": "string" },
10
+ "description": { "type": "string" },
11
+ "metadata": { "type": "object" },
12
+ "modules": { "type": "array", "items": { "type": "string" } },
13
+ "definition": {
14
+ "type": "object",
15
+ "required": ["contractVersion", "name", "nodes"],
16
+ "properties": {
17
+ "contractVersion": { "type": "number", "enum": [1] },
18
+ "name": { "type": "string" },
19
+ "description": { "type": ["string", "null"] },
20
+ "tags": { "type": "array", "items": { "type": "string" } },
21
+ "nodes": { "type": "array", "items": { "$ref": "#/definitions/menuNode" } }
22
+ }
23
+ }
24
+ },
25
+ "definitions": {
26
+ "menuNode": {
27
+ "type": "object",
28
+ "required": ["id", "name", "displayName", "sequence", "children", "navigation", "visibility"],
29
+ "properties": {
30
+ "id": { "type": "string" },
31
+ "name": { "type": "string" },
32
+ "displayName": { "type": "string" },
33
+ "displayNameKey": { "type": ["string", "null"], "description": "i18n key resolved via io.t(...); displayName is the plain-text fallback, never itself passed through io.t." },
34
+ "description": { "type": ["string", "null"] },
35
+ "icon": { "type": ["string", "null"] },
36
+ "color": { "type": ["string", "null"] },
37
+ "tags": { "type": "array", "items": { "type": "string" } },
38
+ "sequence": { "type": "number" },
39
+ "children": { "type": "array", "items": { "$ref": "#/definitions/menuNode" } },
40
+ "navigation": {
41
+ "type": "object",
42
+ "required": ["openMode"],
43
+ "properties": {
44
+ "route": { "type": ["string", "null"] },
45
+ "targetPage": { "type": ["string", "null"] },
46
+ "openMode": { "type": "string", "enum": ["current-tab", "new-tab", "dialog", "drawer", "external-url"] },
47
+ "externalUrl": { "type": ["string", "null"], "description": "Only meaningful when openMode is external-url." }
48
+ }
49
+ },
50
+ "visibility": {
51
+ "type": "object",
52
+ "required": ["visible", "enabled", "devices"],
53
+ "properties": {
54
+ "visible": { "type": "boolean" },
55
+ "enabled": { "type": "boolean" },
56
+ "devices": { "type": "array", "items": { "type": "string", "enum": ["desktop", "mobile", "tablet"] }, "description": "Empty = visible on every device type." }
57
+ }
58
+ },
59
+ "security": {
60
+ "type": "object",
61
+ "properties": {
62
+ "roles": { "type": "array", "items": { "type": "string" } },
63
+ "permissionGroups": { "type": "array", "items": { "type": "string" } },
64
+ "featureFlags": { "type": "array", "items": { "type": "string" } },
65
+ "licenseRequirement": { "type": ["string", "null"] },
66
+ "tenantScope": { "type": ["string", "null"] },
67
+ "companyIds": { "type": "array", "items": { "type": "string" } },
68
+ "branchIds": { "type": "array", "items": { "type": "string" } },
69
+ "countryIds": { "type": "array", "items": { "type": "string" } },
70
+ "departmentIds": { "type": "array", "items": { "type": "string" } }
71
+ }
72
+ },
73
+ "badge": {
74
+ "type": "object",
75
+ "required": ["source"],
76
+ "properties": {
77
+ "source": { "type": "string", "enum": ["pending-approvals", "notification-count", "employee-count", "data-service", "custom-expression"] },
78
+ "expression": { "type": ["string", "null"], "description": "Only meaningful when source is custom-expression." },
79
+ "dataServiceName": { "type": ["string", "null"], "description": "Only meaningful when source is data-service — a published Data Service artifact's name." },
80
+ "color": { "type": ["string", "null"] }
81
+ }
82
+ },
83
+ "personalization": {
84
+ "type": "object",
85
+ "properties": {
86
+ "pinnable": { "type": "boolean" },
87
+ "defaultLanding": { "type": "boolean" },
88
+ "variants": { "type": "array", "items": { "type": "string" } }
89
+ }
90
+ },
91
+ "advanced": {
92
+ "type": "object",
93
+ "properties": {
94
+ "cache": { "type": "boolean" },
95
+ "lazyLoading": { "type": "boolean" },
96
+ "breadcrumb": { "type": "boolean" },
97
+ "seoName": { "type": ["string", "null"] },
98
+ "analyticsId": { "type": ["string", "null"] },
99
+ "cssClass": { "type": ["string", "null"] },
100
+ "metadataJson": { "type": ["string", "null"], "description": "Raw JSON text, an open escape hatch — not parsed into a typed shape here." }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://erp.internal/schema/mobile-nav-definition.schema.json",
4
+ "title": "MobileNavDefinitionArtifact",
5
+ "description": "The real on-disk authoring shape for a mobile-nav JSON artifact (spk-assembly/metadata/mobile_nav/*.json), grounded in frontend/packages/mobile-nav-engine/src/types.ts's MobileNavDefinition/MobileNavItem — confirmed against a live shipped file (hcm-mobile-nav.json). Deliberately a DISTINCT, narrower content model from menu-definition.schema.json, not a device filter on it: a FLAT item list (no children/tree, no security/badge/personalization/advanced blocks), because the two real mobile surfaces (a bottom tab row, a slide-out drawer) never nest, per the type file's own doc comment. `mode`/`items[].surfaces` are the mobile-specific fields this schema exists to capture — every menu-definition MenuNode field with no mobile-nav counterpart (children, security, badge, personalization, advanced, displayNameKey) is intentionally absent here, not merged in.",
6
+ "type": "object",
7
+ "required": ["name", "definition"],
8
+ "properties": {
9
+ "name": { "type": "string" },
10
+ "description": { "type": "string" },
11
+ "metadata": { "type": "object" },
12
+ "modules": { "type": "array", "items": { "type": "string" } },
13
+ "definition": {
14
+ "type": "object",
15
+ "required": ["contractVersion", "name", "mode", "items"],
16
+ "properties": {
17
+ "contractVersion": { "type": "number", "enum": [1] },
18
+ "name": { "type": "string" },
19
+ "description": { "type": ["string", "null"] },
20
+ "mode": {
21
+ "type": "string",
22
+ "enum": ["bottom-tabs", "drawer", "hybrid"],
23
+ "description": "\"hybrid\" is not a third render shape: a page authors TWO core.mobile-nav block instances against this same definition, one bottom-tabs one drawer — see mobile-nav-engine's own doc comment."
24
+ },
25
+ "items": {
26
+ "type": "array",
27
+ "items": {
28
+ "type": "object",
29
+ "required": ["id", "label"],
30
+ "properties": {
31
+ "id": { "type": "string" },
32
+ "label": { "type": "string" },
33
+ "icon": { "type": ["string", "null"] },
34
+ "color": { "type": ["string", "null"] },
35
+ "route": { "type": ["string", "null"] },
36
+ "badge": { "type": ["string", "null"] },
37
+ "isFab": { "type": "boolean", "description": "Raised primary action tile — bottom-tabs/hybrid only." },
38
+ "surfaces": {
39
+ "type": "array",
40
+ "items": { "type": "string", "enum": ["bottom-tabs", "drawer"] },
41
+ "description": "Which rendered surface(s) show this item. Absent/empty = every surface `mode` implies."
42
+ },
43
+ "companyIds": { "type": "array", "items": { "type": "string" } },
44
+ "branchIds": { "type": "array", "items": { "type": "string" } },
45
+ "countryIds": { "type": "array", "items": { "type": "string" } },
46
+ "departmentIds": { "type": "array", "items": { "type": "string" } }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://erp-platform.dev/schemas/page-template.schema.json",
4
+ "title": "Page template contract v1",
5
+ "description": "W1-04 (gap 1) - reusable page chrome with named slot placeholders a page fills. One level of inheritance max: a specialized template's `extends` is checked by PageTemplateRegistry.register against its parent (never itself extended) and its own slot names must be a subset of the parent's - the schema alone cannot express that cross-reference, so it is enforced at registration, not here. `kind: \"wizard\"` (added 2026-07-16, UI-designer audit) declares an ordered multi-step template: `steps` (required, >= 2) each reference a declared slot; the runtime shows one step at a time with beforeSave gating per step and a single deferred commit at the end.",
6
+ "type": "object",
7
+ "$defs": {
8
+ "identifier": { "type": "string", "minLength": 1, "maxLength": 128, "pattern": "^[A-Za-z_][A-Za-z0-9_-]*$" },
9
+ "i18nKey": { "type": "string", "minLength": 1, "maxLength": 256 },
10
+ "row": { "type": "object", "description": "Same shape as page-definition.schema.json's #/$defs/row." }
11
+ },
12
+ "properties": {
13
+ "contractVersion": { "const": 1 },
14
+ "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$" },
15
+ "version": { "type": "string", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$" },
16
+ "publisher": { "type": "string", "minLength": 1 },
17
+ "extends": { "type": "string" },
18
+ "kind": { "enum": ["standard", "wizard"] },
19
+ "steps": {
20
+ "type": "array",
21
+ "minItems": 2,
22
+ "items": {
23
+ "type": "object",
24
+ "properties": { "slot": { "$ref": "#/$defs/identifier" }, "titleKey": { "$ref": "#/$defs/i18nKey" } },
25
+ "required": ["slot", "titleKey"],
26
+ "additionalProperties": false
27
+ }
28
+ },
29
+ "regions": {
30
+ "type": "array",
31
+ "items": {
32
+ "oneOf": [
33
+ { "type": "object", "properties": { "region": { "const": "row" }, "row": { "$ref": "#/$defs/row" } }, "required": ["region", "row"], "additionalProperties": false },
34
+ {
35
+ "type": "object",
36
+ "properties": { "region": { "const": "slot" }, "name": { "$ref": "#/$defs/identifier" }, "required": { "type": "boolean" } },
37
+ "required": ["region", "name"],
38
+ "additionalProperties": false
39
+ }
40
+ ]
41
+ }
42
+ }
43
+ },
44
+ "required": ["contractVersion", "id", "version", "publisher", "regions"],
45
+ "additionalProperties": false
46
+ }
@@ -0,0 +1,292 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://erp-platform.dev/schemas/page-definition.schema.json",
4
+ "title": "Page definition contract v1",
5
+ "description": "The machine-readable contract a page ships (W1-03, ADR 0012): the Page -> Row -> Column -> Item layout taxonomy (an item is a block instance, an embedded form instance, or a link/shortcut - gap 10's dashboard seam), per-node visibility rules, the mobile-first breakpoint shape (still no `xs` key inside breakpointOverrides itself - sm/md/lg/xl only), the route + typed-parameter + menu + breadcrumb contract (gaps 2/3), and variant overrides (gap 4). 2026-07-31: a row/column/item's own `hiddenAtXs` (sibling to `breakpoints`, not part of breakpointOverrides) is now the way desktop-only-shown content is expressed - authored hidden at xs, re-shown via a wider breakpoint's `hidden: false` override. Cross-references (does a blockType/formId actually resolve, does a template/slot exist) are checked by validatePageDefinition/resolveTemplate against the live registries, not by this schema alone - the same division of labor block-definition.schema.json and form-definition.schema.json already draw.",
6
+ "type": "object",
7
+ "$defs": {
8
+ "identifier": {
9
+ "type": "string",
10
+ "minLength": 1,
11
+ "maxLength": 128,
12
+ "pattern": "^[A-Za-z_][A-Za-z0-9_-]*$"
13
+ },
14
+ "i18nKey": {
15
+ "type": "string",
16
+ "minLength": 1,
17
+ "maxLength": 256
18
+ },
19
+ "expression": {
20
+ "type": "string",
21
+ "minLength": 1,
22
+ "maxLength": 2000
23
+ },
24
+ "breakpointOverride": {
25
+ "type": "object",
26
+ "properties": {
27
+ "hidden": { "type": "boolean" },
28
+ "span": { "type": "integer", "minimum": 1, "maximum": 12 }
29
+ },
30
+ "additionalProperties": false
31
+ },
32
+ "breakpointOverrides": {
33
+ "description": "Mobile-first: no `xs` key exists here at all - xs's own hidden state is instead the sibling `hiddenAtXs` property on the item/column/row itself (2026-07-31), not a key of this shape.",
34
+ "type": "object",
35
+ "properties": {
36
+ "sm": { "$ref": "#/$defs/breakpointOverride" },
37
+ "md": { "$ref": "#/$defs/breakpointOverride" },
38
+ "lg": { "$ref": "#/$defs/breakpointOverride" },
39
+ "xl": { "$ref": "#/$defs/breakpointOverride" }
40
+ },
41
+ "additionalProperties": false
42
+ },
43
+ "linkItem": {
44
+ "type": "object",
45
+ "properties": {
46
+ "route": { "type": "string", "minLength": 1 },
47
+ "params": { "type": "object" },
48
+ "labelKey": { "$ref": "#/$defs/i18nKey" },
49
+ "icon": { "type": "string" }
50
+ },
51
+ "required": ["route", "labelKey"],
52
+ "additionalProperties": false
53
+ },
54
+ "item": {
55
+ "type": "object",
56
+ "properties": {
57
+ "id": { "$ref": "#/$defs/identifier" },
58
+ "kind": { "enum": ["block", "form", "link"] },
59
+ "visibility": { "$ref": "#/$defs/expression" },
60
+ "block": { "type": "object", "description": "A frozen @erp/block-engine BlockInstanceConfig - validated against the live BlockRegistry at mount time." },
61
+ "form": { "type": "object", "description": "A frozen @erp/form-engine FormInstanceConfig - validated against the live FormRegistry at mount time." },
62
+ "link": { "$ref": "#/$defs/linkItem" },
63
+ "breakpoints": { "$ref": "#/$defs/breakpointOverrides" },
64
+ "itemRef": { "type": "string", "minLength": 1, "description": "References an existing xs item's id (2026-07-19) - set on a non-xs breakpoint's own arrangement tree for the default 'Shared' rearrangement case; a Detached breakpoint's tree instead holds items with real block/form/link content and no itemRef at all." },
65
+ "hiddenAtXs": { "type": "boolean", "description": "2026-07-31 - whether this item starts hidden at the mobile-first base breakpoint; absent/false = unchanged default (visible at xs). Lets a wider breakpoint's `breakpoints.{bp}.hidden = false` legitimately re-show it." }
66
+ },
67
+ "required": ["id", "kind"],
68
+ "additionalProperties": false
69
+ },
70
+ "column": {
71
+ "type": "object",
72
+ "properties": {
73
+ "id": { "$ref": "#/$defs/identifier" },
74
+ "name": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Author-facing display name (optional) - never read at runtime, purely for Studio's own labeling." },
75
+ "items": { "type": "array", "items": { "$ref": "#/$defs/item" } },
76
+ "visibility": { "$ref": "#/$defs/expression" },
77
+ "breakpoints": { "$ref": "#/$defs/breakpointOverrides" },
78
+ "hiddenAtXs": { "type": "boolean", "description": "2026-07-31 - ItemSpec.hiddenAtXs's identical sibling, one level up." },
79
+ "spanAtXs": { "type": "integer", "minimum": 1, "maximum": 12, "description": "2026-07-31 - a real, independent Bootstrap-style col-{n} for xs, not a mirror of breakpoints.sm.span. Absent = existing mirror-sm default." },
80
+ "card": {
81
+ "type": "object",
82
+ "properties": {
83
+ "maxWidth": { "type": "number" },
84
+ "padding": { "type": "number" },
85
+ "elevation": { "type": "number" }
86
+ },
87
+ "additionalProperties": false
88
+ }
89
+ },
90
+ "required": ["id", "items"],
91
+ "additionalProperties": false
92
+ },
93
+ "row": {
94
+ "type": "object",
95
+ "properties": {
96
+ "id": { "$ref": "#/$defs/identifier" },
97
+ "name": { "type": "string", "minLength": 1, "maxLength": 128, "description": "Author-facing display name (optional) - never read at runtime, purely for Studio's own labeling." },
98
+ "columns": { "type": "array", "items": { "$ref": "#/$defs/column" } },
99
+ "visibility": { "$ref": "#/$defs/expression" },
100
+ "breakpoints": { "$ref": "#/$defs/breakpointOverrides" },
101
+ "defer": { "type": "boolean", "description": "Deferred/lazy region mounting (gap 6) - items are not mounted until first activation." },
102
+ "align": { "enum": ["start", "center", "end"], "description": "Horizontal placement of this row's column(s) within the full row width (2026-07-17) - e.g. a login card anchored to one side of a full-bleed background." },
103
+ "edgeSpacing": { "type": "number", "description": "Pixels of breathing room off the viewport edge a start/end align anchors to (2026-07-17) - authored data, not a fixed adapter constant." },
104
+ "hiddenAtXs": { "type": "boolean", "description": "2026-07-31 - ItemSpec.hiddenAtXs's identical sibling, one level up." }
105
+ },
106
+ "required": ["id", "columns"],
107
+ "additionalProperties": false
108
+ },
109
+ "routeParam": {
110
+ "type": "object",
111
+ "properties": {
112
+ "name": { "$ref": "#/$defs/identifier" },
113
+ "type": { "enum": ["string", "number", "boolean", "date"] },
114
+ "required": { "type": "boolean" }
115
+ },
116
+ "required": ["name", "type", "required"],
117
+ "additionalProperties": false
118
+ },
119
+ "route": {
120
+ "type": "object",
121
+ "properties": {
122
+ "pattern": { "type": "string", "minLength": 1, "pattern": "^/" },
123
+ "params": { "type": "array", "items": { "$ref": "#/$defs/routeParam" } }
124
+ },
125
+ "required": ["pattern", "params"],
126
+ "additionalProperties": false
127
+ },
128
+ "menuEntry": {
129
+ "type": "object",
130
+ "properties": {
131
+ "area": { "type": "string", "minLength": 1 },
132
+ "group": { "type": "string", "minLength": 1 },
133
+ "labelKey": { "$ref": "#/$defs/i18nKey" },
134
+ "icon": { "type": "string" },
135
+ "visibility": { "$ref": "#/$defs/expression" },
136
+ "order": { "type": "number" }
137
+ },
138
+ "required": ["area", "group", "labelKey"],
139
+ "additionalProperties": false
140
+ },
141
+ "breadcrumb": {
142
+ "type": "object",
143
+ "properties": {
144
+ "labelExpression": { "$ref": "#/$defs/expression" },
145
+ "parentRoute": { "type": "string" }
146
+ },
147
+ "required": ["labelExpression"],
148
+ "additionalProperties": false
149
+ },
150
+ "overlay": {
151
+ "description": "Overlay surface (2026-07-16 UI-designer audit): a modal/drawer region hosting composed rows, opened/closed by openDialog/closeDialog intents and rendered through the Phase 5 dialog render-node contract.",
152
+ "type": "object",
153
+ "properties": {
154
+ "id": { "$ref": "#/$defs/identifier" },
155
+ "kind": { "enum": ["modal", "drawer"] },
156
+ "titleKey": { "$ref": "#/$defs/i18nKey" },
157
+ "rows": { "type": "array", "items": { "$ref": "#/$defs/row" } },
158
+ "dismissible": { "type": "boolean" }
159
+ },
160
+ "required": ["id", "kind", "titleKey", "rows"],
161
+ "additionalProperties": false
162
+ },
163
+ "variantRule": {
164
+ "type": "object",
165
+ "properties": {
166
+ "role": { "type": "string" },
167
+ "deviceClass": {
168
+ "oneOf": [
169
+ { "enum": ["mobile", "tablet", "desktop"] },
170
+ { "type": "array", "items": { "enum": ["mobile", "tablet", "desktop"] }, "minItems": 1 }
171
+ ]
172
+ },
173
+ "paramEquals": { "type": "object", "additionalProperties": { "type": "string" } }
174
+ },
175
+ "additionalProperties": false
176
+ },
177
+ "variant": {
178
+ "type": "object",
179
+ "properties": {
180
+ "id": { "$ref": "#/$defs/identifier" },
181
+ "rule": { "$ref": "#/$defs/variantRule" },
182
+ "template": { "type": "string" },
183
+ "slotFills": { "type": "object", "additionalProperties": { "type": "array", "items": { "$ref": "#/$defs/row" } } },
184
+ "rows": { "type": "array", "items": { "$ref": "#/$defs/row" } }
185
+ },
186
+ "required": ["id", "rule"],
187
+ "additionalProperties": false
188
+ },
189
+ "canvasPosition": {
190
+ "description": "Freeform placement envelope (2026-07-24, mirrors @erp/block-engine's own canvas position shape) - authored data, not derived from flow layout.",
191
+ "type": "object",
192
+ "properties": {
193
+ "x": { "type": "number" },
194
+ "y": { "type": "number" },
195
+ "width": { "type": "number" },
196
+ "height": { "type": "number" },
197
+ "zIndex": { "type": "number" },
198
+ "hidden": { "type": "boolean" }
199
+ },
200
+ "required": ["x", "y", "width", "height"],
201
+ "additionalProperties": false
202
+ },
203
+ "canvasItem": {
204
+ "description": "One freeform-placed item in a Page's own `canvas` (2026-07-24, the same 'Free canvas' mode @erp/block-engine already has) - `item` reuses the same `item` $def a row's own column places, just positioned absolutely instead of flowed.",
205
+ "type": "object",
206
+ "properties": {
207
+ "id": { "$ref": "#/$defs/identifier" },
208
+ "item": { "$ref": "#/$defs/item" },
209
+ "position": { "$ref": "#/$defs/canvasPosition" },
210
+ "locked": { "type": "boolean" }
211
+ },
212
+ "required": ["id", "item", "position"],
213
+ "additionalProperties": false
214
+ },
215
+ "designer": {
216
+ "type": "object",
217
+ "properties": {
218
+ "displayNameKey": { "$ref": "#/$defs/i18nKey" },
219
+ "descriptionKey": { "$ref": "#/$defs/i18nKey" },
220
+ "icon": { "type": "string" },
221
+ "category": { "enum": ["workspace", "detail", "list", "dashboard", "landing"] },
222
+ "propertyGroups": {
223
+ "type": "array",
224
+ "items": {
225
+ "type": "object",
226
+ "properties": { "id": { "type": "string" }, "titleKey": { "$ref": "#/$defs/i18nKey" } },
227
+ "required": ["id", "titleKey"],
228
+ "additionalProperties": false
229
+ }
230
+ },
231
+ "preview": {
232
+ "type": "object",
233
+ "properties": { "kind": { "const": "page" }, "sampleKey": { "type": "string" } },
234
+ "required": ["kind"],
235
+ "additionalProperties": false
236
+ }
237
+ },
238
+ "required": ["displayNameKey", "descriptionKey", "icon", "category", "propertyGroups", "preview"],
239
+ "additionalProperties": false
240
+ },
241
+ "devicePersistenceRule": {
242
+ "type": "object",
243
+ "description": "\"Remember this across a browser restart\" (2026-08-13) — see PageDefinition.devicePersistence's own doc comment (page-engine/src/types.ts). Runtime-host contract only, not read by this package.",
244
+ "properties": {
245
+ "valueScope": { "enum": ["page", "form"] },
246
+ "valueKey": { "type": "string", "minLength": 1 },
247
+ "valueScopeKey": { "type": "string", "minLength": 1 },
248
+ "enabledScope": { "enum": ["page", "form"] },
249
+ "enabledKey": { "type": "string", "minLength": 1 },
250
+ "enabledScopeKey": { "type": "string", "minLength": 1 },
251
+ "triggerScope": { "enum": ["session", "page", "form"] },
252
+ "triggerKey": { "type": "string", "minLength": 1 },
253
+ "triggerScopeKey": { "type": "string", "minLength": 1 },
254
+ "storageKey": { "type": "string", "minLength": 1 }
255
+ },
256
+ "required": ["valueScope", "valueKey", "triggerScope", "triggerKey", "storageKey"],
257
+ "additionalProperties": false
258
+ }
259
+ },
260
+ "properties": {
261
+ "contractVersion": { "const": 1 },
262
+ "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$" },
263
+ "version": { "type": "string", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$" },
264
+ "publisher": { "type": "string", "minLength": 1 },
265
+ "title": { "$ref": "#/$defs/i18nKey" },
266
+ "template": { "type": "string" },
267
+ "slotFills": { "type": "object", "additionalProperties": { "type": "array", "items": { "$ref": "#/$defs/row" } } },
268
+ "rows": { "type": "array", "items": { "$ref": "#/$defs/row" } },
269
+ "canvas": { "type": "array", "items": { "$ref": "#/$defs/canvasItem" } },
270
+ "overlays": { "type": "array", "items": { "$ref": "#/$defs/overlay" } },
271
+ "theme": {
272
+ "type": "object",
273
+ "properties": { "tokenSetId": { "type": "string" }, "overrides": { "type": "object" } },
274
+ "required": ["tokenSetId"],
275
+ "additionalProperties": false
276
+ },
277
+ "route": { "$ref": "#/$defs/route" },
278
+ "menu": { "$ref": "#/$defs/menuEntry" },
279
+ "breadcrumb": { "$ref": "#/$defs/breadcrumb" },
280
+ "variants": { "type": "array", "items": { "$ref": "#/$defs/variant" } },
281
+ "designer": { "$ref": "#/$defs/designer" },
282
+ "access": { "type": "string", "enum": ["public", "authenticated"] },
283
+ "backgroundImageUrl": { "type": "string" },
284
+ "isLoginPage": { "type": "boolean" },
285
+ "isSessionExpiredPage": { "type": "boolean" },
286
+ "isAccessDeniedPage": { "type": "boolean" },
287
+ "devicePersistence": { "type": "array", "items": { "$ref": "#/$defs/devicePersistenceRule" } },
288
+ "modules": { "type": "array", "items": { "type": "string" }, "description": "App-module ids this page belongs to (e.g. \"hcm-foundation-home\") - TenantPageHost's per-module page listing only surfaces a page if it's present here. Load-bearing in practice for any page reached via the app sidenav/module shell, even on pages authored before this was documented." }
289
+ },
290
+ "required": ["contractVersion", "id", "version", "publisher", "title", "rows", "route", "designer"],
291
+ "additionalProperties": false
292
+ }