@ethisyscore/eslint-plugin-coreconnect 1.76.0 → 1.78.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethisyscore/eslint-plugin-coreconnect",
3
- "version": "1.76.0",
3
+ "version": "1.78.0",
4
4
  "description": "ESLint rules enforcing EthisysCore plugin frontend conventions. Published so a new rule reaches every plugin on a version bump, rather than being copied into each scaffold and drifting.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import noHardcodedColors from "./rules/no-hardcoded-colors.js";
2
2
  import noInlineObjectType from "./rules/no-inline-object-type.js";
3
3
  import noInlineStringUnion from "./rules/no-inline-string-union.js";
4
+ import noLocalCompletenessBanner from "./rules/no-local-completeness-banner.js";
4
5
  import noLocalDataGrid from "./rules/no-local-data-grid.js";
5
6
  import noLocalDateInput from "./rules/no-local-date-input.js";
6
7
  import noLocalEmptyState from "./rules/no-local-empty-state.js";
8
+ import noLocalMappingEditor from "./rules/no-local-mapping-editor.js";
7
9
  import noLocalSearchInput from "./rules/no-local-search-input.js";
8
10
  import noParamInNavPath from "./rules/no-param-in-nav-path.js";
9
11
 
@@ -11,9 +13,11 @@ const rules = {
11
13
  "no-hardcoded-colors": noHardcodedColors,
12
14
  "no-inline-object-type": noInlineObjectType,
13
15
  "no-inline-string-union": noInlineStringUnion,
16
+ "no-local-completeness-banner": noLocalCompletenessBanner,
14
17
  "no-local-data-grid": noLocalDataGrid,
15
18
  "no-local-date-input": noLocalDateInput,
16
19
  "no-local-empty-state": noLocalEmptyState,
20
+ "no-local-mapping-editor": noLocalMappingEditor,
17
21
  "no-local-search-input": noLocalSearchInput,
18
22
  "no-param-in-nav-path": noParamInNavPath,
19
23
  };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Disallow a plugin declaring its own connector mapping-completeness banner.
3
+ *
4
+ * The second backstop to the Tool Mappings promotion, and the one that shows why these rules report
5
+ * rather than warn. `MappingCompletenessBanner` moved into
6
+ * `@ethisyscore/plugin-ui/components/connectors` in 1.71.6, and `timesheets` adopted it. Measured on
7
+ * origin/develop months later, `self-healing` and `tech` still carried their own - 126 and 155 lines
8
+ * - and one of them had drifted on the string that matters most:
9
+ *
10
+ * - `self-healing`'s optional-gap alert is titled "Optional operations not mapped". The SDK's, and
11
+ * `tech`'s, read "Optional operations aren't fully mapped".
12
+ *
13
+ * That reads like a wording nit and is not. BOTH alerts select on
14
+ * `unmappedRequiredCanonicalFields`, i.e. on FIELD mapping, but "not mapped" describes TOOL mapping,
15
+ * which is a binary an operator can close with one button. So operators pressed auto-match - which
16
+ * picks the tool and cannot touch its fields - watched the count refuse to move, and raised the
17
+ * banner as broken. It was right; the sentence describing it was wrong, and the cost was paid in
18
+ * support round-trips rather than in a stack trace. Nothing was ever going to catch that in review,
19
+ * because reviewing a copy tells you nothing about the copy next door.
20
+ *
21
+ * Fires on a DECLARATION, never a use, so importing `MappingCompletenessBanner` from
22
+ * `@ethisyscore/plugin-ui/components/connectors` and rendering it is exactly what the rule wants -
23
+ * as is a local wrapper that composes it, which the shared helper exempts.
24
+ *
25
+ * ## Why there is no companion rule for the selectors
26
+ *
27
+ * `mappingCompleteness.ts` - `selectRequiredIncompleteOperations`,
28
+ * `selectOptionalIncompleteOperations`, `selectMappingReadiness` - is duplicated in the same two
29
+ * plugins, and it is where the field-vs-tool predicate actually lives, so it looks like the more
30
+ * dangerous copy. Measured, it is not:
31
+ *
32
+ * - Both plugin copies are BEHAVIOURALLY IDENTICAL to the promoted ones. Diffed against the SDK,
33
+ * they differ only by the type import's source and by selectors they never used being absent.
34
+ * Three copies, months, zero logic drift. What drifted was the prose explaining the predicate to
35
+ * an operator - which lives in the banner, and is what this rule guards.
36
+ * - The local selectors have exactly ONE consumer each, the local banner beside them. No page,
37
+ * hook or service imports them, and `tech`'s `selectIncompleteOperations` is referenced by
38
+ * nothing but its own docstring. So this rule already closes the duplication: `timesheets`,
39
+ * the one plugin that adopted the shared banner, deleted its whole `components/connectors`
40
+ * directory in the same move.
41
+ * - `createNoLocalComponentRule` could not express it anyway. It gates on `isComponentName`
42
+ * (`^[A-Z]`) and on a component-shaped initializer, and a selector is neither. A selectors rule
43
+ * would be new machinery for no coverage any measured file lacks.
44
+ *
45
+ * If a plugin ever imports those selectors somewhere other than its own banner, that reasoning stops
46
+ * holding and the rule becomes worth adding. Nothing in the estate does today.
47
+ *
48
+ * @type {import("eslint").Rule.RuleModule}
49
+ */
50
+ import { createNoLocalComponentRule } from "./componentDeclarations.js";
51
+
52
+ export default createNoLocalComponentRule({
53
+ // Both a MAPPING context AND the literal `Completeness` AND a banner-shaped suffix. All three are
54
+ // load-bearing, and each was chosen against a count rather than a guess.
55
+ //
56
+ // A bare `Banner$` is unusable: the estate declares 25 other banners, among them
57
+ // `ConnectorWarningBanner`, `AiPlatformStatusBanner`, `PluginRuntimeBanner`, `ErrorBanner`,
58
+ // `SuspiciousActivityBanner`, `PinnedMessagesBanner` and `InvoiceWarningsBanner` - the first of
59
+ // which sits on the very same Tool Mappings page. Requiring `Completeness` spares all of them.
60
+ //
61
+ // A bare `Completeness(Banner|Alert)$` is no better, because `Completeness` is NOT this feature's
62
+ // private word. `repo-monitoring` ships a `DataCompleteness` union ("Complete" |
63
+ // "PartialFallbackApplied" | "Degraded") in both the host contracts and `tech`, and a
64
+ // `DataCompletenessBanner` warning that commit data is degraded is exactly the component that
65
+ // would appear next. So the qualifier alternation is required, and it is the same shape
66
+ // `no-local-mapping-editor` uses: `Mapping` normally, `Connector` or `Tool` for a rename that
67
+ // drops it. A prefixed spelling needs no branch of its own - `ToolMappingCompletenessBanner` ends
68
+ // in the branch-one form already.
69
+ //
70
+ // The suffix set is likewise measured. `Alert` is the MUI component the banner literally renders,
71
+ // so it is the likeliest rename; `Notice` and `Callout` are estate vocabulary (`DeveloperCallout`).
72
+ // `(View|Panel)` is optional so the adapter-view spelling matches. What is deliberately EXCLUDED
73
+ // is the rest of the `MappingCompleteness*` family, which is legitimately duplicated everywhere as
74
+ // wire contracts: `MappingCompletenessItem` (112 references), `MappingCompletenessResult` (34),
75
+ // `ConnectorMappingCompletenessResult`, `GetToolMappingCompletenessResponse` and
76
+ // `MappingCompletenessBannerProps`. Anchoring on a banner-shaped suffix is what keeps them out.
77
+ //
78
+ // Hooks are excluded by the shared helper, which matters here: `useConnectorMappingCompleteness`
79
+ // is a real hook in three repos. Lowercase names are excluded too, which covers
80
+ // `getMappingCompleteness`, `mappingCompleteness`, `dataCompleteness` and
81
+ // `invalidateMappingsAndCompleteness`.
82
+ pattern: /(Mappings?|Connector|Tool)Completeness(Banner|Alert|Notice|Callout)(View|Panel)?$/,
83
+ canonical: {
84
+ source: "@ethisyscore/plugin-ui/components/connectors",
85
+ name: "MappingCompletenessBanner",
86
+ },
87
+ messageId: "noLocalCompletenessBanner",
88
+ description:
89
+ "Disallow a locally-declared mapping completeness banner - import MappingCompletenessBanner from @ethisyscore/plugin-ui/components/connectors",
90
+ message:
91
+ "'{{name}}' re-declares the mapping completeness banner. Import { MappingCompletenessBanner } from '@ethisyscore/plugin-ui/components/connectors' instead: a local copy had already drifted to \"Optional operations not mapped\", which describes TOOL mapping when the alert selects on unmapped FIELDS - so operators pressed auto-match, saw the count refuse to move, and reported the banner as broken. If this reports completeness for something other than connector mappings, rename it so it does not read as one.",
92
+ });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Disallow a plugin declaring its own connector-mapping field-translation editor.
3
+ *
4
+ * The backstop to the promotion that finished the Tool Mappings surface. Measured on origin/develop:
5
+ * `ConnectorMappingTranslationEditorView.tsx` existed in FOUR places - the host's gogo-ui adapter
6
+ * plus `self-healing`, `tech` and `timesheets` - at 592/522/527/527 lines, and the copies had
7
+ * already drifted in three ways, one of them a live defect:
8
+ *
9
+ * - `self-healing` rendered the "Test mapping" button unconditionally. Its enclosing block opens
10
+ * on `(onTestMapping || onSuggestTranslation)`, so a consumer wiring only auto-configure got a
11
+ * button whose handler returns on its first line - a DEAD CONTROL. Nobody noticed, because
12
+ * there was no single source to notice it against.
13
+ * - `self-healing` washed two panels with a literal `rgba(0,0,0,0.03)`, invisible in dark mode.
14
+ * - `self-healing` carried emoji in the extraction chips, on top of the icon and colour the chip
15
+ * already had.
16
+ *
17
+ * Meanwhile the three plugin copies had dropped the HOST's `?? []` coalescing on the canonical field
18
+ * lists, which is the only thing standing between an operation with no canonical schema and a
19
+ * TypeError - the host copy carries a comment recording that exact crash. So each copy held a fix
20
+ * the others lacked, in both directions. That is what makes duplication here expensive rather than
21
+ * merely untidy, and it is why the rule reports rather than warns.
22
+ *
23
+ * Fires on a DECLARATION, never a use, so importing the shared component from
24
+ * `@ethisyscore/plugin-ui/components/connectors` and rendering it is exactly what the rule wants -
25
+ * as is a local wrapper that composes it, which the shared helper exempts.
26
+ *
27
+ * @type {import("eslint").Rule.RuleModule}
28
+ */
29
+ import { createNoLocalComponentRule } from "./componentDeclarations.js";
30
+
31
+ export default createNoLocalComponentRule({
32
+ // Every branch requires the MAPPING context, and that is the whole design of this pattern.
33
+ //
34
+ // A bare `Editor$` is unusable here: measured across the plugin repos, legitimate local editors
35
+ // ending in `Editor` include `BreachAssessmentTaskTemplateEditor`, `BusinessHoursHolidaysEditor`,
36
+ // `SlaPolicyEscalationRulesEditor`, `SupplierDdIntervalDaysByTierEditor`,
37
+ // `SupplementSequenceEditorView` and `InlineMessageEditor` - none of them this component. A bare
38
+ // `TranslationEditor$` is no better, because the estate runs i18next and a localisation admin
39
+ // screen would carry exactly that name.
40
+ //
41
+ // So branch one needs the literal `Mapping` before `Editor`, which also spares
42
+ // `LicenceTypeSeverityMapEditor` (`Map`, not `Mapping`); branch two allows the `Mapping` token to
43
+ // be dropped only when `Connector` or `Tool` is present instead, which is how a rename would most
44
+ // plausibly still mean this component. `(View|Dialog)` is optional so both the adapter-view
45
+ // spelling and a dialog-named rewrite match. Hook names are excluded by the shared helper, so
46
+ // `useTranslationEditor` and `useMappingEditor` do not match, and lowercase `translationEditorView`
47
+ // - a real local variable in two of these repos - is excluded as a non-component name.
48
+ pattern: /(Mapping(Translation)?Editor|(Connector|Tool)Translation(Field)?Editor)(View|Dialog)?$/,
49
+ canonical: {
50
+ source: "@ethisyscore/plugin-ui/components/connectors",
51
+ name: "ConnectorMappingTranslationEditorView",
52
+ },
53
+ messageId: "noLocalMappingEditor",
54
+ description:
55
+ "Disallow a locally-declared connector-mapping translation editor - import ConnectorMappingTranslationEditorView from @ethisyscore/plugin-ui/components/connectors",
56
+ message:
57
+ "'{{name}}' re-declares the connector-mapping translation editor. Import { ConnectorMappingTranslationEditorView } from '@ethisyscore/plugin-ui/components/connectors' instead: four copies had already drifted, one of them rendering a dead 'Test mapping' button and three of them dropping the null tolerance on the canonical field lists. If this edits something other than a connector mapping, rename it so it does not read as one.",
58
+ });