@starci/eslint-grammar-core 0.16.0 → 0.16.2
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 +9 -0
- package/README.md +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/why.js +5 -0
- package/dist/surface-depth.d.ts +7 -0
- package/dist/surface-depth.js +92 -0
- package/docs/surface-depth.md +40 -0
- package/package.json +2 -2
- package/runtime/knowledge/hfs/canon-pins.yaml +6 -6
- package/runtime/knowledge/hfs/rules.yaml +23 -0
- package/runtime/knowledge/patterns/fe/ui.yaml +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.2 - 2026-10-31
|
|
4
|
+
|
|
5
|
+
- Changed: the peer moves to eslint-canon-fe 9.6.2; the bundled canon pins name @starci/grammar 0.16.2 (the horizontal Stepper connector line; SurfaceListCard takes its depth from the surface context).
|
|
6
|
+
- Added: starci-grammar/no-raised-surface-in-surface (R340, FE_RAISED_SURFACE_IN_SURFACE): a depth="top" forced on a SurfaceListCard that a SurfaceCard, SurfaceListCard or SurfaceAccordionCard of the same file encloses is a finding (BOUNDARY-6, DNA-COMMON-NO-RAISED-SURFACE-IN-SURFACE); the Grammar resolves a nested surface's depth from the surface context. Specs: src/surface-depth.spec.ts.
|
|
7
|
+
|
|
8
|
+
## 0.16.1 - 2026-10-30
|
|
9
|
+
|
|
10
|
+
- Changed: the peer moves to eslint-canon-fe 9.6.1; the bundled canon pins name @starci/grammar 0.16.1 (accentCorner, Backdrop, the shell filling the viewport).
|
|
11
|
+
|
|
3
12
|
## 0.16.0 - 2026-10-30
|
|
4
13
|
|
|
5
14
|
- Changed: the peer moves to eslint-canon-fe 9.6.0; the bundled runtime copies are now generated from the hfs build (TypeScript emitted as .mjs) per runtime/bundle.json, with a drift check in CI; the copies the package keeps as its own are listed there and stay hand-maintained; the bundled canon pins name hfs 4.12.0, eslint-canon-be 3.9.0, eslint-canon-fe 9.6.0, the Grammar family 0.16.0, jest-preset 2.5.3 and test-world 1.3.3. No rule of this package changed.
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Family helpers and the generated runtime closure belong to this package.
|
|
|
19
19
|
|
|
20
20
|
The rule IDs remain stable when their namespace moves. Laws cover the fixed app shell and navbar,
|
|
21
21
|
Grammar-owned structural elements, glyphs and icon sizes, destructive menu entries, status inks,
|
|
22
|
-
chip labels that start uppercase, generated art rendered through `Mascot` or `MediaFrame` with every `Mascot` naming its asset slot, the page head composed by `PageHeader`, the route page as one `Page` with its tabs in the header, heading and token scales, vendor ownership and slot-status composition. Each rule's metadata cites
|
|
22
|
+
chip labels that start uppercase, generated art rendered through `Mascot` or `MediaFrame` with every `Mascot` naming its asset slot, the page head composed by `PageHeader`, the route page as one `Page` with its tabs in the header, heading and token scales, vendor ownership and slot-status composition, and a surface nested in another surface left subordinate (no forced `depth="top"`). Each rule's metadata cites
|
|
23
23
|
its behavioral knowledge source and the governing ownership test. The R318 surface composition
|
|
24
24
|
rules (nested control variants, panel-list alignment, layout-only CSS) stay in the universal canon.
|
|
25
25
|
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { rules as menuEntriesRules } from "./menu-entries.js";
|
|
|
10
10
|
import { rules as pageRules } from "./page.js";
|
|
11
11
|
import { rules as pageHeaderRules } from "./page-header.js";
|
|
12
12
|
import { rules as statusColorsRules } from "./status-colors.js";
|
|
13
|
+
import { rules as surfaceDepthRules } from "./surface-depth.js";
|
|
13
14
|
import { rules as tokensRules } from "./tokens.js";
|
|
14
15
|
import { rules as typographyRules } from "./typography.js";
|
|
15
16
|
import { rules as vendorRules } from "./vendor-boundary.js";
|
|
@@ -25,6 +26,7 @@ const CONTRIBUTIONS = [
|
|
|
25
26
|
{ law: "page", rules: pageRules },
|
|
26
27
|
{ law: "page-header", rules: pageHeaderRules },
|
|
27
28
|
{ law: "status-colors", rules: statusColorsRules },
|
|
29
|
+
{ law: "surface-depth", rules: surfaceDepthRules },
|
|
28
30
|
{ law: "tokens", rules: tokensRules },
|
|
29
31
|
{ law: "typography", rules: typographyRules },
|
|
30
32
|
{ law: "vendor-boundary", rules: vendorRules },
|
package/dist/lib/why.js
CHANGED
|
@@ -54,6 +54,11 @@ export const why = {
|
|
|
54
54
|
en: "The page or layout owner `<file>` draws something beside its Page, a Page inside another (or inside the Page its tabbed segment's layout draws), a bare PageHeader or PageContainer, or no Page at all (itself, through its pure half, or through the one connected block it renders as its root).",
|
|
55
55
|
fix: "Draw one Page: the band as its header, everything else in its body, the measure as its measure. When client data builds the header, render as the root the one connected block that draws the Page and pass the body as its children. Under a segment layout whose Page carries header.tabs, a page draws its body only; that layout's Page has nothing beside it, its head in its header. A page mounted only under the (auth) route group is framed by that group's layout.",
|
|
56
56
|
},
|
|
57
|
+
"no-raised-surface-in-surface": {
|
|
58
|
+
code: "FE_RAISED_SURFACE_IN_SURFACE",
|
|
59
|
+
en: "A Grammar surface in `<file>` forces `depth=\"top\"` inside another surface, raising a surface inside a surface (BOUNDARY-6).",
|
|
60
|
+
fix: "Drop `depth`: a nested surface is subordinate (outlined, BOUNDARY-5) and the Grammar resolves it from the surface context.",
|
|
61
|
+
},
|
|
57
62
|
"no-shell-bottom-tabs": {
|
|
58
63
|
code: "FE_APP_SHELL_BOTTOM_TABS",
|
|
59
64
|
en: "Bottom tab navigation replaces the required mobile sidebar drawer.",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Rule } from "eslint";
|
|
2
|
+
/** A surface inside another surface never forces `depth="top"`: it is subordinate, and the Grammar resolves it. */
|
|
3
|
+
export declare const noRaisedSurfaceInSurface: Rule.RuleModule;
|
|
4
|
+
/** The rules this law contributes to the plugin. */
|
|
5
|
+
export declare const rules: Record<string, Rule.RuleModule>;
|
|
6
|
+
/** Every rule of this law is an error. */
|
|
7
|
+
export declare const recommended: Record<string, "error">;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { isProductSource } from "@starci/eslint-canon-fe/lib/scope.mjs";
|
|
2
|
+
import { defineFamilyRules, defineRule } from "./lib/rule-contract.js";
|
|
3
|
+
/**
|
|
4
|
+
* The surface depth law (HFS R340 `FE_RAISED_SURFACE_IN_SURFACE`; Grammar BOUNDARY-5 and BOUNDARY-6, DNA
|
|
5
|
+
* `DNA-COMMON-NO-RAISED-SURFACE-IN-SURFACE`): a surface inside another surface is subordinate (outlined, BOUNDARY-5), never
|
|
6
|
+
* raised (BOUNDARY-6) again.
|
|
7
|
+
*
|
|
8
|
+
* Grammar's bounded surfaces read their depth from the surface context (`useSurfaceNesting`): `SurfaceCard`,
|
|
9
|
+
* `SurfaceAccordionCard` and `SurfaceListCard` draw outlined inside another surface and raised only at the top.
|
|
10
|
+
* `SurfaceListCard` alone also takes a `depth` prop that overrides the context, so `depth="top"` on a list that sits inside
|
|
11
|
+
* another surface raises a surface inside a surface.
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT READS. A `depth` attribute whose value is the string `"top"` (a literal, or an expression container holding one)
|
|
14
|
+
* on a depth-accepting surface element (`SurfaceListCard` or `<ns>.SurfaceListCard`), when a `SurfaceCard`,
|
|
15
|
+
* `SurfaceListCard` or `SurfaceAccordionCard` element of the same file encloses it. A depth computed at run time is not
|
|
16
|
+
* judged, and neither is a surface nobody encloses in this file (its parent may nest it; Grammar resolves that).
|
|
17
|
+
*
|
|
18
|
+
* WHERE IT APPLIES. Product source (`isProductSource`).
|
|
19
|
+
*/
|
|
20
|
+
/** The bounded surfaces that open a surface context for what they enclose. */
|
|
21
|
+
const SURFACES = new Set(["SurfaceCard", "SurfaceListCard", "SurfaceAccordionCard"]);
|
|
22
|
+
/** The surfaces that accept a `depth` prop overriding the one read from context. */
|
|
23
|
+
const DEPTH_PROP = new Set(["SurfaceListCard"]);
|
|
24
|
+
/** The element name of a JSX name: `SurfaceCard`, or `G.SurfaceCard` as `SurfaceCard`. */
|
|
25
|
+
const nameOf = (name) => {
|
|
26
|
+
if (name.type === "JSXIdentifier")
|
|
27
|
+
return name.name;
|
|
28
|
+
if (name.type === "JSXMemberExpression")
|
|
29
|
+
return name.property.name;
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
/** True when a bounded surface element encloses the element the opening tag belongs to. */
|
|
33
|
+
const insideSurface = (opening) => {
|
|
34
|
+
// The opening tag's parent is the element itself, so the walk starts above that; the Program's parent is null at run time
|
|
35
|
+
// though the tree's types say a node always has one.
|
|
36
|
+
for (let current = opening.parent?.parent; current !== undefined && current !== null; current = current.parent) {
|
|
37
|
+
if (current.type !== "JSXElement")
|
|
38
|
+
continue;
|
|
39
|
+
const name = nameOf(current.openingElement.name);
|
|
40
|
+
if (name !== null && SURFACES.has(name))
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
45
|
+
/** True when an attribute value is statically the string `"top"`. */
|
|
46
|
+
const isTop = (value) => {
|
|
47
|
+
if (value === null)
|
|
48
|
+
return false;
|
|
49
|
+
if (value.type === "Literal")
|
|
50
|
+
return value.value === "top";
|
|
51
|
+
if (value.type !== "JSXExpressionContainer")
|
|
52
|
+
return false;
|
|
53
|
+
const expression = value.expression;
|
|
54
|
+
if (expression.type === "Literal")
|
|
55
|
+
return expression.value === "top";
|
|
56
|
+
return expression.type === "TemplateLiteral" && expression.expressions.length === 0 && expression.quasis.length === 1 && expression.quasis[0]?.value.cooked === "top";
|
|
57
|
+
};
|
|
58
|
+
/** A surface inside another surface never forces `depth="top"`: it is subordinate, and the Grammar resolves it. */
|
|
59
|
+
export const noRaisedSurfaceInSurface = defineRule({
|
|
60
|
+
meta: {
|
|
61
|
+
type: "problem",
|
|
62
|
+
docs: { description: "R340: a Grammar surface nested in another surface does not force depth=\"top\" (BOUNDARY-6); a nested surface is subordinate (BOUNDARY-5)." },
|
|
63
|
+
schema: [],
|
|
64
|
+
messages: {
|
|
65
|
+
raised: "`<{{element}} depth=\"top\">` is nested inside another Grammar surface, so it raises a surface inside a surface (BOUNDARY-6, DNA-COMMON-NO-RAISED-SURFACE-IN-SURFACE): a nested surface is subordinate (outlined, BOUNDARY-5). Drop `depth` and let the Grammar resolve it from the surface context.",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
create(context) {
|
|
69
|
+
if (!isProductSource(context))
|
|
70
|
+
return {};
|
|
71
|
+
return {
|
|
72
|
+
JSXAttribute(attribute) {
|
|
73
|
+
if (attribute.name.type !== "JSXIdentifier" || attribute.name.name !== "depth" || !isTop(attribute.value))
|
|
74
|
+
return;
|
|
75
|
+
const opening = attribute.parent;
|
|
76
|
+
if (opening.type !== "JSXOpeningElement")
|
|
77
|
+
return;
|
|
78
|
+
const element = nameOf(opening.name);
|
|
79
|
+
if (element === null || !DEPTH_PROP.has(element) || !insideSurface(opening))
|
|
80
|
+
return;
|
|
81
|
+
context.report({ node: attribute, messageId: "raised", data: { element } });
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
/** The rules this law contributes to the plugin. */
|
|
87
|
+
export const rules = {
|
|
88
|
+
"no-raised-surface-in-surface": noRaisedSurfaceInSurface,
|
|
89
|
+
};
|
|
90
|
+
defineFamilyRules(rules, "knowledge/hfs/rules.yaml#R340");
|
|
91
|
+
/** Every rule of this law is an error. */
|
|
92
|
+
export const recommended = Object.fromEntries(Object.keys(rules).map((name) => [`starci-grammar/${name}`, "error"]));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# A nested surface stays subordinate
|
|
2
|
+
|
|
3
|
+
Law module: `surface-depth.ts`. Catalogue: R340 FE_RAISED_SURFACE_IN_SURFACE.
|
|
4
|
+
|
|
5
|
+
Grammar's bounded surfaces (`SurfaceCard`, `SurfaceAccordionCard`, `SurfaceListCard`) read their depth from the surface
|
|
6
|
+
context: raised at the top of a page (BOUNDARY-6), outlined inside another surface (BOUNDARY-5), so two raised surfaces never
|
|
7
|
+
stack (`DNA-COMMON-NO-RAISED-SURFACE-IN-SURFACE`). `SurfaceListCard` also takes a `depth` prop that overrides the context;
|
|
8
|
+
forcing `depth="top"` on a list that sits inside another surface raises a surface inside a surface.
|
|
9
|
+
|
|
10
|
+
Every rule below is an error in `coreGrammarConfig`; none can be switched off or suppressed inline. It applies to product
|
|
11
|
+
source (the route, feature, component, hook, foundation, module and transport tiers and the workspace packages); a script or
|
|
12
|
+
a file no slot owns is not judged.
|
|
13
|
+
|
|
14
|
+
## `starci-grammar/no-raised-surface-in-surface`
|
|
15
|
+
|
|
16
|
+
A `depth` attribute whose value is statically the string `"top"` (a literal, an expression container holding one, or a
|
|
17
|
+
template literal without expressions) on a depth-accepting surface element (today `SurfaceListCard`, or `<ns>.SurfaceListCard`)
|
|
18
|
+
is a finding when a `SurfaceCard`, `SurfaceListCard` or `SurfaceAccordionCard` element of the same file encloses it. A depth
|
|
19
|
+
computed at run time, `depth="nested"`, and a surface no element of the file encloses are not judged. The fix is to drop
|
|
20
|
+
`depth` and let the Grammar resolve it from the surface context.
|
|
21
|
+
|
|
22
|
+
**Invalid** (`src/components/blocks/Runs/component.tsx`)
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
<SurfaceCard label="Run">
|
|
26
|
+
<SurfaceListCard label="Steps" depth="top" items={steps} />
|
|
27
|
+
</SurfaceCard>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Valid** (`src/components/blocks/Runs/component.tsx`)
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
<SurfaceCard label="Run">
|
|
34
|
+
<SurfaceListCard label="Steps" items={steps} />
|
|
35
|
+
</SurfaceCard>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Finding code:** `FE_RAISED_SURFACE_IN_SURFACE`
|
|
39
|
+
|
|
40
|
+
Rule ownership: [FE-RULE-OWNERSHIP](../../../contexts/code/fe/README.md#the-placement-test-fe-rule-ownership).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starci/eslint-grammar-core",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "ESLint rules for the core Grammar's component, token and composition contracts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"node": ">=22"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@starci/eslint-canon-fe": "9.6.
|
|
44
|
+
"@starci/eslint-canon-fe": "9.6.2",
|
|
45
45
|
"eslint": ">=9",
|
|
46
46
|
"typescript": ">=5.9"
|
|
47
47
|
},
|
|
@@ -31,35 +31,35 @@ pins:
|
|
|
31
31
|
source: packages/event-stream/package.json
|
|
32
32
|
why: 'the one server-sent-events client of every front end (fe-stack event-stream, owner:event-stream-package): the transport client layer (modules/api/sse/clients) creates one instance for the app with the bearer token source and the stream operations follow on it; it carries Last-Event-ID resume, the capped doubling backoff, the heartbeat timeout, the single 401 renewal and the useSWRSubscription adapter, so no app keeps a hand-written reader.'
|
|
33
33
|
'@starci/grammar':
|
|
34
|
-
version: 0.16.
|
|
34
|
+
version: 0.16.2
|
|
35
35
|
group: starci
|
|
36
36
|
install: registry
|
|
37
37
|
side: fe
|
|
38
38
|
source: packages/grammar/package.json
|
|
39
39
|
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.'
|
|
40
40
|
'@starci/eslint-canon-be':
|
|
41
|
-
version: 3.9.
|
|
41
|
+
version: 3.9.2
|
|
42
42
|
group: starci
|
|
43
43
|
install: registry
|
|
44
44
|
side: be
|
|
45
45
|
source: packages/eslint-canon-be/package.json
|
|
46
46
|
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.'
|
|
47
47
|
'@starci/eslint-canon-fe':
|
|
48
|
-
version: 9.6.
|
|
48
|
+
version: 9.6.2
|
|
49
49
|
group: starci
|
|
50
50
|
install: registry
|
|
51
51
|
side: fe
|
|
52
52
|
source: packages/eslint-canon-fe/package.json
|
|
53
53
|
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.'
|
|
54
54
|
'@starci/eslint-grammar-core':
|
|
55
|
-
version: 0.16.
|
|
55
|
+
version: 0.16.2
|
|
56
56
|
group: starci
|
|
57
57
|
install: registry
|
|
58
58
|
side: fe
|
|
59
59
|
source: packages/eslint-grammar-core/package.json
|
|
60
60
|
why: 'the StarCi Grammar component API lint, versioned with @starci/grammar; front ends compose this family package with the universal FE canon.'
|
|
61
61
|
'@starci/eslint-grammar-offset-pop':
|
|
62
|
-
version: 0.16.
|
|
62
|
+
version: 0.16.2
|
|
63
63
|
group: starci
|
|
64
64
|
install: registry
|
|
65
65
|
side: fe
|
|
@@ -106,7 +106,7 @@ pins:
|
|
|
106
106
|
source: packages/ai-router/package.json
|
|
107
107
|
why: 'the AI pool router of a back end: a NestJS module that routes OpenAI-compatible chat, stream and embeddings calls across keys, models and providers, with @starci/ai-router/testing for its specs; @starci/jest-preset compiles it as an ESM-only dependency.'
|
|
108
108
|
'@starci/hfs':
|
|
109
|
-
version: 4.12.
|
|
109
|
+
version: 4.12.2
|
|
110
110
|
group: starci
|
|
111
111
|
install: registry
|
|
112
112
|
side: both
|
|
@@ -5956,3 +5956,26 @@ rules:
|
|
|
5956
5956
|
source: owner
|
|
5957
5957
|
quote: "A Mascot reuses the provenance props of Image and MediaFrame; a Mascot without an assetSlot is a finding (relayed by the nivo-lite app)."
|
|
5958
5958
|
ruling: "Mascot takes AssetSlotProps (assetSlot, assetSha256, assetPrompt) and emits them as data-asset-* on its root; the lint requires assetSlot on every Mascot."
|
|
5959
|
+
- id: R340
|
|
5960
|
+
code: "FE_RAISED_SURFACE_IN_SURFACE"
|
|
5961
|
+
law: "A Grammar surface that accepts a depth prop (SurfaceListCard) does not pass depth=\"top\" when a SurfaceCard, SurfaceListCard or SurfaceAccordionCard of the same file encloses it: a nested surface is subordinate (outlined, BOUNDARY-5), never raised again (BOUNDARY-6, DNA-COMMON-NO-RAISED-SURFACE-IN-SURFACE); the Grammar resolves the depth from the surface context. A depth computed at run time is not judged."
|
|
5962
|
+
kinds: [lint]
|
|
5963
|
+
gates: [pre-commit, settle, land, ci]
|
|
5964
|
+
failureCodes: ["FE_RAISED_SURFACE_IN_SURFACE"]
|
|
5965
|
+
enforcers:
|
|
5966
|
+
- {kind: eslint-grammar-core, id: no-raised-surface-in-surface}
|
|
5967
|
+
editions: [lite, full]
|
|
5968
|
+
docs: [knowledge/patterns/fe/ui.yaml#FE-UI]
|
|
5969
|
+
templateNa: "A template forces no surface depth; the app composes its surfaces."
|
|
5970
|
+
proves:
|
|
5971
|
+
- packages/eslint-grammar-core/src/surface-depth.spec.ts
|
|
5972
|
+
exemplifiedBy:
|
|
5973
|
+
- {path: packages/eslint-grammar-core/src/surface-depth.spec.ts, editions: [lite, full], codes: [FE_RAISED_SURFACE_IN_SURFACE]}
|
|
5974
|
+
witness:
|
|
5975
|
+
- {path: packages/eslint-grammar-core/src/surface-depth.spec.ts, editions: [lite, full], codes: [FE_RAISED_SURFACE_IN_SURFACE]}
|
|
5976
|
+
cases:
|
|
5977
|
+
- id: case-1
|
|
5978
|
+
date: "2026-10-31"
|
|
5979
|
+
source: owner
|
|
5980
|
+
quote: "A list inside another surface is outlined (BOUNDARY-5, DNA-COMMON-NO-RAISED-SURFACE-IN-SURFACE); SurfaceListCard takes its depth from the surface context (grammar 0.16.2)."
|
|
5981
|
+
ruling: "SurfaceListCard resolves its depth from the surface context like SurfaceCard and SurfaceAccordionCard; an app that forces depth=\"top\" on a surface nested in another surface raises a surface inside a surface, so the lint refuses it."
|
|
@@ -24,7 +24,7 @@ rules:
|
|
|
24
24
|
- id: FE-UI
|
|
25
25
|
title: Compose the selected Grammar
|
|
26
26
|
kind: mandatory
|
|
27
|
-
hfsRules: [R93, R244, R313, R314, R315, R316, R317, R318, R319, R320, R338, R339]
|
|
27
|
+
hfsRules: [R93, R244, R313, R314, R315, R316, R317, R318, R319, R320, R338, R339, R340]
|
|
28
28
|
requirement: |-
|
|
29
29
|
Components compose the selected Grammar through its published entry and props; application CSS carries
|
|
30
30
|
layout only (css.yaml). A missing visual capability is added to that Grammar before the app consumes it.
|
|
@@ -92,5 +92,6 @@ rules:
|
|
|
92
92
|
- FE_CONTROL_HEIGHT
|
|
93
93
|
- FE_GENERATED_ART_OUTSIDE_FRAME
|
|
94
94
|
- FE_MASCOT_ASSET_SLOT_MISSING
|
|
95
|
+
- FE_RAISED_SURFACE_IN_SURFACE
|
|
95
96
|
manual: []
|
|
96
97
|
relatedExamples: [full-app-shell, lite-app-shell]
|