@starci/eslint-canon-fe 8.4.42 → 9.0.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/CHANGELOG.md +17 -1
- package/README.md +36 -84
- package/brand-values.mjs +1 -1
- package/docs/hooks-folder.md +5 -3
- package/docs/native-controls.md +1 -5
- package/docs/next-conventions.md +8 -8
- package/docs/size-and-state-budget.md +22 -0
- package/docs/surface-rules.md +11 -9
- package/drawing.mjs +4 -66
- package/hooks-folder.mjs +5 -3
- package/index.mjs +7 -25
- package/landmark.mjs +0 -3
- package/lib/class-text.mjs +29 -0
- package/lib/config.mjs +4 -6
- package/lib/drawing-scope.mjs +15 -0
- package/lib/typed-class-values.mjs +10 -1
- package/lib/why.mjs +10 -40
- package/next-conventions.mjs +33 -16
- package/package.json +4 -3
- package/runtime/knowledge/grammars/index.yaml +102 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +22 -8
- package/runtime/knowledge/hfs/fe-stack.yaml +7 -0
- package/runtime/knowledge/hfs/slots.yaml +3 -3
- package/runtime/modules/kernel/removed-vocabulary.yaml +29 -0
- package/runtime/scripts/hfs/architecture/config.mjs +8 -8
- package/runtime/scripts/hfs/declaration-shape.mjs +8 -2
- package/runtime/scripts/hfs/rule-catalog.mjs +2 -2
- package/runtime/scripts/hfs/slots.mjs +1 -0
- package/runtime/scripts/hfs/view.mjs +1 -0
- package/runtime/scripts/lib/grammar-lint.mjs +48 -0
- package/size-and-state-budget.mjs +116 -4
- package/surface-rules.mjs +159 -26
- package/tokens.mjs +5 -217
- package/app-shell.mjs +0 -161
- package/docs/app-shell.md +0 -50
- package/docs/grammar-boundary.md +0 -57
- package/docs/status-colors.md +0 -32
- package/grammar-boundary.mjs +0 -203
- package/icon.mjs +0 -310
- package/lib/app-shell-cluster.mjs +0 -66
- package/lib/app-shell-create.mjs +0 -33
- package/lib/app-shell-owner-source.mjs +0 -124
- package/lib/app-shell-owner.mjs +0 -65
- package/lib/app-shell-scope.mjs +0 -79
- package/lib/app-shell-visible.mjs +0 -92
- package/lib/app-shell.mjs +0 -148
- package/lib/status-tones.generated.mjs +0 -9
- package/menu-entries.mjs +0 -133
- package/runtime/modules/goal/source-phrases.yaml +0 -95
- package/runtime/scripts/lib/source-phrases.mjs +0 -34
- package/status-colors.mjs +0 -137
- package/typography.mjs +0 -78
- package/vendor-boundary.mjs +0 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 9.0.0 - 2026-10-26
|
|
4
|
+
- Changed: concrete Grammar component, variant, token and vendor rules move to `@starci/eslint-grammar-starci` under the `starci-grammar` namespace. Consumers compose both flat-config factories for their selected family.
|
|
5
|
+
- Added: explicit shared infrastructure exports under `@starci/eslint-canon-fe/lib/*`; family packages reuse these readers without copying them.
|
|
6
|
+
- Kept universal: the R318 surface composition law (`nested-control-variant`, `no-list-panel-padding`, `no-app-surface-paint`) and the R65 `no-app-relative-clock`, now in `size-and-state-budget.mjs`.
|
|
7
|
+
|
|
8
|
+
### Migrating from 8.x
|
|
9
|
+
- What moved: twenty rules leave this package with their IDs, finding codes and severity unchanged; only the namespace changes from `starci-fe/` to `starci-grammar/`. They are `app-shell-frame`, `no-shell-bottom-tabs`, `navbar-control-size`, `navbar-cluster`, `no-navbar-create`, `no-raw-structural-element`, `no-heading-tag-outside-heading-component`, `status-text-uses-soft-foreground`, `vendor-primitive-has-named-owner`, `no-vendor-icon-outside-icon-leaf`, `heroicons-is-the-glyph-vendor`, `no-off-scale-glyph-size`, `no-icon-size-class`, `destructive-menu-entry-needs-danger-tone`, `no-decorative-icon-in-metric-cell`, `no-inline-svg-outside-brand-mark-leaf`, `no-fractional-step`, `no-hand-rolled-heading`, `no-unresolved-token-class` and `slot-status-through-slotview`. StarCi apps get them from `@starci/eslint-grammar-starci`; Offset Pop apps get the same implementations from `@starci/eslint-grammar-offset-pop`.
|
|
10
|
+
- Select the family explicitly: set `sides.fe.grammar` to `starci` or `offset-pop` in `hfs.json`. No family is inferred.
|
|
11
|
+
- Install the family package at its canon-pins version beside this one (app root `devDependencies`). `starci app check` refuses an app that selects a family and declares canon-fe 9 without that package (`FE_STACK_FAMILY_LINT_MISSING`, R288), so the twenty rules cannot be lost silently.
|
|
12
|
+
- Flat config order: the universal factory first, then the family factory, both with the same HFS view: `export default [...starciFeConfig({ hfs }), ...starciGrammarConfig({ hfs })]` (Offset Pop: `offsetPopGrammarConfig`). `starci app sync` renders this managed `fe/eslint.config.mjs`.
|
|
13
|
+
- Rename any reference to a moved rule from `starci-fe/<rule>` to `starci-grammar/<rule>`; the removed-vocabulary check refuses the old spelling.
|
|
14
|
+
- NIVO moves in v0.3.7.
|
|
15
|
+
|
|
16
|
+
## 8.4.43 - 2026-10-26
|
|
17
|
+
- Changed: the bundled canon pins follow back-end canon 3.4.43, hfs 4.7.34 and cli 1.7.34.
|
|
18
|
+
|
|
3
19
|
## 8.4.42 - 2026-10-26
|
|
4
20
|
- Changed: the bundled canon pins follow back-end canon 3.4.42.
|
|
5
21
|
|
|
@@ -9,7 +25,7 @@
|
|
|
9
25
|
- Fixed: `operations-only-in-swr-hooks` (FE_OP_OUTSIDE_SWR_HOOK) no longer reports named imports that all resolve, through relative re-exports, to a generated `__generated__/input-bounds` module (`INPUT_BOUNDS`, `inputBoundsIssues`): they are contract data, not API operations. Mixed, default and namespace imports of the barrel still report.
|
|
10
26
|
- Added: the stream reader of server-sent events (`fe.transport.stream`, `modules/api/sse/clients/`) is part of the API layer, so it may call `fetch` (FE_TRANSPORT_OWNER); a stream operation (`sse/<area>/stream-<channel>.ts`) must import it.
|
|
11
27
|
- Added: `useSWRSubscription` (`swr/subscription`) is an SWR request hook of the family (FE_SWR_OUTSIDE_FAMILY, FE_OP_OUTSIDE_SWR_HOOK, FE_HOOKS_ARE_HOOKS): a stream operation is consumed only by `hooks/swr/api/sse/subscriptions/useSubscribe<X>Swr.ts`.
|
|
12
|
-
-
|
|
28
|
+
- Changed: `nested-control-variant` (`FE_NESTED_VARIANT`) refuses static variants on layer-aware fields throughout app code and primary Tabs inside painted Grammar surfaces. R318 owns Grammar composition; `no-app-surface-paint` uses the separate layout-only code `FE_GRAMMAR_LAYOUT_ONLY`. `no-list-panel-padding` (`FE_LIST_PANEL_PADDING`) checks all horizontal padding forms only within the nearest painted surface.
|
|
13
29
|
|
|
14
30
|
## 8.4.40 - 2026-10-25
|
|
15
31
|
- Changed: the bundled canon pins select @starci/stylelint-canon 2.0.11, @starci/hfs 4.7.33 and @starci/cli 1.7.33.
|
package/README.md
CHANGED
|
@@ -1,91 +1,43 @@
|
|
|
1
1
|
# @starci/eslint-canon-fe
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm i -D @starci/eslint-canon-fe eslint-plugin-react-hooks typescript
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
A repository's `eslint.config.mjs` is the managed one-liner `starci app sync` renders (and `starci app check` compares, R05/R17):
|
|
3
|
+
Universal front-end laws for TypeScript and Next.js applications, classified by
|
|
4
|
+
[FE-RULE-OWNERSHIP](../../../knowledge/patterns/fe/index.yaml#FE-RULE-OWNERSHIP).
|
|
5
|
+
The canon owns composition, data and effect boundaries, translations, accessibility, source
|
|
6
|
+
structure, typed contracts, token discipline and check integrity across Grammar families.
|
|
12
7
|
|
|
13
8
|
```js
|
|
14
9
|
import { loadHfs, starciFeConfig } from "@starci/eslint-canon-fe"
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
`loadHfs` reads the repository's `hfs.json` and the slot manifest shipped in `runtime/`. The factory returns two
|
|
19
|
-
flat-config blocks and owns everything about the law: which files are linted (from the HFS profile), which rules are on
|
|
20
|
-
(every one, at `error` - nothing is off and nothing is a warning), the React Hooks rules (`eslint-plugin-react-hooks` 7, its
|
|
21
|
-
recommended set lifted to `error`), typed linting, and the inline-directive fence (`noInlineConfig` plus
|
|
22
|
-
`reportUnusedDisableDirectives`). The repository states nothing.
|
|
23
|
-
|
|
24
|
-
- The **Sonar parity** law (R235): the smells SonarCloud measures that a lint rule can decide, `no-await-in-loop`, `prefer-code-point`, `prefer-string-raw`, `no-nested-conditional`, `no-void-operator`, `no-unused-import`, `prefer-export-from` and `no-unused-prop-types`, plus the typescript-eslint rules `no-deprecated`, `no-base-to-string` and `require-await` the factory turns on (they read types, so the source block is typed). A Next `matcher` string keeps its escapes (the segment config is read statically); `*.spec.ts` and `*.e2e-spec.ts` are outside the scan, so outside these rules.
|
|
25
|
-
- The **fe-stack** law: `fe-stack-import` refuses an import of a library the front-end stack catalog forbids (one canonical library per concern, read from `runtime/knowledge/hfs/fe-stack.yaml`) and `@supabase/*` outside `modules/auth`.
|
|
26
|
-
- The **ignore** block: `node_modules`, `.next`, `dist`, `coverage`, `__generated__` and `.starci/`.
|
|
27
|
-
- The **source** block governs `apps/*/src/**` and `packages/*/src/**` with types (`parserOptions.projectService`) and
|
|
28
|
-
`settings.starci.hfs`, the slot view path-scoped rules ask (`lib/hfs.mjs` `hfsOf`), never a path pattern.
|
|
29
|
-
|
|
30
|
-
`recommended`, `why`, `loadHfs`, `linterOptions` and the plugin itself are also
|
|
31
|
-
exported; `why[rule]` is `{ code, vi, fixVi }`, the finding code and the Vietnamese why the harness quotes.
|
|
32
|
-
|
|
33
|
-
## Rules
|
|
34
|
-
|
|
35
|
-
The [app-shell](docs/app-shell.md) law holds the fixed `WorkspaceShell` frame, mobile drawer navigation,
|
|
36
|
-
navbar action kinds and order, shared bar control size, and page ownership of create actions. Its
|
|
37
|
-
rules are gathered into the normal FE lint and application gate.
|
|
10
|
+
import { starciGrammarConfig } from "@starci/eslint-grammar-starci"
|
|
38
11
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
[next-conventions](docs/next-conventions.md), [translation](docs/translation.md) (no literal copy at any
|
|
43
|
-
tier), [brand-values](docs/brand-values.md), [native-controls](docs/native-controls.md),
|
|
44
|
-
[size-and-state-budget](docs/size-and-state-budget.md) and [lint-escape-hatch](docs/lint-escape-hatch.md). New in 5.1: [lists](docs/lists.md), [hygiene](docs/hygiene.md), [formatting](docs/formatting.md) and [type-safety](docs/type-safety.md), plus rules added to next-conventions, client-boundary, native-controls and transport. New in 6.1: `no-vietnamese-in-source` (English-only identifiers, strings, comments and test titles; the i18n fixtures slot `e2e/fixtures/i18n/` is the one exemption). New in 6.0: `effect-subscription-needs-cleanup` (timers, frames, listeners, observers, sockets and subscriptions are released against the same handle), `server-module-marks-server-only` ([client-boundary](docs/client-boundary.md)), `no-data-fetch-in-effect` sees indirect loads, `no-hardcoded-copy` reads templates and sentence values, `no-inline-lint-config` refuses `NOSONAR` and the coverage/formatter switches. Also new in 6.0: [grammar-boundary](docs/grammar-boundary.md) (`no-raw-structural-element`), `one-outcome-union` ([transport](docs/transport.md)), `i18n-stack-in-one-module` ([next-conventions](docs/next-conventions.md)), [size-growth](docs/size-growth.md) (`file-size-growth`: a file over the manifest's line budget is not born large and does not grow; the budget is `ruleParams.fe.fileLines`, read from the manifest copy in `runtime/`).
|
|
45
|
-
|
|
46
|
-
Older laws cover comments, file layout, icons and vendor ownership, landmarks, loading states,
|
|
47
|
-
naming, props, served locales, component splits, design tokens, type safety, typography, and
|
|
48
|
-
class-name ownership. Reusable class names belong in a colocated `classNames.ts` module and should
|
|
49
|
-
be composed with HeroUI `cn` using one utility token per argument.
|
|
50
|
-
|
|
51
|
-
The `drawing` law holds the split surfaces (blocks, pages, layouts, overlays under `components/`
|
|
52
|
-
or `features/`): the pure `XBase` takes `{ state, props, on }` of atoms, with named `render`/`renderX`
|
|
53
|
-
callbacks in feature `props` for block composition (`base-props-atom`), only its
|
|
54
|
-
sibling `index.tsx` imports it and never re-exports it (`base-import-pair`), `XState` names drawn
|
|
55
|
-
shapes and never a data status (`no-data-status-shape`), and a block renders each slot's
|
|
56
|
-
loading/forbidden/error/empty through `SlotView` (`slot-status-through-slotview`). The law's name is
|
|
57
|
-
the `drawing` slot role of `component.tsx`; a split-surface reference tree lives under
|
|
58
|
-
`.claude/examples/`.
|
|
59
|
-
|
|
60
|
-
Page and layout owners in `features/{pages,layouts}/<Name>/index.tsx` may compose overlay and block public entries. A server
|
|
61
|
-
page may import and render a client overlay entry while remaining a server component: `FE_CLIENT_BOUNDARY` accepts the
|
|
62
|
-
overlay entry, and `FE_CLIENT_SERVER_IMPORT` / `FE_CLIENT_REACHES_SERVER` reject server-only imports from its client closure.
|
|
63
|
-
The only feature-to-feature edge is pages or layouts to overlays; layouts never import pages, overlays never import other
|
|
64
|
-
overlays, components never import overlays, and blocks never import another block. For a client list, a server page mounts an overlay whose connected entry reads list
|
|
65
|
-
identities and supplies a block renderer to its pure Base. A pure component or branch may render that keyed list by
|
|
66
|
-
calling the supplied render prop; it imports no block and fetches no data. The supplied renderer composes blocks only,
|
|
67
|
-
with no intrinsic markup or hook calls, and each connected item block still renders its exact sibling `XBase`.
|
|
68
|
-
|
|
69
|
-
A layout-level overlay host passes rendered overlay candidates into a client `features/overlays/OverlayRouteHost` entry.
|
|
70
|
-
Its hook under `hooks/overlays` reads `usePathname()` and `useSearchParams()` and supplies scalar selection to the pure
|
|
71
|
-
`components/branches/OverlayHost` branch. The layout imports the candidate overlay entries; the host and branch only
|
|
72
|
-
receive rendered nodes. No callback crosses the server-to-client boundary, and pages stay server components. This
|
|
73
|
-
composition-only host renders an existing public pure branch without a forwarding private Base; connected blocks still
|
|
74
|
-
require their exact sibling Base. The worked tree is in
|
|
75
|
-
[`knowledge/patterns/fe/boundaries.yaml`](../../../knowledge/patterns/fe/boundaries.yaml), `FE-BOUNDARY-5`.
|
|
76
|
-
|
|
77
|
-
The one locale pattern everywhere is `next-intl`, a `[locale]` segment, default `vi`,
|
|
78
|
-
`localePrefix: "as-needed"`, and `proxy.ts` instead of `middleware.ts`.
|
|
79
|
-
|
|
80
|
-
Grammar is a business-neutral HeroUI-backed component package. Its components accept ordinary
|
|
81
|
-
typed React props and children.
|
|
82
|
-
|
|
83
|
-
The [surface composition law](docs/surface-rules.md) holds ANATOMY-2 and PADDING-0:
|
|
84
|
-
`nested-control-variant` refuses explicit primary fields inside Grammar surfaces and raw app
|
|
85
|
-
`bg-surface` paint (`FE_NESTED_VARIANT`); `no-list-panel-padding` refuses horizontal padding
|
|
86
|
-
wrappers around Grammar lists and section captions (`FE_LIST_PANEL_PADDING`). Grammar supplies
|
|
87
|
-
the nested secondary default and owns the list's panel inset.
|
|
88
|
-
|
|
89
|
-
## Requirements
|
|
12
|
+
const hfs = loadHfs(import.meta.url)
|
|
13
|
+
export default [...starciFeConfig({ hfs }), ...starciGrammarConfig({ hfs })]
|
|
14
|
+
```
|
|
90
15
|
|
|
91
|
-
|
|
16
|
+
`starci app sync` renders the selected family's managed flat config. Both factories cover every
|
|
17
|
+
app and workspace package's source. Universal rules, React Hooks rules, type-aware rules and
|
|
18
|
+
Sonar rules run at error; the factory sets typed parsing and refuses inline lint directives.
|
|
19
|
+
Generated wire types, dependencies and build outputs follow the shared source-scope contract.
|
|
20
|
+
|
|
21
|
+
Concrete Grammar component names, props, variants, scales and vendor recipes belong to
|
|
22
|
+
`@starci/eslint-grammar-starci` or the selected family's package. The family rules keep their
|
|
23
|
+
bare IDs under the `starci-grammar` namespace. This package exports shared AST, binding, type,
|
|
24
|
+
slot and configuration infrastructure through `@starci/eslint-canon-fe/lib/*`; family packages
|
|
25
|
+
import those readers without copying them.
|
|
26
|
+
|
|
27
|
+
Every universal law remains gathered by `index.mjs`; its recommendation enables every rule.
|
|
28
|
+
The `docs/` directory carries the detailed behavior of the documented laws. `why` supplies
|
|
29
|
+
failure codes and English guidance for the catalogued rules.
|
|
30
|
+
|
|
31
|
+
`starciPlainSourceConfig({ root })` applies slot-free universal rules to a TypeScript `src/`
|
|
32
|
+
tree. `starciTypedSourceConfig({ root })` provides the shared typed, React Hooks and Sonar
|
|
33
|
+
configuration for source that has not adopted the product composition laws.
|
|
34
|
+
|
|
35
|
+
The [surface composition law](docs/surface-rules.md) (R318) holds ANATOMY-2 and PADDING-0 for every
|
|
36
|
+
family that publishes the shared HeroUI surface and field API: `nested-control-variant` refuses static
|
|
37
|
+
variants on layer-aware fields and primary Tabs inside painted Grammar surfaces (`FE_NESTED_VARIANT`),
|
|
38
|
+
`no-app-surface-paint` keeps surface paint in Grammar (`FE_GRAMMAR_LAYOUT_ONLY`) and
|
|
39
|
+
`no-list-panel-padding` refuses horizontal padding wrappers around Grammar lists and section captions
|
|
40
|
+
within the nearest painted surface (`FE_LIST_PANEL_PADDING`). The R65 budget law's
|
|
41
|
+
`no-app-relative-clock` leaves live relative time and countdowns to Grammar's shared clock.
|
|
42
|
+
|
|
43
|
+
Regression specs belong to this package. Application front ends and Grammar libraries have no specs.
|
package/brand-values.mjs
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* breakpoint. Spacing and sizing come from the grammar's utility tokens.
|
|
14
14
|
*
|
|
15
15
|
* WHAT IT DELIBERATELY LEAVES ALONE: a fragment identifier (`href="#top"`, `id`, `htmlFor`), a
|
|
16
|
-
* `data:` URI, a component's
|
|
16
|
+
* `data:` URI, a component's semantic appearance prop (a named role rather than a raw colour), and
|
|
17
17
|
* Tailwind arbitrary values (`w-[12px]`), which `no-arbitrary-value` already reports - two rules
|
|
18
18
|
* for one span would be two findings for one fix.
|
|
19
19
|
*/
|
package/docs/hooks-folder.md
CHANGED
|
@@ -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`, `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 (owner ruling 2026-10-16).
|
|
12
12
|
|
|
13
13
|
**Invalid** (`src/hooks/course/readCourse.ts`)
|
|
14
14
|
|
|
@@ -19,7 +19,8 @@ export const readCourse = () => 1
|
|
|
19
19
|
**Valid** (`src/hooks/course/useCourse.ts`)
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
|
|
22
|
+
import { useState } from "react"
|
|
23
|
+
export const useCourse = () => useState(1)
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
**Finding code:** `FE_HOOKS_ARE_HOOKS`
|
|
@@ -33,7 +34,8 @@ export const useGetCourseSwr = () => 1
|
|
|
33
34
|
**Valid** (`src/hooks/swr/api/graphql/queries/useQueryCourseSwr.ts`)
|
|
34
35
|
|
|
35
36
|
```ts
|
|
36
|
-
|
|
37
|
+
import { useState } from "react"
|
|
38
|
+
export const useQueryCourseSwr = () => useState(1)
|
|
37
39
|
```
|
|
38
40
|
|
|
39
41
|
**Why:** `<file>` inside `hooks/` is not a React hook, or is not a file its hooks family names. A call to the back end goes to `modules/api`, a helper to `<domain>.shared.ts`.
|
package/docs/native-controls.md
CHANGED
|
@@ -16,11 +16,7 @@ Use the grammar's renderers; never a bare select, input, textarea or button.
|
|
|
16
16
|
<button onClick={go} />
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
**Valid
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
<Button onPress={go}>{t("go")}</Button>
|
|
23
|
-
```
|
|
19
|
+
**Valid:** Compose the selected grammar's published control with its supported event props and a translated label.
|
|
24
20
|
|
|
25
21
|
**Finding code:** `FE_NATIVE_FORM_CONTROL`
|
|
26
22
|
|
package/docs/next-conventions.md
CHANGED
|
@@ -141,7 +141,7 @@ export default function Page() { return <CoursesPage /> }
|
|
|
141
141
|
|
|
142
142
|
## `starci-fe/navigation-from-intl`
|
|
143
143
|
|
|
144
|
-
`Link`, `useRouter`, `usePathname` and `redirect` come from `modules/
|
|
144
|
+
`Link`, `useRouter`, `usePathname` and `redirect` come from `modules/routes`, not from Next (catalogue R59, sub-check `FE_I18N_NAVIGATION`).
|
|
145
145
|
|
|
146
146
|
**Invalid** (`src/components/blocks/Feed/component.tsx`)
|
|
147
147
|
|
|
@@ -152,18 +152,18 @@ import { useRouter } from "next/navigation"
|
|
|
152
152
|
**Valid** (`src/components/blocks/Feed/component.tsx`)
|
|
153
153
|
|
|
154
154
|
```tsx
|
|
155
|
-
import { useRouter } from "@/modules/
|
|
155
|
+
import { useRouter } from "@/modules/routes"
|
|
156
156
|
```
|
|
157
157
|
|
|
158
158
|
**Finding code:** `FE_I18N_NAVIGATION`
|
|
159
159
|
|
|
160
160
|
**Why:** `<file>` imports `Link`, `useRouter`, `usePathname` or `redirect` from Next, which is unaware of the locale: paths lose the `[locale]` prefix.
|
|
161
161
|
|
|
162
|
-
**Fix:** Import from `modules/
|
|
162
|
+
**Fix:** Import from `modules/routes`, which next-intl builds from `routing.ts`.
|
|
163
163
|
|
|
164
164
|
## `starci-fe/no-native-anchor`
|
|
165
165
|
|
|
166
|
-
An internal link is `Link` from `modules/
|
|
166
|
+
An internal link is `Link` from `modules/routes`; an external `_blank` link carries `rel` (catalogue R59, sub-check `FE_I18N_NAVIGATION`).
|
|
167
167
|
|
|
168
168
|
**Invalid** (`src/components/blocks/Feed/component.tsx`)
|
|
169
169
|
|
|
@@ -181,7 +181,7 @@ An internal link is `Link` from `modules/i18n/navigation`; an external `_blank`
|
|
|
181
181
|
|
|
182
182
|
**Why:** `<a>` in `<file>` points at an internal route (reloads the whole page, loses the locale, loses prefetch) or opens a new tab without `rel`.
|
|
183
183
|
|
|
184
|
-
**Fix:** Use `Link` from `modules/
|
|
184
|
+
**Fix:** Use `Link` from `modules/routes` with an href built by `modules/routes`; for `_blank` links add `rel="noopener noreferrer"`.
|
|
185
185
|
|
|
186
186
|
## `starci-fe/no-hardcoded-route`
|
|
187
187
|
|
|
@@ -207,9 +207,9 @@ router.push(routes.newWorkspace())
|
|
|
207
207
|
|
|
208
208
|
## `starci-fe/i18n-stack-in-one-module`
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
The shared i18n package owns a multi-app stack. A single app keeps routing/request config in `modules/i18n` and navigation in the `modules/routes` public entry.
|
|
211
211
|
|
|
212
|
-
`defineRouting`
|
|
212
|
+
`defineRouting` and `getRequestConfig` remain in the i18n owner. `createNavigation` belongs to a shared `fe.package.i18n` owner or the single app's `fe.modules.routes` owner. A single app's proxy mounts locale middleware using the routing-only public i18n entry. A shared package exposes its routing-only proxy entry as a declared package export. The proxy does not import `next-intl/navigation` or the app's routes/navigation barrel. Named import aliases and namespace imports keep their factory identity.
|
|
213
213
|
|
|
214
214
|
**Invalid** (`apps/web/src/modules/i18n/request.ts`, a repository with two apps)
|
|
215
215
|
|
|
@@ -229,4 +229,4 @@ export const i18n = createAppI18n({ locales: ["vi"] })
|
|
|
229
229
|
|
|
230
230
|
**Why:** `<file>` builds a layer of the next-intl stack itself (`defineRouting`, `createNavigation`, `getRequestConfig`, `createMiddleware`). The stack may be written only once per repo.
|
|
231
231
|
|
|
232
|
-
**Fix:**
|
|
232
|
+
**Fix:** Use the shared i18n factory, or keep routing/request configuration in the single app's i18n module and navigation in its public routes entry. Keep navigation imports out of the proxy.
|
|
@@ -71,3 +71,25 @@ const d = useQueryFeedSwr({ refreshInterval: 5000 })
|
|
|
71
71
|
**Why:** `<unit>` writes its own poll loop (`setInterval` or a self-calling `setTimeout`).
|
|
72
72
|
|
|
73
73
|
**Fix:** Refresh through a data hook (`refreshInterval`) or a socket, exactly one mechanism per resource.
|
|
74
|
+
|
|
75
|
+
## `starci-fe/no-app-relative-clock`
|
|
76
|
+
|
|
77
|
+
Grammar owns the shared clock for live relative time and countdowns. App components, hooks, modules and shared packages, including a product UI package, pass `dateTime`, `locale` and `mode` to `RelativeTime`.
|
|
78
|
+
|
|
79
|
+
The rule rejects a recurring `setInterval` or self-scheduling `setTimeout` whose callback or same-file helper reads `Date.now()`, `Date()`, zero-argument `new Date()` or `performance.now()`. It follows local aliases and destructured platform methods. Static reads, one-shot delays and timers that do not read the clock remain available. Indirect external clock helpers and counters that only decrement state need semantic review.
|
|
80
|
+
|
|
81
|
+
**Invalid** (`src/hooks/time/useNow.ts`)
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const timer = setInterval(() => update(Date.now()), 1000)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Valid** (`src/components/leaves/UpdatedAt/index.tsx`)
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
const UpdatedAt = () => <RelativeTime dateTime={instant} locale={locale} mode="relative" />
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Finding code:** `FE_SIZE_AND_STATE_BUDGET`
|
|
94
|
+
|
|
95
|
+
**Fix:** Render the Grammar primitive and pass the instant; its single shared ticker owns updates and its deterministic fallback owns the server render.
|
package/docs/surface-rules.md
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
# Grammar surface composition
|
|
2
2
|
|
|
3
|
-
Law module: `surface-rules.mjs`. Canon: ANATOMY-2 and PADDING-0.
|
|
3
|
+
Law module: `surface-rules.mjs`. HFS rule: R318. Canon: ANATOMY-2 and PADDING-0.
|
|
4
4
|
|
|
5
5
|
## `starci-fe/nested-control-variant`
|
|
6
6
|
|
|
7
|
-
Finding code: `FE_NESTED_VARIANT`. Grammar
|
|
8
|
-
|
|
9
|
-
App `className` values carry layout. A raw `bg-surface` utility paints a surface and is refused, including conditional classes, class builders, responsive variants, important modifiers and opacity suffixes. Static props spreads keep the same obligations. Typed app lint follows imported colocated class declarations as well as local constants.
|
|
7
|
+
Finding code: `FE_NESTED_VARIANT`. Grammar context supplies field variants on the page background and inside painted surfaces. App code omits every static `variant` on layer-aware fields, including fields rendered by imported or helper components. Named import aliases, namespace imports, static constants and props spreads preserve Grammar ownership. Explicit primary `Tabs` inside a painted surface is also refused; its default follows the surface context.
|
|
10
8
|
|
|
11
9
|
Invalid:
|
|
12
10
|
|
|
13
11
|
```tsx
|
|
14
|
-
import {
|
|
15
|
-
const
|
|
12
|
+
import { SearchField } from "@starci/grammar/core"
|
|
13
|
+
const Search = () => <SearchField variant="secondary" />
|
|
16
14
|
```
|
|
17
15
|
|
|
18
16
|
Valid:
|
|
@@ -22,20 +20,24 @@ import { SurfaceCard, SearchField } from "@starci/grammar/core"
|
|
|
22
20
|
const Panel = () => <SurfaceCard><SearchField /></SurfaceCard>
|
|
23
21
|
```
|
|
24
22
|
|
|
23
|
+
## `starci-fe/no-app-surface-paint`
|
|
24
|
+
|
|
25
|
+
Finding code: `FE_GRAMMAR_LAYOUT_ONLY`. App classes carry layout. A raw `bg-surface` utility paints a surface and is refused, including conditional classes, class builders, responsive variants, important modifiers and opacity suffixes. Compose a painted Grammar surface such as `SurfaceCard`. Static props spreads keep the same obligations. Typed app lint follows imported colocated class declarations as well as local constants.
|
|
26
|
+
|
|
25
27
|
## `starci-fe/no-list-panel-padding`
|
|
26
28
|
|
|
27
|
-
Finding code: `FE_LIST_PANEL_PADDING`.
|
|
29
|
+
Finding code: `FE_LIST_PANEL_PADDING`. Within the nearest painted Grammar surface, a wrapper around `ListBox` or its section caption cannot add horizontal padding through `p-*`, `px-*`, `ps-*`, `pe-*`, `pl-*`, `pr-*` or arbitrary `padding-inline` properties. These insets add to the list's own padding. Page gutters outside that surface and lists outside any surface retain their layout padding. Unpainted containers such as `Rail` pass the parent context through.
|
|
28
30
|
|
|
29
31
|
Invalid:
|
|
30
32
|
|
|
31
33
|
```tsx
|
|
32
34
|
import { SurfaceCard, ListBox } from "@starci/grammar/core"
|
|
33
|
-
const Panel = () => <SurfaceCard><div className="
|
|
35
|
+
const Panel = () => <SurfaceCard><div className="pl-2"><ListBox label="Members" items={members} /></div></SurfaceCard>
|
|
34
36
|
```
|
|
35
37
|
|
|
36
38
|
Valid:
|
|
37
39
|
|
|
38
40
|
```tsx
|
|
39
41
|
import { SurfaceCard, ListBox } from "@starci/grammar/core"
|
|
40
|
-
const Panel = () => <SurfaceCard><ListBox inset="panel" label="Members" items={members} /></SurfaceCard>
|
|
42
|
+
const Panel = () => <div className="px-4"><SurfaceCard><ListBox inset="panel" label="Members" items={members} /></SurfaceCard></div>
|
|
41
43
|
```
|
package/drawing.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* TWO AXES, AND ONLY ONE IS DRAWN. A surface's `state` is its SHAPE: a layout the owner approves as
|
|
5
5
|
* one drawing. A slot's data status (loading, forbidden, error, empty) is not a shape; it is a
|
|
6
|
-
* recipe applied per
|
|
6
|
+
* status recipe applied per resource, and it is never a drawn shape. Mixing the two is what made
|
|
7
7
|
* every block ship four hand-drawn variants that disagreed with each other.
|
|
8
8
|
*
|
|
9
9
|
* THE PURE HALF TAKES ATOMS. `component.tsx` receives `{ state, props, on }`: `props` is data
|
|
@@ -19,21 +19,9 @@
|
|
|
19
19
|
* never wrong, beyond it.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import {
|
|
23
|
-
import { classifyImport, fileOf, inSlot, kindOfFile, roleOfFile } from "./lib/scope.mjs"
|
|
22
|
+
import { classifyImport, roleOfFile } from "./lib/scope.mjs"
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
const SPLIT_KINDS = new Set(["blocks", "pages", "layouts", "overlays"])
|
|
27
|
-
|
|
28
|
-
/** The folder name of the linted file: the surface a `component.tsx` or `index.tsx` belongs to. */
|
|
29
|
-
const surfaceName = (context) => hfsOf(context).relative(fileOf(context)).split("/").slice(-2)[0]
|
|
30
|
-
|
|
31
|
-
/** Whether the linted file sits in a component or feature owner of a split kind. */
|
|
32
|
-
const inSplitTier = (context) => inSlot(context, "fe.components", "fe.feature") && SPLIT_KINDS.has(kindOfFile(context))
|
|
33
|
-
|
|
34
|
-
/** The pure half of a split tier (the drawing role of a surface folder), and which tier it is. */
|
|
35
|
-
const pureHalf = (context) =>
|
|
36
|
-
inSplitTier(context) && roleOfFile(context) === "drawing" && /^[A-Z][A-Za-z0-9]*$/.test(surfaceName(context)) ? kindOfFile(context) : null
|
|
24
|
+
import { inSplitTier, pureHalf } from "./lib/drawing-scope.mjs"
|
|
37
25
|
|
|
38
26
|
/** Type names that carry a rendered tree rather than data. */
|
|
39
27
|
const NON_ATOM_TYPE = /^(?:ReactNode|ReactElement|ReactPortal|JSX\.Element|Element|ComponentType|FC|FunctionComponent|ElementType|ComponentProps|RenderFunction)$/
|
|
@@ -294,7 +282,7 @@ export const noDataStatusShape = {
|
|
|
294
282
|
schema: [],
|
|
295
283
|
messages: {
|
|
296
284
|
status:
|
|
297
|
-
"`{{name}}` lists \"{{value}}\" as a shape. Loading, forbidden, error and empty
|
|
285
|
+
"`{{name}}` lists \"{{value}}\" as a shape. Loading, forbidden, error and empty describe resource status: pass them as data to the owned status renderer. Open/closed is a boolean atom. A shape is a layout the owner approves as one drawing.",
|
|
298
286
|
},
|
|
299
287
|
},
|
|
300
288
|
create(context) {
|
|
@@ -316,61 +304,11 @@ export const noDataStatusShape = {
|
|
|
316
304
|
},
|
|
317
305
|
}
|
|
318
306
|
|
|
319
|
-
// -- DRAWING-4 ----------------------------------------------------------------------------------
|
|
320
|
-
|
|
321
|
-
/** Flags that are a slot's data status. */
|
|
322
|
-
const STATUS_FLAG = /^(?:isLoading|isError|isForbidden|isPending|isValidating|isEmpty)$/
|
|
323
|
-
|
|
324
|
-
/** The name a test expression reads, if it is a status flag. */
|
|
325
|
-
const statusFlagOf = (node) => {
|
|
326
|
-
if (!node) return null
|
|
327
|
-
if (node.type === "UnaryExpression") return statusFlagOf(node.argument)
|
|
328
|
-
if (node.type === "ChainExpression") return statusFlagOf(node.expression)
|
|
329
|
-
if (node.type === "Identifier" && STATUS_FLAG.test(node.name)) return node.name
|
|
330
|
-
if (node.type === "MemberExpression" && node.property?.type === "Identifier" && STATUS_FLAG.test(node.property.name)) {
|
|
331
|
-
return node.property.name
|
|
332
|
-
}
|
|
333
|
-
if (node.type === "LogicalExpression" || node.type === "BinaryExpression") return statusFlagOf(node.left) ?? statusFlagOf(node.right)
|
|
334
|
-
return null
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/** A block's pure half never branches on a data status itself. */
|
|
338
|
-
export const slotStatusThroughSlotView = {
|
|
339
|
-
meta: {
|
|
340
|
-
type: "problem",
|
|
341
|
-
docs: { description: "A block's pure half renders data status through `SlotView`, never its own branch." },
|
|
342
|
-
schema: [],
|
|
343
|
-
messages: {
|
|
344
|
-
branch:
|
|
345
|
-
"This branches on `{{flag}}` by hand. Every slot renders its status through `SlotView`, the one recipe: a second branch drifts from it and becomes one more variant somebody draws.",
|
|
346
|
-
},
|
|
347
|
-
},
|
|
348
|
-
create(context) {
|
|
349
|
-
if (pureHalf(context) !== "blocks") return {}
|
|
350
|
-
const check = (node, test) => {
|
|
351
|
-
const flag = statusFlagOf(test)
|
|
352
|
-
if (flag) context.report({ node, messageId: "branch", data: { flag } })
|
|
353
|
-
}
|
|
354
|
-
return {
|
|
355
|
-
ConditionalExpression(node) {
|
|
356
|
-
check(node, node.test)
|
|
357
|
-
},
|
|
358
|
-
IfStatement(node) {
|
|
359
|
-
check(node, node.test)
|
|
360
|
-
},
|
|
361
|
-
LogicalExpression(node) {
|
|
362
|
-
if (node.operator === "&&" && node.parent?.type === "JSXExpressionContainer") check(node, node.left)
|
|
363
|
-
},
|
|
364
|
-
}
|
|
365
|
-
},
|
|
366
|
-
}
|
|
367
|
-
|
|
368
307
|
/** The rules this law contributes to the plugin. */
|
|
369
308
|
export const rules = {
|
|
370
309
|
"base-props-atom": basePropsAtom,
|
|
371
310
|
"base-import-pair": baseImportPair,
|
|
372
311
|
"no-data-status-shape": noDataStatusShape,
|
|
373
|
-
"slot-status-through-slotview": slotStatusThroughSlotView,
|
|
374
312
|
}
|
|
375
313
|
|
|
376
314
|
/** Every FE canon rule is an error; existing debt is fixed before adoption. */
|
package/hooks-folder.mjs
CHANGED
|
@@ -90,12 +90,13 @@ const SWR_HOOKS = new Map([
|
|
|
90
90
|
])
|
|
91
91
|
const SWR_DEFAULTS = new Map([["swr", "useSWR"], ["swr/immutable", "useSWRImmutable"], ["swr/infinite", "useSWRInfinite"], ["swr/mutation", "useSWRMutation"], ["swr/subscription", "useSWRSubscription"]])
|
|
92
92
|
/**
|
|
93
|
-
* The library hooks a composite hook may
|
|
94
|
-
* react-hook-form and zustand's React entry points. A name outside the list is not a hook by its spelling.
|
|
93
|
+
* The canonical library hooks a composite hook may call (R56, FE-THEME): next-intl, next/navigation,
|
|
94
|
+
* next-themes, react-hook-form and zustand's React entry points. A name outside the list is not a hook by its spelling.
|
|
95
95
|
*/
|
|
96
96
|
const LIBRARY_HOOKS = new Map([
|
|
97
97
|
["next-intl", new Set(["useTranslations", "useLocale", "useFormatter", "useNow", "useTimeZone", "useMessages"])],
|
|
98
98
|
["next/navigation", new Set(["useRouter", "usePathname", "useSearchParams", "useParams", "useSelectedLayoutSegment", "useSelectedLayoutSegments"])],
|
|
99
|
+
["next-themes", new Set(["useTheme"])],
|
|
99
100
|
["react-hook-form", new Set(["useForm", "useFormContext", "useWatch", "useController", "useFieldArray", "useFormState"])],
|
|
100
101
|
["zustand", new Set(["useStore"])],
|
|
101
102
|
["zustand/react/shallow", new Set(["useShallow"])],
|
|
@@ -121,6 +122,7 @@ const importOrigin = (definition) => {
|
|
|
121
122
|
const source = String(definition.parent.source.value)
|
|
122
123
|
if (definition.parent.importKind === "type" || specifier.importKind === "type") return null
|
|
123
124
|
if (source !== "react" && !SWR_HOOKS.has(source) && !LIBRARY_HOOKS.has(source)) return null
|
|
125
|
+
if (source === "next-themes" && specifier.type === "ImportDefaultSpecifier") return null
|
|
124
126
|
if (specifier.type === "ImportSpecifier") return namedOrigin(source, specifier.imported.name ?? specifier.imported.value)
|
|
125
127
|
if (specifier.type === "ImportDefaultSpecifier" && SWR_DEFAULTS.has(source)) return { source, name: SWR_DEFAULTS.get(source) }
|
|
126
128
|
return { source, name: null }
|
|
@@ -246,7 +248,7 @@ export const hooksFolderHoldsHooksOnly = {
|
|
|
246
248
|
defaultExport:
|
|
247
249
|
"FE_HOOKS_ARE_HOOKS: export the hook as a named use* function. A default export does not identify the hook this file owns.",
|
|
248
250
|
fake:
|
|
249
|
-
"FE_HOOKS_ARE_HOOKS: `{{name}}` must be a function that calls a real hook: a React, Redux or SWR hook, a listed library hook (next-intl, next/navigation, react-hook-form, zustand) or a hook of the app's own `hooks/` folders. A use* name alone does not make a reader or helper a hook.",
|
|
251
|
+
"FE_HOOKS_ARE_HOOKS: `{{name}}` must be a function that calls a real hook: a React, Redux or SWR hook, a listed library hook (next-intl, next/navigation, next-themes useTheme, react-hook-form, zustand) or a hook of the app's own `hooks/` folders. A use* name alone does not make a reader or helper a hook.",
|
|
250
252
|
sharedHook:
|
|
251
253
|
"`{{name}}` is a hook exported from the shared helper file. That file holds non-hook helpers; a hook there is a hook nobody will find. Give it its own `use<Name>.ts`.",
|
|
252
254
|
server:
|
package/index.mjs
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The universal frontend plugin: every applicable law's rules, gathered once.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* exists and what is switched on - and every rule is an error: the repository's `eslint.config.mjs`
|
|
8
|
-
* names its layout and nothing else about the law.
|
|
4
|
+
* The public factory gathers every universal rule at error. A managed repository config composes
|
|
5
|
+
* it with the selected Grammar-family factory instead of registering individual rules. Family
|
|
6
|
+
* packages consume the public generic infrastructure exports; each rule implementation has one owner.
|
|
9
7
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* in every conversation about the failure. Aliasing one here to match a target's older spelling
|
|
13
|
-
* would leave two names for one rule and no way to tell which a message came from.
|
|
8
|
+
* Published IDs identify their owning law. Namespace migrations are explicit in the removed-vocabulary
|
|
9
|
+
* catalog; compatibility aliases cannot create a second owner for a rule.
|
|
14
10
|
*/
|
|
15
11
|
import { createRequire } from "node:module"
|
|
16
|
-
import { recommended as appShellRecommended, rules as appShellRules } from "./app-shell.mjs"
|
|
17
12
|
import { join } from "node:path"
|
|
18
13
|
import { pathToFileURL } from "node:url"
|
|
19
14
|
import { recommended as brandValuesRecommended, rules as brandValuesRules } from "./brand-values.mjs"
|
|
@@ -23,16 +18,13 @@ import { recommended as classNamesRecommended, rules as classNamesRules } from "
|
|
|
23
18
|
import { recommended as envOwnerRecommended, rules as envOwnerRules } from "./env-owner.mjs"
|
|
24
19
|
import { recommended as feStackRecommended, rules as feStackRules } from "./fe-stack.mjs"
|
|
25
20
|
import { recommended as fileLayoutRecommended, rules as fileLayoutRules } from "./file-layout.mjs"
|
|
26
|
-
import { recommended as grammarBoundaryRecommended, rules as grammarBoundaryRules } from "./grammar-boundary.mjs"
|
|
27
21
|
import { recommended as formattingRecommended, rules as formattingRules } from "./formatting.mjs"
|
|
28
22
|
import { recommended as hooksFolderRecommended, rules as hooksFolderRules } from "./hooks-folder.mjs"
|
|
29
23
|
import { recommended as hygieneRecommended, rules as hygieneRules } from "./hygiene.mjs"
|
|
30
|
-
import { recommended as iconRecommended, rules as iconRules } from "./icon.mjs"
|
|
31
24
|
import { recommended as landmarkRecommended, rules as landmarkRules } from "./landmark.mjs"
|
|
32
25
|
import { recommended as listsRecommended, rules as listsRules } from "./lists.mjs"
|
|
33
26
|
import { recommended as loadingRecommended, rules as loadingRules } from "./loading.mjs"
|
|
34
27
|
import { recommended as lintEscapeRecommended, rules as lintEscapeRules } from "./lint-escape-hatch.mjs"
|
|
35
|
-
import { recommended as menuEntriesRecommended, rules as menuEntriesRules } from "./menu-entries.mjs"
|
|
36
28
|
import { recommended as namingRecommended, rules as namingRules } from "./naming.mjs"
|
|
37
29
|
import { recommended as nativeControlsRecommended, rules as nativeControlsRules } from "./native-controls.mjs"
|
|
38
30
|
import { recommended as nextConventionsRecommended, rules as nextConventionsRules } from "./next-conventions.mjs"
|
|
@@ -41,15 +33,12 @@ import { recommended as servedLocaleRecommended, rules as servedLocaleRules } fr
|
|
|
41
33
|
import { recommended as drawingRecommended, rules as drawingRules } from "./drawing.mjs"
|
|
42
34
|
import { recommended as sizeBudgetRecommended, rules as sizeBudgetRules } from "./size-and-state-budget.mjs"
|
|
43
35
|
import { recommended as sizeGrowthRecommended, rules as sizeGrowthRules } from "./size-growth.mjs"
|
|
44
|
-
import { recommended as statusColorsRecommended, rules as statusColorsRules } from "./status-colors.mjs"
|
|
45
36
|
import { recommended as surfaceRulesRecommended, rules as surfaceRules } from "./surface-rules.mjs"
|
|
46
37
|
import { recommended as splitRecommended, rules as splitRules } from "./the-split.mjs"
|
|
47
38
|
import { recommended as tokensRecommended, rules as tokensRules } from "./tokens.mjs"
|
|
48
39
|
import { recommended as transportRecommended, rules as transportRules } from "./transport.mjs"
|
|
49
40
|
import { recommended as translationRecommended, rules as translationRules } from "./translation.mjs"
|
|
50
41
|
import { recommended as typeSafetyRecommended, rules as typeSafetyRules } from "./type-safety.mjs"
|
|
51
|
-
import { recommended as typographyRecommended, rules as typographyRules } from "./typography.mjs"
|
|
52
|
-
import { recommended as vendorRecommended, rules as vendorRules } from "./vendor-boundary.mjs"
|
|
53
42
|
import { recommended as projectGraphRecommended, rules as projectGraphRules } from "./project-graph.mjs"
|
|
54
43
|
import { recommended as sonarParityRecommended, rules as sonarParityRules } from "./sonar-parity.mjs"
|
|
55
44
|
import { buildFeConfig, buildPlainSourceConfig } from "./lib/config.mjs"
|
|
@@ -57,7 +46,6 @@ import { why } from "./lib/why.mjs"
|
|
|
57
46
|
|
|
58
47
|
/** Each law's contribution, kept separate so a duplicate name is detectable rather than silent. */
|
|
59
48
|
const CONTRIBUTIONS = [
|
|
60
|
-
{ law: "app-shell", rules: appShellRules, recommended: appShellRecommended },
|
|
61
49
|
{ law: "project-graph", rules: projectGraphRules, recommended: projectGraphRecommended },
|
|
62
50
|
{ law: "brand-values", rules: brandValuesRules, recommended: brandValuesRecommended },
|
|
63
51
|
{ law: "client-boundary", rules: clientBoundaryRules, recommended: clientBoundaryRecommended },
|
|
@@ -70,16 +58,13 @@ const CONTRIBUTIONS = [
|
|
|
70
58
|
{ law: "env-owner", rules: envOwnerRules, recommended: envOwnerRecommended },
|
|
71
59
|
{ law: "fe-stack", rules: feStackRules, recommended: feStackRecommended },
|
|
72
60
|
{ law: "file-layout", rules: fileLayoutRules, recommended: fileLayoutRecommended },
|
|
73
|
-
{ law: "grammar-boundary", rules: grammarBoundaryRules, recommended: grammarBoundaryRecommended },
|
|
74
61
|
{ law: "formatting", rules: formattingRules, recommended: formattingRecommended },
|
|
75
62
|
{ law: "hooks-folder", rules: hooksFolderRules, recommended: hooksFolderRecommended },
|
|
76
63
|
{ law: "hygiene", rules: hygieneRules, recommended: hygieneRecommended },
|
|
77
|
-
{ law: "icon", rules: iconRules, recommended: iconRecommended },
|
|
78
64
|
{ law: "landmark", rules: landmarkRules, recommended: landmarkRecommended },
|
|
79
65
|
{ law: "lists", rules: listsRules, recommended: listsRecommended },
|
|
80
66
|
{ law: "loading", rules: loadingRules, recommended: loadingRecommended },
|
|
81
67
|
{ law: "lint-escape-hatch", rules: lintEscapeRules, recommended: lintEscapeRecommended },
|
|
82
|
-
{ law: "menu-entries", rules: menuEntriesRules, recommended: menuEntriesRecommended },
|
|
83
68
|
{ law: "naming", rules: namingRules, recommended: namingRecommended },
|
|
84
69
|
{ law: "native-controls", rules: nativeControlsRules, recommended: nativeControlsRecommended },
|
|
85
70
|
{ law: "next-conventions", rules: nextConventionsRules, recommended: nextConventionsRecommended },
|
|
@@ -88,15 +73,12 @@ const CONTRIBUTIONS = [
|
|
|
88
73
|
{ law: "drawing", rules: drawingRules, recommended: drawingRecommended },
|
|
89
74
|
{ law: "size-and-state-budget", rules: sizeBudgetRules, recommended: sizeBudgetRecommended },
|
|
90
75
|
{ law: "size-growth", rules: sizeGrowthRules, recommended: sizeGrowthRecommended },
|
|
91
|
-
{ law: "status-colors", rules: statusColorsRules, recommended: statusColorsRecommended },
|
|
92
76
|
{ law: "surface-rules", rules: surfaceRules, recommended: surfaceRulesRecommended },
|
|
93
77
|
{ law: "the-split", rules: splitRules, recommended: splitRecommended },
|
|
94
78
|
{ law: "tokens", rules: tokensRules, recommended: tokensRecommended },
|
|
95
79
|
{ law: "translation", rules: translationRules, recommended: translationRecommended },
|
|
96
80
|
{ law: "transport", rules: transportRules, recommended: transportRecommended },
|
|
97
81
|
{ law: "type-safety", rules: typeSafetyRules, recommended: typeSafetyRecommended },
|
|
98
|
-
{ law: "typography", rules: typographyRules, recommended: typographyRecommended },
|
|
99
|
-
{ law: "vendor-boundary", rules: vendorRules, recommended: vendorRecommended },
|
|
100
82
|
{ law: "sonar-parity", rules: sonarParityRules, recommended: sonarParityRecommended },
|
|
101
83
|
]
|
|
102
84
|
|
|
@@ -225,7 +207,7 @@ export const reactHooksRules = (reactHooks) => {
|
|
|
225
207
|
}
|
|
226
208
|
|
|
227
209
|
/**
|
|
228
|
-
* The
|
|
210
|
+
* The universal portion of a frontend's flat config, composed with its selected Grammar-family factory.
|
|
229
211
|
*
|
|
230
212
|
* It owns nothing the repository could choose: which files are linted comes from the HFS profile (every app's and every
|
|
231
213
|
* workspace package's `src/`), not which rules are on, not their level, not whether an inline comment may
|