@starci/eslint-canon-fe 9.16.1 → 9.16.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 9.16.3 - 2026-11-07
4
+
5
+ - Added (R235 `SCAN_SMELL`, `no-line-separator-literal`, slot-free): a raw U+2028, U+2029 or U+0085 anywhere in a file, specs included, is an error that names the escape to write (`
`, `
`, `…`) in a string, template or regular expression, or says to delete the character elsewhere. The Sonar scanner splits lines at these characters and TypeScript does not, so its analysis aborts with "Line n is out of range ... File has m lines".
6
+
7
+ ## 9.16.2 - 2026-11-07
8
+
9
+ - A patch: peers `@starci/hfs` ~6.0.0. Comments, rule docs and messages cite rules by their ids and carry no ruling history; the counting cohort entries carry no ruling field.
10
+
3
11
  ## 9.16.1 - 2026-11-07
4
12
 
5
13
  - A patch: `starci-fe/control-height` no longer names `SegmentedControl` (removed from `@starci/grammar` 0.24.0); the controls it holds to the one 40px height are Button, IconButton, Switch, Input, SearchField, Select, Tabs, Textarea, NumberField and ComboBox. Its docs follow.
@@ -14,11 +22,11 @@
14
22
 
15
23
  ## 9.15.0 - 2026-11-05
16
24
 
17
- - Changed (decision release-only-what-changed): the canon reads the @starci/hfs runtime from the installed hfs at run time, which it peers by `~5.13.0`, and bundles no copy; `runtime/` keeps only the project graph rule, the jsdoc, sonar and next helpers and the package-scripts template, and the stale forks (slots 3.13.0) are gone.
25
+ - Changed: the canon reads the @starci/hfs runtime from the installed hfs at run time, which it peers by `~5.13.0`, and bundles no copy; `runtime/` keeps only the project graph rule, the jsdoc, sonar and next helpers and the package-scripts template, and the stale forks (slots 3.13.0) are gone.
18
26
 
19
27
  ## 9.14.4 - 2026-11-05
20
28
 
21
- - Changed: the runtime copy of the canon pins gives the Grammar family its same-minor range (decision grammar-patch-range) and names `@starci/hfs` 5.12.0 and the Grammar family 0.22.0.
29
+ - Changed: the runtime copy of the canon pins gives the Grammar family its same-minor range and names `@starci/hfs` 5.12.0 and the Grammar family 0.22.0.
22
30
 
23
31
  ## 9.14.3 - 2026-11-05
24
32
 
@@ -249,8 +257,8 @@
249
257
  ## 9.6.3 - 2026-10-31
250
258
 
251
259
  - Changed: the bundled canon pins name the 0.16.3 cohort (@starci/grammar, eslint-grammar-core and eslint-grammar-offset-pop 0.16.3, eslint-canon-fe 9.6.3, eslint-canon-be 3.9.3, stylelint-canon 2.3.0, jest-preset 2.6.0, hfs 4.13.0); ChatWorkspace and PaneLayout are removed with no alias; PaneGrid and Pane are the one generic pane grid: grammar names are generic. The surface-rules spec uses PaneGrid.
252
- - Added: lib/freestyle (`exemptFreestyle`, `freestyleContext`): no-arbitrary-value, no-app-surface-paint, no-raw-brand-value, no-list-panel-padding, no-inline-class-name and no-native-img do not report the raw elements inside a `<Freestyle>` subtree of the same file; a Grammar component inside it is judged as before (owner ruling grammar-freestyle-region). Specs: src/freestyle.spec.ts.
253
- - Added: the lint result cache of `starci app check` (ruling app-check-cache): `crossFileInputs`, the files and folders each rule reads beyond the linted file (an fs-audit spec proves every rule declares what it reads), and the `projectGraph` export the cache keys a file's lint record on.
260
+ - Added: lib/freestyle (`exemptFreestyle`, `freestyleContext`): no-arbitrary-value, no-app-surface-paint, no-raw-brand-value, no-list-panel-padding, no-inline-class-name and no-native-img do not report the raw elements inside a `<Freestyle>` subtree of the same file; a Grammar component inside it is judged as before. Specs: src/freestyle.spec.ts.
261
+ - Added: the lint result cache of `starci app check`: `crossFileInputs`, the files and folders each rule reads beyond the linted file (an fs-audit spec proves every rule declares what it reads), and the `projectGraph` export the cache keys a file's lint record on.
254
262
 
255
263
  ## 9.6.2 - 2026-10-31
256
264
 
@@ -343,7 +351,7 @@
343
351
 
344
352
  ## 9.5.4 - 2026-10-29
345
353
 
346
- - Changed: the bundled slot manifest 3.13.0 admits `pgmq` in `ruleParams.db.extensions` (the lite event bus, ruling lite-pgmq-bus-bullmq-jobs), retires `ruleParams.db.queueOutbox` and `sides.be.queueOutbox` with the pg_cron half of R258, lists `public.event_bus_send`, `public.event_bus_read`, `public.event_bus_archive`, `public.event_bus_bury`, `public.event_bus_dead_letters` and `public.event_bus_requeue` in place of `public.event_bus_write` in `ruleParams.be.dbFunctions`, and moves slot `app.db.image` to `.starcistacks/images/db/Dockerfile` (ruling lite-db-image-in-stacks, admitted by the lite `app.starcistacks` allows); the bundled rule catalog keeps R258 `DB_PGMQ_DECLARED` as the event-bus law, retires `DB_CRON_OWNED` and adds R336 `BE_EVENT_BUS_LITE_SHAPE`; the bundled canon pins name hfs 4.11.3, eslint-canon-be 3.8.2, eslint-canon-fe 9.5.4 and the Grammar family 0.15.15. No front-end rule changed.
354
+ - Changed: the bundled slot manifest 3.13.0 admits `pgmq` in `ruleParams.db.extensions` (the lite event bus), retires `ruleParams.db.queueOutbox` and `sides.be.queueOutbox` with the pg_cron half of R258, lists `public.event_bus_send`, `public.event_bus_read`, `public.event_bus_archive`, `public.event_bus_bury`, `public.event_bus_dead_letters` and `public.event_bus_requeue` in place of `public.event_bus_write` in `ruleParams.be.dbFunctions`, and moves slot `app.db.image` to `.starcistacks/images/db/Dockerfile` (admitted by the lite `app.starcistacks` allows); the bundled rule catalog keeps R258 `DB_PGMQ_DECLARED` as the event-bus law, retires `DB_CRON_OWNED` and adds R336 `BE_EVENT_BUS_LITE_SHAPE`; the bundled canon pins name hfs 4.11.3, eslint-canon-be 3.8.2, eslint-canon-fe 9.5.4 and the Grammar family 0.15.15. No front-end rule changed.
347
355
 
348
356
  ## 9.5.3 - 2026-10-29
349
357
 
@@ -415,12 +423,12 @@
415
423
 
416
424
  ## 9.3.0 - 2026-10-28
417
425
 
418
- - Changed (breaking for apps): owner ruling fe-shape-not-state. A pure half takes `{ shape?, props, on? }`; `base-props-atom` refuses the retired `state` key with a message naming the rename, and `no-data-status-shape` reads `XShape` unions (and still the old `XState` names, so a status word is caught during the rename).
426
+ - Changed (breaking for apps): a pure half takes `{ shape?, props, on? }`; `base-props-atom` refuses the retired `state` key with a message naming the rename, and `no-data-status-shape` reads `XShape` unions (and still the old `XState` names, so a status word is caught during the rename).
419
427
  - Changed: the bundled runtime copies follow the retirement of `@starci/cli`: the bundled canon pins replace the retired `@starci/cli` with `starci-harness` 1.1.0-alpha.1 (`source: cli/package.json`), the one command line a product installs for `starci app`, and follow the harness packages (Grammar family 0.14.0, hfs 4.9.0, canon-fe 9.3.0, canon-be 3.5.0); the runtime finds an app's `starci` bin in its installed `starci-harness`.
420
428
 
421
429
  ## 9.2.0 - 2026-10-27
422
430
 
423
- - Changed: owner ruling two-grammars: the bundled Grammar catalog declares exactly two families, `core` (`@starci/eslint-grammar-core`, `coreGrammarConfig`) and `offset-pop`; `starci`, `starci-core` and `heritage` are refused. The Grammar entry checks (button intent, control height, surface rules, UI rulings) accept `@starci/grammar/<common|core|offset-pop>`. The bundled canon pins follow the harness packages (Grammar family 0.13.0, hfs 4.8.0, canon-fe 9.2.0, canon-be 3.4.57, stylelint-canon 2.1.0).
431
+ - Changed: the bundled Grammar catalog declares exactly two families, `core` (`@starci/eslint-grammar-core`, `coreGrammarConfig`) and `offset-pop`; `starci`, `starci-core` and `heritage` are refused. The Grammar entry checks (button intent, control height, surface rules, UI rulings) accept `@starci/grammar/<common|core|offset-pop>`. The bundled canon pins follow the harness packages (Grammar family 0.13.0, hfs 4.8.0, canon-fe 9.2.0, canon-be 3.4.57, stylelint-canon 2.1.0).
424
432
  - Changed: the doc comment of `lint-escape-hatch.mjs` names the three patterns that own the refused spellings instead of spelling them again. No rule or behaviour changed.
425
433
 
426
434
  ## 9.1.11 - 2026-10-27
@@ -434,7 +442,7 @@
434
442
  ## 9.1.9 - 2026-10-26
435
443
 
436
444
  - Added: starci-fe/control-height (R320 FE_CONTROL_HEIGHT), selection-fill-no-check and no-grammar-internal-selector.
437
- - Changed: the bundled runtime copies carry the light push gate and the owner rulings of 2026-10-26.
445
+ - Changed: the bundled runtime copies carry the light push gate.
438
446
 
439
447
  ## 9.1.8 - 2026-10-26
440
448
 
@@ -527,7 +535,7 @@
527
535
  ## 8.4.30 - 2026-10-23
528
536
  - Changed: the bundled canon pins select @starci/hfs 4.7.24 and @starci/cli 1.7.24.
529
537
  - Added: the type-aware rules the back end already borrowed, at error: `@typescript-eslint/no-floating-promises`, `no-misused-promises`, `switch-exhaustiveness-check` and the `no-unsafe-assignment`, `-member-access`, `-call`, `-return`, `-argument` family.
530
- - Changed: no Sonar way rule is left to the server (owner ruling 2026-10-22, no loosening): S3403, S3800, S3782 and S6582 run at error, `unicorn/consistent-function-scoping` runs with its default options, and S7785 (`unicorn/prefer-top-level-await`) runs in the front end (ESM).
538
+ - Changed: no Sonar way rule is left to the server (no loosening): S3403, S3800, S3782 and S6582 run at error, `unicorn/consistent-function-scoping` runs with its default options, and S7785 (`unicorn/prefer-top-level-await`) runs in the front end (ESM).
531
539
  - Added: `starciPlainSourceConfig({ root })` for a TypeScript front-end tree that is no HFS app (a runtime package, the harness UI): typed linting, the inline-directive fence, the React Hooks, type-aware and Sonar way rules and every canon rule that needs no slot view (`SLOT_FREE_RULES`), all at error.
532
540
 
533
541
  ## 8.4.29 - 2026-10-22
@@ -616,9 +624,9 @@
616
624
 
617
625
  ## 8.4.1 - 2026-10-17
618
626
 
619
- - Changed: the bundled runtime copies follow the runtime of 1.0.0-alpha.19 (owner ruling no-runtime-specs, 2026-10-17): the failure codes, the slot manifest (`exampleNa`) and the rule catalog (`specNa`, `exampleNa`) carry the removal of the runtime's own specs; package behavior is unchanged.
620
- - Changed (R287 `FE_CALLBACK_SHAPE`, a18.1 NIVO request F2, owner ruling `auth-callback-pure-helpers`): the auth callback may call pure text helpers of the app's `modules/routes` or `modules/auth` (`requestOrigin`, `safeNextPath`; resolved by TypeScript symbol: synchronous, no await, no I/O global, only owner helpers and `modules/config` readers), use static route tables (`AUTH_ROUTES`), and read the exchange outcome's `success` or `ok` flag. An impure, async or non-text helper, a helper outside those owners and a credential passed to a helper are still refused.
621
- - Changed (R287 `FE_CALLBACK_SHAPE`, a18.1 NIVO request F, owner ruling `auth-callback-email-link`): the auth callback may await one `verifyOtp({ token_hash, type })` of the auth owner (`auth.verifyOtp` or `verifyOtp` from `modules/auth/server`), built from exactly the query values `token_hash` and `type`, beside the one `exchangeAuthCode(code)`; the token hash never reaches a redirect.
627
+ - Changed: the bundled runtime copies follow the runtime of 1.0.0-alpha.19: the failure codes, the slot manifest (`exampleNa`) and the rule catalog (`specNa`, `exampleNa`) carry the removal of the runtime's own specs; package behavior is unchanged.
628
+ - Changed (R287 `FE_CALLBACK_SHAPE`, a18.1 NIVO request F2): the auth callback may call pure text helpers of the app's `modules/routes` or `modules/auth` (`requestOrigin`, `safeNextPath`; resolved by TypeScript symbol: synchronous, no await, no I/O global, only owner helpers and `modules/config` readers), use static route tables (`AUTH_ROUTES`), and read the exchange outcome's `success` or `ok` flag. An impure, async or non-text helper, a helper outside those owners and a credential passed to a helper are still refused.
629
+ - Changed (R287 `FE_CALLBACK_SHAPE`, a18.1 NIVO request F): the auth callback may await one `verifyOtp({ token_hash, type })` of the auth owner (`auth.verifyOtp` or `verifyOtp` from `modules/auth/server`), built from exactly the query values `token_hash` and `type`, beside the one `exchangeAuthCode(code)`; the token hash never reaches a redirect.
622
630
  - Fixed (R55 `FE_CLIENT_BOUNDARY`, a18.1 NIVO request I): the finding on a page or layout owner now says the owner stays a server component that renders the client block or overlay carrying the directive. The rule is unchanged.
623
631
 
624
632
  ## 8.3.0 - 2026-10-15
@@ -639,11 +647,11 @@
639
647
 
640
648
  ## 8.2.16 - 2026-10-13
641
649
 
642
- - Changed: the heroicons family is the one application icon set again (owner ruling 2026-10-13): `heroicons-is-the-glyph-vendor` admits exactly `@heroicons/react/24/outline`, `@heroicons/react/16/solid` and the matching `@starci/heroicons` subpaths (read from the fe-stack `icons` concern), and refuses Phosphor and every other glyph catalogue, a package the catalog forbids included; `one-icon-set` (8.2.15) is gone. Only the Icon leaf and the toast module import a glyph. The bundled runtime mirror carries the fe-stack catalog and the canon pins with `@heroicons/react` 2.2.0 and `@starci/heroicons` 0.3.3.
650
+ - Changed: the heroicons family is the one application icon set again: `heroicons-is-the-glyph-vendor` admits exactly `@heroicons/react/24/outline`, `@heroicons/react/16/solid` and the matching `@starci/heroicons` subpaths (read from the fe-stack `icons` concern), and refuses Phosphor and every other glyph catalogue, a package the catalog forbids included; `one-icon-set` (8.2.15) is gone. Only the Icon leaf and the toast module import a glyph. The bundled runtime mirror carries the fe-stack catalog and the canon pins with `@heroicons/react` 2.2.0 and `@starci/heroicons` 0.3.3.
643
651
 
644
652
  ## 8.2.15 - 2026-10-13
645
653
 
646
- - Changed: carry the runtime mirror of HFS manifest 3.0.0 (one front-end standard for the lite and the full edition, owner ruling 2026-10-13) and the canon pins for Grammar 0.9.14, HFS 4.2.14, jest-preset 2.2.7 and CLI 1.2.14.
654
+ - Changed: carry the runtime mirror of HFS manifest 3.0.0 (one front-end standard for the lite and the full edition) and the canon pins for Grammar 0.9.14, HFS 4.2.14, jest-preset 2.2.7 and CLI 1.2.14.
647
655
  - Added: `fe-stack-import` (law `fe-stack`, finding code `FE_STACK_FORBIDDEN`): an import (static, re-export, dynamic, `require`, type-only included) of a package any concern of the fe-stack catalog (`runtime/knowledge/hfs/fe-stack.yaml`) forbids, and `@supabase/*` outside `modules/auth` (slot `fe.modules.auth`), are refused. The catalog is read from the runtime copy; a missing copy stops the lint run with the fix.
648
656
  - Changed: the transport law follows the Apollo GraphQL + axios API layer. `fetch` (and `Request`, `EventSource`, `sendBeacon`) is legal in every file of the API layer (`modules/api`, the shared api package), `@apollo/client` only there, `axios` only in its REST calls (`modules/api/rest/`), `socket.io-client` only in `hooks/socketio/` (`fetch-only-in-api-client`, message `placed`); `client-fetch-has-signal` and `no-shared-transport-state` judge the whole layer (the generated `graphql/__generated__/` excepted); `no-hand-typed-wire` admits GraphQL documents in the API layer (one file per operation) and refuses them elsewhere; `one-outcome-union` names `GraphQLResponse` in `modules/api/graphql/types.ts` as the one vocabulary and also reads a `success` discriminant. The `modules/db` outcome home and the server reader role are gone.
649
657
  - Changed: `hooks-folder-holds-hooks-only` judges the hooks families `hooks/{swr,rhf,zustand,socketio,effects,reuseables}` by the files their slots name (message `family`); `SwrSideEffects.tsx` and `SocketIoSideEffects.tsx` are legal `.tsx` files of `hooks/`.
package/dist/drawing.js CHANGED
@@ -94,7 +94,7 @@ export const basePropsAtom = defineRule({
94
94
  action: "`{{path}}` is a function inside `props`. Actions live in `on`; feature composition may receive named `render` callbacks that take atoms and return rendered blocks.",
95
95
  onData: "`on.{{key}}` is not an action. `on` holds functions only; data belongs in `props`.",
96
96
  member: "`{{key}}` is not part of the pure contract. A pure half takes `shape`, `props` and `on`{{children}}.",
97
- renamed: "`state` is the retired name of the drawn shape: rename it `shape` (owner ruling fe-shape-not-state).",
97
+ renamed: "The drawn shape is named `shape`, never `state`: rename it `shape`.",
98
98
  },
99
99
  },
100
100
  create(context) {
@@ -11,7 +11,7 @@ export const functionLineBudget = defineRule({
11
11
  },
12
12
  },
13
13
  create(context) {
14
- // C08 adds this common parameter to the shipped manifest; keep the owner-decided cap while it lands.
14
+ // A manifest without `ruleParams.functionLines` gets the cap of 80 lines.
15
15
  const max = hfsOf(context).ruleParams.functionLines ?? 80;
16
16
  if (!Number.isInteger(max) || max < 1)
17
17
  throw new RangeError("ruleParams.functionLines must be a positive integer");
package/dist/index.d.ts CHANGED
@@ -38,7 +38,7 @@ export declare const rules: Record<string, Rule>;
38
38
  *
39
39
  * Every FE canon rule is an error. Existing debt is fixed before adoption; lowering architecture
40
40
  * to warn teaches every later author that the boundary is optional and is not a supported rollout. The one
41
- * exception is the closed counting cohort of lib/config.ts (`COUNTING`), an owner-ruled warn for one release.
41
+ * exception is the closed counting cohort of lib/config.ts (`COUNTING`), a warn for one release.
42
42
  */
43
43
  export declare const recommended: Record<string, "error" | "warn">;
44
44
  /** Flat-config options that make inline directives ineffective rather than merely forbidden. */
package/dist/index.js CHANGED
@@ -132,7 +132,7 @@ export const rules = Object.fromEntries(CONTRIBUTIONS.flatMap((entry) => Object.
132
132
  *
133
133
  * Every FE canon rule is an error. Existing debt is fixed before adoption; lowering architecture
134
134
  * to warn teaches every later author that the boundary is optional and is not a supported rollout. The one
135
- * exception is the closed counting cohort of lib/config.ts (`COUNTING`), an owner-ruled warn for one release.
135
+ * exception is the closed counting cohort of lib/config.ts (`COUNTING`), a warn for one release.
136
136
  */
137
137
  export const recommended = Object.fromEntries(CONTRIBUTIONS.flatMap((entry) => Object.entries(entry.recommended)));
138
138
  /** Flat-config options that make inline directives ineffective rather than merely forbidden. */
@@ -28,13 +28,12 @@ export declare const IGNORED: readonly string[];
28
28
  */
29
29
  export declare const BORROWED: Readonly<Record<string, "error">>;
30
30
  /**
31
- * The counting cohort: a rule the owner ruled to ship at `warn` for one release while a codemod moves apps onto the API
31
+ * The counting cohort: a rule that ships at `warn` for one release while a codemod moves apps onto the API
32
32
  * it asks for, each with the HFS rule id it enforces. `starci app check --gate lint` reports and counts its findings and never fails
33
33
  * on them; the next cohort moves the rule to error and deletes its entry. No other rule may be below error.
34
34
  */
35
35
  export declare const COUNTING: Readonly<Record<string, {
36
36
  readonly rule: string;
37
- readonly ruling: string;
38
37
  }>>;
39
38
  /**
40
39
  * The flat config of one front-end repository.
@@ -41,12 +41,12 @@ export const BORROWED = Object.freeze({
41
41
  });
42
42
  const levelOf = (setting) => (Array.isArray(setting) ? setting[0] : setting);
43
43
  /**
44
- * The counting cohort: a rule the owner ruled to ship at `warn` for one release while a codemod moves apps onto the API
44
+ * The counting cohort: a rule that ships at `warn` for one release while a codemod moves apps onto the API
45
45
  * it asks for, each with the HFS rule id it enforces. `starci app check --gate lint` reports and counts its findings and never fails
46
46
  * on them; the next cohort moves the rule to error and deletes its entry. No other rule may be below error.
47
47
  */
48
48
  export const COUNTING = Object.freeze({
49
- "starci-fe/no-spacing-utility": { rule: "R350", ruling: "spacing-roles" },
49
+ "starci-fe/no-spacing-utility": { rule: "R350" },
50
50
  });
51
51
  /**
52
52
  * The flat config of one front-end repository.
@@ -103,7 +103,7 @@ export const SLOT_FREE_RULES = Object.freeze([
103
103
  "handler-on-prefix", "no-native-form-control", "no-native-img", "image-has-size", "no-second-i18n-stack", "html-lang-from-locale", "no-null-suspense-fallback",
104
104
  "no-native-anchor", "no-inline-parameter-type", "no-hand-rolled-polling", "no-copy-in-call", "no-alert", "no-failure-collapse",
105
105
  "outcome-kinds-exhaustive", "no-await-in-loop", "prefer-code-point", "prefer-string-raw", "no-nested-conditional", "no-void-operator", "no-unused-import",
106
- "prefer-export-from", "no-unused-prop-types", "media-has-caption", "max-params",
106
+ "prefer-export-from", "no-unused-prop-types", "media-has-caption", "max-params", "no-line-separator-literal",
107
107
  "no-list-panel-padding", "no-hand-built-panes", "no-table-in-card", "no-heading-above-card", "no-selectable-tag-group", "no-fill-command-in-header", "no-app-surface-paint", "divider-full-bleed", "control-height", "selection-fill-no-check", "no-grammar-shim",
108
108
  "no-swr-default-array", "no-foreign-slot-status", "no-validating-as-loading", "no-placeholder-object", "no-hand-skeleton", "explicit-export-surface", "directive-first", "renderer-presentation-only",
109
109
  "button-intent-required", "destructive-confirm-in-dialog", "single-commit-per-render", "no-commit-in-collection-item",
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Resolve a `use*` import that comes from the app's own hooks folders (`hooks/swr`, `hooks/<domain>`, `hooks/zustand/<store>`...) by
3
- * TypeScript symbol resolution, never by the spelling of the name (R56 `FE_HOOKS_ARE_HOOKS`, owner ruling 2026-10-16 "Theo y tro").
3
+ * TypeScript symbol resolution, never by the spelling of the name (R56 `FE_HOOKS_ARE_HOOKS`).
4
4
  *
5
5
  * A composite product hook is built from the hooks beneath it: `hooks/<domain>/use<Name>.ts` composes the `use{Query,Mutate}<X>Swr`
6
6
  * wrappers of `hooks/swr`. The import is followed through barrels and re-exports to the declaration, and counts only when that declaration
@@ -2,7 +2,7 @@
2
2
  export declare const PACKAGE_ROOT: string;
3
3
  /**
4
4
  * The runtime of the installed @starci/hfs (its peer, a same-minor range): the slot manifest, the architecture machine and its catalogs are read from it
5
- * at run time, never from a copy bundled here, so an hfs data change releases hfs alone (decision release-only-what-changed).
5
+ * at run time, never from a copy bundled here, so an hfs data change releases hfs alone.
6
6
  */
7
7
  export declare const RUNTIME_ROOT: string;
8
8
  /** The files this package keeps of its own in runtime/ (the project graph rule, the jsdoc, sonar and next helpers, the package-scripts template). */
@@ -9,7 +9,7 @@ import { fileURLToPath } from "node:url";
9
9
  export const PACKAGE_ROOT = fileURLToPath(new URL("../..", import.meta.url));
10
10
  /**
11
11
  * The runtime of the installed @starci/hfs (its peer, a same-minor range): the slot manifest, the architecture machine and its catalogs are read from it
12
- * at run time, never from a copy bundled here, so an hfs data change releases hfs alone (decision release-only-what-changed).
12
+ * at run time, never from a copy bundled here, so an hfs data change releases hfs alone.
13
13
  */
14
14
  export const RUNTIME_ROOT = join(dirname(createRequire(import.meta.url).resolve("@starci/hfs/package.json")), "runtime");
15
15
  /** The files this package keeps of its own in runtime/ (the project graph rule, the jsdoc, sonar and next helpers, the package-scripts template). */
@@ -17,7 +17,7 @@ const syntaxRule = (name) => {
17
17
  throw new TypeError(`the runtime Sonar syntax rules publish no ${name}`);
18
18
  return rule;
19
19
  };
20
- const NAMES = ["no-await-in-loop", "prefer-code-point", "prefer-string-raw", "no-nested-conditional", "no-void-operator", "no-unused-import", "prefer-export-from", "no-unused-prop-types", "media-has-caption", "max-params"];
20
+ const NAMES = ["no-await-in-loop", "prefer-code-point", "prefer-string-raw", "no-nested-conditional", "no-void-operator", "no-unused-import", "prefer-export-from", "no-unused-prop-types", "media-has-caption", "max-params", "no-line-separator-literal"];
21
21
  /** The rules this law contributes to the plugin. */
22
22
  export const rules = Object.fromEntries(NAMES.map((name) => [name, syntaxRule(name)]));
23
23
  /** Starts at error: a repository's fix lane clears the debt before the rule ships. */
@@ -7,5 +7,5 @@ export declare const rules: {
7
7
  "selection-fill-no-check": import("./lib/rule.js").Rule<"indicator", []>;
8
8
  "no-grammar-internal-selector": import("./lib/rule.js").Rule<"internal", []>;
9
9
  };
10
- /** Both owner rulings are universal errors in the frontend canon. */
10
+ /** Both rules are universal errors in the frontend canon. */
11
11
  export declare const recommended: Recommended;
@@ -117,5 +117,5 @@ export const rules = {
117
117
  "selection-fill-no-check": selectionFillNoCheck,
118
118
  "no-grammar-internal-selector": noGrammarInternalSelector,
119
119
  };
120
- /** Both owner rulings are universal errors in the frontend canon. */
120
+ /** Both rules are universal errors in the frontend canon. */
121
121
  export const recommended = Object.fromEntries(Object.keys(rules).map((name) => [`starci-fe/${name}`, "error"]));
@@ -1,6 +1,6 @@
1
1
  # Control height (CONTROL-HEIGHT-1)
2
2
 
3
- Owner ruling `control-height-40` (2026-10-26). One control height, 40px (HeroUI's own `h-10`), is the default of every standalone Grammar control: Button, IconButton, Input, SearchField, Select, Tabs, Switch, the chrome Avatar, Textarea and the composer send button. Grammar draws it; an app never states it.
3
+ One control height, 40px (HeroUI's own `h-10`), is the default of every standalone Grammar control: Button, IconButton, Input, SearchField, Select, Tabs, Switch, the chrome Avatar, Textarea and the composer send button. Grammar draws it; an app never states it.
4
4
 
5
5
  | Rule | Finding code | Required behavior |
6
6
  | --- | --- | --- |
@@ -1,6 +1,6 @@
1
1
  # Grammar shims (R321)
2
2
 
3
- Law module: `grammar-shim.mjs`. HFS rule: R321. Knowledge: `contexts/code/fe/ui.md` FE-UI-1 (the app makes no visual choice; the Grammar owns every visual element) and the Grammar follow-up C-7 in `contexts/decisions/open-grammar.md`.
3
+ Law module: `grammar-shim.mjs`. HFS rule: R321. Knowledge: `contexts/code/fe/ui.md` FE-UI-1 (the app makes no visual choice; the Grammar owns every visual element).
4
4
 
5
5
  An app imports a Grammar component where it renders it. It never hands one on from an app path under its own name: such a shim makes one app green while the defect it works around (a missing `"use client"`, a wrong prop, a broken export) stays in the Grammar for every other app. The defect is reported to the Grammar instead; an op files `blocked` naming the package, the component and the error.
6
6
 
@@ -8,7 +8,7 @@ Every rule below is an error in `starciFeConfig`; none can be switched off or su
8
8
 
9
9
  ## `starci-fe/hooks-folder-holds-hooks-only`
10
10
 
11
- `hooks/` holds `use*.ts` hooks under the names their family slot gives and, per domain, one `<domain>.shared.ts`; nothing else. A hook file exports one hook; `server-only` and `next/headers` are never imported there. A hook is a function that calls a hook: a React, SWR or Redux hook, a `use*` imported from the app's own `hooks/` folders (a `hooks/swr` wrapper or a sibling domain hook, resolved by TypeScript symbol resolution through barrels; that hook's own file is judged when it is linted), or a library hook from an explicit list (`next-intl` `useTranslations`/`useLocale`/`useFormatter`/`useNow`/`useTimeZone`/`useMessages`, `next/navigation`, `next-themes` `useTheme`, `react-hook-form`, `zustand`). A `use*` from outside every `hooks/` folder or an unlisted library is not a hook by its spelling, and a file that exports a helper, a constant or a component still fails (owner ruling 2026-10-16).
11
+ `hooks/` holds `use*.ts` hooks under the names their family slot gives and, per domain, one `<domain>.shared.ts`; nothing else. A hook file exports one hook; `server-only` and `next/headers` are never imported there. A hook is a function that calls a hook: a React, SWR or Redux hook, a `use*` imported from the app's own `hooks/` folders (a `hooks/swr` wrapper or a sibling domain hook, resolved by TypeScript symbol resolution through barrels; that hook's own file is judged when it is linted), or a library hook from an explicit list (`next-intl` `useTranslations`/`useLocale`/`useFormatter`/`useNow`/`useTimeZone`/`useMessages`, `next/navigation`, `next-themes` `useTheme`, `react-hook-form`, `zustand`). A `use*` from outside every `hooks/` folder or an unlisted library is not a hook by its spelling, and a file that exports a helper, a constant or a component still fails.
12
12
 
13
13
  **Invalid** (`src/hooks/course/readCourse.ts`)
14
14
 
@@ -59,7 +59,7 @@ const Frame = () => (
59
59
 
60
60
  ## `starci-fe/no-table-in-card`
61
61
 
62
- Finding code: `FE_TABLE_IN_CARD` (R351). A `DataTable` is not placed in a `SurfaceCard`. Plain rows with a few facts are a `SurfaceCard kind="list"` with the label outside and the note in its `footer`; a table that compares records across many columns sits in its section with no card around it. The rule reads the file: a Grammar `DataTable` enclosed by a Grammar `SurfaceCard` element is a finding, and so is a `DataTable` rendered by a component of the same file (a declaration, a variable's function, a `memo` or `forwardRef` wrapper, followed through other components of the file) that a `SurfaceCard` encloses. A card in another file that renders a table component is judged where the card is written. There is no inline exemption; a table the owner rules complex enough to sit in a card is named in this rule.
62
+ Finding code: `FE_TABLE_IN_CARD` (R351). A `DataTable` is not placed in a `SurfaceCard`. Plain rows with a few facts are a `SurfaceCard kind="list"` with the label outside and the note in its `footer`; a table that compares records across many columns sits in its section with no card around it. The rule reads the file: a Grammar `DataTable` enclosed by a Grammar `SurfaceCard` element is a finding, and so is a `DataTable` rendered by a component of the same file (a declaration, a variable's function, a `memo` or `forwardRef` wrapper, followed through other components of the file) that a `SurfaceCard` encloses. A card in another file that renders a table component is judged where the card is written. There is no inline exemption; a table complex enough to sit in a card is named in this rule.
63
63
 
64
64
  Invalid:
65
65
 
@@ -127,7 +127,7 @@ const Filter = () => <FilterButtonGroup label="Team" options={options} value={te
127
127
 
128
128
  ## `starci-fe/no-heading-above-card`
129
129
 
130
- Finding code: `FE_HEADING_ABOVE_CARD` (R354). A card has a label by design (ruling `card-carries-its-own-label`), so a heading over it is wrong. A Grammar `Section` (not `kind="list"`) whose only child is one `SurfaceCard` is refused, and so is a card whose `ariaLabel` is the `Section`'s `title`. Several cards under one `Section` stay legal, and a `Section` holding anything else is not judged. The fix moves the title to the card's `label`, the count to `fact`, the action to `labelEnd` and the note to `footer`.
130
+ Finding code: `FE_HEADING_ABOVE_CARD` (R354). A card carries its own label, so a heading over it is wrong. A Grammar `Section` (not `kind="list"`) whose only child is one `SurfaceCard` is refused, and so is a card whose `ariaLabel` is the `Section`'s `title`. Several cards under one `Section` stay legal, and a `Section` holding anything else is not judged. The fix moves the title to the card's `label`, the count to `fact`, the action to `labelEnd` and the note to `footer`.
131
131
 
132
132
  Invalid:
133
133
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starci/eslint-canon-fe",
3
- "version": "9.16.1",
3
+ "version": "9.16.3",
4
4
  "description": "Universal front-end lint laws for TypeScript and Next.js, independent of the selected Grammar family.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -51,7 +51,7 @@
51
51
  "node": ">=22.13.0"
52
52
  },
53
53
  "peerDependencies": {
54
- "@starci/hfs": "~5.13.0",
54
+ "@starci/hfs": "~6.0.0",
55
55
  "eslint": ">=9",
56
56
  "eslint-plugin-react-hooks": ">=7",
57
57
  "typescript": ">=5.9"
@@ -247,6 +247,23 @@ const mediaHasCaption = {
247
247
  },
248
248
  };
249
249
 
250
+ /** The line breaks the scanner counts and the language does not: a raw U+2028 or U+2029 inside a string, template, regular expression or comment is legal source, and U+0085 is no line terminator in it. */
251
+ const SCANNER_LINE_BREAKS = /[\u2028\u2029\u0085]/gu;
252
+ const LINE_BREAK_NAMES = Object.freeze({ '\u2028': ['U+2028', 'LINE SEPARATOR', '\\u2028'], '\u2029': ['U+2029', 'PARAGRAPH SEPARATOR', '\\u2029'], '\u0085': ['U+0085', 'NEXT LINE', '\\u0085'] });
253
+ /** Sonar scanner crash ("Line n is out of range ... File has m lines"): the scanner splits a file at U+2028, U+2029 and U+0085, the language service does not, so their line numbers disagree and the analysis aborts. Applies to every file the scan reads, specs included. */
254
+ const noLineSeparatorLiteral = {
255
+ meta: { type: 'problem', schema: [], messages: { raw: 'The scanner splits lines at a raw {{ code }} ({{ name }}) and TypeScript does not, so its analysis aborts with "Line n is out of range". Write the escape `{{ escape }}` in a string, template or regular expression, or delete the character anywhere else.' } },
256
+ create: (context) => ({
257
+ Program: () => {
258
+ const { text } = context.sourceCode;
259
+ for (const found of text.matchAll(SCANNER_LINE_BREAKS)) {
260
+ const [code, name, escape] = LINE_BREAK_NAMES[found[0]];
261
+ context.report({ loc: context.sourceCode.getLocFromIndex(found.index), messageId: 'raw', data: { code, name, escape } });
262
+ }
263
+ },
264
+ }),
265
+ };
266
+
250
267
  /** The files the example scans leave out: a unit spec and an end-to-end spec (the regex, and the flat-config globs of the same set). */
251
268
  const OUTSIDE_THE_SCAN = /\.(?:e2e-)?spec\.[cm]?[jt]sx?$/;
252
269
  export const OUTSIDE_THE_SCAN_FILES = Object.freeze(['**/*.spec.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', '**/*.e2e-spec.{js,mjs,cjs,ts,mts,cts,jsx,tsx}']);
@@ -266,4 +283,5 @@ export const SONAR_SYNTAX_RULES = Object.freeze({
266
283
  'no-unused-prop-types': scanned(noUnusedPropTypes),
267
284
  'media-has-caption': scanned(mediaHasCaption),
268
285
  'max-params': scanned(maxParams),
286
+ 'no-line-separator-literal': noLineSeparatorLiteral,
269
287
  });