@sparkerp/plugin-sdk 0.1.0 → 1.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.
- package/bundle/blocks.json +169 -0
- package/bundle/catalog.json +614 -5
- package/bundle/docs/applications/hcm/admin-access-policies.md +38 -0
- package/bundle/docs/applications/hcm/admin-approval-hierarchies.md +33 -0
- package/bundle/docs/applications/hcm/admin-data-transfer.md +40 -0
- package/bundle/docs/applications/hcm/admin-hcm-users.md +35 -0
- package/bundle/docs/applications/hcm/admin-logs.md +57 -0
- package/bundle/docs/applications/hcm/admin-permissions-catalog.md +64 -0
- package/bundle/docs/applications/hcm/ai-intelligence-overview.md +37 -0
- package/bundle/docs/applications/hcm/ai-intelligence-people-risk.md +33 -0
- package/bundle/docs/applications/hcm/ai-intelligence-recruitment.md +37 -0
- package/bundle/docs/applications/hcm/ai-intelligence-tools.md +39 -0
- package/bundle/docs/applications/hcm/analytics-operations.md +31 -0
- package/bundle/docs/applications/hcm/analytics-overview.md +31 -0
- package/bundle/docs/applications/hcm/analytics-people.md +36 -0
- package/bundle/docs/applications/hcm/analytics-tools.md +35 -0
- package/bundle/docs/applications/hcm/assets-audits.md +30 -0
- package/bundle/docs/applications/hcm/assets-custody.md +36 -0
- package/bundle/docs/applications/hcm/assets-inventory.md +34 -0
- package/bundle/docs/applications/hcm/assets-maintenance.md +25 -0
- package/bundle/docs/applications/hcm/assets-software-licenses.md +27 -0
- package/bundle/docs/applications/hcm/attendance-core.md +46 -0
- package/bundle/docs/applications/hcm/attendance-exceptions.md +33 -0
- package/bundle/docs/applications/hcm/attendance-location.md +25 -0
- package/bundle/docs/applications/hcm/attendance-policies.md +23 -0
- package/bundle/docs/applications/hcm/attendance-reports.md +20 -0
- package/bundle/docs/applications/hcm/benefits-allowances.md +39 -0
- package/bundle/docs/applications/hcm/benefits-analytics.md +25 -0
- package/bundle/docs/applications/hcm/benefits-employee-processes.md +44 -0
- package/bundle/docs/applications/hcm/benefits-plans.md +60 -0
- package/bundle/docs/applications/hcm/communications-announcements.md +31 -0
- package/bundle/docs/applications/hcm/communications-history.md +23 -0
- package/bundle/docs/applications/hcm/communications-notifications.md +31 -0
- package/bundle/docs/applications/hcm/communications-templates.md +35 -0
- package/bundle/docs/applications/hcm/compensation-allowances-benefits.md +44 -0
- package/bundle/docs/applications/hcm/compensation-analytics.md +30 -0
- package/bundle/docs/applications/hcm/compensation-bonus-incentive.md +35 -0
- package/bundle/docs/applications/hcm/compensation-cycles.md +49 -0
- package/bundle/docs/applications/hcm/compensation-equity.md +36 -0
- package/bundle/docs/applications/hcm/compensation-salary.md +47 -0
- package/bundle/docs/applications/hcm/compliance-analytics.md +25 -0
- package/bundle/docs/applications/hcm/compliance-audits-calendar.md +52 -0
- package/bundle/docs/applications/hcm/compliance-employee-tracking.md +41 -0
- package/bundle/docs/applications/hcm/compliance-regulatory.md +31 -0
- package/bundle/docs/applications/hcm/country-packs-admin.md +33 -0
- package/bundle/docs/applications/hcm/country-packs-compliance.md +31 -0
- package/bundle/docs/applications/hcm/country-packs-localization.md +37 -0
- package/bundle/docs/applications/hcm/country-packs.md +102 -0
- package/bundle/docs/applications/hcm/dashboard.md +38 -0
- package/bundle/docs/applications/hcm/designations.md +32 -0
- package/bundle/docs/applications/hcm/emp-documents-analytics.md +27 -0
- package/bundle/docs/applications/hcm/emp-documents-core.md +33 -0
- package/bundle/docs/applications/hcm/emp-documents-letters.md +40 -0
- package/bundle/docs/applications/hcm/emp-documents-signatures.md +24 -0
- package/bundle/docs/applications/hcm/emp-documents-workflow.md +32 -0
- package/bundle/docs/applications/hcm/employee-directory.md +55 -0
- package/bundle/docs/applications/hcm/employee-documents.md +51 -0
- package/bundle/docs/applications/hcm/employee-info-background.md +41 -0
- package/bundle/docs/applications/hcm/employee-info-core.md +32 -0
- package/bundle/docs/applications/hcm/employee-info-documents.md +31 -0
- package/bundle/docs/applications/hcm/employee-info-other.md +21 -0
- package/bundle/docs/applications/hcm/employee-movements.md +46 -0
- package/bundle/docs/applications/hcm/employee-reference-data.md +40 -0
- package/bundle/docs/applications/hcm/employee-relations-analytics.md +23 -0
- package/bundle/docs/applications/hcm/employee-relations-cases.md +26 -0
- package/bundle/docs/applications/hcm/employee-relations-conflicts-feedback.md +25 -0
- package/bundle/docs/applications/hcm/employee-relations-processes.md +34 -0
- package/bundle/docs/applications/hcm/employee-services-daily.md +32 -0
- package/bundle/docs/applications/hcm/employee-services-manager.md +21 -0
- package/bundle/docs/applications/hcm/employee-services-more.md +41 -0
- package/bundle/docs/applications/hcm/employee-services-overview.md +43 -0
- package/bundle/docs/applications/hcm/expenses-analytics.md +26 -0
- package/bundle/docs/applications/hcm/expenses-claims.md +47 -0
- package/bundle/docs/applications/hcm/expenses-self-service.md +30 -0
- package/bundle/docs/applications/hcm/expenses-setup.md +43 -0
- package/bundle/docs/applications/hcm/health-safety-analytics.md +26 -0
- package/bundle/docs/applications/hcm/health-safety-incidents.md +44 -0
- package/bundle/docs/applications/hcm/health-safety-medical.md +25 -0
- package/bundle/docs/applications/hcm/health-safety-training.md +29 -0
- package/bundle/docs/applications/hcm/helpdesk.md +28 -0
- package/bundle/docs/applications/hcm/holiday-calendar.md +69 -0
- package/bundle/docs/applications/hcm/hr-policies.md +64 -0
- package/bundle/docs/applications/hcm/hr-settings.md +98 -0
- package/bundle/docs/applications/hcm/index.md +272 -0
- package/bundle/docs/applications/hcm/industry-it-software.md +52 -0
- package/bundle/docs/applications/hcm/job-classifications.md +48 -0
- package/bundle/docs/applications/hcm/learning-analytics.md +21 -0
- package/bundle/docs/applications/hcm/learning-assessments.md +22 -0
- package/bundle/docs/applications/hcm/learning-catalog.md +25 -0
- package/bundle/docs/applications/hcm/learning-certifications.md +27 -0
- package/bundle/docs/applications/hcm/learning-enrollment.md +26 -0
- package/bundle/docs/applications/hcm/learning-instructors-providers.md +21 -0
- package/bundle/docs/applications/hcm/leave-accrual-and-carryforward.md +30 -0
- package/bundle/docs/applications/hcm/leave-balance.md +26 -0
- package/bundle/docs/applications/hcm/leave-dashboard-and-reports.md +32 -0
- package/bundle/docs/applications/hcm/leave-encashment.md +27 -0
- package/bundle/docs/applications/hcm/leave-requests.md +26 -0
- package/bundle/docs/applications/hcm/leave-setup.md +30 -0
- package/bundle/docs/applications/hcm/leave-team-and-calendar.md +25 -0
- package/bundle/docs/applications/hcm/navigation-and-approvals.md +61 -0
- package/bundle/docs/applications/hcm/offboarding-and-exit.md +71 -0
- package/bundle/docs/applications/hcm/onboarding-documents-verification.md +43 -0
- package/bundle/docs/applications/hcm/onboarding-orientation-probation.md +65 -0
- package/bundle/docs/applications/hcm/onboarding-overview.md +47 -0
- package/bundle/docs/applications/hcm/onboarding-provisioning-assets.md +48 -0
- package/bundle/docs/applications/hcm/onboarding-reports.md +38 -0
- package/bundle/docs/applications/hcm/onboarding-templates-checklists.md +38 -0
- package/bundle/docs/applications/hcm/onboarding-to-confirmation.md +45 -0
- package/bundle/docs/applications/hcm/org-structure.md +53 -0
- package/bundle/docs/applications/hcm/org-units.md +86 -0
- package/bundle/docs/applications/hcm/organizations.md +81 -0
- package/bundle/docs/applications/hcm/payroll-analytics.md +31 -0
- package/bundle/docs/applications/hcm/payroll-post-run.md +40 -0
- package/bundle/docs/applications/hcm/payroll-runs.md +30 -0
- package/bundle/docs/applications/hcm/payroll-setup.md +38 -0
- package/bundle/docs/applications/hcm/payroll-tax.md +21 -0
- package/bundle/docs/applications/hcm/payroll-transactions.md +43 -0
- package/bundle/docs/applications/hcm/performance-analytics.md +23 -0
- package/bundle/docs/applications/hcm/performance-appraisals.md +50 -0
- package/bundle/docs/applications/hcm/performance-continuous-feedback.md +23 -0
- package/bundle/docs/applications/hcm/performance-cycles-and-goals.md +54 -0
- package/bundle/docs/applications/hcm/performance-okrs.md +33 -0
- package/bundle/docs/applications/hcm/performance-pips.md +29 -0
- package/bundle/docs/applications/hcm/performance-ratings.md +25 -0
- package/bundle/docs/applications/hcm/positions.md +67 -0
- package/bundle/docs/applications/hcm/recruitment-agencies-and-sources.md +26 -0
- package/bundle/docs/applications/hcm/recruitment-analytics.md +25 -0
- package/bundle/docs/applications/hcm/recruitment-candidates.md +33 -0
- package/bundle/docs/applications/hcm/recruitment-offers.md +32 -0
- package/bundle/docs/applications/hcm/recruitment-pipeline.md +46 -0
- package/bundle/docs/applications/hcm/recruitment-requisitions-and-openings.md +33 -0
- package/bundle/docs/applications/hcm/roles-permissions.md +129 -0
- package/bundle/docs/applications/hcm/separation-clearance.md +33 -0
- package/bundle/docs/applications/hcm/separation-documents.md +28 -0
- package/bundle/docs/applications/hcm/separation-final-settlement.md +24 -0
- package/bundle/docs/applications/hcm/separation-reports.md +23 -0
- package/bundle/docs/applications/hcm/separation-resignation.md +43 -0
- package/bundle/docs/applications/hcm/settings-extensibility.md +31 -0
- package/bundle/docs/applications/hcm/settings-general.md +29 -0
- package/bundle/docs/applications/hcm/settings-integrations.md +14 -0
- package/bundle/docs/applications/hcm/settings-module-defaults.md +41 -0
- package/bundle/docs/applications/hcm/settings-process.md +28 -0
- package/bundle/docs/applications/hcm/shift-scheduling.md +33 -0
- package/bundle/docs/applications/hcm/talent-analytics.md +26 -0
- package/bundle/docs/applications/hcm/talent-career.md +31 -0
- package/bundle/docs/applications/hcm/talent-competencies-skills.md +30 -0
- package/bundle/docs/applications/hcm/talent-profiles.md +40 -0
- package/bundle/docs/applications/hcm/talent-succession.md +42 -0
- package/bundle/docs/applications/hcm/teams-and-tags.md +46 -0
- package/bundle/docs/applications/hcm/timesheets.md +33 -0
- package/bundle/docs/applications/hcm/travel-advances-expenses.md +34 -0
- package/bundle/docs/applications/hcm/travel-analytics.md +28 -0
- package/bundle/docs/applications/hcm/travel-bookings.md +35 -0
- package/bundle/docs/applications/hcm/travel-requests.md +38 -0
- package/bundle/docs/applications/hcm/workforce-org-design.md +22 -0
- package/bundle/docs/applications/hcm/workforce-planning-analytics.md +26 -0
- package/bundle/docs/applications/hcm/workforce-planning-core.md +36 -0
- package/bundle/docs/applications/hcm/workforce-planning-scenarios.md +29 -0
- package/bundle/docs/docs.json +1 -0
- package/bundle/docs/guides/add-app-owned-roles-and-permissions.md +144 -0
- package/bundle/docs/guides/checkout-an-installed-plugin.md +151 -0
- package/bundle/docs/guides/extend-a-shipped-application.md +132 -0
- package/bundle/docs/guides/index.md +3 -0
- package/bundle/docs/reference/entity-aggregation-config.md +1 -1
- package/bundle/docs/reference/entity-document-generator-config.md +1 -1
- package/bundle/docs/tutorial/01-create-the-plugin.md +7 -1
- package/bundle/docs/tutorial/07-return-due-reminder-job.md +28 -5
- package/bundle/docs/tutorial/08-menus-i18n-publish.md +5 -5
- package/bundle/manifest.json +4 -4
- package/bundle/schemas/page.schema.json +13 -0
- package/bundle/schemas/plugin-manifest.schema.json +13 -0
- package/bundle/validators/block-engine.mjs +167 -7
- package/bundle/validators/page-engine.mjs +226 -21
- package/erp-cli/authoring-root.mjs +12 -1
- package/erp-cli/erp.mjs +679 -15
- package/package.json +1 -1
|
@@ -3061,6 +3061,25 @@ function endsWith(args) {
|
|
|
3061
3061
|
const [s, suffix] = args;
|
|
3062
3062
|
return rvBool(str(s).endsWith(str(suffix)));
|
|
3063
3063
|
}
|
|
3064
|
+
function displayString(v) {
|
|
3065
|
+
switch (v.type) {
|
|
3066
|
+
case "String":
|
|
3067
|
+
return v.value;
|
|
3068
|
+
case "Integer":
|
|
3069
|
+
return v.value.toString();
|
|
3070
|
+
case "Decimal":
|
|
3071
|
+
return v.value.toString();
|
|
3072
|
+
case "Boolean":
|
|
3073
|
+
return String(v.value);
|
|
3074
|
+
case "Null":
|
|
3075
|
+
return "";
|
|
3076
|
+
default:
|
|
3077
|
+
throw new EvaluationError(`concat() does not support ${v.type} operands`);
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
function concatFn(args) {
|
|
3081
|
+
return rvString(args.map(displayString).join(""));
|
|
3082
|
+
}
|
|
3064
3083
|
function registerStringFunctions(registry, version) {
|
|
3065
3084
|
registry.register(version, "upper", upper);
|
|
3066
3085
|
registry.register(version, "lower", lower);
|
|
@@ -3069,6 +3088,7 @@ function registerStringFunctions(registry, version) {
|
|
|
3069
3088
|
registry.register(version, "substring", substring);
|
|
3070
3089
|
registry.register(version, "startsWith", startsWith);
|
|
3071
3090
|
registry.register(version, "endsWith", endsWith);
|
|
3091
|
+
registry.register(version, "concat", concatFn);
|
|
3072
3092
|
}
|
|
3073
3093
|
function contains(args) {
|
|
3074
3094
|
const [collection, needle] = args;
|
|
@@ -3115,12 +3135,19 @@ function size(args) {
|
|
|
3115
3135
|
function isEmpty(args) {
|
|
3116
3136
|
return rvBool(sizeOf(args[0]) === 0);
|
|
3117
3137
|
}
|
|
3138
|
+
function pluck(args) {
|
|
3139
|
+
const [arr, key] = args;
|
|
3140
|
+
if (arr.type !== "Array") throw new EvaluationError(`pluck() expects an Array as its first argument, got ${arr.type}`);
|
|
3141
|
+
if (key.type !== "String") throw new EvaluationError("pluck() expects a String field name as its second argument");
|
|
3142
|
+
return rvArray(arr.value.map((row) => row.type === "Object" ? row.value[key.value] ?? RV_NULL : RV_NULL));
|
|
3143
|
+
}
|
|
3118
3144
|
function registerCollectionFunctions(registry, version) {
|
|
3119
3145
|
registry.register(version, "contains", contains);
|
|
3120
3146
|
registry.register(version, "first", first);
|
|
3121
3147
|
registry.register(version, "last", last);
|
|
3122
3148
|
registry.register(version, "size", size);
|
|
3123
3149
|
registry.register(version, "isEmpty", isEmpty);
|
|
3150
|
+
registry.register(version, "pluck", pluck);
|
|
3124
3151
|
}
|
|
3125
3152
|
function ifImpl(args) {
|
|
3126
3153
|
const [cond, thenValue, elseValue] = args;
|
|
@@ -3193,12 +3220,14 @@ var FUNCTION_CATALOG = [
|
|
|
3193
3220
|
c({ name: "substring", minArity: 2, maxArity: 3, argKinds: ["String", "Numeric", "Numeric"], returnType: "String", category: "String", description: "Part of a string from a 0-based start position, optionally limited to a length." }),
|
|
3194
3221
|
c({ name: "startsWith", minArity: 2, maxArity: 2, argKinds: ["String", "String"], returnType: "Boolean", category: "String", description: "True when the string begins with the given prefix." }),
|
|
3195
3222
|
c({ name: "endsWith", minArity: 2, maxArity: 2, argKinds: ["String", "String"], returnType: "Boolean", category: "String", description: "True when the string ends with the given suffix." }),
|
|
3223
|
+
c({ name: "concat", minArity: 2, maxArity: Infinity, argKinds: ["Any"], returnType: "String", category: "String", description: `Joins 2+ values' display text into one string, e.g. concat("Total: ", page.count).` }),
|
|
3196
3224
|
// Collection
|
|
3197
3225
|
c({ name: "contains", minArity: 2, maxArity: 2, argKinds: ["Collection", "Any"], returnType: "Boolean", category: "Collection", description: "True when a list, string, or object contains the given element/substring/key." }),
|
|
3198
3226
|
c({ name: "first", minArity: 1, maxArity: 1, argKinds: ["Array"], returnType: "Any", category: "Collection", description: "The first element of a list." }),
|
|
3199
3227
|
c({ name: "last", minArity: 1, maxArity: 1, argKinds: ["Array"], returnType: "Any", category: "Collection", description: "The last element of a list." }),
|
|
3200
3228
|
c({ name: "size", minArity: 1, maxArity: 1, argKinds: ["Collection"], returnType: "Integer", category: "Collection", description: "Number of elements in a list, characters in a string, or keys in an object." }),
|
|
3201
3229
|
c({ name: "isEmpty", minArity: 1, maxArity: 1, argKinds: ["Collection"], returnType: "Boolean", category: "Collection", description: "True when a list, string, or object has no elements." }),
|
|
3230
|
+
c({ name: "pluck", minArity: 2, maxArity: 2, argKinds: ["Array", "String"], returnType: "Array", category: "Collection", description: `pluck(rows, "fieldName") \u2014 a new list of that field's value from each object in the list, e.g. turning [{date, value}, ...] into [value, value, ...].` }),
|
|
3202
3231
|
// Utility
|
|
3203
3232
|
c({ name: "if", minArity: 3, maxArity: 3, argKinds: ["Boolean", "Any", "Any"], returnType: "Any", category: "Utility", description: "if(condition, thenValue, elseValue) \u2014 returns one of two values based on a condition." }),
|
|
3204
3233
|
c({ name: "coalesce", minArity: 1, maxArity: Infinity, argKinds: ["Any"], returnType: "Any", category: "Utility", description: "The first argument that is not null." }),
|
|
@@ -4425,9 +4454,9 @@ function resolvePath(scopes, rawPath) {
|
|
|
4425
4454
|
return value;
|
|
4426
4455
|
}
|
|
4427
4456
|
function resolveString(scopes, text) {
|
|
4428
|
-
const
|
|
4429
|
-
if (
|
|
4430
|
-
const [, path, field] =
|
|
4457
|
+
const pluck2 = PLUCK_TEMPLATE.exec(text);
|
|
4458
|
+
if (pluck2) {
|
|
4459
|
+
const [, path, field] = pluck2;
|
|
4431
4460
|
const resolved = runtimeValueToJs(resolvePath(scopes, path));
|
|
4432
4461
|
if (!Array.isArray(resolved)) {
|
|
4433
4462
|
throw new ActionError("unresolved-variable", `'${path}|pluck:${field}' requires an array at '${path}'`);
|
|
@@ -10320,6 +10349,26 @@ ${failedSteps}` : "")
|
|
|
10320
10349
|
});
|
|
10321
10350
|
if (faulted && this.phase === "ready") this.present();
|
|
10322
10351
|
}
|
|
10352
|
+
/**
|
|
10353
|
+
* A real, previously-undiscovered gap (2026-09-22, found live authoring the HCM Permissions
|
|
10354
|
+
* "Create Permission" dialog): a `core.text-input` (or any other inputClass block) whose page
|
|
10355
|
+
* JSON only ever sets `properties.value.binding` — the "obviously two-way" way EVERY such field
|
|
10356
|
+
* in this codebase's own already-shipped pages is authored (`hcm-users.json`'s Create User
|
|
10357
|
+
* dialog included) — never actually wrote a keystroke back to that bound page-scope key. `commit`
|
|
10358
|
+
* → `writeOutput` only ever consulted `config.outputs[name]`, a SEPARATE, explicit redirect
|
|
10359
|
+
* nothing in this codebase's own page-authoring convention ever sets; absent that, every write
|
|
10360
|
+
* landed in a `{scope:"block", scopeKey:this.instanceId}` bucket no page action chain can ever
|
|
10361
|
+
* read — the DOM still showed the typed characters (via `RevealableTextField`'s own local echo
|
|
10362
|
+
* state), so this was invisible in every purely-visual check, only surfacing when a create
|
|
10363
|
+
* dialog's Submit button's `${page.createX}` params were inspected against the REAL network
|
|
10364
|
+
* payload, which showed every field empty. Fixed by falling back to the SAME resolved binding
|
|
10365
|
+
* target `resolveProperties()` already computed for the identically-named PROPERTY's own read
|
|
10366
|
+
* side (`this.boundProps`) when no explicit `config.outputs[name]` redirect exists — making a
|
|
10367
|
+
* `source:"binding"` property's own binding genuinely two-way by default, matching what every
|
|
10368
|
+
* page author already (reasonably) assumed it meant. An explicit `config.outputs[name]` still
|
|
10369
|
+
* wins unchanged (e.g. `core.file-upload`'s own redirect to a different scope/key than its
|
|
10370
|
+
* `value` property reads from); this only fills in the previously-silent gap.
|
|
10371
|
+
*/
|
|
10323
10372
|
async writeOutput(name, value) {
|
|
10324
10373
|
const port = (this.type.definition.outputs ?? []).find((o) => o.name === name);
|
|
10325
10374
|
if (!port) {
|
|
@@ -10327,7 +10376,7 @@ ${failedSteps}` : "")
|
|
|
10327
10376
|
return;
|
|
10328
10377
|
}
|
|
10329
10378
|
const redirect = this.config.outputs?.[name];
|
|
10330
|
-
const target = redirect ? resolveBoundKey(redirect, this.bindingContext()) : { scope: "block", scopeKey: this.instanceId, key: name };
|
|
10379
|
+
const target = redirect ? resolveBoundKey(redirect, this.bindingContext()) : this.boundProps.get(name) ?? { scope: "block", scopeKey: this.instanceId, key: name };
|
|
10331
10380
|
await this.ctx.state.set(target, value);
|
|
10332
10381
|
}
|
|
10333
10382
|
/* ---------------- error isolation & standard states (W2-04) ---------------- */
|
|
@@ -10918,6 +10967,47 @@ var imageDefinition = definition({
|
|
|
10918
10967
|
type: "string",
|
|
10919
10968
|
sources: ["static"],
|
|
10920
10969
|
designer: { group: "content", editor: "text", labelKey: "core.image.property.height" }
|
|
10970
|
+
},
|
|
10971
|
+
{
|
|
10972
|
+
// 2026-09-17 (page-builder "Customize" panel parity) — a clip-path-class
|
|
10973
|
+
// shape, the same fixed vocabulary a real page-builder's own "Shape"
|
|
10974
|
+
// picker offers (never an arbitrary path — that stays a `style`
|
|
10975
|
+
// escape-hatch concern).
|
|
10976
|
+
name: "shape",
|
|
10977
|
+
type: "string",
|
|
10978
|
+
sources: ["static"],
|
|
10979
|
+
default: "none",
|
|
10980
|
+
constraints: { options: [{ value: "none", labelKey: "core.image.shape.none" }, { value: "circle", labelKey: "core.image.shape.circle" }, { value: "rounded", labelKey: "core.image.shape.rounded" }] },
|
|
10981
|
+
designer: { group: "content", editor: "select", labelKey: "core.image.property.shape" }
|
|
10982
|
+
},
|
|
10983
|
+
{
|
|
10984
|
+
/** A native browser tooltip (HTML `title` attribute) — distinct from `alt` (accessibility text, always present) and from a rich hover-card (out of scope here, a real different feature). */
|
|
10985
|
+
name: "tooltip",
|
|
10986
|
+
type: "string",
|
|
10987
|
+
sources: ["static"],
|
|
10988
|
+
designer: { group: "content", editor: "text", labelKey: "core.image.property.tooltip" }
|
|
10989
|
+
},
|
|
10990
|
+
{
|
|
10991
|
+
/** A raw CSS `transform` value, e.g. "rotate(4deg)" — same "text travels as data" posture as `PageTheme.customCss`, never `dangerouslySetInnerHTML`, just an inline style value. */
|
|
10992
|
+
name: "transform",
|
|
10993
|
+
type: "string",
|
|
10994
|
+
sources: ["static"],
|
|
10995
|
+
designer: { group: "content", editor: "text", labelKey: "core.image.property.transform" }
|
|
10996
|
+
},
|
|
10997
|
+
{
|
|
10998
|
+
name: "filter",
|
|
10999
|
+
type: "string",
|
|
11000
|
+
sources: ["static"],
|
|
11001
|
+
default: "none",
|
|
11002
|
+
constraints: {
|
|
11003
|
+
options: [
|
|
11004
|
+
{ value: "none", labelKey: "core.image.filter.none" },
|
|
11005
|
+
{ value: "grayscale", labelKey: "core.image.filter.grayscale" },
|
|
11006
|
+
{ value: "sepia", labelKey: "core.image.filter.sepia" },
|
|
11007
|
+
{ value: "blur", labelKey: "core.image.filter.blur" }
|
|
11008
|
+
]
|
|
11009
|
+
},
|
|
11010
|
+
designer: { group: "content", editor: "select", labelKey: "core.image.property.filter" }
|
|
10921
11011
|
}
|
|
10922
11012
|
],
|
|
10923
11013
|
events: [],
|
|
@@ -13562,6 +13652,31 @@ var gridDefinition = definition({
|
|
|
13562
13652
|
designer: { group: "data", editor: "binding", labelKey: "core.grid.property.refreshTrigger" }
|
|
13563
13653
|
}
|
|
13564
13654
|
],
|
|
13655
|
+
// 2026-09-23 — these are the block's own internal state (written via
|
|
13656
|
+
// `io.setOwnState` in this file's `query()`/handlers, read back into
|
|
13657
|
+
// `node.props` for the adapter), not authored properties: a page author
|
|
13658
|
+
// never sets them, but a plugin/theme developer inspecting `core.grid`
|
|
13659
|
+
// via `erp blocks list --type core.grid` had no way to discover they
|
|
13660
|
+
// exist at all — the empty-grid production bug this session traced back
|
|
13661
|
+
// to exactly this state (rows/total both came from the same query
|
|
13662
|
+
// response, one reached the render, the other didn't) took a live
|
|
13663
|
+
// Playwright session plus a source read to pin down, when this list
|
|
13664
|
+
// alone would have shown the full internal-state surface immediately.
|
|
13665
|
+
outputs: [
|
|
13666
|
+
{ name: "rows", type: "json" },
|
|
13667
|
+
{ name: "total", type: "number" },
|
|
13668
|
+
{ name: "pageIndex", type: "number" },
|
|
13669
|
+
{ name: "sort", type: "json" },
|
|
13670
|
+
{ name: "filter", type: "json" },
|
|
13671
|
+
{ name: "groups", type: "json" },
|
|
13672
|
+
{ name: "aggregates", type: "json" },
|
|
13673
|
+
{ name: "expandedGroups", type: "json" },
|
|
13674
|
+
{ name: "error", type: "string" },
|
|
13675
|
+
{ name: "circuitOpen", type: "boolean" },
|
|
13676
|
+
{ name: "selectedIds", type: "json" },
|
|
13677
|
+
{ name: "density", type: "string" },
|
|
13678
|
+
{ name: "groupBy", type: "string" }
|
|
13679
|
+
],
|
|
13565
13680
|
events: [
|
|
13566
13681
|
{ name: "selectionChanged", catalogType: "GridSelectionChanged" },
|
|
13567
13682
|
{ name: "rowClicked", catalogType: "GridRowClicked" },
|
|
@@ -14311,6 +14426,30 @@ var fileUploadDefinition = definition({
|
|
|
14311
14426
|
type: "string",
|
|
14312
14427
|
sources: ["static", "expression"],
|
|
14313
14428
|
designer: { group: "data", editor: "text", labelKey: "core.file-upload.property.cabinetId" }
|
|
14429
|
+
},
|
|
14430
|
+
{
|
|
14431
|
+
// 2026-09-21 (HCM Users spec gap-closing pass) — every pre-existing
|
|
14432
|
+
// page never sets this (undefined === "dms"), so it's a zero-risk
|
|
14433
|
+
// addition: `"dms"` keeps today's exact behavior (a real DMS cabinet
|
|
14434
|
+
// upload via the injected `FileUploadSource`, `value` becomes real
|
|
14435
|
+
// `UploadedFileRef`s). `"raw"` skips the DMS upload entirely — `value`
|
|
14436
|
+
// becomes the picked file's own `{filename, contentType, dataUrl}`
|
|
14437
|
+
// straight from the browser's file picker, with no `id`/`url` (nothing
|
|
14438
|
+
// was uploaded anywhere) — meant to feed a `callApi` action's
|
|
14439
|
+
// `multipart: true` body (see that handler's own doc comment), the
|
|
14440
|
+
// real fix for the disclosed "core.file-upload cannot emit raw bytes
|
|
14441
|
+
// into a callApi body" gap (`ai/patterns/import.md`).
|
|
14442
|
+
name: "mode",
|
|
14443
|
+
type: "string",
|
|
14444
|
+
sources: ["static"],
|
|
14445
|
+
default: "dms",
|
|
14446
|
+
constraints: {
|
|
14447
|
+
options: [
|
|
14448
|
+
{ value: "dms", labelKey: "core.file-upload.mode.dms" },
|
|
14449
|
+
{ value: "raw", labelKey: "core.file-upload.mode.raw" }
|
|
14450
|
+
]
|
|
14451
|
+
},
|
|
14452
|
+
designer: { group: "behavior", editor: "select", labelKey: "core.file-upload.property.mode" }
|
|
14314
14453
|
}
|
|
14315
14454
|
],
|
|
14316
14455
|
outputs: [{ name: "value", type: "json" }],
|
|
@@ -14323,15 +14462,23 @@ var fileUploadDefinition = definition({
|
|
|
14323
14462
|
category: "input",
|
|
14324
14463
|
propertyGroups: [
|
|
14325
14464
|
{ id: "data", titleKey: "core.designer.group.data" },
|
|
14326
|
-
{ id: "content", titleKey: "core.designer.group.content" }
|
|
14465
|
+
{ id: "content", titleKey: "core.designer.group.content" },
|
|
14466
|
+
{ id: "behavior", titleKey: "core.designer.group.behavior" }
|
|
14327
14467
|
],
|
|
14328
14468
|
allowedTargets: ["page", "form", "container", "dashboard"],
|
|
14329
14469
|
preview: { kind: "field" }
|
|
14330
14470
|
}
|
|
14331
14471
|
});
|
|
14472
|
+
var rawFileIdCounter = 0;
|
|
14473
|
+
function nextRawFileId() {
|
|
14474
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
14475
|
+
rawFileIdCounter += 1;
|
|
14476
|
+
return `raw-file-${Date.now()}-${rawFileIdCounter}`;
|
|
14477
|
+
}
|
|
14332
14478
|
function createFileUploadLogic(uploadSource) {
|
|
14333
14479
|
return {
|
|
14334
14480
|
render(io) {
|
|
14481
|
+
const mode = io.props["mode"] === "raw" ? "raw" : "dms";
|
|
14335
14482
|
const files = Array.isArray(io.props["value"]) ? io.props["value"] : [];
|
|
14336
14483
|
const accept = typeof io.props["accept"] === "string" ? io.props["accept"] : void 0;
|
|
14337
14484
|
const multiple = io.props["multiple"] !== false;
|
|
@@ -14340,8 +14487,17 @@ function createFileUploadLogic(uploadSource) {
|
|
|
14340
14487
|
props: { value: files, accept, multiple },
|
|
14341
14488
|
handlers: {
|
|
14342
14489
|
upload: async (payload) => {
|
|
14343
|
-
if (!uploadSource) return;
|
|
14344
14490
|
const incoming = payload ?? [];
|
|
14491
|
+
if (mode === "raw") {
|
|
14492
|
+
await io.runLoading(async () => {
|
|
14493
|
+
const picked = incoming.map((f) => ({ id: nextRawFileId(), ...f }));
|
|
14494
|
+
const next = [...files, ...picked];
|
|
14495
|
+
await io.writeOutput("value", next);
|
|
14496
|
+
await io.emit("committed", { field: "value", old: files, new: next });
|
|
14497
|
+
});
|
|
14498
|
+
return;
|
|
14499
|
+
}
|
|
14500
|
+
if (!uploadSource) return;
|
|
14345
14501
|
await io.runLoading(async () => {
|
|
14346
14502
|
const uploaded = [];
|
|
14347
14503
|
for (const f of incoming) uploaded.push(await uploadSource.upload(f, cabinetId));
|
|
@@ -18681,6 +18837,7 @@ async function applyRuleBindings(definition2, changedField, options) {
|
|
|
18681
18837
|
continue;
|
|
18682
18838
|
}
|
|
18683
18839
|
mergeBlockEntry(options.scopes, "fieldOptions", rule.targetId, result);
|
|
18840
|
+
options.scopes.set("form", `__fieldOptions.${rule.targetId}`, result);
|
|
18684
18841
|
let optionsReset = false;
|
|
18685
18842
|
if (rule.resetOnChange) {
|
|
18686
18843
|
const current2 = options.scopes.values("form")[rule.targetId];
|
|
@@ -18963,7 +19120,7 @@ var FormPermissionEvaluator = class {
|
|
|
18963
19120
|
return this.base.masked(subject) || this.staticSurface(subject.instanceId).masked;
|
|
18964
19121
|
}
|
|
18965
19122
|
};
|
|
18966
|
-
function fieldProperties(field, value, blockDef) {
|
|
19123
|
+
function fieldProperties(field, value, blockDef, ctx, definition2) {
|
|
18967
19124
|
const portName = blockDef.outputs?.[0]?.name ?? (blockDef.type === "core.label" ? "text" : "value");
|
|
18968
19125
|
const props = { [portName]: value };
|
|
18969
19126
|
const c2 = field.constraints;
|
|
@@ -18979,7 +19136,13 @@ function fieldProperties(field, value, blockDef) {
|
|
|
18979
19136
|
maybe("icon", c2.icon);
|
|
18980
19137
|
maybe("revealToggle", c2.revealToggle);
|
|
18981
19138
|
if (c2.options && blockDef.properties.some((p) => p.name === "options")) {
|
|
18982
|
-
|
|
19139
|
+
const dynamicRule = (definition2.rules ?? []).find((r) => r.output === "options" && r.targetKind === "field" && r.targetId === field.name);
|
|
19140
|
+
if (dynamicRule) {
|
|
19141
|
+
ctx.scopes.set("form", `__fieldOptions.${field.name}`, jsToRuntimeValue(c2.options));
|
|
19142
|
+
props["options"] = { source: "binding", binding: { scope: "form", key: `__fieldOptions.${field.name}` } };
|
|
19143
|
+
} else {
|
|
19144
|
+
props["options"] = { source: "static", value: c2.options };
|
|
19145
|
+
}
|
|
18983
19146
|
}
|
|
18984
19147
|
}
|
|
18985
19148
|
return props;
|
|
@@ -18989,7 +19152,7 @@ function fieldOutputs(field, blockDef) {
|
|
|
18989
19152
|
const portName = blockDef.outputs?.[0]?.name;
|
|
18990
19153
|
return portName ? { [portName]: { scope: "form", key: field.name } } : void 0;
|
|
18991
19154
|
}
|
|
18992
|
-
async function mountField(field, ctx, registry) {
|
|
19155
|
+
async function mountField(field, ctx, registry, definition2) {
|
|
18993
19156
|
const blockType = field.kind === "computed" ? "core.label" : field.blockType;
|
|
18994
19157
|
if (!blockType) {
|
|
18995
19158
|
return { ok: false, diagnostics: [formDiagnostic("unknown-field", `Field "${field.name}" declares no blockType`, { path: field.name })] };
|
|
@@ -19007,7 +19170,7 @@ async function mountField(field, ctx, registry) {
|
|
|
19007
19170
|
instanceId: field.name,
|
|
19008
19171
|
blockType,
|
|
19009
19172
|
blockVersion: registered.definition.version,
|
|
19010
|
-
properties: fieldProperties(field, value, registered.definition),
|
|
19173
|
+
properties: fieldProperties(field, value, registered.definition, ctx, definition2),
|
|
19011
19174
|
outputs: fieldOutputs(field, registered.definition),
|
|
19012
19175
|
// W10: the authored labelKey becomes the instance's accessible name,
|
|
19013
19176
|
// resolved per locale by the runtime i18n resolver at render.
|
|
@@ -19111,7 +19274,7 @@ async function mountForm(options) {
|
|
|
19111
19274
|
const fieldInstances = /* @__PURE__ */ new Map();
|
|
19112
19275
|
const diagnostics = [];
|
|
19113
19276
|
for (const field of options.definition.fields) {
|
|
19114
|
-
const result = field.kind === "child-table" ? await mountChildTable(field, rootCtx, options.blockRegistry) : await mountField(field, rootCtx, options.blockRegistry);
|
|
19277
|
+
const result = field.kind === "child-table" ? await mountChildTable(field, rootCtx, options.blockRegistry) : await mountField(field, rootCtx, options.blockRegistry, options.definition);
|
|
19115
19278
|
if (!result.ok) {
|
|
19116
19279
|
diagnostics.push(...result.diagnostics);
|
|
19117
19280
|
continue;
|
|
@@ -19180,6 +19343,8 @@ var FormInstance = class {
|
|
|
19180
19343
|
const result = await openForm(this.definition, this.instanceConfig, this.lifecycleOptions());
|
|
19181
19344
|
this.subscription = this.bus.subscribe(this.tenantId, "ValueCommitted", (e) => this.onValueCommitted(e));
|
|
19182
19345
|
this.lastSaved = snapshotFormValues(this.scopes);
|
|
19346
|
+
const { effects } = await applyRuleBindings(this.definition, "onLoad", { tenantId: this.tenantId, scopes: this.scopes });
|
|
19347
|
+
await Promise.all(effects.filter((e) => e.targetKind === "field").map((e) => this.fieldInstances.get(e.targetId)?.refresh()));
|
|
19183
19348
|
this.present();
|
|
19184
19349
|
return result;
|
|
19185
19350
|
}
|
|
@@ -20244,7 +20409,7 @@ function resolveVariant(definition2, context) {
|
|
|
20244
20409
|
}
|
|
20245
20410
|
return { variantId: void 0, template: definition2.template, slotFills: definition2.slotFills, rows: definition2.rows };
|
|
20246
20411
|
}
|
|
20247
|
-
var XS_BASE = { span: void 0, width: void 0, height: void 0, fontSize: void 0, spacing: void 0 };
|
|
20412
|
+
var XS_BASE = { span: void 0, offset: void 0, width: void 0, height: void 0, fontSize: void 0, spacing: void 0 };
|
|
20248
20413
|
function resolveBreakpoint(breakpoints, target, hiddenAtXs) {
|
|
20249
20414
|
const targetIndex = BREAKPOINTS2.indexOf(target);
|
|
20250
20415
|
let resolved = { ...XS_BASE, hidden: hiddenAtXs ?? false };
|
|
@@ -20254,6 +20419,7 @@ function resolveBreakpoint(breakpoints, target, hiddenAtXs) {
|
|
|
20254
20419
|
if (!override) continue;
|
|
20255
20420
|
if (override.hidden !== void 0) resolved = { ...resolved, hidden: override.hidden };
|
|
20256
20421
|
if (override.span !== void 0) resolved = { ...resolved, span: override.span };
|
|
20422
|
+
if (override.offset !== void 0) resolved = { ...resolved, offset: override.offset };
|
|
20257
20423
|
if (override.width !== void 0) resolved = { ...resolved, width: override.width };
|
|
20258
20424
|
if (override.height !== void 0) resolved = { ...resolved, height: override.height };
|
|
20259
20425
|
if (override.fontSize !== void 0) resolved = { ...resolved, fontSize: override.fontSize };
|
|
@@ -20642,7 +20808,8 @@ async function mountPage(options) {
|
|
|
20642
20808
|
rows: mountedRows,
|
|
20643
20809
|
overlays: options.definition.overlays ?? [],
|
|
20644
20810
|
wizardSteps: wizardSteps === void 0 ? void 0 : mountedSteps,
|
|
20645
|
-
canvasItems: mountedCanvasItems
|
|
20811
|
+
canvasItems: mountedCanvasItems,
|
|
20812
|
+
disableEmptyCollapse: options.disableEmptyCollapse
|
|
20646
20813
|
});
|
|
20647
20814
|
await instance.open();
|
|
20648
20815
|
currentPageByRegistry.set(options.actionRegistry, instance);
|
|
@@ -20663,6 +20830,7 @@ var PageInstance = class {
|
|
|
20663
20830
|
overlays = /* @__PURE__ */ new Map();
|
|
20664
20831
|
wizardSteps;
|
|
20665
20832
|
canvasItems;
|
|
20833
|
+
disableEmptyCollapse;
|
|
20666
20834
|
wizardStep = 0;
|
|
20667
20835
|
openCounter = 0;
|
|
20668
20836
|
rowVisible = /* @__PURE__ */ new Map();
|
|
@@ -20685,6 +20853,7 @@ var PageInstance = class {
|
|
|
20685
20853
|
}
|
|
20686
20854
|
this.wizardSteps = options.wizardSteps;
|
|
20687
20855
|
this.canvasItems = options.canvasItems;
|
|
20856
|
+
this.disableEmptyCollapse = options.disableEmptyCollapse ?? false;
|
|
20688
20857
|
}
|
|
20689
20858
|
get pageId() {
|
|
20690
20859
|
return this.ctx.pageId;
|
|
@@ -20714,6 +20883,34 @@ var PageInstance = class {
|
|
|
20714
20883
|
this.consumeOverlayIntent();
|
|
20715
20884
|
void this.evaluateVisibility();
|
|
20716
20885
|
});
|
|
20886
|
+
await this.fireOnLoad();
|
|
20887
|
+
}
|
|
20888
|
+
/**
|
|
20889
|
+
* Page-level `onLoad` (2026-09-22, 12-Column spec §23 gap-closing pass) —
|
|
20890
|
+
* fires once, after the page's own content has mounted and its first
|
|
20891
|
+
* visibility pass has run (so an `onLoad` action's `setValue`s land on a
|
|
20892
|
+
* page whose rows/columns/items already exist to react to them). Mirrors
|
|
20893
|
+
* `runLifecycleHook`'s own `onLoad` branch in `@erp/form-engine` — same
|
|
20894
|
+
* `executeChain` call, same "fire and forget, never a page-open blocker"
|
|
20895
|
+
* posture: a failing/invalid onLoad chain is logged to the console and
|
|
20896
|
+
* swallowed, never thrown back into `mountPage`'s caller — an author
|
|
20897
|
+
* mistake in a page-level hook must not take the whole page down the way
|
|
20898
|
+
* one bad block instance already doesn't (see the diagnostics-collection
|
|
20899
|
+
* loop in `mountPage` above).
|
|
20900
|
+
*/
|
|
20901
|
+
async fireOnLoad() {
|
|
20902
|
+
const binding = this.definition.events?.find((e) => e.hook === "onLoad");
|
|
20903
|
+
if (!binding || binding.actions.length === 0) return;
|
|
20904
|
+
try {
|
|
20905
|
+
await executeChain(binding.actions, {
|
|
20906
|
+
tenantId: this.ctx.tenantId,
|
|
20907
|
+
registry: this.deps.actionRegistry,
|
|
20908
|
+
scopes: this.ctx.scopes,
|
|
20909
|
+
resolveConnection: this.ctx.resolveConnection
|
|
20910
|
+
});
|
|
20911
|
+
} catch (e) {
|
|
20912
|
+
console.error(`Page "${this.definition.id}" onLoad action chain failed:`, e);
|
|
20913
|
+
}
|
|
20717
20914
|
}
|
|
20718
20915
|
/**
|
|
20719
20916
|
* Overlay intents (2026-07-16 audit): the Phase 2 `openDialog`/`closeDialog`
|
|
@@ -20809,11 +21006,12 @@ var PageInstance = class {
|
|
|
20809
21006
|
`page-${this.pageId}`
|
|
20810
21007
|
);
|
|
20811
21008
|
}
|
|
20812
|
-
/** Empty-region collapse (W2-03, gap 5): a column with no currently-visible items emits no node at all. */
|
|
20813
|
-
columnNode(mountedColumn, rowAlign) {
|
|
21009
|
+
/** Empty-region collapse (W2-03, gap 5): a column with no currently-visible items emits no node at all — skipped when `disableEmptyCollapse` is set (see that option's own doc comment). */
|
|
21010
|
+
columnNode(mountedColumn, rowAlign, rowLayout) {
|
|
20814
21011
|
if (this.columnVisible.get(mountedColumn.column.id) === false) return void 0;
|
|
20815
21012
|
const itemNodes = mountedColumn.items.filter((i) => this.itemVisible.get(i.item.id) !== false).map((i) => this.itemNode(i));
|
|
20816
|
-
|
|
21013
|
+
const empty = isEmptyContainer(itemNodes.map(() => true));
|
|
21014
|
+
if (empty && !this.disableEmptyCollapse) return void 0;
|
|
20817
21015
|
return node("container", `${mountedColumn.column.id}-wrapper`, {
|
|
20818
21016
|
props: {
|
|
20819
21017
|
pageLayoutRole: "column",
|
|
@@ -20821,20 +21019,25 @@ var PageInstance = class {
|
|
|
20821
21019
|
hiddenAtXs: mountedColumn.column.hiddenAtXs,
|
|
20822
21020
|
spanAtXs: mountedColumn.column.spanAtXs,
|
|
20823
21021
|
card: mountedColumn.column.card,
|
|
21022
|
+
style: mountedColumn.column.style,
|
|
20824
21023
|
align: rowAlign,
|
|
20825
|
-
|
|
21024
|
+
rowLayout,
|
|
21025
|
+
widthOverride: this.columnWidth.get(mountedColumn.column.id),
|
|
21026
|
+
// Only ever true when `disableEmptyCollapse` kept an otherwise-collapsed column alive — the render
|
|
21027
|
+
// adapter's own signal to show a real "drop a component here" placeholder instead of a blank box.
|
|
21028
|
+
emptyPlaceholder: empty && this.disableEmptyCollapse
|
|
20826
21029
|
},
|
|
20827
21030
|
children: itemNodes
|
|
20828
21031
|
});
|
|
20829
21032
|
}
|
|
20830
|
-
/** Empty-region collapse (W2-03, gap 5): a row whose every column collapsed emits no node at all — proven on xs-stacked layout where an empty row would otherwise be a visible hole. */
|
|
21033
|
+
/** Empty-region collapse (W2-03, gap 5): a row whose every column collapsed emits no node at all — proven on xs-stacked layout where an empty row would otherwise be a visible hole; skipped when `disableEmptyCollapse` is set. */
|
|
20831
21034
|
rowNode(mountedRow) {
|
|
20832
21035
|
if (!mountedRow.activated) {
|
|
20833
21036
|
return node("skeleton", `${mountedRow.row.id}-deferred`, { a11y: { role: "status" } });
|
|
20834
21037
|
}
|
|
20835
21038
|
if (this.rowVisible.get(mountedRow.row.id) === false) return void 0;
|
|
20836
|
-
const columnNodes = mountedRow.columns.map((c2) => this.columnNode(c2, mountedRow.row.align)).filter((n) => n !== void 0);
|
|
20837
|
-
if (isEmptyContainer(columnNodes.map(() => true))) return void 0;
|
|
21039
|
+
const columnNodes = mountedRow.columns.map((c2) => this.columnNode(c2, mountedRow.row.align, mountedRow.row.layout)).filter((n) => n !== void 0);
|
|
21040
|
+
if (isEmptyContainer(columnNodes.map(() => true)) && !this.disableEmptyCollapse) return void 0;
|
|
20838
21041
|
return node("container", `${mountedRow.row.id}-wrapper`, {
|
|
20839
21042
|
props: {
|
|
20840
21043
|
pageLayoutRole: "row",
|
|
@@ -20842,7 +21045,9 @@ var PageInstance = class {
|
|
|
20842
21045
|
hiddenAtXs: mountedRow.row.hiddenAtXs,
|
|
20843
21046
|
align: mountedRow.row.align,
|
|
20844
21047
|
edgeSpacing: mountedRow.row.edgeSpacing,
|
|
20845
|
-
sticky: mountedRow.row.sticky
|
|
21048
|
+
sticky: mountedRow.row.sticky,
|
|
21049
|
+
style: mountedRow.row.style,
|
|
21050
|
+
layout: mountedRow.row.layout
|
|
20846
21051
|
},
|
|
20847
21052
|
children: columnNodes
|
|
20848
21053
|
});
|
|
@@ -246,7 +246,18 @@ export function docsSearchRoots() {
|
|
|
246
246
|
if (existsSync(bun)) roots.push({ dir: bun, source: "developer-docs (bundled)", relBase: bun });
|
|
247
247
|
return roots;
|
|
248
248
|
}
|
|
249
|
-
|
|
249
|
+
// Monorepo mode — real bug found 2026-09-22: this used to return ONLY
|
|
250
|
+
// ai/, so every developer-docs/ page (the actual tenant-facing content —
|
|
251
|
+
// what the SDK bundle ships, what erp docs search's own packaged-mode
|
|
252
|
+
// branch above searches) was invisible to `erp docs search`/MCP's
|
|
253
|
+
// `erp_search_docs` while working directly in this repo, the one place a
|
|
254
|
+
// platform contributor is most likely to be adding new pages. Both roots
|
|
255
|
+
// now included; developer-docs/ first since it's the tenant-facing
|
|
256
|
+
// surface this tool exists to help developers not re-derive.
|
|
257
|
+
return [
|
|
258
|
+
{ dir: path.join(REPO_ROOT, "developer-docs"), source: "developer-docs", relBase: REPO_ROOT },
|
|
259
|
+
{ dir: path.join(REPO_ROOT, "ai"), source: "ai/domains doc", relBase: REPO_ROOT },
|
|
260
|
+
];
|
|
250
261
|
}
|
|
251
262
|
|
|
252
263
|
/** backend/modules root for filename-substring example search. */
|