@starci/eslint-canon-fe 9.2.0 → 9.4.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 +14 -0
- package/README.md +5 -1
- package/dist/brand-values.d.ts +28 -0
- package/dist/brand-values.js +172 -0
- package/dist/button-intent.d.ts +11 -0
- package/dist/button-intent.js +361 -0
- package/dist/callback-shape.d.ts +7 -0
- package/dist/callback-shape.js +396 -0
- package/dist/class-names.d.ts +19 -0
- package/dist/class-names.js +170 -0
- package/dist/client-boundary.d.ts +42 -0
- package/dist/client-boundary.js +404 -0
- package/dist/comments.d.ts +47 -0
- package/dist/comments.js +156 -0
- package/dist/control-height.d.ts +7 -0
- package/dist/control-height.js +83 -0
- package/dist/drawing.d.ts +31 -0
- package/dist/drawing.js +376 -0
- package/dist/env-owner.d.ts +22 -0
- package/dist/env-owner.js +291 -0
- package/dist/fe-stack.d.ts +10 -0
- package/dist/fe-stack.js +73 -0
- package/dist/file-layout.d.ts +71 -0
- package/dist/file-layout.js +465 -0
- package/dist/formatting.d.ts +25 -0
- package/dist/formatting.js +104 -0
- package/dist/function-budget.d.ts +9 -0
- package/dist/function-budget.js +48 -0
- package/dist/grammar-shim.d.ts +5 -0
- package/dist/grammar-shim.js +126 -0
- package/dist/hooks-folder.d.ts +34 -0
- package/dist/hooks-folder.js +320 -0
- package/dist/hygiene.d.ts +39 -0
- package/dist/hygiene.js +540 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +310 -0
- package/{landmark.mjs → dist/landmark.d.ts} +4 -6
- package/dist/landmark.js +6 -0
- package/dist/lib/ast.d.ts +36 -0
- package/dist/lib/ast.js +94 -0
- package/dist/lib/bindings.d.ts +84 -0
- package/dist/lib/bindings.js +168 -0
- package/dist/lib/class-text.d.ts +3 -0
- package/dist/lib/class-text.js +30 -0
- package/dist/lib/config.d.ts +81 -0
- package/dist/lib/config.js +141 -0
- package/dist/lib/drawing-scope.d.ts +5 -0
- package/{lib/drawing-scope.mjs → dist/lib/drawing-scope.js} +6 -11
- package/dist/lib/eslint-boundary.d.ts +23 -0
- package/dist/lib/eslint-boundary.js +6 -0
- package/dist/lib/fe-stack.d.ts +49 -0
- package/dist/lib/fe-stack.js +98 -0
- package/dist/lib/hfs-view.d.ts +74 -0
- package/dist/lib/hfs-view.js +6 -0
- package/dist/lib/hfs.d.ts +34 -0
- package/dist/lib/hfs.js +72 -0
- package/dist/lib/next.d.ts +4 -0
- package/dist/lib/next.js +10 -0
- package/dist/lib/own-hook-origin.d.ts +17 -0
- package/dist/lib/own-hook-origin.js +52 -0
- package/dist/lib/package-root.d.ts +4 -0
- package/dist/lib/package-root.js +10 -0
- package/dist/lib/path.d.ts +2 -0
- package/{lib/path.mjs → dist/lib/path.js} +1 -1
- package/dist/lib/platform.d.ts +26 -0
- package/{lib/platform.mjs → dist/lib/platform.js} +38 -35
- package/dist/lib/program-for.d.ts +5 -0
- package/dist/lib/program-for.js +23 -0
- package/dist/lib/pure-owner-helper.d.ts +10 -0
- package/dist/lib/pure-owner-helper.js +156 -0
- package/dist/lib/react.d.ts +16 -0
- package/dist/lib/react.js +22 -0
- package/dist/lib/redux-hook-origin.d.ts +4 -0
- package/dist/lib/redux-hook-origin.js +50 -0
- package/dist/lib/rule.d.ts +28 -0
- package/dist/lib/rule.js +5 -0
- package/dist/lib/scope.d.ts +88 -0
- package/{lib/scope.mjs → dist/lib/scope.js} +83 -93
- package/dist/lib/typed-class-values.d.ts +13 -0
- package/dist/lib/typed-class-values.js +92 -0
- package/dist/lib/types.d.ts +44 -0
- package/dist/lib/types.js +64 -0
- package/dist/lib/why.d.ts +350 -0
- package/dist/lib/why.js +350 -0
- package/dist/lint-escape-hatch.d.ts +24 -0
- package/{lint-escape-hatch.mjs → dist/lint-escape-hatch.js} +38 -45
- package/dist/lists.d.ts +31 -0
- package/dist/lists.js +202 -0
- package/dist/loading.d.ts +34 -0
- package/dist/loading.js +157 -0
- package/dist/naming.d.ts +33 -0
- package/dist/naming.js +176 -0
- package/dist/native-controls.d.ts +24 -0
- package/dist/native-controls.js +105 -0
- package/dist/next-conventions.d.ts +48 -0
- package/dist/next-conventions.js +452 -0
- package/dist/project-graph.d.ts +5 -0
- package/{project-graph.mjs → dist/project-graph.js} +8 -12
- package/dist/props-and-slots.d.ts +19 -0
- package/dist/props-and-slots.js +320 -0
- package/dist/served-locale.d.ts +38 -0
- package/dist/served-locale.js +263 -0
- package/dist/shape-slot.d.ts +18 -0
- package/dist/shape-slot.js +324 -0
- package/dist/size-and-state-budget.d.ts +46 -0
- package/dist/size-and-state-budget.js +424 -0
- package/dist/size-growth.d.ts +24 -0
- package/dist/size-growth.js +64 -0
- package/dist/sonar-parity.d.ts +5 -0
- package/{sonar-parity.mjs → dist/sonar-parity.js} +11 -7
- package/dist/surface-rules.d.ts +18 -0
- package/dist/surface-rules.js +491 -0
- package/dist/swr-family.d.ts +15 -0
- package/dist/swr-family.js +419 -0
- package/dist/the-split.d.ts +27 -0
- package/dist/the-split.js +157 -0
- package/dist/tokens.d.ts +9 -0
- package/dist/tokens.js +35 -0
- package/dist/translation.d.ts +51 -0
- package/dist/translation.js +403 -0
- package/dist/transport.d.ts +73 -0
- package/dist/transport.js +885 -0
- package/dist/type-safety.d.ts +43 -0
- package/dist/type-safety.js +139 -0
- package/dist/ui-rulings.d.ts +11 -0
- package/dist/ui-rulings.js +121 -0
- package/package.json +31 -9
- package/runtime/knowledge/hfs/canon-pins.yaml +22 -14
- package/runtime/scripts/lib/package-at.mjs +5 -5
- package/brand-values.mjs +0 -172
- package/button-intent.mjs +0 -320
- package/callback-shape.mjs +0 -332
- package/class-names.mjs +0 -149
- package/client-boundary.mjs +0 -400
- package/comments.mjs +0 -159
- package/control-height.mjs +0 -77
- package/drawing.mjs +0 -315
- package/env-owner.mjs +0 -244
- package/fe-stack.mjs +0 -86
- package/file-layout.mjs +0 -472
- package/formatting.mjs +0 -110
- package/function-budget.mjs +0 -49
- package/grammar-shim.mjs +0 -123
- package/hooks-folder.mjs +0 -300
- package/hygiene.mjs +0 -513
- package/index.mjs +0 -317
- package/lib/ast.mjs +0 -97
- package/lib/bindings.mjs +0 -159
- package/lib/class-text.mjs +0 -29
- package/lib/config.mjs +0 -142
- package/lib/fe-stack.mjs +0 -77
- package/lib/hfs.mjs +0 -49
- package/lib/next.mjs +0 -6
- package/lib/own-hook-origin.mjs +0 -53
- package/lib/program-for.mjs +0 -22
- package/lib/pure-owner-helper.mjs +0 -148
- package/lib/react.mjs +0 -28
- package/lib/redux-hook-origin.mjs +0 -44
- package/lib/typed-class-values.mjs +0 -81
- package/lib/types.mjs +0 -63
- package/lib/why.mjs +0 -352
- package/lists.mjs +0 -193
- package/loading.mjs +0 -156
- package/naming.mjs +0 -185
- package/native-controls.mjs +0 -108
- package/next-conventions.mjs +0 -447
- package/props-and-slots.mjs +0 -276
- package/served-locale.mjs +0 -243
- package/size-and-state-budget.mjs +0 -376
- package/size-growth.mjs +0 -62
- package/surface-rules.mjs +0 -443
- package/swr-family.mjs +0 -326
- package/the-split.mjs +0 -155
- package/tokens.mjs +0 -39
- package/translation.mjs +0 -392
- package/transport.mjs +0 -830
- package/type-safety.mjs +0 -147
- package/ui-rulings.mjs +0 -134
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/** Static class declarations in imported colocated modules, resolved by the consuming TypeScript program. */
|
|
2
|
-
import ts from "typescript"
|
|
3
|
-
|
|
4
|
-
/** The expression's literal fragments, following imported const/property declarations without executing code. */
|
|
5
|
-
const fragments = (checker, node, seen, mode) => {
|
|
6
|
-
if (!node || seen.has(node)) return []
|
|
7
|
-
seen.add(node)
|
|
8
|
-
if (ts.isStringLiteralLike(node) || ts.isTemplateHead(node) || ts.isTemplateMiddle(node) || ts.isTemplateTail(node)) return mode === "style-keys" ? [] : [node.text]
|
|
9
|
-
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node) || ts.isSatisfiesExpression(node) || ts.isParenthesizedExpression(node) || ts.isNonNullExpression(node)) return fragments(checker, node.expression, seen, mode)
|
|
10
|
-
if (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
|
|
11
|
-
const symbol = ts.isElementAccessExpression(node) && ts.isStringLiteralLike(node.argumentExpression)
|
|
12
|
-
? checker.getTypeAtLocation(node.expression).getProperty(node.argumentExpression.text)
|
|
13
|
-
: checker.getSymbolAtLocation(ts.isPropertyAccessExpression(node) ? node.name : node)
|
|
14
|
-
return symbolFragments(checker, symbol, seen, mode)
|
|
15
|
-
}
|
|
16
|
-
if (mode === "style-keys" && ts.isObjectLiteralExpression(node)) {
|
|
17
|
-
return node.properties.flatMap((property) => {
|
|
18
|
-
if (ts.isSpreadAssignment(property)) return fragments(checker, property.expression, seen, mode)
|
|
19
|
-
if (!ts.isPropertyAssignment(property) && !ts.isShorthandPropertyAssignment(property)) return []
|
|
20
|
-
return ts.isComputedPropertyName(property.name)
|
|
21
|
-
? fragments(checker, property.name.expression, seen, "variant")
|
|
22
|
-
: [property.name.text]
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
return fragmentChildren(node, mode).flatMap((child) => fragments(checker, child, seen, mode))
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** An object binding reads only its selected property's symbol, including aliases and nested patterns. */
|
|
29
|
-
const bindingProperty = (checker, declaration) => {
|
|
30
|
-
const source = declaration.parent.parent
|
|
31
|
-
const owner = ts.isBindingElement(source) ? bindingProperty(checker, source) : null
|
|
32
|
-
const type = owner ? checker.getTypeOfSymbolAtLocation(owner, source) : source.initializer ? checker.getTypeAtLocation(source.initializer) : null
|
|
33
|
-
const name = declaration.propertyName ?? declaration.name
|
|
34
|
-
return ts.isIdentifier(name) || ts.isStringLiteralLike(name) ? type?.getProperty(name.text) : null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Alias and destructuring symbols retain their declaration values without reading sibling properties. */
|
|
38
|
-
const symbolFragments = (checker, input, seen, mode) => {
|
|
39
|
-
let symbol = input
|
|
40
|
-
if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) symbol = checker.getAliasedSymbol(symbol)
|
|
41
|
-
return (symbol?.declarations ?? []).flatMap((declaration) => {
|
|
42
|
-
const selected = ts.isBindingElement(declaration) ? bindingProperty(checker, declaration) : null
|
|
43
|
-
return selected ? symbolFragments(checker, selected, seen, mode) : fragments(checker, declaration.initializer, seen, mode)
|
|
44
|
-
})
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Class-producing positions inside a declaration, including cn dictionaries and conditional recipes. */
|
|
48
|
-
const fragmentChildren = (node, mode) => {
|
|
49
|
-
if (ts.isCallExpression(node)) return mode === "classes" ? node.arguments : []
|
|
50
|
-
if (ts.isArrayLiteralExpression(node)) return node.elements
|
|
51
|
-
if (ts.isConditionalExpression(node)) return [node.whenTrue, node.whenFalse]
|
|
52
|
-
if (ts.isBinaryExpression(node)) {
|
|
53
|
-
if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) return [node.right]
|
|
54
|
-
return [ts.SyntaxKind.BarBarToken, ts.SyntaxKind.QuestionQuestionToken].includes(node.operatorToken.kind) ? [node.left, node.right] : []
|
|
55
|
-
}
|
|
56
|
-
if (ts.isObjectLiteralExpression(node)) return node.properties.flatMap((property) => ts.isPropertyAssignment(property) ? [property.name, property.initializer] : [])
|
|
57
|
-
if (ts.isTemplateExpression(node) && mode === "classes") return [node.head, ...node.templateSpans.flatMap((span) => [span.expression, span.literal])]
|
|
58
|
-
return []
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Imported class values are reported at their use in the linted file, while their text comes from the typed declaration.
|
|
63
|
-
* Syntax-only rule consumers have no imported-file program and retain same-file inspection.
|
|
64
|
-
*/
|
|
65
|
-
export const typedClassStrings = (context, node, mode) => {
|
|
66
|
-
const services = context.sourceCode.parserServices
|
|
67
|
-
if (!services?.program || !services.esTreeNodeToTSNodeMap) return []
|
|
68
|
-
const source = services.esTreeNodeToTSNodeMap.get(node)
|
|
69
|
-
const checker = services.program.getTypeChecker()
|
|
70
|
-
return fragments(checker, source, new Set(), mode).map((text) => ({ node, text }))
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Known imported props spreads expose the same static property values as named JSX attributes. */
|
|
74
|
-
export const typedPropertyStrings = (context, node, key, mode) => {
|
|
75
|
-
const services = context.sourceCode.parserServices
|
|
76
|
-
if (!services?.program || !services.esTreeNodeToTSNodeMap) return []
|
|
77
|
-
const source = services.esTreeNodeToTSNodeMap.get(node)
|
|
78
|
-
const checker = services.program.getTypeChecker()
|
|
79
|
-
const symbol = checker.getTypeAtLocation(source).getProperty(key)
|
|
80
|
-
return symbolFragments(checker, symbol, new Set(), mode).map((text) => ({ node, text }))
|
|
81
|
-
}
|
package/lib/types.mjs
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type-aware helpers of the front-end canon: a rule asks what a node IS (its TypeScript type, the symbol an identifier
|
|
3
|
-
* resolves to, the module a declaration comes from), never what it is called. `starciFeConfig` turns typed linting on for
|
|
4
|
-
* the source tree (`parserOptions.projectService`), so these helpers refuse to run without the program instead of falling
|
|
5
|
-
* back to a name match.
|
|
6
|
-
*/
|
|
7
|
-
import ts from "typescript"
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* The TypeScript services of the file being linted.
|
|
11
|
-
*
|
|
12
|
-
* @param {object} context - The ESLint rule context.
|
|
13
|
-
* @returns {{ program: object, checker: object, toTs: (node: object) => object }} The program, its checker and the ESTree to TS node map.
|
|
14
|
-
*/
|
|
15
|
-
export const typed = (context) => {
|
|
16
|
-
const services = context.sourceCode?.parserServices ?? context.parserServices
|
|
17
|
-
if (!services?.program || !services.esTreeNodeToTSNodeMap) {
|
|
18
|
-
throw new Error(`${context.id ?? "a starci-fe rule"} needs typed linting (parserOptions.projectService); lint through starciFeConfig, which turns it on`)
|
|
19
|
-
}
|
|
20
|
-
const program = services.program
|
|
21
|
-
return { program, checker: program.getTypeChecker(), toTs: (node) => services.esTreeNodeToTSNodeMap.get(node) }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* True when the type of an expression is a promise (a thenable carrying a `then` method), after union members are read.
|
|
26
|
-
*
|
|
27
|
-
* @param {object} context - The ESLint rule context.
|
|
28
|
-
* @param {object} node - An ESTree expression.
|
|
29
|
-
* @returns {boolean} Whether evaluating the node yields a promise.
|
|
30
|
-
*/
|
|
31
|
-
export const isPromiseValued = (context, node) => {
|
|
32
|
-
const { checker, toTs } = typed(context)
|
|
33
|
-
const tsNode = toTs(node)
|
|
34
|
-
if (!tsNode) return false
|
|
35
|
-
const type = checker.getTypeAtLocation(tsNode)
|
|
36
|
-
const parts = type.isUnion?.() ? type.types : [type]
|
|
37
|
-
return parts.some((part) => Boolean(part.getProperty?.("then")))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* The declarations the symbol of an identifier resolves to, after import aliases are followed.
|
|
42
|
-
*
|
|
43
|
-
* @param {object} context - The ESLint rule context.
|
|
44
|
-
* @param {object} identifier - An ESTree identifier (or any node with a symbol).
|
|
45
|
-
* @returns {Array<object>} The TypeScript declarations (empty when the checker has no symbol).
|
|
46
|
-
*/
|
|
47
|
-
export const declarationsOf = (context, identifier) => {
|
|
48
|
-
const { checker, toTs } = typed(context)
|
|
49
|
-
const tsNode = toTs(identifier)
|
|
50
|
-
if (!tsNode) return []
|
|
51
|
-
let symbol = checker.getSymbolAtLocation(tsNode)
|
|
52
|
-
if (!symbol) return []
|
|
53
|
-
if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) symbol = checker.getAliasedSymbol(symbol)
|
|
54
|
-
return symbol.getDeclarations?.() ?? []
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* The forward-slash file name a declaration lives in.
|
|
59
|
-
*
|
|
60
|
-
* @param {object} declaration - A TypeScript declaration.
|
|
61
|
-
* @returns {string} Its source file name.
|
|
62
|
-
*/
|
|
63
|
-
export const fileOf = (declaration) => String(declaration.getSourceFile().fileName).split("\\").join("/")
|
package/lib/why.mjs
DELETED
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The "why" of each rule the HFS catalogue assigns to this canon.
|
|
3
|
-
*
|
|
4
|
-
* WHERE IT IS SURFACED. An ESLint message is written for the developer at the terminal, in English,
|
|
5
|
-
* and says what is wrong in the vocabulary of the rule. The agent that reads a failed land gate needs
|
|
6
|
-
* the catalogue's sentence instead: the finding code (the key in `modules/kernel/failure-codes.yaml`)
|
|
7
|
-
* and a headline and next step it can quote to the owner. This map is the canon's half of that
|
|
8
|
-
* contract: `code` is the finding code, `en` the English source of the headline with `<file>` /
|
|
9
|
-
* `<what>` placeholders the reader fills from the ESLint location, `fix` the one sentence "what do I
|
|
10
|
-
* do now". The owner reads both in Vietnamese through the declared message catalog: the `en` and
|
|
11
|
-
* `fix` strings are the keys of `modules/i18n/messages/canon.yaml`, so `translator('vi')(entry.en)` and
|
|
12
|
-
* `translator('vi')(entry.fix)` hand back the Vietnamese of the same sentence.
|
|
13
|
-
*
|
|
14
|
-
* Every rule that carries a catalogue id (R18, R22, R49-R59, R60-R62, R65, the fe-stack catalog) has an entry;
|
|
15
|
-
* the twin test refuses a rule of those laws with no entry and an entry for a rule that does not exist.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/** @typedef {{ code: string, en: string, fix: string }} Why */
|
|
19
|
-
|
|
20
|
-
/** @type {Record<string, Why>} */
|
|
21
|
-
export const why = {
|
|
22
|
-
"selection-fill-no-check": {
|
|
23
|
-
code: "FE_NESTED_VARIANT",
|
|
24
|
-
en: "`<file>` adds a check indicator to a navigation-like single-select list.",
|
|
25
|
-
fix: 'Set selectionIndicator="none" and let the row fill carry selection.',
|
|
26
|
-
},
|
|
27
|
-
"no-grammar-internal-selector": {
|
|
28
|
-
code: "FE_GRAMMAR_LAYOUT_ONLY",
|
|
29
|
-
en: "`<file>` targets a Grammar internal class from application styles.",
|
|
30
|
-
fix: "Use the Grammar public props and slots; keep internal selectors inside Grammar.",
|
|
31
|
-
},
|
|
32
|
-
"divider-full-bleed": {
|
|
33
|
-
code: "FE_DIVIDER_FULL_BLEED",
|
|
34
|
-
en: "`<file>` insets a horizontal Grammar divider with wrapper padding.",
|
|
35
|
-
fix: "Move horizontal padding to the sibling content bands so the divider reaches both surface edges.",
|
|
36
|
-
},
|
|
37
|
-
"button-intent-required": {
|
|
38
|
-
code: "FE_BUTTON_INTENT_REQUIRED",
|
|
39
|
-
en: "`<file>` renders a Button or IconButton without the command's intent.",
|
|
40
|
-
fix: "Declare intent from the command's consequence and rank so Grammar supplies its variant.",
|
|
41
|
-
},
|
|
42
|
-
"control-height": {
|
|
43
|
-
code: "FE_CONTROL_HEIGHT",
|
|
44
|
-
en: "`<file>` picks a larger control size or mixes control sizes in one row.",
|
|
45
|
-
fix: "Omit size so Grammar draws the one 40px control height; use sm only for every control of an item or dense toolbar row, and lg only for a landing hero call to action.",
|
|
46
|
-
},
|
|
47
|
-
"no-grammar-shim": {
|
|
48
|
-
code: "FE_GRAMMAR_SHIM",
|
|
49
|
-
en: "`<file>` re-exports a Grammar component under the app's own name.",
|
|
50
|
-
fix: "Delete the shim and import the component from the Grammar where it is rendered; report the Grammar defect the shim worked around (the op files blocked) so the Grammar fixes it.",
|
|
51
|
-
},
|
|
52
|
-
"button-no-variant": {
|
|
53
|
-
code: "FE_BUTTON_NO_VARIANT",
|
|
54
|
-
en: "`<file>` chooses a Button or IconButton variant directly.",
|
|
55
|
-
fix: "Replace variant with intent and let Grammar resolve placement and visual treatment.",
|
|
56
|
-
},
|
|
57
|
-
"destructive-confirm-in-dialog": {
|
|
58
|
-
code: "FE_DESTRUCTIVE_CONFIRM_IN_DIALOG",
|
|
59
|
-
en: "`<file>` renders a final destructive press outside its confirmation dialog.",
|
|
60
|
-
fix: "Place the destructive confirm inside the same-file AlertDialog or ConfirmDialog; use entry on its trigger.",
|
|
61
|
-
},
|
|
62
|
-
"single-commit-per-render": {
|
|
63
|
-
code: "FE_SINGLE_COMMIT_PER_RENDER",
|
|
64
|
-
en: "`<file>` renders more than one commit in the same component render path.",
|
|
65
|
-
fix: "Keep the next action as commit and assign the other commands their consequence-based intents.",
|
|
66
|
-
},
|
|
67
|
-
"no-commit-in-collection-item": {
|
|
68
|
-
code: "FE_NO_COMMIT_IN_COLLECTION_ITEM",
|
|
69
|
-
en: "`<file>` repeats a commit inside a collection item render.",
|
|
70
|
-
fix: "Use alternative for the item's next action and keep the enclosing view's commit unique.",
|
|
71
|
-
},
|
|
72
|
-
"nested-control-variant": {
|
|
73
|
-
code: "FE_NESTED_VARIANT",
|
|
74
|
-
en: "`<file>` sets a static Grammar field variant, or forces primary Tabs inside a painted surface.",
|
|
75
|
-
fix: "Omit the field or nested Tabs variant so Grammar supplies the default from its surface context.",
|
|
76
|
-
},
|
|
77
|
-
"no-app-surface-paint": {
|
|
78
|
-
code: "FE_GRAMMAR_LAYOUT_ONLY",
|
|
79
|
-
en: "`<file>` sets paint or typography in app CSS instead of composing Grammar's published components and props.",
|
|
80
|
-
fix: "Keep app CSS layout-only and compose a painted Grammar surface such as SurfaceCard.",
|
|
81
|
-
},
|
|
82
|
-
"no-list-panel-padding": {
|
|
83
|
-
code: "FE_LIST_PANEL_PADDING",
|
|
84
|
-
en: "`<file>` adds horizontal wrapper padding to a Grammar list or its section caption, stacking the content inset.",
|
|
85
|
-
fix: "Remove the wrapper padding and use ListBox inset=\"panel\" so headers, captions and rows share the panel content edge.",
|
|
86
|
-
},
|
|
87
|
-
"no-env-outside-config": {
|
|
88
|
-
code: "FE_ENV_OWNER",
|
|
89
|
-
en: "`<file>` reads an environment variable. Only `modules/config` may read env.",
|
|
90
|
-
fix: "Read the value from a typed export of `modules/config` instead of `process.env`.",
|
|
91
|
-
},
|
|
92
|
-
"no-hardcoded-endpoint-fallback": {
|
|
93
|
-
code: "FE_ENV_OWNER",
|
|
94
|
-
en: "`<file>` carries a hardcoded fallback for an address or a secret (`?? \"http://localhost…\"`). A variable missing in production must throw at config load, not silently point at a dev machine.",
|
|
95
|
-
fix: "Delete the fallback; let `modules/config` throw when a variable is missing in production.",
|
|
96
|
-
},
|
|
97
|
-
"fetch-only-in-api-client": {
|
|
98
|
-
code: "FE_TRANSPORT_OWNER",
|
|
99
|
-
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`).",
|
|
100
|
-
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`.",
|
|
101
|
-
},
|
|
102
|
-
"client-fetch-has-signal": {
|
|
103
|
-
code: "FE_TRANSPORT_OWNER",
|
|
104
|
-
en: "`fetch` in `<file>` has no `signal`. Every request of the API layer must carry a timeout and an `AbortSignal`.",
|
|
105
|
-
fix: "Pass `signal` (`AbortSignal.timeout(...)` combined with the caller's signal).",
|
|
106
|
-
},
|
|
107
|
-
"no-shared-transport-state": {
|
|
108
|
-
code: "FE_TRANSPORT_OWNER",
|
|
109
|
-
en: "`<file>` holds shared state as `let`/`var` in the API layer. Token and locale must not sit in a singleton.",
|
|
110
|
-
fix: "Pass credential and locale as parameters or through context.",
|
|
111
|
-
},
|
|
112
|
-
"client-maps-auth-to-refused": {
|
|
113
|
-
code: "FE_HTTP_STATUS_COLLAPSE",
|
|
114
|
-
en: "The client in `<file>` has no branch comparing `response.status` with 401 and 403 and returning `{ kind: \"refused\" }`, so the \"sign-in required\" state is never reached.",
|
|
115
|
-
fix: "Add a `response.status === 401 || response.status === 403` branch returning `{ ok: false, kind: \"refused\" }` right inside the client.",
|
|
116
|
-
},
|
|
117
|
-
"no-failure-collapse": {
|
|
118
|
-
code: "FE_HTTP_STATUS_COLLAPSE",
|
|
119
|
-
en: "`<file>` merges every failure (an error HTTP response or a failed `Outcome`) into one branch or one empty value, so the reason is lost.",
|
|
120
|
-
fix: "Keep the reason: for an `Outcome` branch on the reason code; for an HTTP response turn the status code into an `Outcome` (`refused` for 401/403, `not-found`, `invalid`, `unavailable`). Do not use the server's raw error as the reason.",
|
|
121
|
-
},
|
|
122
|
-
"no-hand-typed-wire": {
|
|
123
|
-
code: "FE_WIRE_GENERATED",
|
|
124
|
-
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/`.",
|
|
125
|
-
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`.",
|
|
126
|
-
},
|
|
127
|
-
"use-client-only-at-boundary": {
|
|
128
|
-
code: "FE_CLIENT_BOUNDARY",
|
|
129
|
-
en: "`\"use client\"` in `<file>` (`<slot>` may not be a client component).",
|
|
130
|
-
fix: "Put `\"use client\"` on the `index.tsx` of an interactive block, a leaf, or `error.tsx`/`global-error.tsx`; keep layout and page as server components.",
|
|
131
|
-
},
|
|
132
|
-
"hooks-folder-holds-hooks-only": {
|
|
133
|
-
code: "FE_HOOKS_ARE_HOOKS",
|
|
134
|
-
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`.",
|
|
135
|
-
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`.",
|
|
136
|
-
},
|
|
137
|
-
"no-middleware-file": {
|
|
138
|
-
code: "FE_NEXT_CONVENTIONS",
|
|
139
|
-
en: "`<file>` uses the name `middleware`. From Next 16 the request gate is `proxy.ts` exporting `proxy`.",
|
|
140
|
-
fix: "Rename the file to `proxy.ts` and rename the export to `proxy`.",
|
|
141
|
-
},
|
|
142
|
-
"locale-segment-is-locale": {
|
|
143
|
-
code: "FE_I18N_PLACEMENT",
|
|
144
|
-
en: "File `<file>` sits under a locale segment that is not `[locale]`. There is one pattern: `next-intl`, `[locale]`, default `vi`.",
|
|
145
|
-
fix: "Rename the segment directory to `[locale]`.",
|
|
146
|
-
},
|
|
147
|
-
"no-second-i18n-stack": {
|
|
148
|
-
code: "FE_I18N_PLACEMENT",
|
|
149
|
-
en: "`<file>` imports a second i18n library. The app uses only `next-intl` through `modules/i18n`.",
|
|
150
|
-
fix: "Switch to `next-intl`; delete the other library.",
|
|
151
|
-
},
|
|
152
|
-
"html-lang-from-locale": {
|
|
153
|
-
code: "FE_I18N_LITERAL",
|
|
154
|
-
en: "`<html lang>` in `<file>` is a hardcoded string. The `lang` attribute must come from the `[locale]` segment.",
|
|
155
|
-
fix: "Use `lang={locale}` from the parameter of the `[locale]` layout.",
|
|
156
|
-
},
|
|
157
|
-
"no-hardcoded-copy": {
|
|
158
|
-
code: "FE_I18N_LITERAL",
|
|
159
|
-
en: "The text `\"<text>\"` in `<file>:<line>` does not go through `t()`.",
|
|
160
|
-
fix: "Move the sentence into `modules/i18n/messages/<locale>.json` and read it with `t(\"key\")`; no exceptions.",
|
|
161
|
-
},
|
|
162
|
-
"no-raw-brand-value": {
|
|
163
|
-
code: "FE_STYLE_TOKEN_ONLY",
|
|
164
|
-
en: "`<file>` uses a raw colour/length value (`<what>`). Colour and spacing come only from grammar tokens; brand colour lives only in `brand.css`.",
|
|
165
|
-
fix: "Use a grammar token class or variable; a colour value is declared only in `modules/brand/brand.css`.",
|
|
166
|
-
},
|
|
167
|
-
"no-native-form-control": {
|
|
168
|
-
code: "FE_NATIVE_FORM_CONTROL",
|
|
169
|
-
en: "Raw `<tag>` in `<file>`. Use the grammar renderer.",
|
|
170
|
-
fix: "Replace it with the grammar component; if none exists yet, add it to grammar instead of drawing it in place.",
|
|
171
|
-
},
|
|
172
|
-
"component-line-budget": {
|
|
173
|
-
code: "FE_SIZE_AND_STATE_BUDGET",
|
|
174
|
-
en: "`<file>` is `<n>` lines long, over the 300-line budget of a component.",
|
|
175
|
-
fix: "Split the drawing part from the data part and each area into its own unit.",
|
|
176
|
-
},
|
|
177
|
-
"unit-hook-budget": {
|
|
178
|
-
code: "FE_SIZE_AND_STATE_BUDGET",
|
|
179
|
-
en: "`<unit>` has `<n>` data hooks / `<m>` useState, over the budget of 6.",
|
|
180
|
-
fix: "Fold state that changes together into a reducer or a dedicated hook; split each data area into its own connection block.",
|
|
181
|
-
},
|
|
182
|
-
"no-hand-rolled-polling": {
|
|
183
|
-
code: "FE_SIZE_AND_STATE_BUDGET",
|
|
184
|
-
en: "`<unit>` hand-writes a poll loop (`setInterval` or a self-rescheduling `setTimeout`).",
|
|
185
|
-
fix: "Refresh through a data hook (`refreshInterval`) or a socket, one mechanism per resource.",
|
|
186
|
-
},
|
|
187
|
-
"no-inline-lint-config": {
|
|
188
|
-
code: "HFS_INLINE_SUPPRESSION",
|
|
189
|
-
en: "A comment disabling a rule sits at `<file>:<line>`. HFS does not allow in-place suppression - fix the code, or propose a rule change.",
|
|
190
|
-
fix: "Remove `eslint-disable`, `@ts-ignore` or `@ts-expect-error` and fix the cause.",
|
|
191
|
-
},
|
|
192
|
-
"no-double-cast": {
|
|
193
|
-
code: "FE_TYPE_ESCAPE",
|
|
194
|
-
en: "`<file>` casts through `unknown` (`as unknown as T`): the compiler forgets everything it knows right where the data enters.",
|
|
195
|
-
fix: "Narrow from `unknown` with a type guard or a parser; do not cast.",
|
|
196
|
-
},
|
|
197
|
-
"no-type-assertion": {
|
|
198
|
-
code: "FE_TYPE_ESCAPE",
|
|
199
|
-
en: "`<file>` uses `as T` or `<T>x`: an assertion the compiler cannot check. If it is wrong, the error lands in the user's browser.",
|
|
200
|
-
fix: "Narrow with a type guard, `in`, a discriminant or a parser where the data enters; use `satisfies` when you only mean to check a literal.",
|
|
201
|
-
},
|
|
202
|
-
"no-non-null-assertion": {
|
|
203
|
-
code: "FE_TYPE_ESCAPE",
|
|
204
|
-
en: "`<file>` uses `x!`: asserting a value is present without proving it.",
|
|
205
|
-
fix: "Handle the absent branch (`if`, `??`, an early return) or fix the type so the value cannot be absent.",
|
|
206
|
-
},
|
|
207
|
-
"no-explicit-any": {
|
|
208
|
-
code: "FE_TYPE_ESCAPE",
|
|
209
|
-
en: "`<file>` uses `any`: it switches off type checking for that value and for everything inferred from it.",
|
|
210
|
-
fix: "Use a real type, a generic, or `unknown` narrowed at the use site.",
|
|
211
|
-
},
|
|
212
|
-
"list-item-has-key": {
|
|
213
|
-
code: "FE_LIST_KEY",
|
|
214
|
-
en: "An element returned from `.map` in `<file>` has no `key`, so React identifies the row by position and the row loses state on delete or reorder.",
|
|
215
|
-
fix: "Add a `key` taken from the data's id; for a fragment use `<Fragment key={...}>`.",
|
|
216
|
-
},
|
|
217
|
-
"no-index-key": {
|
|
218
|
-
code: "FE_LIST_KEY",
|
|
219
|
-
en: "A `key` in `<file>` comes from the `.map` index or a randomly generated value; an index is a position, not an identity.",
|
|
220
|
-
fix: "Use the id the data carries (`key={row.id}`); never the index, `Math.random`, `Date.now`.",
|
|
221
|
-
},
|
|
222
|
-
"no-inline-literal-prop-in-list": {
|
|
223
|
-
code: "FE_LIST_KEY",
|
|
224
|
-
en: "A component in a `.map` in `<file>` receives an object or array literal as a prop: every row gets a new value every render, so memo never hits.",
|
|
225
|
-
fix: "Hoist the constant outside the component or build it once before `.map`.",
|
|
226
|
-
},
|
|
227
|
-
"effect-subscription-needs-cleanup": {
|
|
228
|
-
code: "FE_EFFECT_CLEANUP",
|
|
229
|
-
en: "`<what>` in `<file>` (timer, frame, listener, observer, socket or subscription) is started by an effect or `subscribe` but the returned cleanup does not release that same handle/target/listener.",
|
|
230
|
-
fix: "Keep the handle and return a cleanup calling `clearTimeout(id)`, `cancelAnimationFrame(id)`, `removeEventListener` with the same type and listener, `.disconnect()`, `.close()` or `.unsubscribe()` on the right object.",
|
|
231
|
-
},
|
|
232
|
-
"no-data-fetch-in-effect": {
|
|
233
|
-
code: "FE_EFFECT_FETCH",
|
|
234
|
-
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.",
|
|
235
|
-
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).",
|
|
236
|
-
},
|
|
237
|
-
"no-empty-catch": {
|
|
238
|
-
code: "FE_SWALLOWED_ERROR",
|
|
239
|
-
en: "A `catch` or `.catch` in `<file>` does nothing: the error vanishes, no notice, no trace.",
|
|
240
|
-
fix: "Return a typed outcome carrying the cause, show an error state, or rethrow.",
|
|
241
|
-
},
|
|
242
|
-
"no-console": {
|
|
243
|
-
code: "FE_CONSOLE_CALL",
|
|
244
|
-
en: "`console.<method>` in `<file>`: it reaches no log pipeline and leaks internal detail to the user.",
|
|
245
|
-
fix: "Return a typed outcome, show an error state, or let the error reach `error.tsx`.",
|
|
246
|
-
},
|
|
247
|
-
"page-exports-metadata": {
|
|
248
|
-
code: "FE_PAGE_METADATA_MISSING",
|
|
249
|
-
en: "`<file>` is `page.tsx` but exports no `metadata` or `generateMetadata`; every page in the branch carries the same title.",
|
|
250
|
-
fix: "Export `metadata` (or `generateMetadata` when the title comes from data) with a title and description taken from the message catalog.",
|
|
251
|
-
},
|
|
252
|
-
"no-null-suspense-fallback": {
|
|
253
|
-
code: "FE_SUSPENSE_NULL_FALLBACK",
|
|
254
|
-
en: "`<Suspense>` in `<file>` has an empty `fallback`: the user sees a blank instead of a loading state.",
|
|
255
|
-
fix: "Pass the skeleton (loading state) of the very thing loading as the `fallback`.",
|
|
256
|
-
},
|
|
257
|
-
"navigation-from-intl": {
|
|
258
|
-
code: "FE_I18N_NAVIGATION",
|
|
259
|
-
en: "`<file>` imports `Link`, `useRouter`, `usePathname` or `redirect` from Next, which does not know the locale: the path loses the `[locale]` prefix.",
|
|
260
|
-
fix: "Import from the locale-aware public routes entry or shared i18n package, built from the one routing configuration.",
|
|
261
|
-
},
|
|
262
|
-
"no-native-anchor": {
|
|
263
|
-
code: "FE_I18N_NAVIGATION",
|
|
264
|
-
en: "`<a>` in `<file>` points at an internal route (reloads the whole page, loses the locale, loses prefetch) or opens a new tab without `rel`.",
|
|
265
|
-
fix: "Use the locale-aware Link with an href built by modules/routes; a _blank link adds rel=\"noopener noreferrer\".",
|
|
266
|
-
},
|
|
267
|
-
"use-intl-formatter": {
|
|
268
|
-
code: "FE_I18N_FORMATTER",
|
|
269
|
-
en: "`<file>` formats a number, money or a date with `toLocale*String`, `new Intl.*`, `toFixed`, a currency sign pasted into a template or a date library, instead of the next-intl formatter.",
|
|
270
|
-
fix: "Use `useFormatter()` (or `getFormatter()` on the server): `number(...)`, `dateTime(...)`, `relativeTime(...)`.",
|
|
271
|
-
},
|
|
272
|
-
"no-hardcoded-route": {
|
|
273
|
-
code: "FE_ROUTE_HARDCODED",
|
|
274
|
-
en: "`<file>` writes a route path inline at the call site; when the route moves, the copy points at a 404.",
|
|
275
|
-
fix: "Build the href with a function of `modules/routes` and use that same function everywhere.",
|
|
276
|
-
},
|
|
277
|
-
"client-no-server-import": {
|
|
278
|
-
code: "FE_CLIENT_SERVER_IMPORT",
|
|
279
|
-
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).",
|
|
280
|
-
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.",
|
|
281
|
-
},
|
|
282
|
-
"server-module-marks-server-only": {
|
|
283
|
-
code: "FE_SERVER_ONLY_MARK",
|
|
284
|
-
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\"`.",
|
|
285
|
-
fix: "Make `import \"server-only\"` the file's first statement; route files (`page`, `layout`, `route`, `proxy`) are server components already and need none.",
|
|
286
|
-
},
|
|
287
|
-
"no-server-action": {
|
|
288
|
-
code: "FE_SERVER_ACTION_FORBIDDEN",
|
|
289
|
-
en: "`<file>` carries a `\"use server\"` directive: a Server Action, an endpoint the front end serves. The front end serves no API.",
|
|
290
|
-
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`.",
|
|
291
|
-
},
|
|
292
|
-
"web-storage-only-in-modules": {
|
|
293
|
-
code: "FE_STORAGE_OUTSIDE_MODULES",
|
|
294
|
-
en: "`<file>` uses `localStorage`/`sessionStorage` outside `modules/`; storage does not exist on the server and throws when over quota.",
|
|
295
|
-
fix: "Put the read/write behind a hook or a module in `modules/` with a guard (`typeof window`, try/catch) and call it from the block.",
|
|
296
|
-
},
|
|
297
|
-
"no-dangerous-html": {
|
|
298
|
-
code: "FE_DANGEROUS_HTML",
|
|
299
|
-
en: "`dangerouslySetInnerHTML` on `<tag>` in `<file>`: any string becomes runnable HTML.",
|
|
300
|
-
fix: "Render content as elements; only a `<script>` carrying JSON-LD or theme code from an app constant may use it.",
|
|
301
|
-
},
|
|
302
|
-
"response-cookie-attributes": {
|
|
303
|
-
code: "FE_COOKIE_ATTRIBUTES",
|
|
304
|
-
en: "`<file>` writes a cookie through the Next response cookie without stating fixed `httpOnly`, missing `secure` or a `sameSite` that is not `lax`/`strict`.",
|
|
305
|
-
fix: "Pass one `as const` options constant of the module owning the cookie: `httpOnly: true` (`false` only for a cookie an optional script must read), `secure` from `modules/config`, `sameSite: \"lax\"` or `\"strict\"`.",
|
|
306
|
-
},
|
|
307
|
-
"props-fields-readonly": {
|
|
308
|
-
code: "FE_PROPS_MUTABLE",
|
|
309
|
-
en: "A props type of a component in `<file>` has a field or collection that is not `readonly`, so the component could write into what it is passed.",
|
|
310
|
-
fix: "Mark every field and index signature `readonly`, and write collections as `readonly T[]`, `readonly [A, B]` or `ReadonlyArray<T>`.",
|
|
311
|
-
},
|
|
312
|
-
"no-native-img": {
|
|
313
|
-
code: "FE_NATIVE_IMAGE",
|
|
314
|
-
en: "Raw `<img>` in `<file>`: it loads the original image and holds no space, so the page jumps when the image arrives.",
|
|
315
|
-
fix: "Use `Image` of `next/image` with `width` and `height` (or `fill` and `sizes`) and an `alt` from the catalog.",
|
|
316
|
-
},
|
|
317
|
-
"image-has-size": {
|
|
318
|
-
code: "FE_NATIVE_IMAGE",
|
|
319
|
-
en: "`Image` in `<file>` lacks `width` and `height` (or has `fill` without `sizes`): the browser cannot hold the space.",
|
|
320
|
-
fix: "Give both `width` and `height`, or `fill` inside a sized frame with `sizes`.",
|
|
321
|
-
},
|
|
322
|
-
"outcome-kinds-exhaustive": {
|
|
323
|
-
code: "FE_OUTCOME_KIND_UNHANDLED",
|
|
324
|
-
en: "A `switch` on `kind` in `<file>` has an `ok` branch but misses one of refused, invalid, not-found, unavailable.",
|
|
325
|
-
fix: "Write all five branches of `Outcome<T>`, one screen each; do not lean on `default`.",
|
|
326
|
-
},
|
|
327
|
-
"one-outcome-union": {
|
|
328
|
-
code: "FE_HTTP_STATUS_COLLAPSE",
|
|
329
|
-
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>`.",
|
|
330
|
-
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.",
|
|
331
|
-
},
|
|
332
|
-
"i18n-stack-in-one-module": {
|
|
333
|
-
code: "FE_I18N_PLACEMENT",
|
|
334
|
-
en: "`<file>` builds a layer of the next-intl stack itself (`defineRouting`, `createNavigation`, `getRequestConfig`, `createMiddleware`). The stack is written once for the whole repo.",
|
|
335
|
-
fix: "Use the shared i18n factory; a single app keeps routing/request config in modules/i18n and navigation in modules/routes. The proxy imports routing without navigation.",
|
|
336
|
-
},
|
|
337
|
-
"no-app-relative-clock": {
|
|
338
|
-
code: "FE_SIZE_AND_STATE_BUDGET",
|
|
339
|
-
en: "`<file>` starts a recurring timer that reads the wall clock for live time labels.",
|
|
340
|
-
fix: "Render Grammar RelativeTime with the target dateTime, locale and mode; its shared ticker owns live relative time and countdown updates.",
|
|
341
|
-
},
|
|
342
|
-
"fe-stack-import": {
|
|
343
|
-
code: "FE_STACK_FORBIDDEN",
|
|
344
|
-
en: "`<file>` imports `<what>`, a package or scoped package family the front-end stack forbids, or a Supabase package outside `modules/auth`.",
|
|
345
|
-
fix: "Use the concern's canonical library from `knowledge/hfs/fe-stack.yaml` (the message names it). For @dicebear/*, pass the identity id as seed on Grammar Avatar, AvatarButton or AvatarGroup members. Supabase on the front end is the auth client in `modules/auth` only, data goes through `modules/api`.",
|
|
346
|
-
},
|
|
347
|
-
"file-size-growth": {
|
|
348
|
-
code: "HFS_SIZE_GROWTH",
|
|
349
|
-
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.",
|
|
350
|
-
fix: "Split the new part into its own file by responsibility; a file already over budget may only stay or shrink.",
|
|
351
|
-
},
|
|
352
|
-
}
|