@starci/eslint-grammar-core 0.15.3 → 0.15.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/why.js +10 -0
- package/dist/page.d.ts +12 -0
- package/dist/page.js +147 -0
- package/docs/page.md +64 -0
- package/package.json +2 -2
- package/runtime/knowledge/hfs/canon-pins.yaml +7 -7
- package/runtime/knowledge/hfs/rules.yaml +47 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.6 - 2026-10-28
|
|
4
|
+
|
|
5
|
+
- Changed: `starci-grammar/route-page-is-one-page` (R325) also refuses a page owner file that draws JSX but no `Page` and draws through no pure half of its own (an element imported from `./component`), with the message `missing` on its first element. The segment boundary owners of FE-BOUNDARY-1 (`*ErrorPage`, `*LoadingPage`, `*NotFoundPage`) are not judged for it; a file that draws a bare `PageHeader` or `PageContainer` is reported once, as `part`. Spec: `src/page.spec.ts`; docs: `docs/page.md`.
|
|
6
|
+
- Changed: the R324 message names link tabs (items with `href`) as the other form of `header.tabs`.
|
|
7
|
+
- Changed: versioned together with Grammar 0.15.6; the bundled canon pins name the Grammar family 0.15.6 and hfs 4.9.8.
|
|
8
|
+
|
|
9
|
+
## 0.15.5 - 2026-10-28
|
|
10
|
+
|
|
11
|
+
- Changed: versioned together with Grammar 0.15.5 (the family equality spec); `@starci/eslint-canon-fe` 9.4.5 is the peer dependency; the bundled canon pins name the Grammar family 0.15.5. No lint rule changed.
|
|
12
|
+
- Changed: the bundled rule catalog states R225 with the app pre-push hook's dependency push gate (`starci deps check`). No lint rule changed.
|
|
13
|
+
|
|
14
|
+
## 0.15.4 - 2026-10-28
|
|
15
|
+
|
|
16
|
+
- Added: `starci-grammar/page-tabs-in-header` (R324, `FE_PAGE_TABS_OUTSIDE_HEADER`): a `Tabs` that is the first element child of a `Page`, or the next element sibling of a `PageHeader`, is a page tab bar outside the band; pass it as `header.tabs`.
|
|
17
|
+
- Added: `starci-grammar/route-page-is-one-page` (R325, `FE_ROUTE_PAGE_OUTSIDE_PAGE`): a page owner (`fe.feature`, kind `pages`) draws one `Page` with no element sibling and no `PageHeader` or `PageContainer` of its own. Spec: `src/page.spec.ts`; docs: `docs/page.md`.
|
|
18
|
+
- Changed: versioned together with Grammar 0.15.4; `@starci/eslint-canon-fe` 9.4.4 is the peer dependency; the bundled rule catalog carries R324 and R325, and the bundled canon pins follow release G (Grammar family 0.15.4, hfs 4.9.5, canon-fe 9.4.4, canon-be 3.6.4, ai-router 0.3.0).
|
|
19
|
+
|
|
3
20
|
## 0.15.3 - 2026-10-28
|
|
4
21
|
|
|
5
22
|
- Added: `starci-grammar/no-hand-rolled-page-header` (R323, `FE_PAGE_HEADER_HAND_ROLLED`): a `Breadcrumbs` followed by a `SectionHeader` among one parent's element children is a page head composed by hand; compose Grammar 0.15.3's `PageHeader`. Spec: `src/page-header.spec.ts`; docs: `docs/page-header.md`.
|
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, the page head composed by `PageHeader`, heading and token scales, vendor ownership and slot-status composition. Each rule's metadata cites
|
|
22
|
+
chip labels that start uppercase, 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
|
|
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
|
@@ -6,6 +6,7 @@ import { rules as drawingRules } from "./drawing.js";
|
|
|
6
6
|
import { rules as grammarBoundaryRules } from "./grammar-boundary.js";
|
|
7
7
|
import { rules as iconRules } from "./icon.js";
|
|
8
8
|
import { rules as menuEntriesRules } from "./menu-entries.js";
|
|
9
|
+
import { rules as pageRules } from "./page.js";
|
|
9
10
|
import { rules as pageHeaderRules } from "./page-header.js";
|
|
10
11
|
import { rules as statusColorsRules } from "./status-colors.js";
|
|
11
12
|
import { rules as tokensRules } from "./tokens.js";
|
|
@@ -19,6 +20,7 @@ const CONTRIBUTIONS = [
|
|
|
19
20
|
{ law: "grammar-boundary", rules: grammarBoundaryRules },
|
|
20
21
|
{ law: "icon", rules: iconRules },
|
|
21
22
|
{ law: "menu-entries", rules: menuEntriesRules },
|
|
23
|
+
{ law: "page", rules: pageRules },
|
|
22
24
|
{ law: "page-header", rules: pageHeaderRules },
|
|
23
25
|
{ law: "status-colors", rules: statusColorsRules },
|
|
24
26
|
{ law: "tokens", rules: tokensRules },
|
package/dist/lib/why.js
CHANGED
|
@@ -34,6 +34,16 @@ export const why = {
|
|
|
34
34
|
en: "A page head in `<file>` is a Breadcrumbs trail and a SectionHeader composed by hand.",
|
|
35
35
|
fix: "Compose Grammar PageHeader with breadcrumbs, title and the slots the head needs (description, meta, status, mark, actions, tabs).",
|
|
36
36
|
},
|
|
37
|
+
"page-tabs-in-header": {
|
|
38
|
+
code: "FE_PAGE_TABS_OUTSIDE_HEADER",
|
|
39
|
+
en: "A page tab bar in `<file>` is a Tabs at the top of the Page body or right after a PageHeader, outside the header band.",
|
|
40
|
+
fix: "Pass the tabs as Page header.tabs (or PageHeader tabs): { label, items, selectedKey, onSelect }, or link tabs whose items carry an href each.",
|
|
41
|
+
},
|
|
42
|
+
"route-page-is-one-page": {
|
|
43
|
+
code: "FE_ROUTE_PAGE_OUTSIDE_PAGE",
|
|
44
|
+
en: "The page owner `<file>` draws something beside its Page, a bare PageHeader or PageContainer, or no Page at all.",
|
|
45
|
+
fix: "Draw one Page: the band as its header, everything else in its body, the measure as its measure.",
|
|
46
|
+
},
|
|
37
47
|
"no-shell-bottom-tabs": {
|
|
38
48
|
code: "FE_APP_SHELL_BOTTOM_TABS",
|
|
39
49
|
en: "Bottom tab navigation replaces the required mobile sidebar drawer.",
|
package/dist/page.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Rule } from "eslint";
|
|
2
|
+
import type { TSESTree } from "@typescript-eslint/types";
|
|
3
|
+
/** The `Tabs` element drawn as page tabs outside the header band, among one parent's children, or null. */
|
|
4
|
+
export declare const tabsOutsideHeader: (parent: TSESTree.JSXElement | TSESTree.JSXFragment) => TSESTree.JSXElement | null;
|
|
5
|
+
/** Page tabs are the header's `tabs`, never a `Tabs` at the top of the body or beside a `PageHeader`. */
|
|
6
|
+
export declare const pageTabsInHeader: Rule.RuleModule;
|
|
7
|
+
/** A page owner draws one `Page` and nothing beside it; `PageHeader` and `PageContainer` are Page's own parts. */
|
|
8
|
+
export declare const routePageIsOnePage: Rule.RuleModule;
|
|
9
|
+
/** The rules these laws contribute to the plugin. */
|
|
10
|
+
export declare const rules: Record<string, Rule.RuleModule>;
|
|
11
|
+
/** Every rule of these laws is an error. */
|
|
12
|
+
export declare const recommended: Record<string, "error">;
|
package/dist/page.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { inSlot, isProductSource, kindOfFile } from "@starci/eslint-canon-fe/lib/scope.mjs";
|
|
2
|
+
import { defineFamilyRules, defineRule } from "./lib/rule-contract.js";
|
|
3
|
+
/**
|
|
4
|
+
* The rules that hold the page frame laws: Grammar's `Page` is the one owner of a route page's header band, its inset
|
|
5
|
+
* and its rhythm.
|
|
6
|
+
*
|
|
7
|
+
* - R324 (`FE_PAGE_TABS_OUTSIDE_HEADER`): page tabs are the header's tabs. A `Tabs` that is the first element child of a
|
|
8
|
+
* `Page` (the top of the body, right under the band), or the next element sibling of a `PageHeader`, is a page tab bar
|
|
9
|
+
* drawn outside the band: it misses the separator it stands on and takes the body's inset and gap.
|
|
10
|
+
* - R325 (`FE_ROUTE_PAGE_OUTSIDE_PAGE`): a page owner (`fe.feature`, kind `pages`) draws one `Page` and nothing beside it.
|
|
11
|
+
* A `Page` with an element sibling, or a bare `PageHeader` or `PageContainer` in the page owner, re-decides the inset
|
|
12
|
+
* and the rhythm `Page` owns. A page owner file that draws JSX but no `Page`, and hands drawing to no pure half of its
|
|
13
|
+
* own (an element imported from `./component`), has no page frame at all.
|
|
14
|
+
*
|
|
15
|
+
* WHAT IT READS. Element names (`Page`, or `Grammar.Page` as `Page`), the element children of one JSX parent, and the
|
|
16
|
+
* names a file imports from a relative module; whitespace text and JSX comments are not children. A `Tabs` lower in the
|
|
17
|
+
* body, a `Page` wrapped alone in a provider, and files outside a page owner (for R325) are not judged. The segment
|
|
18
|
+
* boundary owners of FE-BOUNDARY-1 (`*ErrorPage`, `*LoadingPage`, `*NotFoundPage`) are not judged for a missing `Page`:
|
|
19
|
+
* an app-level boundary stands in for whichever page failed or waits and draws its notice.
|
|
20
|
+
*/
|
|
21
|
+
/** The element name of a JSX child (`Tabs`, or `Grammar.Tabs` as `Tabs`), or null for text and expressions. */
|
|
22
|
+
const elementName = (child) => {
|
|
23
|
+
if (child?.type !== "JSXElement")
|
|
24
|
+
return null;
|
|
25
|
+
const name = child.openingElement.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
|
+
/** The children that render: whitespace-only text and `{/* comments *\/}` are left out. */
|
|
33
|
+
const renderedChildren = (children) => children.filter((child) => {
|
|
34
|
+
if (child.type === "JSXText")
|
|
35
|
+
return child.value.trim() !== "";
|
|
36
|
+
if (child.type === "JSXExpressionContainer")
|
|
37
|
+
return child.expression.type !== "JSXEmptyExpression";
|
|
38
|
+
return true;
|
|
39
|
+
});
|
|
40
|
+
/** The `Tabs` element drawn as page tabs outside the header band, among one parent's children, or null. */
|
|
41
|
+
export const tabsOutsideHeader = (parent) => {
|
|
42
|
+
const children = renderedChildren(parent.children);
|
|
43
|
+
const first = children[0];
|
|
44
|
+
if (elementName(parent) === "Page" && elementName(first) === "Tabs")
|
|
45
|
+
return first;
|
|
46
|
+
for (let index = 0; index + 1 < children.length; index += 1) {
|
|
47
|
+
const next = children[index + 1];
|
|
48
|
+
if (elementName(children[index]) === "PageHeader" && elementName(next) === "Tabs")
|
|
49
|
+
return next;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
/** Page tabs are the header's `tabs`, never a `Tabs` at the top of the body or beside a `PageHeader`. */
|
|
54
|
+
export const pageTabsInHeader = defineRule({
|
|
55
|
+
meta: {
|
|
56
|
+
type: "problem",
|
|
57
|
+
docs: { description: "R324: page tabs are Page header.tabs (or PageHeader tabs), never a Tabs at the top of the body." },
|
|
58
|
+
schema: [],
|
|
59
|
+
messages: {
|
|
60
|
+
outside: "This `Tabs` is the page's tab bar drawn under the header band instead of on it: it misses the separator the page tabs stand on and takes the body's inset. Pass it as `header.tabs` of `Page` (or `tabs` of `PageHeader`): `{ label, items, selectedKey, onSelect }`, or link tabs whose items carry an `href` each.",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
create(context) {
|
|
64
|
+
if (!isProductSource(context))
|
|
65
|
+
return {};
|
|
66
|
+
const judge = (parent) => {
|
|
67
|
+
const tabs = tabsOutsideHeader(parent);
|
|
68
|
+
if (tabs !== null)
|
|
69
|
+
context.report({ node: tabs, messageId: "outside" });
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
JSXElement(element) { judge(element); },
|
|
73
|
+
JSXFragment(fragment) { judge(fragment); },
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
const PAGE_PARTS = new Set(["PageHeader", "PageContainer"]);
|
|
78
|
+
/** True for a page owner's file: `fe.feature`, kind `pages`. */
|
|
79
|
+
const isPageOwner = (context) => inSlot(context, "fe.feature") && kindOfFile(context) === "pages";
|
|
80
|
+
/** The segment boundary owners of FE-BOUNDARY-1: an error, loading or not-found page, which may draw a notice. */
|
|
81
|
+
const BOUNDARY_OWNER = /\/features\/pages\/[^/]*(?:ErrorPage|LoadingPage|NotFoundPage)\//;
|
|
82
|
+
/** True when the file is a boundary owner's, judged by its owner folder. */
|
|
83
|
+
const isBoundaryOwner = (context) => BOUNDARY_OWNER.test(context.filename.replaceAll("\\", "/"));
|
|
84
|
+
/** The pure half of a page owner: the module its connected half draws through. */
|
|
85
|
+
const PURE_HALF = /^\.\/component(?:\.[cm]?[jt]sx?)?$/;
|
|
86
|
+
/** A page owner draws one `Page` and nothing beside it; `PageHeader` and `PageContainer` are Page's own parts. */
|
|
87
|
+
export const routePageIsOnePage = defineRule({
|
|
88
|
+
meta: {
|
|
89
|
+
type: "problem",
|
|
90
|
+
docs: { description: "R325: a page owner draws one Page with nothing beside it, never a bare PageHeader or PageContainer, and never no Page at all." },
|
|
91
|
+
schema: [],
|
|
92
|
+
messages: {
|
|
93
|
+
sibling: "This `Page` has a sibling: a route page is one `Page`, and whatever sits beside it escapes the header band, the measure and the rhythm `Page` owns. Move the sibling into the `Page` body, or into `header` when it heads the page.",
|
|
94
|
+
part: "`{{name}}` is drawn by `Page`: a page owner composes `Page` (`header` for the band, `measure` for the body), so the inset and the rhythm have one owner. Replace it with `Page`.",
|
|
95
|
+
missing: "This page owner draws no `Page`: a route page is one Grammar `Page`, which owns the header band, the measure and the rhythm, so without it the page has no heading band and its body sits on no inset. Wrap what it draws in a `Page` whose `header.title` is the page heading (never a `Heading` of its own), or draw it through the owner's pure half in `./component`.",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
create(context) {
|
|
99
|
+
if (!isProductSource(context) || !isPageOwner(context))
|
|
100
|
+
return {};
|
|
101
|
+
const judgeMissing = !isBoundaryOwner(context);
|
|
102
|
+
const pureHalf = new Set();
|
|
103
|
+
let first = null;
|
|
104
|
+
let drawsPage = false;
|
|
105
|
+
return {
|
|
106
|
+
ImportDeclaration(declaration) {
|
|
107
|
+
if (!PURE_HALF.test(String(declaration.source.value)))
|
|
108
|
+
return;
|
|
109
|
+
for (const specifier of declaration.specifiers)
|
|
110
|
+
pureHalf.add(specifier.local.name);
|
|
111
|
+
},
|
|
112
|
+
JSXElement(element) {
|
|
113
|
+
first ??= element;
|
|
114
|
+
const name = elementName(element);
|
|
115
|
+
const opening = element.openingElement.name;
|
|
116
|
+
if (opening.type === "JSXIdentifier" && pureHalf.has(opening.name))
|
|
117
|
+
drawsPage = true;
|
|
118
|
+
if (name !== null && PAGE_PARTS.has(name)) {
|
|
119
|
+
// The part's own finding already says to draw a `Page` instead; the missing Page is not reported twice.
|
|
120
|
+
drawsPage = true;
|
|
121
|
+
context.report({ node: element, messageId: "part", data: { name } });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (name !== "Page")
|
|
125
|
+
return;
|
|
126
|
+
drawsPage = true;
|
|
127
|
+
const parent = element.parent;
|
|
128
|
+
if (parent.type !== "JSXElement" && parent.type !== "JSXFragment")
|
|
129
|
+
return;
|
|
130
|
+
if (renderedChildren(parent.children).length > 1)
|
|
131
|
+
context.report({ node: element, messageId: "sibling" });
|
|
132
|
+
},
|
|
133
|
+
"Program:exit"() {
|
|
134
|
+
if (judgeMissing && first !== null && !drawsPage)
|
|
135
|
+
context.report({ node: first, messageId: "missing" });
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
const tabsRules = { "page-tabs-in-header": pageTabsInHeader };
|
|
141
|
+
const rootRules = { "route-page-is-one-page": routePageIsOnePage };
|
|
142
|
+
defineFamilyRules(tabsRules, "knowledge/hfs/rules.yaml#R324");
|
|
143
|
+
defineFamilyRules(rootRules, "knowledge/hfs/rules.yaml#R325");
|
|
144
|
+
/** The rules these laws contribute to the plugin. */
|
|
145
|
+
export const rules = { ...tabsRules, ...rootRules };
|
|
146
|
+
/** Every rule of these laws is an error. */
|
|
147
|
+
export const recommended = Object.fromEntries(Object.keys(rules).map((name) => [`starci-grammar/${name}`, "error"]));
|
package/docs/page.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# A route page is one Page
|
|
2
|
+
|
|
3
|
+
Law module: `page.ts`. Catalogue: R324 FE_PAGE_TABS_OUTSIDE_HEADER, R325 FE_ROUTE_PAGE_OUTSIDE_PAGE.
|
|
4
|
+
|
|
5
|
+
Grammar's `Page` is a route page: the header band full width, then the body inside the page measure and gutter one region step below it. It is the single owner of the page inset and the page rhythm, so the page tabs belong to its header and nothing of the page sits beside it.
|
|
6
|
+
|
|
7
|
+
Every rule below is an error in `coreGrammarConfig`; none can be switched off or suppressed inline.
|
|
8
|
+
|
|
9
|
+
## `starci-grammar/page-tabs-in-header`
|
|
10
|
+
|
|
11
|
+
A `Tabs` element that is the first element child of a `Page`, or the next element sibling of a `PageHeader`, is a finding, reported on the `Tabs`. Whitespace text and JSX comments are not children. A `Tabs` lower in the body, or inside a body section, is region tabs and is not judged.
|
|
12
|
+
|
|
13
|
+
**Invalid** (`src/features/pages/ConnectionPage/component.tsx`)
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<Page header={{ title: "Main account" }}>
|
|
17
|
+
<Tabs label="Connection" items={items} selectedKey={tab} onSelect={setTab} />
|
|
18
|
+
<WebhookCard />
|
|
19
|
+
</Page>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Valid**
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
<Page header={{ title: "Main account", tabs: { label: "Connection", items, selectedKey: tab, onSelect: setTab } }}>
|
|
26
|
+
<WebhookCard />
|
|
27
|
+
</Page>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Finding code:** `FE_PAGE_TABS_OUTSIDE_HEADER`
|
|
31
|
+
|
|
32
|
+
## `starci-grammar/route-page-is-one-page`
|
|
33
|
+
|
|
34
|
+
In a page owner (`fe.feature`, kind `pages`), a `Page` with an element sibling in its parent is a finding, and so is any `PageHeader` or `PageContainer`: both are Page's own parts. A page owner file that draws JSX but no `Page`, and draws no element imported from its own pure half (`./component`), is a finding too, reported on its first element: the page has no frame at all. A `Page` alone in a fragment or a provider, and one `Page` per branch of a conditional, are legal. Files outside a page owner are not judged, and the segment boundary owners of FE-BOUNDARY-1 (`*ErrorPage`, `*LoadingPage`, `*NotFoundPage`) are not judged for a missing `Page`: an app-level boundary stands in for whichever page failed or waits and draws its notice.
|
|
35
|
+
|
|
36
|
+
**Invalid** (`src/features/pages/ConnectionPage/component.tsx`)
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<>
|
|
40
|
+
<PageHeader title="Main account" />
|
|
41
|
+
<PageContainer><WebhookCard /></PageContainer>
|
|
42
|
+
</>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Invalid** (`src/features/pages/HomePage/component.tsx`)
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<>
|
|
49
|
+
<Heading level={1}>Home</Heading>
|
|
50
|
+
<SignInForm />
|
|
51
|
+
</>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Valid**
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<Page header={{ title: "Main account" }}>
|
|
58
|
+
<WebhookCard />
|
|
59
|
+
</Page>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Finding code:** `FE_ROUTE_PAGE_OUTSIDE_PAGE`
|
|
63
|
+
|
|
64
|
+
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.15.
|
|
3
|
+
"version": "0.15.6",
|
|
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.4.
|
|
44
|
+
"@starci/eslint-canon-fe": "9.4.5",
|
|
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.15.
|
|
34
|
+
version: 0.15.6
|
|
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.6.
|
|
41
|
+
version: 3.6.4
|
|
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.4.
|
|
48
|
+
version: 9.4.5
|
|
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.15.
|
|
55
|
+
version: 0.15.6
|
|
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.15.
|
|
62
|
+
version: 0.15.6
|
|
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.9.
|
|
109
|
+
version: 4.9.8
|
|
110
110
|
group: starci
|
|
111
111
|
install: registry
|
|
112
112
|
side: both
|
|
@@ -487,7 +487,7 @@ pins:
|
|
|
487
487
|
version: 11.16.0
|
|
488
488
|
group: ui
|
|
489
489
|
side: fe
|
|
490
|
-
why: '
|
|
490
|
+
why: 'runtime dependency of @starci/grammar for MermaidDiagram (fe-stack diagrams), imported lazily on the first diagram and themed from the grammar root; apps render diagrams through MermaidDiagram and never import mermaid.'
|
|
491
491
|
'react-dropzone':
|
|
492
492
|
version: 15.0.0
|
|
493
493
|
group: ui
|
|
@@ -3894,7 +3894,7 @@ rules:
|
|
|
3894
3894
|
- {kind: runtime, id: protected-zone, at: scripts/hfs/runtime-rules/rights-policy.mjs}
|
|
3895
3895
|
- id: R225
|
|
3896
3896
|
code: "RT_HOOK_SHAPE"
|
|
3897
|
-
law: "The app hook templates keep the gate model: pre-commit is L0 only (no typecheck or test run); pre-push invokes the bundled scripts/guards/release-push-gate.mjs policy for fast-forward main, a clean outgoing secret scan and annotated v* tags. It runs no build, test, typecheck or lint."
|
|
3897
|
+
law: "The app hook templates keep the gate model: pre-commit is L0 only (no typecheck or test run); pre-push invokes the bundled scripts/guards/release-push-gate.mjs policy for fast-forward main, a clean outgoing secret scan and annotated v* tags, then the harness's `starci deps check` (every committed @starci pin installable from npm, no local package copy ahead of npm). It runs no build, test, typecheck or lint."
|
|
3898
3898
|
scope: runtime
|
|
3899
3899
|
kinds: [check]
|
|
3900
3900
|
gates: [land, runtime]
|
|
@@ -5710,3 +5710,49 @@ rules:
|
|
|
5710
5710
|
source: owner
|
|
5711
5711
|
quote: "Publish Grammar 0.15.3 with PageHeader now, then move all 18 pages to it before deploying. (translated from Vietnamese)"
|
|
5712
5712
|
ruling: "A page is headed by Grammar's PageHeader (owner ruling page-header-band): a surface band whose content shares the page content's edges and which ends in its own full-width separator, page tabs standing on it; the lint refuses a page head composed by hand from Breadcrumbs and SectionHeader."
|
|
5713
|
+
- id: R324
|
|
5714
|
+
code: "FE_PAGE_TABS_OUTSIDE_HEADER"
|
|
5715
|
+
law: "Page tabs are the header's tabs; a Tabs that is the first element of a Page body or the next element after a PageHeader is a page tab bar drawn outside the band."
|
|
5716
|
+
kinds: [lint]
|
|
5717
|
+
gates: [pre-commit, settle, land, ci]
|
|
5718
|
+
failureCodes: ["FE_PAGE_TABS_OUTSIDE_HEADER"]
|
|
5719
|
+
enforcers:
|
|
5720
|
+
- {kind: eslint-grammar-core, id: page-tabs-in-header}
|
|
5721
|
+
editions: [lite, full]
|
|
5722
|
+
docs: [knowledge/patterns/fe/index.yaml#FE-RULE-OWNERSHIP]
|
|
5723
|
+
templateNa: "A template states no page; the pages are the app's."
|
|
5724
|
+
proves:
|
|
5725
|
+
- packages/eslint-grammar-core/src/page.spec.ts
|
|
5726
|
+
exemplifiedBy:
|
|
5727
|
+
- {path: packages/eslint-grammar-core/src/page.spec.ts, editions: [lite, full], codes: [FE_PAGE_TABS_OUTSIDE_HEADER]}
|
|
5728
|
+
witness:
|
|
5729
|
+
- {path: packages/eslint-grammar-core/src/page.spec.ts, editions: [lite, full], codes: [FE_PAGE_TABS_OUTSIDE_HEADER]}
|
|
5730
|
+
cases:
|
|
5731
|
+
- id: case-1
|
|
5732
|
+
date: "2026-10-28"
|
|
5733
|
+
source: owner
|
|
5734
|
+
quote: "ok trò"
|
|
5735
|
+
ruling: "A route page is one Grammar Page (owner ruling page-frame); page tabs are its header's tabs, and the lint refuses a Tabs at the top of the Page body or right after a PageHeader."
|
|
5736
|
+
- id: R325
|
|
5737
|
+
code: "FE_ROUTE_PAGE_OUTSIDE_PAGE"
|
|
5738
|
+
law: "A page owner draws one Grammar Page with nothing beside it; a sibling of the Page, or a bare PageHeader or PageContainer in the page owner, re-decides the inset and rhythm Page owns."
|
|
5739
|
+
kinds: [lint]
|
|
5740
|
+
gates: [pre-commit, settle, land, ci]
|
|
5741
|
+
failureCodes: ["FE_ROUTE_PAGE_OUTSIDE_PAGE"]
|
|
5742
|
+
enforcers:
|
|
5743
|
+
- {kind: eslint-grammar-core, id: route-page-is-one-page}
|
|
5744
|
+
editions: [lite, full]
|
|
5745
|
+
docs: [knowledge/patterns/fe/index.yaml#FE-RULE-OWNERSHIP]
|
|
5746
|
+
templateNa: "A template states no page; the pages are the app's."
|
|
5747
|
+
proves:
|
|
5748
|
+
- packages/eslint-grammar-core/src/page.spec.ts
|
|
5749
|
+
exemplifiedBy:
|
|
5750
|
+
- {path: packages/eslint-grammar-core/src/page.spec.ts, editions: [lite, full], codes: [FE_ROUTE_PAGE_OUTSIDE_PAGE]}
|
|
5751
|
+
witness:
|
|
5752
|
+
- {path: packages/eslint-grammar-core/src/page.spec.ts, editions: [lite, full], codes: [FE_ROUTE_PAGE_OUTSIDE_PAGE]}
|
|
5753
|
+
cases:
|
|
5754
|
+
- id: case-1
|
|
5755
|
+
date: "2026-10-28"
|
|
5756
|
+
source: owner
|
|
5757
|
+
quote: "ok trò"
|
|
5758
|
+
ruling: "A route page is one Grammar Page (owner ruling page-frame): the single owner of the page inset and rhythm; the lint refuses a sibling beside the Page and a bare PageHeader or PageContainer in a page owner."
|