@starci/eslint-canon-fe 8.2.13 → 8.2.15
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 +19 -1
- package/README.md +1 -0
- package/client-boundary.mjs +66 -23
- package/docs/client-boundary.md +9 -9
- package/docs/hooks-folder.md +16 -4
- package/docs/hygiene.md +2 -2
- package/docs/transport.md +26 -26
- package/docs/type-safety.md +6 -6
- package/fe-stack.mjs +86 -0
- package/formatting.mjs +12 -6
- package/hooks-folder.mjs +73 -31
- package/hygiene.mjs +4 -4
- package/icon.mjs +25 -17
- package/index.mjs +2 -0
- package/lib/fe-stack.mjs +73 -0
- package/lib/scope.mjs +23 -2
- package/lib/why.mjs +23 -13
- package/next-conventions.mjs +4 -3
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +207 -15
- package/runtime/knowledge/hfs/fe-stack.yaml +218 -0
- package/runtime/knowledge/hfs/slots.yaml +319 -179
- package/runtime/knowledge/patterns/fe/folder.yaml +24 -18
- package/runtime/knowledge/sonar-gate.yaml +10 -2
- package/runtime/modules/kernel/removed-vocabulary.yaml +48 -0
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +0 -2
- package/runtime/scripts/hfs/architecture/config.mjs +3 -2
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +6 -2
- package/runtime/scripts/hfs/architecture/error-masked.mjs +10 -1
- package/runtime/scripts/hfs/architecture/frontend.mjs +10 -3
- package/runtime/scripts/hfs/architecture/hfs.mjs +6 -3
- package/runtime/scripts/hfs/architecture/index.mjs +2 -0
- package/runtime/scripts/hfs/architecture/owners.mjs +19 -16
- package/runtime/scripts/hfs/architecture/route-files-thin.mjs +4 -1
- package/runtime/scripts/hfs/architecture/server-action.mjs +75 -20
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +2 -2
- package/runtime/scripts/hfs/architecture/supabase.mjs +36 -210
- package/runtime/scripts/hfs/architecture/tiers.mjs +1 -33
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +110 -54
- package/runtime/scripts/hfs/architecture/type-context.mjs +4 -1
- package/runtime/scripts/hfs/edition-slots.mjs +13 -4
- package/runtime/scripts/hfs/manifest-shape.mjs +3 -3
- package/runtime/scripts/hfs/rule-catalog.mjs +21 -1
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +4 -5
- package/served-locale.mjs +10 -13
- package/the-split.mjs +5 -2
- package/transport.mjs +81 -67
- package/runtime/scripts/hfs/architecture/supabase-results.mjs +0 -182
package/lib/why.mjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* `fix` strings are the keys of `modules/i18n/messages/canon.yaml`, so `translator('vi')(entry.en)` and
|
|
12
12
|
* `translator('vi')(entry.fix)` hand back the Vietnamese of the same sentence.
|
|
13
13
|
*
|
|
14
|
-
* Every rule that carries a catalogue id (R18, R22, R49-R59, R60-R62, R65) has an entry;
|
|
14
|
+
* Every rule that carries a catalogue id (R18, R22, R49-R59, R60-R62, R65, the fe-stack catalog) has an entry;
|
|
15
15
|
* the twin test refuses a rule of those laws with no entry and an entry for a rule that does not exist.
|
|
16
16
|
*/
|
|
17
17
|
|
|
@@ -31,12 +31,12 @@ export const why = {
|
|
|
31
31
|
},
|
|
32
32
|
"fetch-only-in-api-client": {
|
|
33
33
|
code: "FE_TRANSPORT_OWNER",
|
|
34
|
-
en: "`fetch` (or `Request`, `EventSource`, `sendBeacon`, another HTTP library)
|
|
35
|
-
fix: "Call
|
|
34
|
+
en: "`<file>` reaches the back end outside the API layer: `fetch` (or `Request`, `EventSource`, `sendBeacon`, another HTTP library) outside `modules/api`, or Apollo, axios or socket.io-client outside its home (`modules/api`, `modules/api/rest`, `hooks/socketio`).",
|
|
35
|
+
fix: "Call an operation of `modules/api` (Apollo GraphQL, or axios in `modules/api/rest`) through its SWR hook in `hooks/swr`; listen to realtime through `hooks/socketio`.",
|
|
36
36
|
},
|
|
37
37
|
"client-fetch-has-signal": {
|
|
38
38
|
code: "FE_TRANSPORT_OWNER",
|
|
39
|
-
en: "`fetch` in `<file>` has no `signal`.
|
|
39
|
+
en: "`fetch` in `<file>` has no `signal`. Every request of the API layer must carry a timeout and an `AbortSignal`.",
|
|
40
40
|
fix: "Pass `signal` (`AbortSignal.timeout(...)` combined with the caller's signal).",
|
|
41
41
|
},
|
|
42
42
|
"no-shared-transport-state": {
|
|
@@ -56,8 +56,8 @@ export const why = {
|
|
|
56
56
|
},
|
|
57
57
|
"no-hand-typed-wire": {
|
|
58
58
|
code: "FE_WIRE_GENERATED",
|
|
59
|
-
en: "`<file>` hand-types a wire type or
|
|
60
|
-
fix: "Run codegen from the be contract snapshot (`be/contracts/`) and import the generated types;
|
|
59
|
+
en: "`<file>` hand-types a wire type, casts a response, or writes a GraphQL document outside the API layer. Use the types generated from `be/contracts/`.",
|
|
60
|
+
fix: "Run codegen from the be contract snapshot (`be/contracts/`) and import the generated types; write each GraphQL document in its operation file under `modules/api/graphql/queries` or `modules/api/graphql/mutations`.",
|
|
61
61
|
},
|
|
62
62
|
"use-client-only-at-boundary": {
|
|
63
63
|
code: "FE_CLIENT_BOUNDARY",
|
|
@@ -66,8 +66,8 @@ export const why = {
|
|
|
66
66
|
},
|
|
67
67
|
"hooks-folder-holds-hooks-only": {
|
|
68
68
|
code: "FE_HOOKS_ARE_HOOKS",
|
|
69
|
-
en: "`<file>` inside `hooks/` is not a React hook. A
|
|
70
|
-
fix: "One hook per `use*.ts` file; a shared helper goes in `<domain>.shared.ts`; a
|
|
69
|
+
en: "`<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`.",
|
|
70
|
+
fix: "One hook per `use*.ts` file under the name its family gives it (`hooks/swr/api/graphql/queries/useQuery<X>Swr.ts`, `hooks/rhf/use<X>Form.ts`, ...); a shared helper goes in `<domain>.shared.ts`; a back-end call is an operation of `modules/api`.",
|
|
71
71
|
},
|
|
72
72
|
"no-middleware-file": {
|
|
73
73
|
code: "FE_NEXT_CONVENTIONS",
|
|
@@ -172,7 +172,7 @@ export const why = {
|
|
|
172
172
|
"no-data-fetch-in-effect": {
|
|
173
173
|
code: "FE_EFFECT_FETCH",
|
|
174
174
|
en: "`useEffect` in `<file>` starts a promise (`await`, `fetch`, `.then`, `void load()`, `mutate()`/`refresh()` called directly): no cache, no request merging, no loading/error state, no abort. Freshness of data comes from the SWR key, not from an effect.",
|
|
175
|
-
fix: "Read
|
|
175
|
+
fix: "Read with an SWR hook of `hooks/swr` over an operation of `modules/api`, keyed by what changes; call `mutate()` from the event that changes the data (handler, socket message).",
|
|
176
176
|
},
|
|
177
177
|
"no-empty-catch": {
|
|
178
178
|
code: "FE_SWALLOWED_ERROR",
|
|
@@ -216,14 +216,19 @@ export const why = {
|
|
|
216
216
|
},
|
|
217
217
|
"client-no-server-import": {
|
|
218
218
|
code: "FE_CLIENT_SERVER_IMPORT",
|
|
219
|
-
en: "A client component in `<file>` imports code that exists only on the server (`server-only`, `next/headers`, a Node module, a server
|
|
220
|
-
fix: "Read the data
|
|
219
|
+
en: "A client component in `<file>` imports code that exists only on the server (`server-only`, `next/headers`, a Node module, a server-only module).",
|
|
220
|
+
fix: "Read the data through an SWR hook of `hooks/swr` over an operation of `modules/api`; keep request-scoped server APIs in server components.",
|
|
221
221
|
},
|
|
222
222
|
"server-module-marks-server-only": {
|
|
223
223
|
code: "FE_SERVER_ONLY_MARK",
|
|
224
224
|
en: "Module `<file>` imports a server-only API (`next/headers`, `next/server`, `next-intl/server`, a Node module or an already server-only module) but does not open with `import \"server-only\"`.",
|
|
225
225
|
fix: "Make `import \"server-only\"` the file's first statement; route files (`page`, `layout`, `route`, `proxy`) are server components already and need none.",
|
|
226
226
|
},
|
|
227
|
+
"no-server-action": {
|
|
228
|
+
code: "FE_SERVER_ACTION_FORBIDDEN",
|
|
229
|
+
en: "`<file>` carries a `\"use server\"` directive: a Server Action, an endpoint the front end serves. The front end serves no API.",
|
|
230
|
+
fix: "Write it as a GraphQL mutation of the back end and call it through its operation in `modules/api` and its SWR hook in `hooks/swr`.",
|
|
231
|
+
},
|
|
227
232
|
"web-storage-only-in-modules": {
|
|
228
233
|
code: "FE_STORAGE_OUTSIDE_MODULES",
|
|
229
234
|
en: "`<file>` uses `localStorage`/`sessionStorage` outside `modules/`; storage does not exist on the server and throws when over quota.",
|
|
@@ -261,8 +266,8 @@ export const why = {
|
|
|
261
266
|
},
|
|
262
267
|
"one-outcome-union": {
|
|
263
268
|
code: "FE_HTTP_STATUS_COLLAPSE",
|
|
264
|
-
en: "`<file>` declares another result union (`ok` or `kind`) outside `
|
|
265
|
-
fix: "Use `
|
|
269
|
+
en: "`<file>` declares another result union (`success`, `ok` or `kind`) outside `modules/api/graphql/types.ts`. A repo has only one result vocabulary, `GraphQLResponse<T>`.",
|
|
270
|
+
fix: "Use `GraphQLResponse<T>` of `modules/api/graphql/types.ts` (or the shared api package); put the business detail in its `data` instead of declaring a new union.",
|
|
266
271
|
},
|
|
267
272
|
"i18n-stack-in-one-module": {
|
|
268
273
|
code: "FE_I18N_PLACEMENT",
|
|
@@ -274,6 +279,11 @@ export const why = {
|
|
|
274
279
|
en: "Raw `<tag>` in `<file>`. Page structure and text are composed from grammar components, not hand-written HTML.",
|
|
275
280
|
fix: "Replace it with the matching grammar component (`Heading`, `Text`, `SurfaceCard`, ...); if grammar lacks it, add it to grammar or the ui package instead of drawing it in place.",
|
|
276
281
|
},
|
|
282
|
+
"fe-stack-import": {
|
|
283
|
+
code: "FE_STACK_FORBIDDEN",
|
|
284
|
+
en: "`<file>` imports `<what>`, a library the front-end stack forbids (a second library for a concern that has one), or a Supabase package outside `modules/auth`.",
|
|
285
|
+
fix: "Use the concern's canonical library from `knowledge/hfs/fe-stack.yaml` (the message names it); Supabase on the front end is the auth client in `modules/auth` only, data goes through `modules/api`.",
|
|
286
|
+
},
|
|
277
287
|
"file-size-growth": {
|
|
278
288
|
code: "HFS_SIZE_GROWTH",
|
|
279
289
|
en: "`<file>` exceeds the line budget: a new file already too long, or an existing file longer than at the parent commit. Every file must be small enough for one person to read through and one test to cover.",
|
package/next-conventions.mjs
CHANGED
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { attribute, attributeValue, calleeName, leadingTextOf, stringOf } from "./lib/ast.mjs"
|
|
15
|
+
import { forbiddingConcern } from "./lib/fe-stack.mjs"
|
|
15
16
|
import { hfsOf } from "./lib/hfs.mjs"
|
|
16
17
|
import { baseName, classOf, fileOf, inSlot, roleOfFile, slotOfFile, stem } from "./lib/scope.mjs"
|
|
17
18
|
|
|
18
|
-
/** Another i18n stack: a second place that decides which language a reader sees. */
|
|
19
|
-
const OTHER_I18N = /^(?:react-i18next|i18next|next-i18next|react-intl|next-translate|@lingui\/.+|typesafe-i18n|rosetta)
|
|
19
|
+
/** Another i18n stack: a second place that decides which language a reader sees (one the fe-stack catalog forbids by name is fe-stack-import's finding). */
|
|
20
|
+
const OTHER_I18N = /^(?:react-i18next|i18next|next-i18next|react-intl|next-translate|@lingui\/.+|typesafe-i18n|rosetta)(?:\/.*)?$/
|
|
20
21
|
|
|
21
22
|
// -- FE-NEXT-1 -------------------------------------------------------------------------------------
|
|
22
23
|
|
|
@@ -96,7 +97,7 @@ export const noSecondI18nStack = {
|
|
|
96
97
|
return {
|
|
97
98
|
ImportDeclaration(node) {
|
|
98
99
|
const source = String(node.source.value)
|
|
99
|
-
if (OTHER_I18N.test(source)) context.report({ node, messageId: "stack", data: { name: source } })
|
|
100
|
+
if (OTHER_I18N.test(source) && !forbiddingConcern(source)) context.report({ node, messageId: "stack", data: { name: source } })
|
|
100
101
|
},
|
|
101
102
|
}
|
|
102
103
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starci/eslint-canon-fe",
|
|
3
|
-
"version": "8.2.
|
|
3
|
+
"version": "8.2.15",
|
|
4
4
|
"description": "The StarCi front-end lint canon: every rule its laws publish, gathered as one flat-config plugin and attached by one factory.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,43 +14,38 @@ purpose: >-
|
|
|
14
14
|
pins:
|
|
15
15
|
# --- @starci packages. Every @starci package is a PUBLISHED npm package (install: registry): a product repository
|
|
16
16
|
# installs the exact pinned version from the npm registry in the root and every workspace package.json, never `file:`.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
group: starci
|
|
20
|
-
install: registry
|
|
21
|
-
side: fe
|
|
22
|
-
source: packages/heroicons/package.json
|
|
23
|
-
why: 'StarCi custom Heroicons-compatible cuts; eslint-canon-fe (icon.mjs) admits @starci/heroicons/24/outline and /16/solid as icon sources, so an app that uses them gets this exact version.'
|
|
17
|
+
# @starci/heroicons has no pin since 1.0.0-alpha.13: the application icon set is @phosphor-icons/react (fe-stack.yaml
|
|
18
|
+
# icons) and eslint-canon-fe one-icon-set refuses every other glyph catalogue, so no product repository installs it.
|
|
24
19
|
'@starci/grammar':
|
|
25
|
-
version: 0.9.
|
|
20
|
+
version: 0.9.14
|
|
26
21
|
group: starci
|
|
27
22
|
install: registry
|
|
28
23
|
side: fe
|
|
29
24
|
source: packages/grammar/package.json
|
|
30
25
|
why: 'the grammar front ends read: the brand layer sets `--font-sans` and `--font-mono` and the grammar reads them; it carries the Input tel kind, the copyable and read-only Input, the IconButton disclosure props and count badge, ChatBubble, FillViewport, Textarea mentions and Enter-to-send, the leading PrimaryRailLayout rail, the plate-less Image, ListRow, the soft Alert, Progress steps, the floating primary IconButton, FlowGraph, the STATE-8 loading views and the bordered selectable TagGroup chips. The runtime source version wins for a @starci package.'
|
|
31
26
|
'@starci/eslint-canon-be':
|
|
32
|
-
version: 3.2.
|
|
27
|
+
version: 3.2.16
|
|
33
28
|
group: starci
|
|
34
29
|
install: registry
|
|
35
30
|
side: be
|
|
36
31
|
source: packages/eslint/be/package.json
|
|
37
32
|
why: 'the back-end ESLint canon: `loadHfs(import.meta.url)` of be/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the be side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; `starciBeConfig({ hfs: loadHfs(import.meta.url) })` is the typed factory and the BE-CONVENTION laws are its rules.'
|
|
38
33
|
'@starci/eslint-canon-fe':
|
|
39
|
-
version: 8.2.
|
|
34
|
+
version: 8.2.15
|
|
40
35
|
group: starci
|
|
41
36
|
install: registry
|
|
42
37
|
side: fe
|
|
43
38
|
source: packages/eslint/fe/package.json
|
|
44
39
|
why: 'the front-end ESLint canon: `loadHfs(import.meta.url)` of fe/eslint.config.mjs finds the app-root hfs.json (kind app) and gives every linted file the view of the fe side, and the project graph is built per side; its bundled runtime copies (slots, canon-pins, failure codes, the architecture machine) put .starcistacks and .sops.yaml at the app root; the front end has no tests (FE_NO_TESTS R97 in hfs) and no-vietnamese-in-source (R91) applies.'
|
|
45
40
|
'@starci/stylelint-canon':
|
|
46
|
-
version: 2.0.
|
|
41
|
+
version: 2.0.9
|
|
47
42
|
group: starci
|
|
48
43
|
install: registry
|
|
49
44
|
side: fe
|
|
50
45
|
source: packages/stylelint/package.json
|
|
51
46
|
why: 'the front-end stylelint canon: no-class-selector refuses a compound class selector (`div.card`); the brand layer accepts `--font-sans` and `--font-mono`; status-contrast measures the HeroUI soft pairs per theme; brand-layer-shape reads the shared :root,.light,.dark block and the info soft pair; appTokens come from loadAppTokens(import.meta.url) for the managed one-line stylelint.config.mjs.'
|
|
52
47
|
'@starci/tsconfig':
|
|
53
|
-
version: 2.0.
|
|
48
|
+
version: 2.0.4
|
|
54
49
|
group: starci
|
|
55
50
|
install: registry
|
|
56
51
|
side: both
|
|
@@ -62,7 +57,7 @@ pins:
|
|
|
62
57
|
side: both
|
|
63
58
|
source: packages/prettier-config/package.json
|
|
64
59
|
'@starci/jest-preset':
|
|
65
|
-
version: 2.2.
|
|
60
|
+
version: 2.2.7
|
|
66
61
|
group: starci
|
|
67
62
|
install: registry
|
|
68
63
|
side: be
|
|
@@ -76,14 +71,14 @@ pins:
|
|
|
76
71
|
source: packages/test-world/package.json
|
|
77
72
|
why: 'the shared e2e library of every back end (R47, R48), a devDependency of every back end: the warm stack behind toxiproxy, the network-edge fakes, the Nest boot, the typed useTestWorld handle, useSandbox for contract specs and the outage lock. Kafka is real own infra (the one digest-pinned apache/kafka KRaft image, a broker listener and proxy per slot, slot-prefixed topics, consumer groups and client ids) and Postgres is schema-per-context (a schema and a login role per context in a shared database). Each worker has its own data slot (<ns>_w<k>) with its own databases, Keycloak realm, leased Redis DB, bucket/collection/topic prefixes, fakes host, toxiproxy proxies and outage lock; a run provisions min(jest workers, workers cap, default 2) slots, and state file protocol 2 pairs exactly with @starci/jest-preset (TEST_WORLD_PAIR_MISMATCH otherwise). A modules world boots real peer apps beside its modules, world.apps.<name>.during(fn) is the outage of a peer app, w.keycloak.clientSecret(client) answers the run-generated secret of a confidential realm client, world.keycloak.events/sessions read the realm user events and live sessions, world.infra.postgresql.connection(name) takes one database connection down while the others serve, and world.resolve and scope.resolve take any Nest token. Every path a declaration names (`stack`, seeds, the realm, Dockerfiles) resolves from the app root (the directory of hfs.json); the globalSetup registers the path aliases as TypeScript resolves them. Its CLI main is exported at ./cli for starci app stack.'
|
|
78
73
|
'@starci/cli':
|
|
79
|
-
version: 1.2.
|
|
74
|
+
version: 1.2.14
|
|
80
75
|
group: starci
|
|
81
76
|
install: registry
|
|
82
77
|
side: both
|
|
83
78
|
source: packages/cli/package.json
|
|
84
79
|
why: 'the only StarCi command package; product repositories install it for starci app, while @starci/hfs remains its pinned transitive implementation.'
|
|
85
80
|
'@starci/hfs':
|
|
86
|
-
version: 4.2.
|
|
81
|
+
version: 4.2.14
|
|
87
82
|
group: starci
|
|
88
83
|
install: registry
|
|
89
84
|
side: both
|
|
@@ -274,6 +269,11 @@ pins:
|
|
|
274
269
|
side: be
|
|
275
270
|
why: the e2e world of a back end calls the app over HTTP (/health/live in the skeleton); two product back ends resolve 7.3.0, one 7.2.2.
|
|
276
271
|
# --- ui
|
|
272
|
+
'@xyflow/react':
|
|
273
|
+
version: 12.11.6
|
|
274
|
+
group: ui
|
|
275
|
+
side: fe
|
|
276
|
+
why: 'Grammar FlowGraph imports React Flow from @xyflow/react; every Grammar consumer declares this required peer at the version used by the Grammar package.'
|
|
277
277
|
'@heroui/react':
|
|
278
278
|
version: 3.2.6
|
|
279
279
|
group: ui
|
|
@@ -294,6 +294,198 @@ pins:
|
|
|
294
294
|
group: ui
|
|
295
295
|
side: fe
|
|
296
296
|
why: 'the PostCSS plugin tailwindcss 4 runs through; the reference app declares ^4 and resolves 4.3.3.'
|
|
297
|
+
# --- front-end stack (knowledge/hfs/fe-stack.yaml; owner ruling 2026-10-13: lite = full)
|
|
298
|
+
'@apollo/client':
|
|
299
|
+
version: 4.1.6
|
|
300
|
+
group: framework
|
|
301
|
+
side: fe
|
|
302
|
+
why: 'the GraphQL client of every front end (fe-stack graphql): auth and no-auth clients with the link chain in modules/api/graphql; version read from the starci-academy package-lock on 2026-10-13.'
|
|
303
|
+
'@graphql-codegen/cli':
|
|
304
|
+
version: 7.4.6
|
|
305
|
+
group: tooling
|
|
306
|
+
side: fe
|
|
307
|
+
why: 'generates the front-end wire types from be/contracts/<app>/schema.graphql into modules/api/graphql/__generated__ (fe-stack codegen); the latest registry version on 2026-10-13, academy has none.'
|
|
308
|
+
'@graphql-codegen/client-preset':
|
|
309
|
+
version: 6.2.3
|
|
310
|
+
group: tooling
|
|
311
|
+
side: fe
|
|
312
|
+
why: 'the codegen preset of the front-end wire types (fe-stack codegen); the latest registry version on 2026-10-13.'
|
|
313
|
+
'axios':
|
|
314
|
+
version: 1.15.2
|
|
315
|
+
group: framework
|
|
316
|
+
side: fe
|
|
317
|
+
why: 'the REST client of the front end for uploads, presigned URLs and auth endpoints (fe-stack rest); version read from the starci-academy package-lock on 2026-10-13.'
|
|
318
|
+
'swr':
|
|
319
|
+
version: 2.4.2
|
|
320
|
+
group: framework
|
|
321
|
+
side: fe
|
|
322
|
+
why: 'the data hooks and cache of every front end, hooks/swr (fe-stack data); version read from the starci-academy package-lock on 2026-10-13.'
|
|
323
|
+
'react-hook-form':
|
|
324
|
+
version: 7.77.0
|
|
325
|
+
group: framework
|
|
326
|
+
side: fe
|
|
327
|
+
why: 'the form library, hooks/rhf (fe-stack forms); version read from the starci-academy package-lock on 2026-10-13.'
|
|
328
|
+
'@hookform/resolvers':
|
|
329
|
+
version: 5.4.0
|
|
330
|
+
group: framework
|
|
331
|
+
side: fe
|
|
332
|
+
why: 'the zod resolver of react-hook-form (fe-stack forms); version read from the starci-academy package-lock on 2026-10-13.'
|
|
333
|
+
'zod':
|
|
334
|
+
version: 4.4.3
|
|
335
|
+
group: framework
|
|
336
|
+
side: fe
|
|
337
|
+
why: 'the schema library of front-end forms and boundary parsing (fe-stack forms); version read from the starci-academy package-lock on 2026-10-13.'
|
|
338
|
+
'@reduxjs/toolkit':
|
|
339
|
+
version: 2.11.2
|
|
340
|
+
group: framework
|
|
341
|
+
side: fe
|
|
342
|
+
why: 'app-wide shared state, redux/ (fe-stack state-global); version read from the starci-academy package-lock on 2026-10-13.'
|
|
343
|
+
'react-redux':
|
|
344
|
+
version: 9.2.0
|
|
345
|
+
group: framework
|
|
346
|
+
side: fe
|
|
347
|
+
why: 'the React binding of the app-wide store (fe-stack state-global); version read from the starci-academy package-lock on 2026-10-13.'
|
|
348
|
+
'zustand':
|
|
349
|
+
version: 5.0.12
|
|
350
|
+
group: framework
|
|
351
|
+
side: fe
|
|
352
|
+
why: 'feature-local client state, hooks/zustand (fe-stack state-local); version read from the starci-academy package-lock on 2026-10-13.'
|
|
353
|
+
'socket.io-client':
|
|
354
|
+
version: 4.8.3
|
|
355
|
+
group: framework
|
|
356
|
+
side: fe
|
|
357
|
+
why: 'the realtime client, hooks/socketio (fe-stack realtime); version read from the starci-academy package-lock on 2026-10-13.'
|
|
358
|
+
'eventemitter2':
|
|
359
|
+
version: 6.4.9
|
|
360
|
+
group: framework
|
|
361
|
+
side: fe
|
|
362
|
+
why: 'the fan-out emitter behind the socket side-effect component (fe-stack realtime); version read from the starci-academy package-lock on 2026-10-13.'
|
|
363
|
+
'@phosphor-icons/react':
|
|
364
|
+
version: 2.1.10
|
|
365
|
+
group: ui
|
|
366
|
+
side: fe
|
|
367
|
+
why: 'the one application icon set (fe-stack icons); version read from the starci-academy package-lock on 2026-10-13.'
|
|
368
|
+
'next-themes':
|
|
369
|
+
version: 0.4.6
|
|
370
|
+
group: ui
|
|
371
|
+
side: fe
|
|
372
|
+
why: 'light and dark theme switching in components/providers (fe-stack theme); version read from the starci-academy package-lock on 2026-10-13.'
|
|
373
|
+
'framer-motion':
|
|
374
|
+
version: 12.42.2
|
|
375
|
+
group: ui
|
|
376
|
+
side: fe
|
|
377
|
+
why: 'motion (fe-stack motion); version read from the starci-academy package-lock on 2026-10-13.'
|
|
378
|
+
'dayjs':
|
|
379
|
+
version: 1.11.21
|
|
380
|
+
group: framework
|
|
381
|
+
side: fe
|
|
382
|
+
why: 'date math and parsing, modules/dayjs (fe-stack dates); display goes through the next-intl formatter; version read from the starci-academy package-lock on 2026-10-13.'
|
|
383
|
+
'decimal.js':
|
|
384
|
+
version: 10.6.0
|
|
385
|
+
group: framework
|
|
386
|
+
side: fe
|
|
387
|
+
why: 'money and decimal arithmetic, modules/utils (fe-stack numbers); version read from the starci-academy package-lock on 2026-10-13.'
|
|
388
|
+
'superjson':
|
|
389
|
+
version: 2.2.6
|
|
390
|
+
group: framework
|
|
391
|
+
side: fe
|
|
392
|
+
why: 'serialization of stored values, modules/superjson and modules/storage (fe-stack storage); version read from the starci-academy package-lock on 2026-10-13.'
|
|
393
|
+
'lodash':
|
|
394
|
+
version: 4.17.23
|
|
395
|
+
group: framework
|
|
396
|
+
side: fe
|
|
397
|
+
why: 'per-method utilities, modules/utils (fe-stack utils); version read from the starci-academy package-lock on 2026-10-13.'
|
|
398
|
+
'usehooks-ts':
|
|
399
|
+
version: 3.1.1
|
|
400
|
+
group: framework
|
|
401
|
+
side: fe
|
|
402
|
+
why: 'reusable React hooks, hooks/reuseables (fe-stack utils); version read from the starci-academy package-lock on 2026-10-13.'
|
|
403
|
+
'recharts':
|
|
404
|
+
version: 3.8.1
|
|
405
|
+
group: ui
|
|
406
|
+
side: fe
|
|
407
|
+
why: 'charts (fe-stack charts); version read from the starci-academy package-lock on 2026-10-13.'
|
|
408
|
+
'react-markdown':
|
|
409
|
+
version: 10.1.0
|
|
410
|
+
group: ui
|
|
411
|
+
side: fe
|
|
412
|
+
why: 'markdown rendering (fe-stack markdown); version read from the starci-academy package-lock on 2026-10-13.'
|
|
413
|
+
'remark-gfm':
|
|
414
|
+
version: 4.0.1
|
|
415
|
+
group: ui
|
|
416
|
+
side: fe
|
|
417
|
+
why: 'GitHub-flavoured markdown for react-markdown (fe-stack markdown); version read from the starci-academy package-lock on 2026-10-13.'
|
|
418
|
+
'remark-directive':
|
|
419
|
+
version: 4.0.0
|
|
420
|
+
group: ui
|
|
421
|
+
side: fe
|
|
422
|
+
why: 'markdown directives for react-markdown (fe-stack markdown); version read from the starci-academy package-lock on 2026-10-13.'
|
|
423
|
+
'react-shiki':
|
|
424
|
+
version: 0.9.2
|
|
425
|
+
group: ui
|
|
426
|
+
side: fe
|
|
427
|
+
why: 'code highlighting (fe-stack code); version read from the starci-academy package-lock on 2026-10-13.'
|
|
428
|
+
'@monaco-editor/react':
|
|
429
|
+
version: 4.7.0
|
|
430
|
+
group: ui
|
|
431
|
+
side: fe
|
|
432
|
+
why: 'the code editor (fe-stack code); version read from the starci-academy package-lock on 2026-10-13.'
|
|
433
|
+
'mermaid':
|
|
434
|
+
version: 11.16.0
|
|
435
|
+
group: ui
|
|
436
|
+
side: fe
|
|
437
|
+
why: 'diagrams (fe-stack diagrams); version read from the starci-academy package-lock on 2026-10-13.'
|
|
438
|
+
'react-dropzone':
|
|
439
|
+
version: 15.0.0
|
|
440
|
+
group: ui
|
|
441
|
+
side: fe
|
|
442
|
+
why: 'file drop zones of uploads (fe-stack upload); version read from the starci-academy package-lock on 2026-10-13.'
|
|
443
|
+
'react-pdf':
|
|
444
|
+
version: 10.4.1
|
|
445
|
+
group: ui
|
|
446
|
+
side: fe
|
|
447
|
+
why: 'PDF viewing (fe-stack pdf); version read from the starci-academy package-lock on 2026-10-13.'
|
|
448
|
+
'@fingerprintjs/fingerprintjs':
|
|
449
|
+
version: 5.2.0
|
|
450
|
+
group: framework
|
|
451
|
+
side: fe
|
|
452
|
+
why: 'the device id of the GraphQL fingerprint link (fe-stack device-id); version read from the starci-academy package-lock on 2026-10-13.'
|
|
453
|
+
'@sentry/nextjs':
|
|
454
|
+
version: 11.7.0
|
|
455
|
+
group: framework
|
|
456
|
+
side: fe
|
|
457
|
+
why: 'error reporting through sentry.*.config.ts and instrumentation.ts (fe-stack observability); the latest registry version on 2026-10-13, academy has none.'
|
|
458
|
+
# --- back-end transports the front end calls, and the realtime gateway
|
|
459
|
+
'@nestjs/graphql':
|
|
460
|
+
version: 13.4.5
|
|
461
|
+
group: framework
|
|
462
|
+
side: be
|
|
463
|
+
why: 'the GraphQL transport of every back end (be.transport.graphql) that the front end calls; the ecommerce-app resolution on 2026-10-13.'
|
|
464
|
+
'@nestjs/apollo':
|
|
465
|
+
version: 13.4.5
|
|
466
|
+
group: framework
|
|
467
|
+
side: be
|
|
468
|
+
why: 'the Apollo driver of @nestjs/graphql; the ecommerce-app resolution on 2026-10-13.'
|
|
469
|
+
'@apollo/server':
|
|
470
|
+
version: 5.5.1
|
|
471
|
+
group: framework
|
|
472
|
+
side: be
|
|
473
|
+
why: 'the Apollo server behind @nestjs/apollo; the ecommerce-app resolution on 2026-10-13.'
|
|
474
|
+
'@nestjs/websockets':
|
|
475
|
+
version: 11.2.5
|
|
476
|
+
group: framework
|
|
477
|
+
side: be
|
|
478
|
+
why: 'the socket.io gateway of a realtime back end (lite runs it in the api process, owner ruling 2026-10-13); the @nestjs/core pin line.'
|
|
479
|
+
'@nestjs/platform-socket.io':
|
|
480
|
+
version: 11.2.5
|
|
481
|
+
group: framework
|
|
482
|
+
side: be
|
|
483
|
+
why: 'the socket.io platform of @nestjs/websockets; the @nestjs/core pin line.'
|
|
484
|
+
'@as-integrations/express5':
|
|
485
|
+
version: 1.1.2
|
|
486
|
+
group: framework
|
|
487
|
+
side: be
|
|
488
|
+
why: 'the Express 5 integration @nestjs/apollo loads at boot (R111 nestjs-apollo-express5, knowledge/hfs/peer-integrations.yaml); every api app that serves GraphQL installs it; the latest registry version on 2026-10-13.'
|
|
297
489
|
# --- Supabase lite
|
|
298
490
|
'@supabase/ssr':
|
|
299
491
|
version: 0.12.7
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
schema: starci/fe-stack@1
|
|
2
|
+
purpose: >-
|
|
3
|
+
The front-end stack of every app, lite and full alike (owner ruling 2026-10-13, R212 case-2): for each common front-end concern the
|
|
4
|
+
ONE canonical library, its HFS home (slot ids of knowledge/hfs/slots.yaml), the pattern id that teaches it
|
|
5
|
+
(knowledge/patterns/fe/*.yaml) and the alternatives a front end never depends on or imports. Versions live in
|
|
6
|
+
knowledge/hfs/canon-pins.yaml only. `tier: core` packages are installed by every scaffold; `opt` packages are added when a feature
|
|
7
|
+
needs the concern. Source example: starci-lab/starci-academy (origin/main, 2026-10-13); it illustrates, this file decides.
|
|
8
|
+
concerns:
|
|
9
|
+
- id: framework
|
|
10
|
+
tier: core
|
|
11
|
+
packages: [next, react, react-dom, typescript]
|
|
12
|
+
home: [fe.route, fe.source-root-pinned]
|
|
13
|
+
pattern: FE-ROUTE
|
|
14
|
+
forbids: []
|
|
15
|
+
why: App Router with thin routes; route handlers (except health/live and the auth callback slot) and Server Actions are refused by FE_ROUTE_HANDLER_FORBIDDEN and FE_SERVER_ACTION_FORBIDDEN.
|
|
16
|
+
- id: graphql
|
|
17
|
+
tier: core
|
|
18
|
+
packages: ['@apollo/client', graphql]
|
|
19
|
+
home: [fe.modules.api, fe.transport.client, fe.transport.outcome]
|
|
20
|
+
pattern: FE-API-GRAPHQL
|
|
21
|
+
forbids: [urql, graphql-request, '@urql/core', graphql-hooks, apollo-boost]
|
|
22
|
+
why: one file per operation under modules/api/graphql/{queries,mutations}, run by the auth or no-auth Apollo client with the link chain.
|
|
23
|
+
- id: codegen
|
|
24
|
+
tier: core
|
|
25
|
+
packages: ['@graphql-codegen/cli', '@graphql-codegen/client-preset']
|
|
26
|
+
home: [fe.modules.api]
|
|
27
|
+
pattern: FE-WIRE-GENERATED
|
|
28
|
+
forbids: []
|
|
29
|
+
why: the wire types are generated from be/contracts/<app>/schema.graphql into modules/api/graphql/__generated__, never typed by hand.
|
|
30
|
+
- id: rest
|
|
31
|
+
tier: core
|
|
32
|
+
packages: [axios]
|
|
33
|
+
home: [fe.modules.api]
|
|
34
|
+
pattern: FE-API-REST
|
|
35
|
+
forbids: [ky, superagent, node-fetch, cross-fetch, isomorphic-fetch]
|
|
36
|
+
why: modules/api/rest/<area>/*.ts for uploads, presigned URLs and auth endpoints only; GraphQL carries every other call.
|
|
37
|
+
- id: data
|
|
38
|
+
tier: core
|
|
39
|
+
packages: [swr]
|
|
40
|
+
home: [fe.hooks.swr]
|
|
41
|
+
pattern: FE-DATA-SWR
|
|
42
|
+
forbids: ['@tanstack/react-query', react-query]
|
|
43
|
+
why: hooks/swr/api/{graphql,rest}/{queries,mutations}/use*Swr.ts wrap one operation each; components call hooks only.
|
|
44
|
+
- id: toast
|
|
45
|
+
tier: core
|
|
46
|
+
packages: ['@heroui/react']
|
|
47
|
+
home: [fe.modules.toast]
|
|
48
|
+
pattern: FE-TOAST
|
|
49
|
+
forbids: [sonner, react-toastify, react-hot-toast, notistack]
|
|
50
|
+
why: mutation outcomes are reported by modules/toast (showGraphQLToast, use*WithToast) with next-intl labels.
|
|
51
|
+
- id: forms
|
|
52
|
+
tier: core
|
|
53
|
+
packages: [react-hook-form, '@hookform/resolvers', zod]
|
|
54
|
+
home: [fe.hooks.rhf]
|
|
55
|
+
pattern: FE-FORM
|
|
56
|
+
forbids: [formik, yup, final-form, react-final-form]
|
|
57
|
+
why: hooks/rhf/use<X>Form.ts builds a zod schema with next-intl messages and submits through an SWR mutation and the toast.
|
|
58
|
+
- id: state-global
|
|
59
|
+
tier: core
|
|
60
|
+
packages: ['@reduxjs/toolkit', react-redux]
|
|
61
|
+
home: [fe.redux]
|
|
62
|
+
pattern: FE-STATE-REDUX
|
|
63
|
+
forbids: [mobx, mobx-react, mobx-react-lite, recoil, jotai, valtio, redux-saga]
|
|
64
|
+
why: app-wide shared state (selected ids synced from the route, server-pushed entities) lives in redux/slices.
|
|
65
|
+
- id: state-local
|
|
66
|
+
tier: core
|
|
67
|
+
packages: [zustand]
|
|
68
|
+
home: [fe.hooks.zustand]
|
|
69
|
+
pattern: FE-STATE-ZUSTAND
|
|
70
|
+
forbids: []
|
|
71
|
+
why: feature-local UI, wizard and editor state lives in hooks/zustand/<store>.
|
|
72
|
+
- id: realtime
|
|
73
|
+
tier: opt
|
|
74
|
+
packages: [socket.io-client, eventemitter2]
|
|
75
|
+
home: [fe.hooks.socketio]
|
|
76
|
+
pattern: FE-REALTIME
|
|
77
|
+
forbids: [graphql-ws, subscriptions-transport-ws, '@supabase/realtime-js', pusher-js, ably]
|
|
78
|
+
why: one socket.io-client singleton per namespace and one side-effect component that dispatches to redux and SWR; lite runs the gateway in the be api process.
|
|
79
|
+
- id: auth
|
|
80
|
+
tier: core
|
|
81
|
+
packages: []
|
|
82
|
+
home: [fe.modules.auth]
|
|
83
|
+
pattern: FE-AUTH
|
|
84
|
+
forbids: []
|
|
85
|
+
why: modules/auth is the identity-provider client (lite - the @supabase/supabase-js auth client; full - the app's IdP client); it yields the session and the access token for the Apollo link and never reads data.
|
|
86
|
+
- id: i18n
|
|
87
|
+
tier: core
|
|
88
|
+
packages: [next-intl]
|
|
89
|
+
home: [fe.modules.i18n]
|
|
90
|
+
pattern: FE-I18N
|
|
91
|
+
forbids: [i18next, react-i18next, next-i18next, react-intl, '@lingui/react']
|
|
92
|
+
why: useTranslations for copy and the next-intl formatter for numbers, dates and currency.
|
|
93
|
+
- id: ui
|
|
94
|
+
tier: core
|
|
95
|
+
packages: ['@heroui/react', '@heroui/styles', tailwindcss, '@starci/grammar']
|
|
96
|
+
home: [fe.components, fe.feature]
|
|
97
|
+
pattern: FE-UI
|
|
98
|
+
forbids: ['@mantine/core', '@mui/material', '@chakra-ui/react', antd, styled-components, '@emotion/react', '@emotion/styled']
|
|
99
|
+
why: the grammar renderers over HeroUI and Tailwind; no second UI kit and no CSS-in-JS.
|
|
100
|
+
- id: icons
|
|
101
|
+
tier: core
|
|
102
|
+
packages: ['@phosphor-icons/react']
|
|
103
|
+
home: [fe.components]
|
|
104
|
+
pattern: FE-ICON
|
|
105
|
+
forbids: [react-icons, lucide-react, '@iconify/react', '@gravity-ui/icons', '@tabler/icons-react', '@fortawesome/react-fontawesome']
|
|
106
|
+
why: one application icon set.
|
|
107
|
+
- id: theme
|
|
108
|
+
tier: core
|
|
109
|
+
packages: [next-themes]
|
|
110
|
+
home: [fe.components.providers]
|
|
111
|
+
pattern: FE-THEME
|
|
112
|
+
forbids: []
|
|
113
|
+
why: the theme provider lives in components/providers.
|
|
114
|
+
- id: motion
|
|
115
|
+
tier: opt
|
|
116
|
+
packages: [framer-motion]
|
|
117
|
+
home: [fe.components]
|
|
118
|
+
pattern: FE-MOTION
|
|
119
|
+
forbids: [react-spring, '@react-spring/web', gsap]
|
|
120
|
+
why: one motion library.
|
|
121
|
+
- id: dates
|
|
122
|
+
tier: core
|
|
123
|
+
packages: [dayjs]
|
|
124
|
+
home: [fe.modules]
|
|
125
|
+
pattern: FE-DATE
|
|
126
|
+
forbids: [moment, moment-timezone, date-fns, luxon]
|
|
127
|
+
why: dayjs (modules/dayjs) for date math and parsing; display through the next-intl formatter.
|
|
128
|
+
- id: numbers
|
|
129
|
+
tier: opt
|
|
130
|
+
packages: [decimal.js]
|
|
131
|
+
home: [fe.modules]
|
|
132
|
+
pattern: FE-NUMBER
|
|
133
|
+
forbids: [big.js, bignumber.js, numeral]
|
|
134
|
+
why: decimal.js for money arithmetic in modules/utils; display through the next-intl formatter.
|
|
135
|
+
- id: storage
|
|
136
|
+
tier: core
|
|
137
|
+
packages: [superjson]
|
|
138
|
+
home: [fe.modules]
|
|
139
|
+
pattern: FE-STORAGE
|
|
140
|
+
forbids: [localforage, store2]
|
|
141
|
+
why: modules/storage (local and session storage) serializes through modules/superjson; components never touch storage directly.
|
|
142
|
+
- id: utils
|
|
143
|
+
tier: opt
|
|
144
|
+
packages: [lodash, usehooks-ts]
|
|
145
|
+
home: [fe.modules, fe.hooks.reuseables]
|
|
146
|
+
pattern: FE-UTILS
|
|
147
|
+
forbids: [underscore, ramda, lodash-es]
|
|
148
|
+
why: per-method lodash imports in modules/utils and reusable hooks in hooks/reuseables.
|
|
149
|
+
- id: charts
|
|
150
|
+
tier: opt
|
|
151
|
+
packages: [recharts]
|
|
152
|
+
home: [fe.components]
|
|
153
|
+
pattern: FE-CHART
|
|
154
|
+
forbids: [chart.js, react-chartjs-2, '@nivo/core', d3, echarts, echarts-for-react]
|
|
155
|
+
why: one chart library, styled by the dataviz grammar.
|
|
156
|
+
- id: flow
|
|
157
|
+
tier: opt
|
|
158
|
+
packages: ['@xyflow/react']
|
|
159
|
+
home: [fe.components]
|
|
160
|
+
pattern: FE-FLOW
|
|
161
|
+
forbids: [reactflow, cytoscape]
|
|
162
|
+
why: graph canvases go through the grammar FlowGraph over React Flow.
|
|
163
|
+
- id: markdown
|
|
164
|
+
tier: opt
|
|
165
|
+
packages: [react-markdown, remark-gfm, remark-directive]
|
|
166
|
+
home: [fe.components]
|
|
167
|
+
pattern: FE-MARKDOWN
|
|
168
|
+
forbids: [marked, markdown-it, showdown]
|
|
169
|
+
why: markdown renders through react-markdown, never dangerouslySetInnerHTML.
|
|
170
|
+
- id: code
|
|
171
|
+
tier: opt
|
|
172
|
+
packages: [react-shiki, '@monaco-editor/react']
|
|
173
|
+
home: [fe.components]
|
|
174
|
+
pattern: FE-CODE
|
|
175
|
+
forbids: [prismjs, react-syntax-highlighter, highlight.js, '@uiw/react-codemirror', codemirror]
|
|
176
|
+
why: react-shiki highlights and Monaco edits.
|
|
177
|
+
- id: diagrams
|
|
178
|
+
tier: opt
|
|
179
|
+
packages: [mermaid]
|
|
180
|
+
home: [fe.components]
|
|
181
|
+
pattern: FE-DIAGRAM
|
|
182
|
+
forbids: []
|
|
183
|
+
why: diagrams render through mermaid in a block.
|
|
184
|
+
- id: upload
|
|
185
|
+
tier: opt
|
|
186
|
+
packages: [react-dropzone]
|
|
187
|
+
home: [fe.components, fe.modules.api]
|
|
188
|
+
pattern: FE-UPLOAD
|
|
189
|
+
forbids: [react-dropzone-uploader, filepond, react-filepond, uppy]
|
|
190
|
+
why: react-dropzone in a block and a presigned PUT through modules/api/rest.
|
|
191
|
+
- id: pdf
|
|
192
|
+
tier: opt
|
|
193
|
+
packages: [react-pdf]
|
|
194
|
+
home: [fe.components]
|
|
195
|
+
pattern: FE-PDF
|
|
196
|
+
forbids: ['@react-pdf-viewer/core', pdfjs-dist]
|
|
197
|
+
why: PDF viewing through react-pdf in a block.
|
|
198
|
+
- id: device-id
|
|
199
|
+
tier: opt
|
|
200
|
+
packages: ['@fingerprintjs/fingerprintjs']
|
|
201
|
+
home: [fe.transport.client]
|
|
202
|
+
pattern: FE-API-GRAPHQL
|
|
203
|
+
forbids: []
|
|
204
|
+
why: the fingerprint link of the Apollo client attaches the device id.
|
|
205
|
+
- id: observability
|
|
206
|
+
tier: opt
|
|
207
|
+
packages: ['@sentry/nextjs']
|
|
208
|
+
home: [fe.source-root-pinned]
|
|
209
|
+
pattern: FE-OBSERVE
|
|
210
|
+
forbids: [logrocket, bugsnag-js, '@bugsnag/js', '@datadog/browser-rum']
|
|
211
|
+
why: errors are reported through @sentry/nextjs (sentry.*.config.ts, instrumentation.ts), never console-only.
|
|
212
|
+
- id: lint
|
|
213
|
+
tier: core
|
|
214
|
+
packages: [eslint, prettier, '@starci/eslint-canon-fe', '@starci/prettier-config']
|
|
215
|
+
home: [fe.tool-config]
|
|
216
|
+
pattern: FE-LINT
|
|
217
|
+
forbids: []
|
|
218
|
+
why: the canon lint and format configuration.
|