@brighterly/lib-fe-codestyle 0.2.0 → 0.3.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/README.md CHANGED
@@ -53,28 +53,28 @@ exported as data.
53
53
 
54
54
  | Rule | Level | Checks |
55
55
  |---|---|---|
56
- | `_R1` | error | bare `localStorage` / `sessionStorage` — the accessor itself throws on Safari ITP and in-app webviews |
57
- | `_R2` | error | SCSS rules inline in a Vue `<style>` block instead of a referenced `.scss` file |
58
- | `_R3` | error | `JSON.parse` with no enclosing `try`/`catch` — a corrupt entry breaks that path permanently |
59
- | `_R4` | error | `@font-face` declaring a font *stack* — an invalid descriptor, so the browser drops the face silently |
60
- | `_C1` | warn | hand-rolled browser API with a VueUse equivalent |
61
- | `_C2` | warn | a component duplicating an existing UI-kit component name |
62
- | `_C3` | warn | a literal colour in SCSS instead of a design token |
63
- | `_C4` | warn | a hand-rolled date format string, or `toISOString()` |
64
- | `_C5` | warn | a type file outside the `types/<entity>/{enums,interfaces,types}.ts` structure |
65
- | `_C6` | warn | a raw `font-family` / `font-size` / `font-weight` declaration instead of the app's typography source |
66
- | `_C7` | warn | `<script>` attribute order differing from the app's canonical (`setup lang` vs `lang setup`) |
67
-
68
- Level discipline: `error` is reserved for crash-class rules (`_R*` — a visitor-facing
56
+ | `core/R1` | error | bare `localStorage` / `sessionStorage` — the accessor itself throws on Safari ITP and in-app webviews |
57
+ | `core/R2` | error | SCSS rules inline in a Vue `<style>` block instead of a referenced `.scss` file |
58
+ | `core/R3` | error | `JSON.parse` with no enclosing `try`/`catch` — a corrupt entry breaks that path permanently |
59
+ | `core/R4` | error | `@font-face` declaring a font *stack* — an invalid descriptor, so the browser drops the face silently |
60
+ | `core/C1` | warn | hand-rolled browser API with a VueUse equivalent |
61
+ | `core/C2` | warn | a component duplicating an existing UI-kit component name |
62
+ | `core/C3` | warn | a literal colour in SCSS instead of a design token |
63
+ | `core/C4` | warn | a hand-rolled date format string, or `toISOString()` |
64
+ | `core/C5` | warn | a type file outside the `types/<entity>/{enums,interfaces,types}.ts` structure |
65
+ | `core/C6` | warn | a raw `font-family` / `font-size` / `font-weight` declaration instead of the app's typography source |
66
+ | `core/C7` | warn | `<script>` attribute order differing from the app's canonical (`setup lang` vs `lang setup`) |
67
+
68
+ Level discipline: `error` is reserved for crash-class rules (`core/R*` — a visitor-facing
69
69
  throw or silent production breakage); `warn` is for conventions and style choices
70
- (`_C*`). A consumer's `levels` ceiling on an `_R*` rule is NOT the tool for an existing
70
+ (`core/C*`). A consumer's `levels` ceiling on an `core/R*` rule is NOT the tool for an existing
71
71
  backlog — fix the call sites or allowlist the reviewed exceptions with reasons.
72
72
  Underscore-prefixed SCSS partials (`_extends.scss`, `_colors-*.scss`, …) are declaration
73
- files by convention in every Brighterly app and are exempt from `_C3`/`_C6` wholesale.
73
+ files by convention in every Brighterly app and are exempt from `core/C3`/`core/C6` wholesale.
74
74
 
75
- `_R*` were ported from land-app, where each was a production scar (as `R1`, `R7`, `R18`,
75
+ `core/R*` were ported from land-app, where each was a production scar (as `R1`, `R7`, `R18`,
76
76
  `R28` — renumbered sequentially when the prefix landed, pre-publish, while no released
77
- version pinned the old tokens). `_C*` are conventions with
77
+ version pinned the old tokens). `core/C*` are conventions with
78
78
  no incident behind them *yet* — they ship at `warn` so a consuming app can measure what they
79
79
  find before anyone decides to block on them. Promoting one to `error` is a **major** bump.
80
80
 
@@ -100,21 +100,21 @@ A shared rule must not hardcode a consuming app's paths. Each app declares them
100
100
  // invariants/config.mts
101
101
  export const settings = {
102
102
  safeStorageModule: 'safeLocalStorage from @helpers/safe-local-storage',
103
- safeStorageFile: 'app/utils/helpers/safe-local-storage.ts', // _R1 auto-exempts the wrapper itself
103
+ safeStorageFile: 'app/utils/helpers/safe-local-storage.ts', // core/R1 auto-exempts the wrapper itself
104
104
  safeJsonParse: 'parseStoredJson() from @helpers/safe-local-storage',
105
105
  scssTokensFile: 'app/assets/scss/_colors.scss',
106
106
  uiKitRoot: 'app/components/UI',
107
107
  dateFormatModule: 'DateFormat from @ts-types/date/enums',
108
- typesRoot: 'app/types', // _C5 (default shown)
109
- typographySource: 'the font() mixin from @scss/_mixins.scss', // _C6 — cs-app points this at its AppText component instead
110
- scriptAttrOrder: 'setup-first', // _C7 — or 'lang-first' (default 'setup-first')
108
+ typesRoot: 'app/types', // core/C5 (default shown)
109
+ typographySource: 'the font() mixin from @scss/_mixins.scss', // core/C6 — cs-app points this at its AppText component instead
110
+ scriptAttrOrder: 'setup-first', // core/C7 — or 'lang-first' (default 'setup-first')
111
111
  }
112
112
  ```
113
113
 
114
114
  A missing key degrades the rule's *message*, never its firing. A rule that went quiet
115
115
  because a path was unset would be the exact silent failure this package exists to prevent.
116
- Two keys shape behaviour rather than the message — `typesRoot` (where `_C5` looks) and
117
- `scriptAttrOrder` (which direction `_C7` flags) — and both fall back to the documented
116
+ Two keys shape behaviour rather than the message — `typesRoot` (where `core/C5` looks) and
117
+ `scriptAttrOrder` (which direction `core/C7` flags) — and both fall back to the documented
118
118
  default above, so leaving them unset still never silences the rule.
119
119
 
120
120
  ## What consumers see in the IDE
@@ -124,7 +124,7 @@ requires opening this repo:
124
124
 
125
125
  - **`sharedRuleIds`** (from `…/invariants/rules`) — the catalog: one constant per rule,
126
126
  each JSDoc'd with level, what it checks, and the fix. Use them as `allowlists` /
127
- `levels` keys (`[sharedRuleIds._C6]: 'off'`) and hover for the contract. A test pins
127
+ `levels` keys (`[sharedRuleIds.core/C6]: 'off'`) and hover for the contract. A test pins
128
128
  the catalog to the shipped rules, so it cannot drift.
129
129
  - **`SharedRuleSettings`** (from `…/invariants`) — annotate the app's `settings` const
130
130
  with it to get completion plus the per-key contract, and a type error on an invalid
@@ -171,18 +171,18 @@ The ESLint config and its seven rules are already plain `.mjs` and ship as-is.
171
171
 
172
172
  ## Rule IDs
173
173
 
174
- Shared rules carry an **underscore prefix**: `_R1`, `_C3`. App-local rules stay bare:
175
- `R1`, `B2`. The prefix marks provenance — an `_`-id is shipped by this package, never
174
+ Shared rules carry the **`core/` prefix**: `core/R1`, `core/C3`. App-local rules stay bare:
175
+ `R1`, `B2`. The prefix marks provenance — a `core/`-id is shipped by this package, never
176
176
  defined, renumbered or recycled in a consumer — and it keeps the two number lines
177
177
  independent: every app allocates its own `R1, R2, …` naturally, with no risk of
178
178
  colliding with a rule the package ships later. `run()` still exits 2 on a duplicate,
179
179
  so a collision is loud, not silent.
180
180
 
181
- Within the package each family is numbered sequentially with no gaps (`_R1`–`_R4`,
182
- `_C1`–`_C7`) — enforced by `test/invariants/rule-ids.test.ts`. The family letter keeps
181
+ Within the package each family is numbered sequentially with no gaps (`core/R1`–`core/R4`,
182
+ `core/C1`–`core/C7`) — enforced by `test/invariants/rule-ids.test.ts`. The family letter keeps
183
183
  meaning provenance — `R` regression scar, `B` business, `C` convention, `L`
184
- linter-owned — not topic. The underscore does **not** mean private (everything here is
185
- the public surface); it means "owned by the library, hands off the numbering."
184
+ linter-owned — not topic. `core/` in a report means exactly one thing: this finding
185
+ comes from the library, and its numbering is the library's to own.
186
186
 
187
187
  ## Versioning is about blocking power
188
188
 
@@ -2,12 +2,12 @@
2
2
  * Convention rules — preferences we chose, with no production incident behind them.
3
3
  * Advisory by default (`warn`): nudged, not policed, and droppable by team consensus.
4
4
  *
5
- * `_C1` was land-app's `C1`, and reads the same everywhere. `_C2`-`_C4` are new, and came out of a
5
+ * `core/C1` was land-app's `C1`, and reads the same everywhere. `core/C2`-`core/C4` are new, and came out of a
6
6
  * real code review rather than an outage: a developer shipped a component that duplicated
7
7
  * an existing UI-kit one, with colours that were not design tokens. Review caught it;
8
8
  * nothing automated did, because the conventions existed only in reviewers\' heads.
9
9
  *
10
- * `_C5`-`_C7` (2026-09-23) come from the org's Notion frontend-conventions doc and from
10
+ * `core/C5`-`core/C7` (2026-09-23) come from the org's Notion frontend-conventions doc and from
11
11
  * mining 400 PR review comments across the four apps: entity-folder type organization,
12
12
  * typography through the app's declared source, and one canonical <script> attribute order.
13
13
  *
@@ -2,12 +2,12 @@
2
2
  * Convention rules — preferences we chose, with no production incident behind them.
3
3
  * Advisory by default (`warn`): nudged, not policed, and droppable by team consensus.
4
4
  *
5
- * `_C1` was land-app's `C1`, and reads the same everywhere. `_C2`-`_C4` are new, and came out of a
5
+ * `core/C1` was land-app's `C1`, and reads the same everywhere. `core/C2`-`core/C4` are new, and came out of a
6
6
  * real code review rather than an outage: a developer shipped a component that duplicated
7
7
  * an existing UI-kit one, with colours that were not design tokens. Review caught it;
8
8
  * nothing automated did, because the conventions existed only in reviewers\' heads.
9
9
  *
10
- * `_C5`-`_C7` (2026-09-23) come from the org's Notion frontend-conventions doc and from
10
+ * `core/C5`-`core/C7` (2026-09-23) come from the org's Notion frontend-conventions doc and from
11
11
  * mining 400 PR review comments across the four apps: entity-folder type organization,
12
12
  * typography through the app's declared source, and one canonical <script> attribute order.
13
13
  *
@@ -16,7 +16,7 @@
16
16
  */
17
17
  import { eachLine, setting } from './shared.js';
18
18
  const vueUseFirst = {
19
- id: '_C1',
19
+ id: 'core/C1',
20
20
  title: 'hand-rolled browser API with a VueUse equivalent',
21
21
  level: 'warn',
22
22
  why: 'VueUse handles the lifecycle, SSR and error paths we otherwise re-implement (and forget to tear down) per call site',
@@ -47,7 +47,7 @@ const vueUseFirst = {
47
47
  },
48
48
  };
49
49
  const uiKitReuseFirst = {
50
- id: '_C2',
50
+ id: 'core/C2',
51
51
  title: 'component duplicates an existing UI-kit component name',
52
52
  level: 'warn',
53
53
  once: true,
@@ -81,7 +81,7 @@ const uiKitReuseFirst = {
81
81
  },
82
82
  };
83
83
  const scssColourTokens = {
84
- id: '_C3',
84
+ id: 'core/C3',
85
85
  title: 'literal colour in SCSS instead of a design token',
86
86
  level: 'warn',
87
87
  why: 'a hardcoded colour is invisible to the token system — it survives theme changes and palette corrections silently, and only a side-by-side screenshot shows the drift',
@@ -108,7 +108,7 @@ const scssColourTokens = {
108
108
  },
109
109
  };
110
110
  const dateFormatEnum = {
111
- id: '_C4',
111
+ id: 'core/C4',
112
112
  title: 'hand-rolled date format string instead of the shared enum',
113
113
  level: 'warn',
114
114
  why: 'a format string written at the call site makes the date\'s shape an accident rather than a contract; `date`-validated endpoints accept several shapes, so a wrong one ships silently and surfaces later as bad data',
@@ -134,7 +134,7 @@ const dateFormatEnum = {
134
134
  },
135
135
  };
136
136
  const typesOrganization = {
137
- id: '_C5',
137
+ id: 'core/C5',
138
138
  title: 'type file outside the entity-folder structure',
139
139
  level: 'warn',
140
140
  once: true,
@@ -166,7 +166,7 @@ const typesOrganization = {
166
166
  },
167
167
  };
168
168
  const typographySource = {
169
- id: '_C6',
169
+ id: 'core/C6',
170
170
  title: 'raw font declaration instead of the app\'s typography source',
171
171
  level: 'warn',
172
172
  why: 'a hand-written font-family/size/weight is invisible to the UI-kit type scale — it survives font and scale changes silently, and the page drifts off-brand one declaration at a time (Notion: UI та стилі)',
@@ -179,7 +179,7 @@ const typographySource = {
179
179
  // Brighterly apps, decided 2026-09-24), plus non-underscore fonts/typography files.
180
180
  if (/\/(?:_[^/]*|fonts|typography)\.scss$/.test(file))
181
181
  return [];
182
- // @font-face declares a face, not a usage — _R4 polices those blocks.
182
+ // @font-face declares a face, not a usage — core/R4 polices those blocks.
183
183
  const stripped = code.replace(/@font-face\s*\{[^}]*\}/g, m => m.replace(/[^\n]/g, ' '));
184
184
  return eachLine(stripped, (line, n) => {
185
185
  const m = /\bfont(?:-family|-size|-weight)?\s*:/.exec(line);
@@ -190,7 +190,7 @@ const typographySource = {
190
190
  },
191
191
  };
192
192
  const scriptAttrOrder = {
193
- id: '_C7',
193
+ id: 'core/C7',
194
194
  title: 'script tag attribute order differs from the app canonical',
195
195
  level: 'warn',
196
196
  why: '<script setup lang="ts"> and <script lang="ts" setup> are compiler-equivalent, so nothing ever fails — the split just grows (723 vs 384 files in one app, Sep 2026) and every diff, codemod and grep has to handle both spellings',
@@ -10,7 +10,7 @@
10
10
  import type { Fixture } from '../self-test.js';
11
11
  export declare const commonFixtures: Record<string, Fixture>;
12
12
  /**
13
- * `_C2` and `_C5` are `once` rules over the whole file list — a single-file fixture
13
+ * `core/C2` and `core/C5` are `once` rules over the whole file list — a single-file fixture
14
14
  * cannot express them, so they are exempt from the fixture requirement and covered by
15
15
  * their own tests instead.
16
16
  */
@@ -8,47 +8,47 @@
8
8
  * `bad` must produce at least one finding, `good` none.
9
9
  */
10
10
  export const commonFixtures = {
11
- '_R1': {
11
+ 'core/R1': {
12
12
  file: 'app/a.ts',
13
13
  bad: 'const t = localStorage.getItem("k")',
14
14
  good: 'const t = safeLocalStorage.getItem("k")',
15
15
  },
16
- '_R2': {
16
+ 'core/R2': {
17
17
  file: 'app/components/A.vue',
18
18
  bad: '<template><div /></template>\n<style scoped lang="scss">.a { color: red; }</style>',
19
19
  good: '<template><div /></template>\n<style scoped lang="scss" src="@scss/components/A.scss" />',
20
20
  },
21
- '_R3': {
21
+ 'core/R3': {
22
22
  file: 'app/a.ts',
23
23
  bad: 'const v = JSON.parse(raw)',
24
24
  good: 'let v = null\ntry { v = JSON.parse(raw) } catch { v = null }',
25
25
  },
26
- '_R4': {
26
+ 'core/R4': {
27
27
  file: 'app/assets/scss/fonts.scss',
28
28
  bad: '@font-face { font-family: Rubik, sans-serif; src: url(a.woff2); }',
29
29
  good: '@font-face { font-family: Rubik; src: url(a.woff2); }',
30
30
  },
31
- '_C1': {
31
+ 'core/C1': {
32
32
  file: 'app/a.ts',
33
33
  bad: 'window.addEventListener("resize", onResize)',
34
34
  good: 'useEventListener(window, "resize", onResize)',
35
35
  },
36
- '_C3': {
36
+ 'core/C3': {
37
37
  file: 'app/assets/scss/components/Card.scss',
38
38
  bad: '.card { color: #1f2937; }',
39
39
  good: '.card { color: $slate-800; }',
40
40
  },
41
- '_C4': {
41
+ 'core/C4': {
42
42
  file: 'app/a.ts',
43
43
  bad: "const d = dayjs().format('YYYY-MM-DD')",
44
44
  good: 'const d = dayjs().format(DateFormat.DefaultDate)',
45
45
  },
46
- '_C6': {
46
+ 'core/C6': {
47
47
  file: 'app/assets/scss/components/Card.scss',
48
48
  bad: '.card { font-size: 14px; }',
49
49
  good: '.card { @include font(14px, 20px); color: $slate-800; }',
50
50
  },
51
- '_C7': {
51
+ 'core/C7': {
52
52
  // Two script tags in OPPOSITE orders, so the bad fixture violates whichever
53
53
  // direction the consuming app declares in settings.scriptAttrOrder. The good
54
54
  // fixture carries no lang attribute, so it is clean under either canonical.
@@ -58,8 +58,8 @@ export const commonFixtures = {
58
58
  },
59
59
  };
60
60
  /**
61
- * `_C2` and `_C5` are `once` rules over the whole file list — a single-file fixture
61
+ * `core/C2` and `core/C5` are `once` rules over the whole file list — a single-file fixture
62
62
  * cannot express them, so they are exempt from the fixture requirement and covered by
63
63
  * their own tests instead.
64
64
  */
65
- export const commonSelfTestSkip = ['_C2', '_C5'];
65
+ export const commonSelfTestSkip = ['core/C2', 'core/C5'];
@@ -9,7 +9,7 @@
9
9
  * C — convention: a preference we chose → team consensus
10
10
  * L — linter: a built-in ESLint rule → eslint.config.mjs
11
11
  *
12
- * Shared rule ids carry the underscore prefix (`_R1`); app-local ids stay bare (`R1`).
12
+ * Shared rule ids carry the `core/` prefix (`core/R1`); app-local ids stay bare (`R1`).
13
13
  * The prefix marks provenance and keeps the two number lines independent — and `run()`
14
14
  * still fails fast on a duplicate rather than letting an allowlist silently suppress
15
15
  * the wrong rule.
@@ -29,27 +29,27 @@ export declare const commonRules: Rule[];
29
29
  */
30
30
  export declare const sharedRuleIds: {
31
31
  /** `error` — bare `localStorage`/`sessionStorage` access. The accessor itself throws SecurityError on Safari ITP, Firefox strict mode and in-app webviews; `if (localStorage)` guards throw too. Fix: the app's safe storage wrapper (`settings.safeStorageModule`). */
32
- readonly _R1: "_R1";
32
+ readonly 'core/R1': "core/R1";
33
33
  /** `error` — SCSS rules written inline in a Vue `<style>` block. Inline styles drift from the design tokens invisibly. Fix: a dedicated `.scss` file referenced via `<style scoped lang="scss" src="…" />`. */
34
- readonly _R2: "_R2";
34
+ readonly 'core/R2': "core/R2";
35
35
  /** `error` — `JSON.parse` with no enclosing `try`/`catch`. A corrupt stored value crashes that code path permanently for the visitor. Fix: the app's guarded parse helper (`settings.safeJsonParse`) or try/catch. */
36
- readonly _R3: "_R3";
36
+ readonly 'core/R3': "core/R3";
37
37
  /** `error` — `@font-face` declaring a font *stack*. Inside `@font-face`, `font-family` names the face being defined; a comma-separated stack is invalid and the browser drops the face silently. Fix: single family name inside, the stack on the selectors that use it. */
38
- readonly _R4: "_R4";
38
+ readonly 'core/R4': "core/R4";
39
39
  /** `warn` — hand-rolled browser API with a VueUse equivalent (`new IntersectionObserver`, `addEventListener`, `setInterval`, …). VueUse owns the lifecycle/SSR/error paths. Fix: the composable named in the message, imported from `@vueuse/core`. */
40
- readonly _C1: "_C1";
40
+ readonly 'core/C1': "core/C1";
41
41
  /** `warn` — a component whose filename duplicates a UI-kit component (`settings.uiKitRoot`). A same-name second component splits the design system in two. Fix: import or extend the UI-kit one. */
42
- readonly _C2: "_C2";
42
+ readonly 'core/C2': "core/C2";
43
43
  /** `warn` — a literal colour (`#hex`, numeric `rgba()`) in SCSS/Vue instead of a palette token. Hardcoded colours survive theme and palette changes silently. Fix: a token from `settings.scssTokensFile`. */
44
- readonly _C3: "_C3";
44
+ readonly 'core/C3': "core/C3";
45
45
  /** `warn` — a hand-rolled date format string (`'YYYY-MM-DD'`) or `toISOString()`. The date's shape becomes an accident instead of a contract. Fix: the shared enum (`settings.dateFormatModule`). */
46
- readonly _C4: "_C4";
46
+ readonly 'core/C4': "core/C4";
47
47
  /** `warn` — a type file outside `types/<entity>/{enums,interfaces,types}.ts` (root under `settings.typesRoot`). Root-level or mixed-kind type files accrete unrelated entities. `.d.ts` ambient files are exempt. */
48
- readonly _C5: "_C5";
48
+ readonly 'core/C5': "core/C5";
49
49
  /** `warn` — a raw `font-family`/`font-size`/`font-weight` declaration. Hand-written fonts drift off the UI-kit type scale silently. Fix: the app's typography source (`settings.typographySource`). */
50
- readonly _C6: "_C6";
50
+ readonly 'core/C6': "core/C6";
51
51
  /** `warn` — `<script>` attribute order differing from the app canonical (`settings.scriptAttrOrder`, default `setup-first`). The two spellings are compiler-equivalent, so the split only ever grows. */
52
- readonly _C7: "_C7";
52
+ readonly 'core/C7': "core/C7";
53
53
  };
54
54
  /** Union of every id the shared pack ships — for typing app config keys. */
55
55
  export type SharedRuleId = keyof typeof sharedRuleIds;
@@ -9,7 +9,7 @@
9
9
  * C — convention: a preference we chose → team consensus
10
10
  * L — linter: a built-in ESLint rule → eslint.config.mjs
11
11
  *
12
- * Shared rule ids carry the underscore prefix (`_R1`); app-local ids stay bare (`R1`).
12
+ * Shared rule ids carry the `core/` prefix (`core/R1`); app-local ids stay bare (`R1`).
13
13
  * The prefix marks provenance and keeps the two number lines independent — and `run()`
14
14
  * still fails fast on a duplicate rather than letting an allowlist silently suppress
15
15
  * the wrong rule.
@@ -29,25 +29,25 @@ export const commonRules = [...rRules, ...cRules];
29
29
  */
30
30
  export const sharedRuleIds = {
31
31
  /** `error` — bare `localStorage`/`sessionStorage` access. The accessor itself throws SecurityError on Safari ITP, Firefox strict mode and in-app webviews; `if (localStorage)` guards throw too. Fix: the app's safe storage wrapper (`settings.safeStorageModule`). */
32
- _R1: '_R1',
32
+ 'core/R1': 'core/R1',
33
33
  /** `error` — SCSS rules written inline in a Vue `<style>` block. Inline styles drift from the design tokens invisibly. Fix: a dedicated `.scss` file referenced via `<style scoped lang="scss" src="…" />`. */
34
- _R2: '_R2',
34
+ 'core/R2': 'core/R2',
35
35
  /** `error` — `JSON.parse` with no enclosing `try`/`catch`. A corrupt stored value crashes that code path permanently for the visitor. Fix: the app's guarded parse helper (`settings.safeJsonParse`) or try/catch. */
36
- _R3: '_R3',
36
+ 'core/R3': 'core/R3',
37
37
  /** `error` — `@font-face` declaring a font *stack*. Inside `@font-face`, `font-family` names the face being defined; a comma-separated stack is invalid and the browser drops the face silently. Fix: single family name inside, the stack on the selectors that use it. */
38
- _R4: '_R4',
38
+ 'core/R4': 'core/R4',
39
39
  /** `warn` — hand-rolled browser API with a VueUse equivalent (`new IntersectionObserver`, `addEventListener`, `setInterval`, …). VueUse owns the lifecycle/SSR/error paths. Fix: the composable named in the message, imported from `@vueuse/core`. */
40
- _C1: '_C1',
40
+ 'core/C1': 'core/C1',
41
41
  /** `warn` — a component whose filename duplicates a UI-kit component (`settings.uiKitRoot`). A same-name second component splits the design system in two. Fix: import or extend the UI-kit one. */
42
- _C2: '_C2',
42
+ 'core/C2': 'core/C2',
43
43
  /** `warn` — a literal colour (`#hex`, numeric `rgba()`) in SCSS/Vue instead of a palette token. Hardcoded colours survive theme and palette changes silently. Fix: a token from `settings.scssTokensFile`. */
44
- _C3: '_C3',
44
+ 'core/C3': 'core/C3',
45
45
  /** `warn` — a hand-rolled date format string (`'YYYY-MM-DD'`) or `toISOString()`. The date's shape becomes an accident instead of a contract. Fix: the shared enum (`settings.dateFormatModule`). */
46
- _C4: '_C4',
46
+ 'core/C4': 'core/C4',
47
47
  /** `warn` — a type file outside `types/<entity>/{enums,interfaces,types}.ts` (root under `settings.typesRoot`). Root-level or mixed-kind type files accrete unrelated entities. `.d.ts` ambient files are exempt. */
48
- _C5: '_C5',
48
+ 'core/C5': 'core/C5',
49
49
  /** `warn` — a raw `font-family`/`font-size`/`font-weight` declaration. Hand-written fonts drift off the UI-kit type scale silently. Fix: the app's typography source (`settings.typographySource`). */
50
- _C6: '_C6',
50
+ 'core/C6': 'core/C6',
51
51
  /** `warn` — `<script>` attribute order differing from the app canonical (`settings.scriptAttrOrder`, default `setup-first`). The two spellings are compiler-equivalent, so the split only ever grows. */
52
- _C7: '_C7',
52
+ 'core/C7': 'core/C7',
53
53
  };
@@ -12,7 +12,7 @@
12
12
  import { findJsonParseCalls } from '../ast.js';
13
13
  import { eachLine, setting } from './shared.js';
14
14
  const bareStorage = {
15
- id: '_R1',
15
+ id: 'core/R1',
16
16
  title: 'bare localStorage / sessionStorage access',
17
17
  level: 'error',
18
18
  why: 'the bare API throws SecurityError on Safari ITP, Firefox strict mode and in-app webviews (Instagram/Facebook/TikTok) — the `if (localStorage)` guard does not help, the property accessor itself throws',
@@ -37,7 +37,7 @@ const bareStorage = {
37
37
  },
38
38
  };
39
39
  const scssInStyleBlock = {
40
- id: '_R2',
40
+ id: 'core/R2',
41
41
  title: 'SCSS rules inside a Vue <style> block',
42
42
  level: 'error',
43
43
  why: 'inline style blocks drift from the design tokens and are invisible to the SCSS build — the component stops sharing the system without anything failing',
@@ -66,7 +66,7 @@ const scssInStyleBlock = {
66
66
  },
67
67
  };
68
68
  const unguardedJsonParse = {
69
- id: '_R3',
69
+ id: 'core/R3',
70
70
  title: 'unguarded JSON.parse',
71
71
  level: 'error',
72
72
  async: true,
@@ -83,7 +83,7 @@ const unguardedJsonParse = {
83
83
  },
84
84
  };
85
85
  const fontFaceStack = {
86
- id: '_R4',
86
+ id: 'core/R4',
87
87
  title: '@font-face with a font stack instead of one family name',
88
88
  level: 'error',
89
89
  why: 'inside @font-face, font-family names the face being defined; a comma-separated stack is an invalid descriptor and the browser drops the whole face silently. Every page then renders in the fallback font, nothing errors, and only a side-by-side screenshot shows it',
@@ -17,8 +17,8 @@ export type Level = 'error' | 'warn' | 'off';
17
17
  * IDs are load-bearing (`--rule=`, `invariant-ignore:`, allowlists, levels) —
18
18
  * retire one, never recycle it.
19
19
  */
20
- /** App-local ids are bare; shared-pack ids carry the underscore prefix (`_R1`). */
21
- export type RuleId = `${'' | '_'}${'R' | 'B' | 'C' | 'L'}${number}`;
20
+ /** App-local ids are bare; shared-pack ids carry the `core/` prefix (`core/R1`). */
21
+ export type RuleId = `${'' | 'core/'}${'R' | 'B' | 'C' | 'L'}${number}`;
22
22
  /**
23
23
  * Meta rules. They police the escape hatch rather than the app, so they have no
24
24
  * family letter — but they are addressable exactly like any other rule
@@ -56,23 +56,23 @@ export interface ReportedFinding extends Finding {
56
56
  * the documented default), never its firing.
57
57
  */
58
58
  export interface SharedRuleSettings {
59
- /** `_R1`/`_R3` fix messages: how this app names its safe storage wrapper, e.g. 'safeLocalStorage from @helpers/safe-local-storage'. */
59
+ /** `core/R1`/`core/R3` fix messages: how this app names its safe storage wrapper, e.g. 'safeLocalStorage from @helpers/safe-local-storage'. */
60
60
  safeStorageModule?: string;
61
- /** `_R1`: repo-relative path of the wrapper FILE itself — the one place bare storage access belongs, auto-exempted so no app has to allowlist its own wrapper. E.g. 'app/utils/helpers/safe-local-storage.ts'. */
61
+ /** `core/R1`: repo-relative path of the wrapper FILE itself — the one place bare storage access belongs, auto-exempted so no app has to allowlist its own wrapper. E.g. 'app/utils/helpers/safe-local-storage.ts'. */
62
62
  safeStorageFile?: string;
63
- /** `_R3` fix message: the guarded parse helper, e.g. 'parseStoredJson() from @helpers/safe-local-storage'. */
63
+ /** `core/R3` fix message: the guarded parse helper, e.g. 'parseStoredJson() from @helpers/safe-local-storage'. */
64
64
  safeJsonParse?: string;
65
- /** `_C3`: the palette file that legitimately DEFINES colour literals, e.g. 'app/assets/scss/_colors.scss'. */
65
+ /** `core/C3`: the palette file that legitimately DEFINES colour literals, e.g. 'app/assets/scss/_colors.scss'. */
66
66
  scssTokensFile?: string;
67
- /** `_C2`: root of the UI-kit tree whose component names must not be duplicated. Default 'app/components/UI'. */
67
+ /** `core/C2`: root of the UI-kit tree whose component names must not be duplicated. Default 'app/components/UI'. */
68
68
  uiKitRoot?: string;
69
- /** `_C4` fix message: the shared date-format enum, e.g. 'DateFormat from @ts-types/date/enums'. */
69
+ /** `core/C4` fix message: the shared date-format enum, e.g. 'DateFormat from @ts-types/date/enums'. */
70
70
  dateFormatModule?: string;
71
- /** `_C5`: where entity type folders live. Default 'app/types'. */
71
+ /** `core/C5`: where entity type folders live. Default 'app/types'. */
72
72
  typesRoot?: string;
73
- /** `_C6` fix message: this app's typography source — a mixin in some apps ('the font() mixin from @scss/_mixins.scss'), a component in others ('the AppText component'). */
73
+ /** `core/C6` fix message: this app's typography source — a mixin in some apps ('the font() mixin from @scss/_mixins.scss'), a component in others ('the AppText component'). */
74
74
  typographySource?: string;
75
- /** `_C7`: which `<script>` attribute order is canonical in this app. Default 'setup-first' (`<script setup lang="ts">`). */
75
+ /** `core/C7`: which `<script>` attribute order is canonical in this app. Default 'setup-first' (`<script setup lang="ts">`). */
76
76
  scriptAttrOrder?: 'setup-first' | 'lang-first';
77
77
  }
78
78
  /** What a rule's `check` receives. */
@@ -95,8 +95,8 @@ export const createReader = (root) => {
95
95
  };
96
96
  };
97
97
  export const toRelative = (root, p) => relative(root, resolve(root, p)).split(sep).join('/');
98
- // `_?` — shared-pack rules carry an underscore prefix (`_R1`); app-local rules are bare (`R1`).
99
- const IGNORE_RE = /invariant-ignore:\s*(_?[A-Z]{1,3}\d{1,3}(?:\s*,\s*_?[A-Z]{1,3}\d{1,3})*)\s*(?:—|–|--|-|:)?\s*(.*)$/;
98
+ // `(?:core\/)?` — shared-pack rules carry the core/ prefix (`core/R1`); app-local rules are bare (`R1`).
99
+ const IGNORE_RE = /invariant-ignore:\s*((?:core\/)?[A-Z]{1,3}\d{1,3}(?:\s*,\s*(?:core\/)?[A-Z]{1,3}\d{1,3})*)\s*(?:—|–|--|-|:)?\s*(.*)$/;
100
100
  /**
101
101
  * Parse every `invariant-ignore:` comment in a file.
102
102
  * Returns { suppressions: Map<lineNumber, Set<ruleId>>, unreasoned: [{ line, ids }] }
@@ -198,7 +198,9 @@ export const findDanglingRuleRefs = (input) => {
198
198
  const start = src.indexOf(block);
199
199
  if (start === -1)
200
200
  return 1;
201
- const rel = src.slice(start).search(new RegExp(`^\\s*${id}\\s*:`, 'm'));
201
+ // Ids may contain `/` (core/R1) and appear as quoted keys — escape and allow quotes.
202
+ const escaped = id.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&');
203
+ const rel = src.slice(start).search(new RegExp(`^\\s*(?:'|")?${escaped}(?:'|")?\\s*:`, 'm'));
202
204
  return rel === -1 ? 1 : lineOf(src, start + rel);
203
205
  };
204
206
  const explain = (id) => retired[id] ? `${id} was retired — ${retired[id]}` : `${id} does not name any rule`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brighterly/lib-fe-codestyle",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "description": "Shared invariant-validator engine, rule pack and ESLint config for Brighterly Nuxt frontend apps",