@starci/eslint-canon-fe 9.7.0 → 9.8.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 +5 -0
- package/README.md +7 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +6 -3
- package/dist/lib/config.d.ts +9 -0
- package/dist/lib/config.js +10 -2
- package/dist/lib/rule.d.ts +2 -0
- package/dist/lib/why.d.ts +5 -0
- package/dist/lib/why.js +5 -0
- package/dist/spacing.d.ts +18 -0
- package/dist/spacing.js +83 -0
- package/dist/surface-rules.js +5 -5
- package/docs/surface-rules.md +1 -1
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 9.8.0 - 2026-10-31
|
|
4
|
+
|
|
5
|
+
- Added: `no-spacing-utility` (R350, `FE_SPACING_UTILITY`): no padding, margin, gap or space utility (variants and arbitrary values included) in an app class string; the message names the role prop to use. It ships in the closed counting cohort (`COUNTING` of `src/lib/config.ts`) at warn for this release.
|
|
6
|
+
- Changed: `nested-control-variant` refuses the pill (`variant="secondary"`) on `Tabs` wherever it is set, and no longer refuses `variant="primary"` inside a surface: tabs are the underline bar everywhere.
|
|
7
|
+
|
|
3
8
|
## 9.7.0 - 2026-10-31
|
|
4
9
|
|
|
5
10
|
- Added: R349 `no-hand-built-panes` (`FE_HAND_BUILT_PANES`, surface rules, slot-free): a multi-pane frame is a `PaneGrid`, so a vertical Grammar `Divider` used as a column seam (among the plain columns of a flex row, or anywhere inside a `SurfaceCard composition="joined"`) and a joined card that holds a flex row of two or more plain columns are refused.
|
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ configuration for source that has not adopted the product composition laws.
|
|
|
38
38
|
|
|
39
39
|
The [surface composition law](docs/surface-rules.md) (R318) holds ANATOMY-2 and PADDING-0 for every
|
|
40
40
|
family that publishes the shared HeroUI surface and field API: `nested-control-variant` refuses static
|
|
41
|
-
variants on layer-aware fields and
|
|
41
|
+
variants on layer-aware fields and the pill (`variant="secondary"`) on Tabs, which are the underline bar (`FE_NESTED_VARIANT`),
|
|
42
42
|
`no-app-surface-paint` keeps surface paint in Grammar (`FE_GRAMMAR_LAYOUT_ONLY`) and
|
|
43
43
|
`no-list-panel-padding` refuses horizontal padding wrappers around Grammar lists and section captions
|
|
44
44
|
within the nearest painted surface, a `Pane` or a `PaneSection` included (`FE_LIST_PANEL_PADDING`), and
|
|
@@ -46,6 +46,12 @@ within the nearest painted surface, a `Pane` or a `PaneSection` included (`FE_LI
|
|
|
46
46
|
columns inside a joined card, where `PaneGrid` owns both (`FE_HAND_BUILT_PANES`). The R65 budget law's
|
|
47
47
|
`no-app-relative-clock` leaves live relative time and countdowns to Grammar's shared clock.
|
|
48
48
|
|
|
49
|
+
The spacing law (R350) leaves every distance to Grammar: `no-spacing-utility` refuses a padding, margin, gap
|
|
50
|
+
or space utility (`p-*`, `px-*`, `m*-*`, `gap-*`, `space-*`, arbitrary values and variants included) in an app class
|
|
51
|
+
string, where a role prop names the relation instead (`Stack`/`Cluster` `gap`, `Grid` `kind`, `PaneSection`, `Page`;
|
|
52
|
+
`FE_SPACING_UTILITY`). It runs in the counting cohort (`COUNTING` of `src/lib/config.ts`): `starci app lint` reports
|
|
53
|
+
and counts every finding at warn and fails on none, while `starci app codemod spacing-roles` migrates the app.
|
|
54
|
+
|
|
49
55
|
The [button intent law](docs/button-intent.md) (CTA-6) requires every app Button and IconButton to
|
|
50
56
|
declare intent and leave variants to Grammar. It contains destructive confirms in their same-file
|
|
51
57
|
dialog, counts mutually exclusive render paths separately and reserves commit for the view's
|
package/dist/index.d.ts
CHANGED
|
@@ -37,9 +37,10 @@ export declare const rules: Record<string, Rule>;
|
|
|
37
37
|
* The levels this canon asks for, as the plugin's own opinion.
|
|
38
38
|
*
|
|
39
39
|
* Every FE canon rule is an error. Existing debt is fixed before adoption; lowering architecture
|
|
40
|
-
* to warn teaches every later author that the boundary is optional and is not a supported rollout.
|
|
40
|
+
* to warn teaches every later author that the boundary is optional and is not a supported rollout. The one
|
|
41
|
+
* exception is the closed counting cohort of lib/config.ts (`COUNTING`), an owner-ruled warn for one release.
|
|
41
42
|
*/
|
|
42
|
-
export declare const recommended: Record<string, "error">;
|
|
43
|
+
export declare const recommended: Record<string, "error" | "warn">;
|
|
43
44
|
/** Flat-config options that make inline directives ineffective rather than merely forbidden. */
|
|
44
45
|
export { linterOptions } from "./lib/config.js";
|
|
45
46
|
export { loadHfs } from "./lib/hfs.js";
|
package/dist/index.js
CHANGED
|
@@ -45,7 +45,8 @@ import { recommended as translationRecommended, rules as translationRules } from
|
|
|
45
45
|
import { recommended as typeSafetyRecommended, rules as typeSafetyRules } from "./type-safety.js";
|
|
46
46
|
import { recommended as projectGraphRecommended, rules as projectGraphRules } from "./project-graph.js";
|
|
47
47
|
import { recommended as sonarParityRecommended, rules as sonarParityRules } from "./sonar-parity.js";
|
|
48
|
-
import {
|
|
48
|
+
import { recommended as spacingRecommended, rules as spacingRules } from "./spacing.js";
|
|
49
|
+
import { buildFeConfig, buildPlainSourceConfig, COUNTING } from "./lib/config.js";
|
|
49
50
|
import { why } from "./lib/why.js";
|
|
50
51
|
import { recommended as uiRulingsRecommended, rules as uiRulingsRules } from "./ui-rulings.js";
|
|
51
52
|
/** Each law's contribution, kept separate so a duplicate name is detectable rather than silent. */
|
|
@@ -89,6 +90,7 @@ const CONTRIBUTIONS = [
|
|
|
89
90
|
{ law: "transport", rules: transportRules, recommended: transportRecommended },
|
|
90
91
|
{ law: "type-safety", rules: typeSafetyRules, recommended: typeSafetyRecommended },
|
|
91
92
|
{ law: "sonar-parity", rules: sonarParityRules, recommended: sonarParityRecommended },
|
|
93
|
+
{ law: "spacing", rules: spacingRules, recommended: spacingRecommended },
|
|
92
94
|
];
|
|
93
95
|
/** Every gathered law. */
|
|
94
96
|
export const lawOwners = CONTRIBUTIONS.map((entry) => entry.law);
|
|
@@ -120,7 +122,8 @@ export const rules = Object.fromEntries(CONTRIBUTIONS.flatMap((entry) => Object.
|
|
|
120
122
|
* The levels this canon asks for, as the plugin's own opinion.
|
|
121
123
|
*
|
|
122
124
|
* Every FE canon rule is an error. Existing debt is fixed before adoption; lowering architecture
|
|
123
|
-
* to warn teaches every later author that the boundary is optional and is not a supported rollout.
|
|
125
|
+
* to warn teaches every later author that the boundary is optional and is not a supported rollout. The one
|
|
126
|
+
* exception is the closed counting cohort of lib/config.ts (`COUNTING`), an owner-ruled warn for one release.
|
|
124
127
|
*/
|
|
125
128
|
export const recommended = Object.fromEntries(CONTRIBUTIONS.flatMap((entry) => Object.entries(entry.recommended)));
|
|
126
129
|
/** Flat-config options that make inline directives ineffective rather than merely forbidden. */
|
|
@@ -223,7 +226,7 @@ export const reactHooksRules = (reactHooks) => {
|
|
|
223
226
|
*/
|
|
224
227
|
export const starciFeConfig = ({ hfs } = {}) => {
|
|
225
228
|
const published = Object.keys(rules).map((name) => `starci-fe/${name}`);
|
|
226
|
-
const notRequested = published.filter((name) => recommended[name] !== "error");
|
|
229
|
+
const notRequested = published.filter((name) => recommended[name] !== (name in COUNTING ? "warn" : "error"));
|
|
227
230
|
if (notRequested.length > 0) {
|
|
228
231
|
throw new Error(`starciFeConfig: these published rules are not requested at error: ${notRequested.join(", ")}`);
|
|
229
232
|
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -27,6 +27,15 @@ export declare const IGNORED: readonly string[];
|
|
|
27
27
|
* and the Sonar rules that need the checker: S1874 (a deprecated API), S6551 (an object turned into text) and S7503 (an `async` function that never awaits).
|
|
28
28
|
*/
|
|
29
29
|
export declare const BORROWED: Readonly<Record<string, "error">>;
|
|
30
|
+
/**
|
|
31
|
+
* The counting cohort: a rule the owner ruled to ship at `warn` for one release while a codemod moves apps onto the API
|
|
32
|
+
* it asks for, each with the HFS rule id it enforces. `starci app lint` reports and counts its findings and never fails
|
|
33
|
+
* on them; the next cohort moves the rule to error and deletes its entry. No other rule may be below error.
|
|
34
|
+
*/
|
|
35
|
+
export declare const COUNTING: Readonly<Record<string, {
|
|
36
|
+
readonly rule: string;
|
|
37
|
+
readonly ruling: string;
|
|
38
|
+
}>>;
|
|
30
39
|
/**
|
|
31
40
|
* The flat config of one front-end repository.
|
|
32
41
|
*
|
package/dist/lib/config.js
CHANGED
|
@@ -40,6 +40,14 @@ export const BORROWED = Object.freeze({
|
|
|
40
40
|
"@typescript-eslint/require-await": "error",
|
|
41
41
|
});
|
|
42
42
|
const levelOf = (setting) => (Array.isArray(setting) ? setting[0] : setting);
|
|
43
|
+
/**
|
|
44
|
+
* The counting cohort: a rule the owner ruled to ship at `warn` for one release while a codemod moves apps onto the API
|
|
45
|
+
* it asks for, each with the HFS rule id it enforces. `starci app lint` reports and counts its findings and never fails
|
|
46
|
+
* on them; the next cohort moves the rule to error and deletes its entry. No other rule may be below error.
|
|
47
|
+
*/
|
|
48
|
+
export const COUNTING = Object.freeze({
|
|
49
|
+
"starci-fe/no-spacing-utility": { rule: "R350", ruling: "spacing-roles" },
|
|
50
|
+
});
|
|
43
51
|
/**
|
|
44
52
|
* The flat config of one front-end repository.
|
|
45
53
|
*
|
|
@@ -60,9 +68,9 @@ export const buildFeConfig = ({ hfs, plugin, source, reactHooks, tsPlugin, sonar
|
|
|
60
68
|
throw new Error(`starciFeConfig lints the fe side of an app (its eslint.config.mjs lives in fe/); this view is ${hfs.side ? `the ${hfs.side} side` : "the app root"}`);
|
|
61
69
|
if (Object.keys(source).length === 0)
|
|
62
70
|
throw new Error("starciFeConfig received an empty source recommendation - a config with no rules is not adoption");
|
|
63
|
-
const weak = Object.entries(source).filter(([, setting]) => levelOf(setting) !== "error").map(([name]) => name);
|
|
71
|
+
const weak = Object.entries(source).filter(([name, setting]) => levelOf(setting) !== (name in COUNTING ? "warn" : "error")).map(([name]) => name);
|
|
64
72
|
if (weak.length > 0)
|
|
65
|
-
throw new Error(`starciFeConfig refuses source rules not at error: ${weak.join(", ")}`);
|
|
73
|
+
throw new Error(`starciFeConfig refuses source rules not at error (or not at warn in the counting cohort): ${weak.join(", ")}`);
|
|
66
74
|
const settings = { starci: { hfs } };
|
|
67
75
|
return [
|
|
68
76
|
{ ignores: [...IGNORED] },
|
package/dist/lib/rule.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export type Comment = TSESTree.Comment;
|
|
|
21
21
|
export type Rules = Readonly<Record<string, Rule<string, readonly unknown[]>>>;
|
|
22
22
|
/** The level a law asks for each of its rules, by `starci-fe/<name>`. */
|
|
23
23
|
export type Recommended = Readonly<Record<string, "error">>;
|
|
24
|
+
/** The level of a rule in its counting cohort (lib/config.ts `COUNTING`): reported and counted, never failing. */
|
|
25
|
+
export type CountingRecommended = Readonly<Record<string, "warn">>;
|
|
24
26
|
/**
|
|
25
27
|
* A rule with its message ids and options inferred from the literal: `defineRule({ meta: { messages: { a: "..." } }, ... })`
|
|
26
28
|
* types `context.report({ messageId })` against `a`. It returns the object it is given.
|
package/dist/lib/why.d.ts
CHANGED
package/dist/lib/why.js
CHANGED
|
@@ -32,6 +32,11 @@ export const why = {
|
|
|
32
32
|
en: "`<file>` insets a horizontal Grammar divider with wrapper padding.",
|
|
33
33
|
fix: "Move horizontal padding to the sibling content bands so the divider reaches both surface edges.",
|
|
34
34
|
},
|
|
35
|
+
"no-spacing-utility": {
|
|
36
|
+
code: "FE_SPACING_UTILITY",
|
|
37
|
+
en: "`<file>` writes a padding, margin, gap or space utility (`<what>`): a distance the Grammar owns.",
|
|
38
|
+
fix: "Name the relation instead: a Stack or Cluster gap role, a Grid kind, or the inset of the Grammar box that holds the content.",
|
|
39
|
+
},
|
|
35
40
|
"no-hand-built-panes": {
|
|
36
41
|
code: "FE_HAND_BUILT_PANES",
|
|
37
42
|
en: "`<file>` draws a multi-pane frame by hand: a vertical Divider between columns of a flex row, or a flex row of columns inside a joined SurfaceCard.",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type CountingRecommended } from "./lib/rule.js";
|
|
2
|
+
/** The role a Tailwind gap step names, as the spacing-roles codemod maps it; a step with no role is a judgement. */
|
|
3
|
+
export declare const GAP_STEP_ROLE: Readonly<Record<string, string>>;
|
|
4
|
+
/** The utility a class names once its variants (`sm:`, `hover:`, `[&>*]:`) and importance (`!`) are dropped. */
|
|
5
|
+
export declare const baseUtility: (cls: string) => string;
|
|
6
|
+
/** Every spacing utility of a class string, in the order written. */
|
|
7
|
+
export declare const spacingUtilities: (text: string) => string[];
|
|
8
|
+
/** No padding, margin, gap or space utility in an app class string: the relation is named, the step is the Grammar's. */
|
|
9
|
+
export declare const noSpacingUtility: import("./lib/rule.js").Rule<"utility", []>;
|
|
10
|
+
/** The rules of the spacing law. */
|
|
11
|
+
export declare const rules: {
|
|
12
|
+
"no-spacing-utility": import("./lib/rule.js").Rule<"utility", []>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The rule runs in its counting cohort (lib/config.ts `COUNTING`): every finding is reported and counted by `starci app
|
|
16
|
+
* lint`, none fails the gate, while `starci app codemod spacing-roles` moves the app onto role props.
|
|
17
|
+
*/
|
|
18
|
+
export declare const recommended: CountingRecommended;
|
package/dist/spacing.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spacing is the Grammar's (HFS R350): an app names the relation between the blocks it places through a role prop
|
|
3
|
+
* (`<Stack gap="section">`, `<Cluster gap="inline">`, `<Grid kind="cards">`, `PaneSection`, `Page`), and the Grammar
|
|
4
|
+
* resolves the role to its step. A padding, margin, gap or space utility in an app class string is a second owner of
|
|
5
|
+
* a distance the Grammar already owns.
|
|
6
|
+
*/
|
|
7
|
+
import { classTextVisitors } from "./lib/class-text.js";
|
|
8
|
+
import { exemptFreestyle } from "./lib/freestyle.js";
|
|
9
|
+
import { defineRule } from "./lib/rule.js";
|
|
10
|
+
/** The spacing utility families: padding, margin, gap and the space-between utilities, each side and axis. */
|
|
11
|
+
const SPACING_UTILITY = /^-?(?:p|px|py|pt|pb|ps|pe|pl|pr|m|mx|my|mt|mb|ms|me|ml|mr|gap|gap-x|gap-y|space-x|space-y)-(.+)$/;
|
|
12
|
+
/** The role a Tailwind gap step names, as the spacing-roles codemod maps it; a step with no role is a judgement. */
|
|
13
|
+
export const GAP_STEP_ROLE = Object.freeze({
|
|
14
|
+
"0": "seam (a joined face or a divider)",
|
|
15
|
+
"0.5": "identity",
|
|
16
|
+
"1": "identity",
|
|
17
|
+
"1.5": "identity",
|
|
18
|
+
"2": "inline",
|
|
19
|
+
"2.5": "inline",
|
|
20
|
+
"3": "row",
|
|
21
|
+
"4": "section",
|
|
22
|
+
"5": "section",
|
|
23
|
+
"6": "region",
|
|
24
|
+
"8": "region",
|
|
25
|
+
});
|
|
26
|
+
/** The utility a class names once its variants (`sm:`, `hover:`, `[&>*]:`) and importance (`!`) are dropped. */
|
|
27
|
+
export const baseUtility = (cls) => {
|
|
28
|
+
let depth = 0;
|
|
29
|
+
let start = 0;
|
|
30
|
+
for (let index = 0; index < cls.length; index += 1) {
|
|
31
|
+
const char = cls[index];
|
|
32
|
+
if (char === "[" || char === "(")
|
|
33
|
+
depth += 1;
|
|
34
|
+
else if (char === "]" || char === ")")
|
|
35
|
+
depth -= 1;
|
|
36
|
+
else if (char === ":" && depth === 0)
|
|
37
|
+
start = index + 1;
|
|
38
|
+
}
|
|
39
|
+
return cls.slice(start).replace(/^!|!$/g, "");
|
|
40
|
+
};
|
|
41
|
+
/** Every spacing utility of a class string, in the order written. */
|
|
42
|
+
export const spacingUtilities = (text) => text.split(/\s+/).filter((cls) => SPACING_UTILITY.test(baseUtility(cls)));
|
|
43
|
+
/** What to write instead of one spacing utility. */
|
|
44
|
+
const instead = (cls) => {
|
|
45
|
+
const base = baseUtility(cls);
|
|
46
|
+
const match = SPACING_UTILITY.exec(base);
|
|
47
|
+
const family = base.replace(/^-/, "").split("-")[0] ?? "";
|
|
48
|
+
if (family === "gap" || family === "space") {
|
|
49
|
+
const role = GAP_STEP_ROLE[match?.[1] ?? ""];
|
|
50
|
+
return role === undefined
|
|
51
|
+
? "the role of the relation on a Stack, Cluster or Grid gap"
|
|
52
|
+
: `gap="${role}" on the Stack or Cluster that holds these siblings`;
|
|
53
|
+
}
|
|
54
|
+
if (family.startsWith("m"))
|
|
55
|
+
return "the parent's gap (a Stack or Cluster role); the Grammar alone places a margin";
|
|
56
|
+
return "the inset of the Grammar box that holds this content (SurfaceCard, PaneSection, Page, Dialog); a box of your own takes none";
|
|
57
|
+
};
|
|
58
|
+
/** No padding, margin, gap or space utility in an app class string: the relation is named, the step is the Grammar's. */
|
|
59
|
+
export const noSpacingUtility = exemptFreestyle(defineRule({
|
|
60
|
+
meta: {
|
|
61
|
+
type: "suggestion",
|
|
62
|
+
docs: { description: "Spacing is the Grammar's: an app names the relation through a role prop, never a spacing utility." },
|
|
63
|
+
schema: [],
|
|
64
|
+
messages: {
|
|
65
|
+
utility: "`{{cls}}` writes a distance the Grammar owns. Use {{instead}} (R350 `FE_SPACING_UTILITY`).",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
create(context) {
|
|
69
|
+
return classTextVisitors(context, (node, text) => {
|
|
70
|
+
if (!text)
|
|
71
|
+
return;
|
|
72
|
+
for (const cls of spacingUtilities(text))
|
|
73
|
+
context.report({ node, messageId: "utility", data: { cls, instead: instead(cls) } });
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
}));
|
|
77
|
+
/** The rules of the spacing law. */
|
|
78
|
+
export const rules = { "no-spacing-utility": noSpacingUtility };
|
|
79
|
+
/**
|
|
80
|
+
* The rule runs in its counting cohort (lib/config.ts `COUNTING`): every finding is reported and counted by `starci app
|
|
81
|
+
* lint`, none fails the gate, while `starci app codemod spacing-roles` moves the app onto role props.
|
|
82
|
+
*/
|
|
83
|
+
export const recommended = { "starci-fe/no-spacing-utility": "warn" };
|
package/dist/surface-rules.js
CHANGED
|
@@ -289,11 +289,11 @@ const inSectionCaption = (context, node) => {
|
|
|
289
289
|
export const nestedControlVariant = defineRule({
|
|
290
290
|
meta: {
|
|
291
291
|
type: "problem",
|
|
292
|
-
docs: { description: "ANATOMY-2: Grammar context supplies field variants;
|
|
292
|
+
docs: { description: "ANATOMY-2: Grammar context supplies field variants; Tabs are the underline bar, never the pill." },
|
|
293
293
|
schema: [],
|
|
294
294
|
messages: {
|
|
295
295
|
variant: "FE_NESTED_VARIANT: app fields cannot set a static variant. Omit variant so Grammar's surface context supplies the appropriate default.",
|
|
296
|
-
tabs: "FE_NESTED_VARIANT: Tabs
|
|
296
|
+
tabs: "FE_NESTED_VARIANT: Tabs are the underline bar, their labels text on the content edge; variant=\"secondary\" is the pill, a filter's shape. Omit variant, or filter a collection with a selectable TagGroup.",
|
|
297
297
|
},
|
|
298
298
|
},
|
|
299
299
|
create(context) {
|
|
@@ -308,9 +308,9 @@ export const nestedControlVariant = defineRule({
|
|
|
308
308
|
context.report({ node: variant, messageId: "variant" });
|
|
309
309
|
}
|
|
310
310
|
else if (name[0] === "Tabs") {
|
|
311
|
-
const
|
|
312
|
-
if (
|
|
313
|
-
context.report({ node:
|
|
311
|
+
const pill = node.attributes.find((entry) => propStrings(context, entry, "variant", "variant").some((value) => value.text === "secondary"));
|
|
312
|
+
if (pill)
|
|
313
|
+
context.report({ node: pill, messageId: "tabs" });
|
|
314
314
|
}
|
|
315
315
|
},
|
|
316
316
|
};
|
package/docs/surface-rules.md
CHANGED
|
@@ -4,7 +4,7 @@ Law module: `surface-rules.mjs`. HFS rule: R318. Canon: ANATOMY-2 and PADDING-0.
|
|
|
4
4
|
|
|
5
5
|
## `starci-fe/nested-control-variant`
|
|
6
6
|
|
|
7
|
-
Finding code: `FE_NESTED_VARIANT`. Grammar context supplies field variants on the page background and inside painted surfaces. App code omits every static `variant` on layer-aware fields, including fields rendered by imported or helper components. Named import aliases, namespace imports, static constants and props spreads preserve Grammar ownership.
|
|
7
|
+
Finding code: `FE_NESTED_VARIANT`. Grammar context supplies field variants on the page background and inside painted surfaces. App code omits every static `variant` on layer-aware fields, including fields rendered by imported or helper components. Named import aliases, namespace imports, static constants and props spreads preserve Grammar ownership. `Tabs` are the underline bar everywhere, their labels text on the content edge, so the pill (`variant="secondary"`) is refused wherever it is set: a pill is a filter's shape, and a filter over a collection is a selectable `TagGroup`.
|
|
8
8
|
|
|
9
9
|
Invalid:
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -31,42 +31,42 @@ 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.
|
|
34
|
+
version: 0.18.0
|
|
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.11.
|
|
41
|
+
version: 3.11.1
|
|
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.
|
|
48
|
+
version: 9.8.0
|
|
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.
|
|
55
|
+
version: 0.18.0
|
|
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.
|
|
62
|
+
version: 0.18.0
|
|
63
63
|
group: starci
|
|
64
64
|
install: registry
|
|
65
65
|
side: fe
|
|
66
66
|
source: packages/eslint-grammar-offset-pop/package.json
|
|
67
67
|
why: 'the Offset Pop Grammar component API lint, versioned with @starci/grammar; its shared component contract comes from the StarCi family lint without copying rules.'
|
|
68
68
|
'@starci/stylelint-canon':
|
|
69
|
-
version: 2.
|
|
69
|
+
version: 2.4.0
|
|
70
70
|
group: starci
|
|
71
71
|
install: registry
|
|
72
72
|
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.
|
|
109
|
+
version: 4.16.0
|
|
110
110
|
group: starci
|
|
111
111
|
install: registry
|
|
112
112
|
side: both
|