@objectstack/lint 14.8.0 → 15.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/dist/index.cjs +54 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -7
- package/dist/index.d.ts +29 -7
- package/dist/index.js +54 -11
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.cts
CHANGED
|
@@ -359,11 +359,16 @@ declare function validateFormLayout(stack: AnyRec$5): FormLayoutFinding[];
|
|
|
359
359
|
*
|
|
360
360
|
* - `visibility-alias-deprecated` — a `visibleOn` / `visibility` key in authored
|
|
361
361
|
* source. Autofix intent: rename the key to `visibleWhen` (same value).
|
|
362
|
-
* - `visibility-root-mislayered` — a
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
362
|
+
* - `visibility-root-mislayered` — a visibility predicate whose binding root does
|
|
363
|
+
* not match its layer (ADR-0089 D3, §Context). The check is **bidirectional**:
|
|
364
|
+
* - **runtime** view/page surfaces (`*.view.ts` / `*.page.ts`) bind
|
|
365
|
+
* `record` + `current_user` (pages also expose `page.<var>`), so a `data.`-rooted
|
|
366
|
+
* predicate here is a wrong-layer paste that silently never matches; and
|
|
367
|
+
* - **metadata-editing** forms (`*.form.ts` — the row under edit) bind `data`, so
|
|
368
|
+
* a `record.`-rooted predicate there is the same bug in the other direction.
|
|
369
|
+
* The layer is supplied by the caller (`opts.layer`, default `'runtime'`): the
|
|
370
|
+
* app-lint path (`os validate` / `compile`) always lints runtime surfaces, while a
|
|
371
|
+
* file-aware caller linting a `*.form.ts` passes `layer: 'metadata'`.
|
|
367
372
|
*
|
|
368
373
|
* Scope: `views` (form `sections` / legacy `groups`, and their `fields`) and
|
|
369
374
|
* `pages` (`regions[].components[]`). Data-field `visibleWhen` is already covered
|
|
@@ -372,6 +377,17 @@ declare function validateFormLayout(stack: AnyRec$5): FormLayoutFinding[];
|
|
|
372
377
|
declare const VISIBILITY_ALIAS_DEPRECATED = "visibility-alias-deprecated";
|
|
373
378
|
declare const VISIBILITY_ROOT_MISLAYERED = "visibility-root-mislayered";
|
|
374
379
|
type VisibilitySeverity = 'error' | 'warning';
|
|
380
|
+
/**
|
|
381
|
+
* Which binding environment the linted surface belongs to (ADR-0089 §Context):
|
|
382
|
+
* - `runtime` — `*.view.ts` / `*.page.ts`; binds `record` + `current_user` (+ `page`).
|
|
383
|
+
* - `metadata` — `*.form.ts` metadata-editing forms; binds `data` (the row under edit).
|
|
384
|
+
*/
|
|
385
|
+
type VisibilityLayer = 'runtime' | 'metadata';
|
|
386
|
+
/** Options for {@link validateVisibilityPredicates}. */
|
|
387
|
+
interface VisibilityOptions {
|
|
388
|
+
/** Binding layer of the surface being linted. Defaults to `'runtime'`. */
|
|
389
|
+
layer?: VisibilityLayer;
|
|
390
|
+
}
|
|
375
391
|
interface VisibilityFinding {
|
|
376
392
|
/** Always `warning` today — both rules are advisory (see module note). */
|
|
377
393
|
severity: VisibilitySeverity;
|
|
@@ -394,8 +410,14 @@ type AnyRec$4 = Record<string, unknown>;
|
|
|
394
410
|
* `visibleOn` / `visibility` aliases before the schema folds them into
|
|
395
411
|
* `visibleWhen`. Returns findings (empty = clean); all advisory (`warning`) —
|
|
396
412
|
* the caller must never fail the build on these alone.
|
|
413
|
+
*
|
|
414
|
+
* The binding-root check is layer-directional (ADR-0089 D3): pass
|
|
415
|
+
* `opts.layer = 'metadata'` when linting a `*.form.ts` metadata-editing form (so a
|
|
416
|
+
* `record.`-rooted predicate is flagged), or leave it at the `'runtime'` default for
|
|
417
|
+
* `*.view.ts` / `*.page.ts` surfaces (so a `data.`-rooted predicate is flagged). The
|
|
418
|
+
* alias-deprecated check is layer-agnostic.
|
|
397
419
|
*/
|
|
398
|
-
declare function validateVisibilityPredicates(stack: AnyRec$4): VisibilityFinding[];
|
|
420
|
+
declare function validateVisibilityPredicates(stack: AnyRec$4, opts?: VisibilityOptions): VisibilityFinding[];
|
|
399
421
|
|
|
400
422
|
declare const CAPABILITY_REFERENCE_UNKNOWN = "capability-reference-unknown";
|
|
401
423
|
type CapabilityRefSeverity = 'error' | 'warning';
|
|
@@ -505,4 +527,4 @@ declare function buildAccessMatrix(stack: AnyRec): AccessMatrix;
|
|
|
505
527
|
*/
|
|
506
528
|
declare function diffAccessMatrix(before: AccessMatrix, after: AccessMatrix): string[];
|
|
507
529
|
|
|
508
|
-
export { APPROVAL_APPROVER_TYPE_UNKNOWN, APPROVAL_ESCALATION_REASSIGN_NO_TARGET, APPROVAL_ROLE_NOT_MEMBERSHIP_TIER, type ApprovalApproverFinding, type ApprovalApproverSeverity, CAPABILITY_REFERENCE_UNKNOWN, CHART_CONFIG_MISSING, CHART_FIELD_UNKNOWN, type CapabilityRefFinding, type CapabilityRefSeverity, type ExprIssue, FIELD_GROUP_EMPTY, FIELD_GROUP_UNDECLARED, FORM_COLSPAN_ABSOLUTE, FORM_FIELD_UNKNOWN, type FormLayoutFinding, type FormLayoutSeverity, type JsxPageFinding, type JsxPageSeverity, LIST_VIEW_FILTERS_IN_VIEWS_MODE, type ListViewModeFinding, type ListViewModeSeverity, MEASURE_AGGREGATE_INCOHERENT, PAGE_SOURCE_CLASSNAME, type ReactPageFinding, type ReactPageSeverity, type ReactPropFinding, type ReactPropSeverity, type RecordTitleFinding, type RecordTitleSeverity, SECURITY_ANCHOR_HIGH_PRIVILEGE, SECURITY_BOOK_AUDIENCE_UNKNOWN_SET, SECURITY_DELEGATION_MISSING_REASON, SECURITY_EXTERNAL_WIDER, SECURITY_GRANT_EXPIRED_AT_AUTHORING, SECURITY_MASTER_DETAIL_UNGRANTED, SECURITY_OWD_ALIAS, SECURITY_OWD_UNSET, SECURITY_PRIVATE_NO_READSCOPE, SECURITY_ROLE_WORD, SECURITY_WILDCARD_VAMA, SEMANTIC_ROLE_FIELD_UNKNOWN, STYLE_CLASSNAME_TAILWIND, STYLE_NODE_MISSING_ID, STYLE_RESPONSIVE_NO_BASE, STYLE_UNKNOWN_CSS_PROPERTY, STYLE_UNKNOWN_TOKEN, type SecurityFinding, type SecuritySeverity, type SemanticRoleFinding, type SemanticRoleSeverity, type SourceStyleFinding, type SourceStyleSeverity, type StyleFinding, type StyleSeverity, TABLE_COUNT_ONLY, TITLE_FORMAT_RETIRED, TITLE_UNRESOLVABLE, VISIBILITY_ALIAS_DEPRECATED, VISIBILITY_ROOT_MISLAYERED, type VisibilityFinding, type VisibilitySeverity, WIDGET_DATASET_UNKNOWN, WIDGET_DIMENSION_UNKNOWN, WIDGET_MEASURE_UNKNOWN, type WidgetBindingFinding, type WidgetBindingSeverity, buildAccessMatrix, diffAccessMatrix, validateApprovalApprovers, validateCapabilityReferences, validateFormLayout, validateJsxPages, validateListViewMode, validatePageSourceStyling, validateReactPageProps, validateReactPages, validateRecordTitle, validateResponsiveStyles, validateSecurityPosture, validateSemanticRoles, validateStackExpressions, validateVisibilityPredicates, validateWidgetBindings };
|
|
530
|
+
export { APPROVAL_APPROVER_TYPE_UNKNOWN, APPROVAL_ESCALATION_REASSIGN_NO_TARGET, APPROVAL_ROLE_NOT_MEMBERSHIP_TIER, type ApprovalApproverFinding, type ApprovalApproverSeverity, CAPABILITY_REFERENCE_UNKNOWN, CHART_CONFIG_MISSING, CHART_FIELD_UNKNOWN, type CapabilityRefFinding, type CapabilityRefSeverity, type ExprIssue, FIELD_GROUP_EMPTY, FIELD_GROUP_UNDECLARED, FORM_COLSPAN_ABSOLUTE, FORM_FIELD_UNKNOWN, type FormLayoutFinding, type FormLayoutSeverity, type JsxPageFinding, type JsxPageSeverity, LIST_VIEW_FILTERS_IN_VIEWS_MODE, type ListViewModeFinding, type ListViewModeSeverity, MEASURE_AGGREGATE_INCOHERENT, PAGE_SOURCE_CLASSNAME, type ReactPageFinding, type ReactPageSeverity, type ReactPropFinding, type ReactPropSeverity, type RecordTitleFinding, type RecordTitleSeverity, SECURITY_ANCHOR_HIGH_PRIVILEGE, SECURITY_BOOK_AUDIENCE_UNKNOWN_SET, SECURITY_DELEGATION_MISSING_REASON, SECURITY_EXTERNAL_WIDER, SECURITY_GRANT_EXPIRED_AT_AUTHORING, SECURITY_MASTER_DETAIL_UNGRANTED, SECURITY_OWD_ALIAS, SECURITY_OWD_UNSET, SECURITY_PRIVATE_NO_READSCOPE, SECURITY_ROLE_WORD, SECURITY_WILDCARD_VAMA, SEMANTIC_ROLE_FIELD_UNKNOWN, STYLE_CLASSNAME_TAILWIND, STYLE_NODE_MISSING_ID, STYLE_RESPONSIVE_NO_BASE, STYLE_UNKNOWN_CSS_PROPERTY, STYLE_UNKNOWN_TOKEN, type SecurityFinding, type SecuritySeverity, type SemanticRoleFinding, type SemanticRoleSeverity, type SourceStyleFinding, type SourceStyleSeverity, type StyleFinding, type StyleSeverity, TABLE_COUNT_ONLY, TITLE_FORMAT_RETIRED, TITLE_UNRESOLVABLE, VISIBILITY_ALIAS_DEPRECATED, VISIBILITY_ROOT_MISLAYERED, type VisibilityFinding, type VisibilityLayer, type VisibilityOptions, type VisibilitySeverity, WIDGET_DATASET_UNKNOWN, WIDGET_DIMENSION_UNKNOWN, WIDGET_MEASURE_UNKNOWN, type WidgetBindingFinding, type WidgetBindingSeverity, buildAccessMatrix, diffAccessMatrix, validateApprovalApprovers, validateCapabilityReferences, validateFormLayout, validateJsxPages, validateListViewMode, validatePageSourceStyling, validateReactPageProps, validateReactPages, validateRecordTitle, validateResponsiveStyles, validateSecurityPosture, validateSemanticRoles, validateStackExpressions, validateVisibilityPredicates, validateWidgetBindings };
|
package/dist/index.d.ts
CHANGED
|
@@ -359,11 +359,16 @@ declare function validateFormLayout(stack: AnyRec$5): FormLayoutFinding[];
|
|
|
359
359
|
*
|
|
360
360
|
* - `visibility-alias-deprecated` — a `visibleOn` / `visibility` key in authored
|
|
361
361
|
* source. Autofix intent: rename the key to `visibleWhen` (same value).
|
|
362
|
-
* - `visibility-root-mislayered` — a
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
362
|
+
* - `visibility-root-mislayered` — a visibility predicate whose binding root does
|
|
363
|
+
* not match its layer (ADR-0089 D3, §Context). The check is **bidirectional**:
|
|
364
|
+
* - **runtime** view/page surfaces (`*.view.ts` / `*.page.ts`) bind
|
|
365
|
+
* `record` + `current_user` (pages also expose `page.<var>`), so a `data.`-rooted
|
|
366
|
+
* predicate here is a wrong-layer paste that silently never matches; and
|
|
367
|
+
* - **metadata-editing** forms (`*.form.ts` — the row under edit) bind `data`, so
|
|
368
|
+
* a `record.`-rooted predicate there is the same bug in the other direction.
|
|
369
|
+
* The layer is supplied by the caller (`opts.layer`, default `'runtime'`): the
|
|
370
|
+
* app-lint path (`os validate` / `compile`) always lints runtime surfaces, while a
|
|
371
|
+
* file-aware caller linting a `*.form.ts` passes `layer: 'metadata'`.
|
|
367
372
|
*
|
|
368
373
|
* Scope: `views` (form `sections` / legacy `groups`, and their `fields`) and
|
|
369
374
|
* `pages` (`regions[].components[]`). Data-field `visibleWhen` is already covered
|
|
@@ -372,6 +377,17 @@ declare function validateFormLayout(stack: AnyRec$5): FormLayoutFinding[];
|
|
|
372
377
|
declare const VISIBILITY_ALIAS_DEPRECATED = "visibility-alias-deprecated";
|
|
373
378
|
declare const VISIBILITY_ROOT_MISLAYERED = "visibility-root-mislayered";
|
|
374
379
|
type VisibilitySeverity = 'error' | 'warning';
|
|
380
|
+
/**
|
|
381
|
+
* Which binding environment the linted surface belongs to (ADR-0089 §Context):
|
|
382
|
+
* - `runtime` — `*.view.ts` / `*.page.ts`; binds `record` + `current_user` (+ `page`).
|
|
383
|
+
* - `metadata` — `*.form.ts` metadata-editing forms; binds `data` (the row under edit).
|
|
384
|
+
*/
|
|
385
|
+
type VisibilityLayer = 'runtime' | 'metadata';
|
|
386
|
+
/** Options for {@link validateVisibilityPredicates}. */
|
|
387
|
+
interface VisibilityOptions {
|
|
388
|
+
/** Binding layer of the surface being linted. Defaults to `'runtime'`. */
|
|
389
|
+
layer?: VisibilityLayer;
|
|
390
|
+
}
|
|
375
391
|
interface VisibilityFinding {
|
|
376
392
|
/** Always `warning` today — both rules are advisory (see module note). */
|
|
377
393
|
severity: VisibilitySeverity;
|
|
@@ -394,8 +410,14 @@ type AnyRec$4 = Record<string, unknown>;
|
|
|
394
410
|
* `visibleOn` / `visibility` aliases before the schema folds them into
|
|
395
411
|
* `visibleWhen`. Returns findings (empty = clean); all advisory (`warning`) —
|
|
396
412
|
* the caller must never fail the build on these alone.
|
|
413
|
+
*
|
|
414
|
+
* The binding-root check is layer-directional (ADR-0089 D3): pass
|
|
415
|
+
* `opts.layer = 'metadata'` when linting a `*.form.ts` metadata-editing form (so a
|
|
416
|
+
* `record.`-rooted predicate is flagged), or leave it at the `'runtime'` default for
|
|
417
|
+
* `*.view.ts` / `*.page.ts` surfaces (so a `data.`-rooted predicate is flagged). The
|
|
418
|
+
* alias-deprecated check is layer-agnostic.
|
|
397
419
|
*/
|
|
398
|
-
declare function validateVisibilityPredicates(stack: AnyRec$4): VisibilityFinding[];
|
|
420
|
+
declare function validateVisibilityPredicates(stack: AnyRec$4, opts?: VisibilityOptions): VisibilityFinding[];
|
|
399
421
|
|
|
400
422
|
declare const CAPABILITY_REFERENCE_UNKNOWN = "capability-reference-unknown";
|
|
401
423
|
type CapabilityRefSeverity = 'error' | 'warning';
|
|
@@ -505,4 +527,4 @@ declare function buildAccessMatrix(stack: AnyRec): AccessMatrix;
|
|
|
505
527
|
*/
|
|
506
528
|
declare function diffAccessMatrix(before: AccessMatrix, after: AccessMatrix): string[];
|
|
507
529
|
|
|
508
|
-
export { APPROVAL_APPROVER_TYPE_UNKNOWN, APPROVAL_ESCALATION_REASSIGN_NO_TARGET, APPROVAL_ROLE_NOT_MEMBERSHIP_TIER, type ApprovalApproverFinding, type ApprovalApproverSeverity, CAPABILITY_REFERENCE_UNKNOWN, CHART_CONFIG_MISSING, CHART_FIELD_UNKNOWN, type CapabilityRefFinding, type CapabilityRefSeverity, type ExprIssue, FIELD_GROUP_EMPTY, FIELD_GROUP_UNDECLARED, FORM_COLSPAN_ABSOLUTE, FORM_FIELD_UNKNOWN, type FormLayoutFinding, type FormLayoutSeverity, type JsxPageFinding, type JsxPageSeverity, LIST_VIEW_FILTERS_IN_VIEWS_MODE, type ListViewModeFinding, type ListViewModeSeverity, MEASURE_AGGREGATE_INCOHERENT, PAGE_SOURCE_CLASSNAME, type ReactPageFinding, type ReactPageSeverity, type ReactPropFinding, type ReactPropSeverity, type RecordTitleFinding, type RecordTitleSeverity, SECURITY_ANCHOR_HIGH_PRIVILEGE, SECURITY_BOOK_AUDIENCE_UNKNOWN_SET, SECURITY_DELEGATION_MISSING_REASON, SECURITY_EXTERNAL_WIDER, SECURITY_GRANT_EXPIRED_AT_AUTHORING, SECURITY_MASTER_DETAIL_UNGRANTED, SECURITY_OWD_ALIAS, SECURITY_OWD_UNSET, SECURITY_PRIVATE_NO_READSCOPE, SECURITY_ROLE_WORD, SECURITY_WILDCARD_VAMA, SEMANTIC_ROLE_FIELD_UNKNOWN, STYLE_CLASSNAME_TAILWIND, STYLE_NODE_MISSING_ID, STYLE_RESPONSIVE_NO_BASE, STYLE_UNKNOWN_CSS_PROPERTY, STYLE_UNKNOWN_TOKEN, type SecurityFinding, type SecuritySeverity, type SemanticRoleFinding, type SemanticRoleSeverity, type SourceStyleFinding, type SourceStyleSeverity, type StyleFinding, type StyleSeverity, TABLE_COUNT_ONLY, TITLE_FORMAT_RETIRED, TITLE_UNRESOLVABLE, VISIBILITY_ALIAS_DEPRECATED, VISIBILITY_ROOT_MISLAYERED, type VisibilityFinding, type VisibilitySeverity, WIDGET_DATASET_UNKNOWN, WIDGET_DIMENSION_UNKNOWN, WIDGET_MEASURE_UNKNOWN, type WidgetBindingFinding, type WidgetBindingSeverity, buildAccessMatrix, diffAccessMatrix, validateApprovalApprovers, validateCapabilityReferences, validateFormLayout, validateJsxPages, validateListViewMode, validatePageSourceStyling, validateReactPageProps, validateReactPages, validateRecordTitle, validateResponsiveStyles, validateSecurityPosture, validateSemanticRoles, validateStackExpressions, validateVisibilityPredicates, validateWidgetBindings };
|
|
530
|
+
export { APPROVAL_APPROVER_TYPE_UNKNOWN, APPROVAL_ESCALATION_REASSIGN_NO_TARGET, APPROVAL_ROLE_NOT_MEMBERSHIP_TIER, type ApprovalApproverFinding, type ApprovalApproverSeverity, CAPABILITY_REFERENCE_UNKNOWN, CHART_CONFIG_MISSING, CHART_FIELD_UNKNOWN, type CapabilityRefFinding, type CapabilityRefSeverity, type ExprIssue, FIELD_GROUP_EMPTY, FIELD_GROUP_UNDECLARED, FORM_COLSPAN_ABSOLUTE, FORM_FIELD_UNKNOWN, type FormLayoutFinding, type FormLayoutSeverity, type JsxPageFinding, type JsxPageSeverity, LIST_VIEW_FILTERS_IN_VIEWS_MODE, type ListViewModeFinding, type ListViewModeSeverity, MEASURE_AGGREGATE_INCOHERENT, PAGE_SOURCE_CLASSNAME, type ReactPageFinding, type ReactPageSeverity, type ReactPropFinding, type ReactPropSeverity, type RecordTitleFinding, type RecordTitleSeverity, SECURITY_ANCHOR_HIGH_PRIVILEGE, SECURITY_BOOK_AUDIENCE_UNKNOWN_SET, SECURITY_DELEGATION_MISSING_REASON, SECURITY_EXTERNAL_WIDER, SECURITY_GRANT_EXPIRED_AT_AUTHORING, SECURITY_MASTER_DETAIL_UNGRANTED, SECURITY_OWD_ALIAS, SECURITY_OWD_UNSET, SECURITY_PRIVATE_NO_READSCOPE, SECURITY_ROLE_WORD, SECURITY_WILDCARD_VAMA, SEMANTIC_ROLE_FIELD_UNKNOWN, STYLE_CLASSNAME_TAILWIND, STYLE_NODE_MISSING_ID, STYLE_RESPONSIVE_NO_BASE, STYLE_UNKNOWN_CSS_PROPERTY, STYLE_UNKNOWN_TOKEN, type SecurityFinding, type SecuritySeverity, type SemanticRoleFinding, type SemanticRoleSeverity, type SourceStyleFinding, type SourceStyleSeverity, type StyleFinding, type StyleSeverity, TABLE_COUNT_ONLY, TITLE_FORMAT_RETIRED, TITLE_UNRESOLVABLE, VISIBILITY_ALIAS_DEPRECATED, VISIBILITY_ROOT_MISLAYERED, type VisibilityFinding, type VisibilityLayer, type VisibilityOptions, type VisibilitySeverity, WIDGET_DATASET_UNKNOWN, WIDGET_DIMENSION_UNKNOWN, WIDGET_MEASURE_UNKNOWN, type WidgetBindingFinding, type WidgetBindingSeverity, buildAccessMatrix, diffAccessMatrix, validateApprovalApprovers, validateCapabilityReferences, validateFormLayout, validateJsxPages, validateListViewMode, validatePageSourceStyling, validateReactPageProps, validateReactPages, validateRecordTitle, validateResponsiveStyles, validateSecurityPosture, validateSemanticRoles, validateStackExpressions, validateVisibilityPredicates, validateWidgetBindings };
|
package/dist/index.js
CHANGED
|
@@ -1037,6 +1037,7 @@ function validateRecordTitle(stack) {
|
|
|
1037
1037
|
// src/validate-semantic-roles.ts
|
|
1038
1038
|
var FIELD_GROUP_UNDECLARED = "field-group-undeclared";
|
|
1039
1039
|
var FIELD_GROUP_EMPTY = "field-group-empty";
|
|
1040
|
+
var FIELD_GROUP_SHADOWED = "field-group-shadowed";
|
|
1040
1041
|
var SEMANTIC_ROLE_FIELD_UNKNOWN = "semantic-role-field-unknown";
|
|
1041
1042
|
function asArray10(v) {
|
|
1042
1043
|
if (Array.isArray(v)) return v;
|
|
@@ -1110,6 +1111,31 @@ function validateSemanticRoles(stack) {
|
|
|
1110
1111
|
hint: `Fix the field name (highlightFields drives default columns, cards, previews and the detail highlight strip, in order).`
|
|
1111
1112
|
});
|
|
1112
1113
|
}
|
|
1114
|
+
const declaredStrings = highlights.filter(
|
|
1115
|
+
(h) => typeof h === "string" && h.length > 0
|
|
1116
|
+
);
|
|
1117
|
+
if (declaredStrings.length > 0 && declaredGroups.size > 0) {
|
|
1118
|
+
const declaredTitle = [obj.nameField, obj.primaryField, obj.displayNameField].find((v) => typeof v === "string" && v.length > 0 && fieldNames.has(v));
|
|
1119
|
+
const titleField = declaredTitle ?? ["name", "full_name", "title", "subject", "display_name"].find((c) => fieldNames.has(c));
|
|
1120
|
+
const stripSet = new Set(
|
|
1121
|
+
declaredStrings.filter((h) => h !== titleField).slice(0, 4)
|
|
1122
|
+
);
|
|
1123
|
+
const hiddenFromBody = new Set(stripSet);
|
|
1124
|
+
if (titleField) hiddenFromBody.add(titleField);
|
|
1125
|
+
for (const key of declaredGroups) {
|
|
1126
|
+
const members = Object.entries(fields).filter(([, f]) => f?.group === key && f?.hidden !== true).map(([fname]) => fname);
|
|
1127
|
+
if (members.length === 0) continue;
|
|
1128
|
+
if (!members.every((m) => hiddenFromBody.has(m))) continue;
|
|
1129
|
+
findings.push({
|
|
1130
|
+
severity: "warning",
|
|
1131
|
+
rule: FIELD_GROUP_SHADOWED,
|
|
1132
|
+
where,
|
|
1133
|
+
path: `${path}.fieldGroups`,
|
|
1134
|
+
message: `${objName}: every field in group "${key}" (${members.join(", ")}) is hoisted into the detail highlight strip (or is the record title) \u2014 the group renders on forms but never on detail pages`,
|
|
1135
|
+
hint: `Keep at least one non-highlighted field in "${key}", or remove the group if the strip already covers it. (Detail pages show the first 4 highlightFields as the top strip and hide them from the body.)`
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1113
1139
|
}
|
|
1114
1140
|
return findings;
|
|
1115
1141
|
}
|
|
@@ -1212,10 +1238,22 @@ function predicateSource(v) {
|
|
|
1212
1238
|
}
|
|
1213
1239
|
return void 0;
|
|
1214
1240
|
}
|
|
1215
|
-
function
|
|
1216
|
-
return
|
|
1241
|
+
function usesRoot(source, root) {
|
|
1242
|
+
return new RegExp(`(^|[^.\\w$])${root}\\.\\w`).test(source);
|
|
1217
1243
|
}
|
|
1218
|
-
|
|
1244
|
+
var MISLAYER_BY_LAYER = {
|
|
1245
|
+
runtime: {
|
|
1246
|
+
forbiddenRoot: "data",
|
|
1247
|
+
message: "visibility predicate is rooted at `data.` \u2014 that is the metadata-editing-form root (a `*.form.ts` row under edit), not a runtime surface. A runtime view/page predicate that binds `data.` never matches and the element renders unconditionally (ADR-0089).",
|
|
1248
|
+
hint: "Runtime record surfaces bind `record` + `current_user` (pages also expose `page.<var>`). Use e.g. `record.status == 'open'` instead of `data.status == 'open'`."
|
|
1249
|
+
},
|
|
1250
|
+
metadata: {
|
|
1251
|
+
forbiddenRoot: "record",
|
|
1252
|
+
message: "visibility predicate is rooted at `record.` \u2014 that is the runtime record-surface root (a `*.view.ts` / `*.page.ts` live record), not a metadata-editing form. A `*.form.ts` predicate that binds `record.` never matches and the element renders unconditionally (ADR-0089).",
|
|
1253
|
+
hint: "Metadata-editing forms bind `data` (the row under edit). Use e.g. `data.type == 'grid'` instead of `record.type == 'grid'`."
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
function checkElement(el, where, path, layer, findings) {
|
|
1219
1257
|
for (const alias of ALIASES) {
|
|
1220
1258
|
if (el[alias] !== void 0) {
|
|
1221
1259
|
findings.push({
|
|
@@ -1230,21 +1268,23 @@ function checkElement(el, where, path, findings) {
|
|
|
1230
1268
|
}
|
|
1231
1269
|
const raw = el[CANONICAL] ?? el.visibleOn ?? el.visibility;
|
|
1232
1270
|
const source = predicateSource(raw);
|
|
1233
|
-
|
|
1271
|
+
const rule = MISLAYER_BY_LAYER[layer];
|
|
1272
|
+
if (source && usesRoot(source, rule.forbiddenRoot)) {
|
|
1234
1273
|
findings.push({
|
|
1235
1274
|
severity: "warning",
|
|
1236
1275
|
rule: VISIBILITY_ROOT_MISLAYERED,
|
|
1237
1276
|
where,
|
|
1238
1277
|
path,
|
|
1239
|
-
message:
|
|
1240
|
-
hint:
|
|
1278
|
+
message: rule.message,
|
|
1279
|
+
hint: rule.hint
|
|
1241
1280
|
});
|
|
1242
1281
|
}
|
|
1243
1282
|
}
|
|
1244
1283
|
function isFieldObject(entry) {
|
|
1245
1284
|
return !!entry && typeof entry === "object" && !Array.isArray(entry);
|
|
1246
1285
|
}
|
|
1247
|
-
function validateVisibilityPredicates(stack) {
|
|
1286
|
+
function validateVisibilityPredicates(stack, opts = {}) {
|
|
1287
|
+
const layer = opts.layer ?? "runtime";
|
|
1248
1288
|
const findings = [];
|
|
1249
1289
|
const views = asArray12(stack.views);
|
|
1250
1290
|
for (let i = 0; i < views.length; i++) {
|
|
@@ -1258,12 +1298,12 @@ function validateVisibilityPredicates(stack) {
|
|
|
1258
1298
|
const sec = sections[s];
|
|
1259
1299
|
if (!sec || typeof sec !== "object") continue;
|
|
1260
1300
|
const secPath = `views[${i}].${bucket}[${s}]`;
|
|
1261
|
-
checkElement(sec, where, secPath, findings);
|
|
1301
|
+
checkElement(sec, where, secPath, layer, findings);
|
|
1262
1302
|
const secFields = Array.isArray(sec.fields) ? sec.fields : [];
|
|
1263
1303
|
for (let f = 0; f < secFields.length; f++) {
|
|
1264
1304
|
const entry = secFields[f];
|
|
1265
1305
|
if (isFieldObject(entry)) {
|
|
1266
|
-
checkElement(entry, where, `${secPath}.fields[${f}]`, findings);
|
|
1306
|
+
checkElement(entry, where, `${secPath}.fields[${f}]`, layer, findings);
|
|
1267
1307
|
}
|
|
1268
1308
|
}
|
|
1269
1309
|
}
|
|
@@ -1282,7 +1322,7 @@ function validateVisibilityPredicates(stack) {
|
|
|
1282
1322
|
for (let c = 0; c < components.length; c++) {
|
|
1283
1323
|
const comp = components[c];
|
|
1284
1324
|
if (comp && typeof comp === "object") {
|
|
1285
|
-
checkElement(comp, where, `pages[${i}].regions[${r}].components[${c}]`, findings);
|
|
1325
|
+
checkElement(comp, where, `pages[${i}].regions[${r}].components[${c}]`, layer, findings);
|
|
1286
1326
|
}
|
|
1287
1327
|
}
|
|
1288
1328
|
}
|
|
@@ -1318,6 +1358,9 @@ function validateCapabilityReferences(stack) {
|
|
|
1318
1358
|
const findings = [];
|
|
1319
1359
|
if (!stack || typeof stack !== "object") return findings;
|
|
1320
1360
|
const known = new Set(PLATFORM_CAPABILITY_NAMES);
|
|
1361
|
+
for (const cap of asArray13(stack.capabilities)) {
|
|
1362
|
+
if (typeof cap.name === "string" && cap.name.length > 0) known.add(cap.name);
|
|
1363
|
+
}
|
|
1321
1364
|
for (const ps of asArray13(stack.permissions)) {
|
|
1322
1365
|
for (const cap of asCapArray(ps.systemPermissions)) known.add(cap);
|
|
1323
1366
|
}
|
|
@@ -1328,7 +1371,7 @@ function validateCapabilityReferences(stack) {
|
|
|
1328
1371
|
if (typeof name === "string" && name.length > 0) known.add(name);
|
|
1329
1372
|
}
|
|
1330
1373
|
}
|
|
1331
|
-
const hint = "Fix the capability name, declare it on a permission set\u2019s systemPermissions, ship a sys_capability seed row, or ignore this if the capability is provided by another installed package (references fail closed at runtime).";
|
|
1374
|
+
const hint = "Fix the capability name, define it with defineCapability (stack.capabilities), declare it on a permission set\u2019s systemPermissions, ship a sys_capability seed row, or ignore this if the capability is provided by another installed package (references fail closed at runtime).";
|
|
1332
1375
|
const flag = (cap, where, path) => {
|
|
1333
1376
|
if (known.has(cap)) return;
|
|
1334
1377
|
findings.push({
|