@starci/hfs 4.16.5 → 4.17.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 +36 -3
- package/README.md +4 -4
- package/codemod/loading-states.d.mts +50 -0
- package/codemod/loading-states.mjs +384 -0
- package/codemod/main.d.mts +1 -1
- package/codemod/main.mjs +46 -5
- package/codemod/page-navigation.d.mts +50 -0
- package/codemod/page-navigation.mjs +278 -0
- package/codemod/section-frame.d.mts +50 -0
- package/codemod/section-frame.mjs +483 -0
- package/codemod/small-renames.d.mts +54 -0
- package/codemod/small-renames.mjs +614 -0
- package/codemod/surface-card-kinds.d.mts +49 -0
- package/codemod/surface-card-kinds.mjs +404 -0
- package/codemod/theme-tokens.d.mts +45 -0
- package/codemod/theme-tokens.mjs +114 -0
- package/codemod/tier-binding.d.mts +50 -0
- package/codemod/tier-binding.mjs +214 -0
- package/package.json +1 -1
- package/runtime/knowledge/grammars/core/playbook.yaml +41 -51
- package/runtime/knowledge/grammars/offset-pop/playbook.yaml +5 -5
- package/runtime/knowledge/hfs/canon-pins.yaml +7 -7
- package/runtime/knowledge/hfs/rules.yaml +255 -30
- package/runtime/knowledge/hfs/slots.yaml +12 -5
- package/runtime/knowledge/patterns/fe/folder.yaml +1 -1
- package/runtime/modules/kernel/failure-codes.yaml +23 -13
- package/runtime/scripts/hfs/architecture/clones.mjs +12 -2
- package/runtime/scripts/hfs/architecture/component-tiers.d.mts +46 -0
- package/runtime/scripts/hfs/architecture/component-tiers.mjs +161 -0
- package/runtime/scripts/hfs/architecture/config.mjs +1 -1
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
- package/runtime/scripts/hfs/architecture/frontend.mjs +2 -2
- package/runtime/scripts/hfs/architecture/index.mjs +6 -0
- package/runtime/scripts/hfs/architecture/render-graph.d.mts +125 -0
- package/runtime/scripts/hfs/architecture/render-graph.mjs +449 -0
- package/runtime/scripts/hfs/architecture/render-prop-across-server.d.mts +23 -0
- package/runtime/scripts/hfs/architecture/render-prop-across-server.mjs +58 -0
- package/runtime/scripts/hfs/architecture/surface-nesting.d.mts +44 -0
- package/runtime/scripts/hfs/architecture/surface-nesting.mjs +127 -0
- package/runtime/scripts/hfs/architecture/unit-spec-providers.d.mts +2 -1
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +12 -5
- package/runtime/scripts/hfs/slot-manifest-shape.mjs +16 -1
- package/runtime/scripts/work/draw/draw-dna.mjs +1 -1
- package/scaffold/add-fe-layout.mjs +3 -3
- package/scaffold/add-fe-overlay.mjs +4 -4
- package/scaffold/add-fe-page.mjs +1 -1
- package/scaffold/add-fe-unit.d.mts +2 -1
- package/scaffold/add-fe-unit.mjs +14 -12
- package/scaffold/add.d.mts +1 -1
- package/scaffold/add.mjs +1 -1
- package/src/main.mjs +4 -4
- package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/component.tsx +3 -4
- package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +8 -5
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +1 -3
- package/templates/fe/skeleton/packages/__project__-ui/src/branches/Providers/index.tsx +3 -3
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/LocaleShell/index.tsx +3 -5
- package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +1 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/brand/brand.css +1 -2
- package/templates/fe/units/block/component.tsx.tpl +2 -3
- package/templates/fe/units/branch/index.tsx.tpl +4 -3
- package/templates/fe/units/composite/index.tsx.tpl +3 -3
- package/templates/fe/units/frame/index.tsx.tpl +10 -0
- package/templates/fe/units/layout/index.tsx.tpl +2 -2
- package/templates/fe/units/leaf/index.tsx.tpl +3 -3
- package/templates/fe/units/page/component.tsx.tpl +2 -2
- package/templates/fe/units/page/index.tsx.tpl +2 -2
- package/templates/fe/units/page/stories.tsx.tpl +2 -2
- package/templates/fe/units/slot/SlotView.tsx.tpl +11 -10
- package/templates/fe/units/slot/data-slot.ts.tpl +20 -8
- package/templates/fe/units/slot/slot.shared.ts.tpl +52 -11
- package/templates/fe/units/slot/useSlotOfReads.ts.tpl +19 -0
- /package/templates/fe/skeleton/packages/__project__-ui/src/{composites → frames}/SiteShell/classNames.ts +0 -0
- /package/templates/fe/skeleton/packages/__project__-ui/src/{composites → frames}/SiteShell/index.tsx +0 -0
- /package/templates/fe/skeleton-lite/apps/web/src/components/{composites → frames}/SiteShell/classNames.ts +0 -0
- /package/templates/fe/skeleton-lite/apps/web/src/components/{composites → frames}/SiteShell/index.tsx +0 -0
|
@@ -75,6 +75,7 @@ rules:
|
|
|
75
75
|
- {kind: machine, id: required-files, at: scripts/hfs/architecture/required-files.mjs}
|
|
76
76
|
- {kind: machine, id: repository-tree-required, at: scripts/hfs/architecture/hfs.mjs}
|
|
77
77
|
- {kind: runtime, id: runtime-required, at: scripts/hfs/runtime-check.mjs}
|
|
78
|
+
docs: [contexts/code/be/folder.md]
|
|
78
79
|
- id: R03
|
|
79
80
|
code: "HFS_UNTRACKED_ROOT_ENTRY"
|
|
80
81
|
editions: [lite, full]
|
|
@@ -155,6 +156,7 @@ rules:
|
|
|
155
156
|
failureCodes: ["HFS_AGENT_DATA_TRACKED"]
|
|
156
157
|
enforcers:
|
|
157
158
|
- {kind: work-validate, id: agent-data-tracked, at: scripts/work/validate/check-example-work.mjs}
|
|
159
|
+
docs: [contexts/hfs/README.md#rule-notes]
|
|
158
160
|
- id: R09
|
|
159
161
|
code: "HFS_IDENTITY_CUSTODY"
|
|
160
162
|
law: "An identity points its secret at `secrets/identity-<slug>.enc`; UAT chooses by role."
|
|
@@ -164,6 +166,7 @@ rules:
|
|
|
164
166
|
enforcers:
|
|
165
167
|
- {kind: work-validate, id: sealed-custody-location, at: scripts/work/validate/check-work-artifacts.mjs}
|
|
166
168
|
- {kind: work-validate, id: identity-custody-role, at: scripts/work/validate/check-example-work.mjs}
|
|
169
|
+
docs: [contexts/hfs/README.md#rule-notes]
|
|
167
170
|
- id: R10
|
|
168
171
|
code: "HFS_STACKS_SHAPE"
|
|
169
172
|
editions: [lite, full]
|
|
@@ -336,6 +339,7 @@ rules:
|
|
|
336
339
|
- {kind: eslint-be, id: no-inline-suppression}
|
|
337
340
|
- {kind: eslint-fe, id: no-inline-lint-config}
|
|
338
341
|
- {kind: stylelint, id: no-inline-lint-config}
|
|
342
|
+
docs: [contexts/code/shared/format.md]
|
|
339
343
|
- id: R19
|
|
340
344
|
code: "HFS_FORMAT"
|
|
341
345
|
editions: ["lite", "full"]
|
|
@@ -365,6 +369,7 @@ rules:
|
|
|
365
369
|
- {kind: eslint-fe, id: file-size-growth}
|
|
366
370
|
- {kind: sonar, id: cognitive-complexity, at: knowledge/sonar-gate.yaml}
|
|
367
371
|
- {kind: runtime, id: runtime-size, at: scripts/hfs/runtime-rules/size.mjs}
|
|
372
|
+
docs: [contexts/code/be/function.md]
|
|
368
373
|
- id: R21
|
|
369
374
|
code: "HFS_DUPLICATE_CODE"
|
|
370
375
|
law: "No duplicated blocks or twice-defined helpers."
|
|
@@ -378,6 +383,7 @@ rules:
|
|
|
378
383
|
- {kind: eslint-fe, id: duplicate-symbol}
|
|
379
384
|
- {kind: eslint-fe, id: cross-app-duplicate}
|
|
380
385
|
- {kind: sonar, id: duplicated-lines-density, at: knowledge/sonar-gate.yaml}
|
|
386
|
+
docs: [contexts/code/be/function.md]
|
|
381
387
|
- id: R22
|
|
382
388
|
code: "HFS_TS_STRICT"
|
|
383
389
|
editions: ["lite", "full"]
|
|
@@ -474,10 +480,10 @@ rules:
|
|
|
474
480
|
witness:
|
|
475
481
|
- {path: packages/eslint/be/legacy-owner-graph.a16.spec.mjs, editions: [lite, full]}
|
|
476
482
|
templateNa: "Tier and package direction are constraints on cross-owner import graphs; no dedicated cross-owner graph scaffold is generated."
|
|
477
|
-
law: "The tier direction matrix; a shared package never imports an app. Front-end page and layout owners may import overlay entries; every other cross-owner feature-to-feature edge is forbidden, including layout-to-page and overlay-to-overlay. Components never import overlays. The one platform-to-domain edge is the lite entity registry (be.platform.database.entities) importing a capability's `<c>Entities` alone from its `index.ts`."
|
|
483
|
+
law: "The tier direction matrix; a shared package never imports an app. Front-end page and layout owners may import overlay entries; every other cross-owner feature-to-feature edge is forbidden, including layout-to-page and overlay-to-overlay. Components never import overlays. The one platform-to-domain edge is the lite entity registry (be.platform.database.entities) importing a capability's `<c>Entities` alone from its `index.ts`. A component library's tiers (`tiers.grammar`: leaf, branch, composite, frame) import nothing above themselves, and its `data-tier` stamp is its folder."
|
|
478
484
|
kinds: [lint, design]
|
|
479
485
|
gates: [settle, land, ci]
|
|
480
|
-
failureCodes: ["BE_TIER_DIRECTION", "FE_TIER_DIRECTION", "ARCH_PACKAGE_IMPORTS_APP"]
|
|
486
|
+
failureCodes: ["BE_TIER_DIRECTION", "FE_TIER_DIRECTION", "FE_GRAMMAR_TIER_DIRECTION", "FE_GRAMMAR_TIER_STAMP", "ARCH_PACKAGE_IMPORTS_APP"]
|
|
481
487
|
enforcers:
|
|
482
488
|
- {kind: eslint-be, id: tier-direction}
|
|
483
489
|
- {kind: eslint-fe, id: tier-direction}
|
|
@@ -1266,7 +1272,8 @@ rules:
|
|
|
1266
1272
|
- {kind: stylelint, id: globals-shape}
|
|
1267
1273
|
- {kind: stylelint, id: globals-import-order}
|
|
1268
1274
|
- {kind: stylelint, id: no-token-redefinition}
|
|
1269
|
-
- {kind: stylelint, id:
|
|
1275
|
+
- {kind: stylelint, id: no-grammar-private-token}
|
|
1276
|
+
- {kind: stylelint, id: theme-layer-shape}
|
|
1270
1277
|
- {kind: stylelint, id: status-contrast}
|
|
1271
1278
|
- {kind: stylelint, id: no-css-module}
|
|
1272
1279
|
- {kind: stylelint, id: no-class-selector}
|
|
@@ -1275,9 +1282,7 @@ rules:
|
|
|
1275
1282
|
- {kind: eslint-fe, id: no-raw-brand-value}
|
|
1276
1283
|
- {kind: eslint-grammar-core, id: no-fractional-step}
|
|
1277
1284
|
- {kind: eslint-fe, id: no-arbitrary-value}
|
|
1278
|
-
- {kind: eslint-grammar-core, id: no-hand-rolled-heading}
|
|
1279
1285
|
- {kind: eslint-grammar-core, id: no-unresolved-token-class}
|
|
1280
|
-
- {kind: eslint-grammar-core, id: no-heading-tag-outside-heading-component}
|
|
1281
1286
|
- {kind: eslint-grammar-core, id: status-text-uses-soft-foreground}
|
|
1282
1287
|
proves:
|
|
1283
1288
|
- {path: packages/stylelint/source-resolves.spec.mjs, editions: [lite, full], codes: [FE_STYLE_SOURCE_UNRESOLVED]}
|
|
@@ -1859,7 +1864,7 @@ rules:
|
|
|
1859
1864
|
editions: ["lite","full"]
|
|
1860
1865
|
docs: ["docs/frontend-source-authoring.md#r91-source-form"]
|
|
1861
1866
|
proves: [{"path":"packages/eslint/fe/r91-source-form.spec.mjs","editions":["lite","full"]}]
|
|
1862
|
-
exemplifiedBy: [{"path":"packages/hfs/templates/fe/skeleton-lite/apps/web/src/components/
|
|
1867
|
+
exemplifiedBy: [{"path":"packages/hfs/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/index.tsx","edition":"lite"},{"path":"packages/hfs/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx","edition":"full"},{"path":"packages/eslint/fe/r91-source-form.spec.mjs","editions":["lite","full"]}]
|
|
1863
1868
|
witness: [{"path":"packages/eslint/fe/r91-source-form.spec.mjs","edition":"lite","codes":["FE_SOURCE_FORM","FE_SOURCE_LAYOUT_INVALID"]},{"path":"packages/eslint/fe/r91-source-form.spec.mjs","edition":"full","codes":["FE_SOURCE_FORM","FE_SOURCE_LAYOUT_INVALID"]}]
|
|
1864
1869
|
gates: ["pre-commit","settle","land","ci"]
|
|
1865
1870
|
- id: R92
|
|
@@ -1875,6 +1880,7 @@ rules:
|
|
|
1875
1880
|
- {kind: eslint-fe, id: no-public-classname-prop}
|
|
1876
1881
|
- {kind: eslint-fe, id: no-public-frame-css-props}
|
|
1877
1882
|
- {kind: eslint-fe, id: no-css-door-type-laundering}
|
|
1883
|
+
- {kind: eslint-fe, id: tier-children}
|
|
1878
1884
|
- {kind: eslint-fe, id: base-props-atom}
|
|
1879
1885
|
- {kind: eslint-fe, id: base-import-pair}
|
|
1880
1886
|
- {kind: eslint-fe, id: no-data-status-shape}
|
|
@@ -1915,10 +1921,8 @@ rules:
|
|
|
1915
1921
|
enforcers:
|
|
1916
1922
|
- {kind: eslint-grammar-core, id: no-vendor-icon-outside-icon-leaf}
|
|
1917
1923
|
- {kind: eslint-grammar-core, id: heroicons-is-the-glyph-vendor}
|
|
1918
|
-
- {kind: eslint-grammar-core, id: no-off-scale-glyph-size}
|
|
1919
1924
|
- {kind: eslint-grammar-core, id: no-icon-size-class}
|
|
1920
1925
|
- {kind: eslint-grammar-core, id: destructive-menu-entry-needs-danger-tone}
|
|
1921
|
-
- {kind: eslint-grammar-core, id: no-decorative-icon-in-metric-cell}
|
|
1922
1926
|
- {kind: eslint-grammar-core, id: vendor-primitive-has-named-owner}
|
|
1923
1927
|
- {kind: eslint-fe, id: grammar-entry}
|
|
1924
1928
|
- {kind: machine, id: grammar, at: scripts/hfs/architecture/frontend-grammar.mjs}
|
|
@@ -3809,6 +3813,7 @@ rules:
|
|
|
3809
3813
|
failureCodes: ["RIGHTS_ROLE_DENIED", "RIGHTS_GIT_PUSH", "RIGHTS_GIT_TAG", "RIGHTS_GIT_COMMIT", "RIGHTS_GIT_SYNC", "RIGHTS_NPM_PUBLISH", "RIGHTS_NPM_CI_UNLOCKED", "RIGHTS_SUITE_RUN", "RIGHTS_RELEASE_CUT", "RIGHTS_RAW_TOOL"]
|
|
3810
3814
|
enforcers:
|
|
3811
3815
|
- {kind: runtime, id: role-rights, at: scripts/hfs/runtime-rules/rights-policy.mjs}
|
|
3816
|
+
docs: [contexts/hfs/README.md#rule-notes]
|
|
3812
3817
|
- id: R224
|
|
3813
3818
|
code: "RIGHTS_PROTECTED_ZONE"
|
|
3814
3819
|
law: "The protected zone declared once in modules/kernel/protected-zone.yaml is edited only by the owner path; a supervisor self-upgrade files an owner proposal, and an op never writes the .claude runtime checkout."
|
|
@@ -3818,6 +3823,7 @@ rules:
|
|
|
3818
3823
|
failureCodes: ["RIGHTS_PROTECTED_ZONE", "RIGHTS_OP_RUNTIME_WRITE"]
|
|
3819
3824
|
enforcers:
|
|
3820
3825
|
- {kind: runtime, id: protected-zone, at: scripts/hfs/runtime-rules/rights-policy.mjs}
|
|
3826
|
+
docs: [contexts/hfs/README.md#rule-notes]
|
|
3821
3827
|
- id: R225
|
|
3822
3828
|
code: "RT_HOOK_SHAPE"
|
|
3823
3829
|
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."
|
|
@@ -3827,6 +3833,7 @@ rules:
|
|
|
3827
3833
|
failureCodes: ["RT_HOOK_SHAPE", "RIGHTS_PUSH_NOT_RELEASE"]
|
|
3828
3834
|
enforcers:
|
|
3829
3835
|
- {kind: runtime, id: hook-shape, at: scripts/hfs/runtime-rules/hook-shape.mjs}
|
|
3836
|
+
docs: [contexts/hfs/README.md#rule-notes]
|
|
3830
3837
|
- id: R227
|
|
3831
3838
|
code: "RT_SYNTAX_INVALID"
|
|
3832
3839
|
editions: [lite, full]
|
|
@@ -5468,7 +5475,6 @@ rules:
|
|
|
5468
5475
|
gates: [pre-commit, settle, land, ci]
|
|
5469
5476
|
failureCodes: ["FE_NESTED_VARIANT", "FE_LIST_PANEL_PADDING", "FE_GRAMMAR_LAYOUT_ONLY", "FE_DIVIDER_FULL_BLEED"]
|
|
5470
5477
|
enforcers:
|
|
5471
|
-
- {kind: eslint-fe, id: nested-control-variant}
|
|
5472
5478
|
- {kind: eslint-fe, id: no-list-panel-padding}
|
|
5473
5479
|
- {kind: eslint-fe, id: no-app-surface-paint}
|
|
5474
5480
|
- {kind: eslint-fe, id: divider-full-bleed}
|
|
@@ -5479,15 +5485,15 @@ rules:
|
|
|
5479
5485
|
templateNa: "Product composition is proved by the FE canon's Grammar import and surface fixtures; a template cannot choose product slots or lists."
|
|
5480
5486
|
proves:
|
|
5481
5487
|
- {path: packages/eslint/fe/divider-full-bleed.spec.mjs, editions: [lite, full], codes: [FE_DIVIDER_FULL_BLEED]}
|
|
5482
|
-
- {path: packages/eslint/fe/surface-rules.spec.mjs, editions: [lite, full], codes: [
|
|
5488
|
+
- {path: packages/eslint/fe/surface-rules.spec.mjs, editions: [lite, full], codes: [FE_LIST_PANEL_PADDING, FE_GRAMMAR_LAYOUT_ONLY]}
|
|
5483
5489
|
- {path: packages/eslint/fe/ui-rulings.spec.mjs, editions: [lite, full], codes: [FE_NESTED_VARIANT, FE_GRAMMAR_LAYOUT_ONLY]}
|
|
5484
5490
|
exemplifiedBy:
|
|
5485
5491
|
- {path: packages/eslint/fe/divider-full-bleed.spec.mjs, editions: [lite, full], codes: [FE_DIVIDER_FULL_BLEED]}
|
|
5486
|
-
- {path: packages/eslint/fe/surface-rules.spec.mjs, editions: [lite, full], codes: [
|
|
5492
|
+
- {path: packages/eslint/fe/surface-rules.spec.mjs, editions: [lite, full], codes: [FE_LIST_PANEL_PADDING, FE_GRAMMAR_LAYOUT_ONLY]}
|
|
5487
5493
|
- {path: packages/eslint/fe/ui-rulings.spec.mjs, editions: [lite, full], codes: [FE_NESTED_VARIANT, FE_GRAMMAR_LAYOUT_ONLY]}
|
|
5488
5494
|
witness:
|
|
5489
5495
|
- {path: packages/eslint/fe/divider-full-bleed.spec.mjs, editions: [lite, full], codes: [FE_DIVIDER_FULL_BLEED]}
|
|
5490
|
-
- {path: packages/eslint/fe/surface-rules.spec.mjs, editions: [lite, full], codes: [
|
|
5496
|
+
- {path: packages/eslint/fe/surface-rules.spec.mjs, editions: [lite, full], codes: [FE_LIST_PANEL_PADDING, FE_GRAMMAR_LAYOUT_ONLY]}
|
|
5491
5497
|
- {path: packages/eslint/fe/ui-rulings.spec.mjs, editions: [lite, full], codes: [FE_NESTED_VARIANT, FE_GRAMMAR_LAYOUT_ONLY]}
|
|
5492
5498
|
cases:
|
|
5493
5499
|
- id: case-1
|
|
@@ -5501,10 +5507,9 @@ rules:
|
|
|
5501
5507
|
law: "An app Button or IconButton declares an intent and never a variant; Grammar resolves the variant from intent and placement (CTA-6). A destructive confirm lives in a confirmation dialog, a render holds at most one commit, and a collection item holds none."
|
|
5502
5508
|
kinds: [codemod, lint, design]
|
|
5503
5509
|
gates: [pre-commit, settle, land, ci]
|
|
5504
|
-
failureCodes: ["FE_BUTTON_INTENT", "FE_BUTTON_INTENT_REQUIRED", "
|
|
5510
|
+
failureCodes: ["FE_BUTTON_INTENT", "FE_BUTTON_INTENT_REQUIRED", "FE_DESTRUCTIVE_CONFIRM_IN_DIALOG", "FE_SINGLE_COMMIT_PER_RENDER", "FE_NO_COMMIT_IN_COLLECTION_ITEM"]
|
|
5505
5511
|
enforcers:
|
|
5506
5512
|
- {kind: eslint-fe, id: button-intent-required}
|
|
5507
|
-
- {kind: eslint-fe, id: button-no-variant}
|
|
5508
5513
|
- {kind: eslint-fe, id: destructive-confirm-in-dialog}
|
|
5509
5514
|
- {kind: eslint-fe, id: single-commit-per-render}
|
|
5510
5515
|
- {kind: eslint-fe, id: no-commit-in-collection-item}
|
|
@@ -5513,11 +5518,11 @@ rules:
|
|
|
5513
5518
|
- knowledge/ui/composition/cta.yaml
|
|
5514
5519
|
- docs/code-pattern-enforcement.md#r319-button-intent
|
|
5515
5520
|
proves:
|
|
5516
|
-
- {path: packages/eslint/fe/button-intent.spec.mjs, editions: [lite, full], codes: [FE_BUTTON_INTENT, FE_BUTTON_INTENT_REQUIRED,
|
|
5521
|
+
- {path: packages/eslint/fe/button-intent.spec.mjs, editions: [lite, full], codes: [FE_BUTTON_INTENT, FE_BUTTON_INTENT_REQUIRED, FE_DESTRUCTIVE_CONFIRM_IN_DIALOG, FE_SINGLE_COMMIT_PER_RENDER, FE_NO_COMMIT_IN_COLLECTION_ITEM]}
|
|
5517
5522
|
exemplifiedBy:
|
|
5518
|
-
- {path: packages/eslint/fe/button-intent.spec.mjs, editions: [lite, full], codes: [FE_BUTTON_INTENT, FE_BUTTON_INTENT_REQUIRED,
|
|
5523
|
+
- {path: packages/eslint/fe/button-intent.spec.mjs, editions: [lite, full], codes: [FE_BUTTON_INTENT, FE_BUTTON_INTENT_REQUIRED, FE_DESTRUCTIVE_CONFIRM_IN_DIALOG, FE_SINGLE_COMMIT_PER_RENDER, FE_NO_COMMIT_IN_COLLECTION_ITEM]}
|
|
5519
5524
|
witness:
|
|
5520
|
-
- {path: packages/eslint/fe/button-intent.spec.mjs, editions: [lite, full], codes: [FE_BUTTON_INTENT, FE_BUTTON_INTENT_REQUIRED,
|
|
5525
|
+
- {path: packages/eslint/fe/button-intent.spec.mjs, editions: [lite, full], codes: [FE_BUTTON_INTENT, FE_BUTTON_INTENT_REQUIRED, FE_DESTRUCTIVE_CONFIRM_IN_DIALOG, FE_SINGLE_COMMIT_PER_RENDER, FE_NO_COMMIT_IN_COLLECTION_ITEM]}
|
|
5521
5526
|
templateNa: "The intent of an action comes from the product decision in the .starciwork specification; a template cannot choose it."
|
|
5522
5527
|
cases:
|
|
5523
5528
|
- id: case-1
|
|
@@ -5576,7 +5581,7 @@ rules:
|
|
|
5576
5581
|
ruling: "The label of a status chip starts with an uppercase letter (owner ruling chip-label-uppercase); StatusDot capitalises a text label it renders, and the lint refuses a lowercase literal label at its source."
|
|
5577
5582
|
- id: R323
|
|
5578
5583
|
code: "FE_PAGE_HEADER_HAND_ROLLED"
|
|
5579
|
-
law: "A page is headed by Grammar's
|
|
5584
|
+
law: "A page is headed by the header of Grammar's Page; PageHeader and Breadcrumbs are Page's parts and are not exported, so importing either from the Grammar composes a page head by hand, with no band, no top inset and no separator."
|
|
5580
5585
|
kinds: [lint]
|
|
5581
5586
|
gates: [pre-commit, settle, land, ci]
|
|
5582
5587
|
failureCodes: ["FE_PAGE_HEADER_HAND_ROLLED"]
|
|
@@ -5596,10 +5601,10 @@ rules:
|
|
|
5596
5601
|
date: "2026-10-28"
|
|
5597
5602
|
source: owner
|
|
5598
5603
|
quote: "Publish Grammar 0.15.3 with PageHeader now, then move all 18 pages to it before deploying. (translated from Vietnamese)"
|
|
5599
|
-
ruling: "A page is headed by Grammar's
|
|
5604
|
+
ruling: "A page is headed by Grammar's Page header (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 PageHeader or Breadcrumbs imported by hand, since both are Page's own parts."
|
|
5600
5605
|
- id: R324
|
|
5601
5606
|
code: "FE_PAGE_TABS_OUTSIDE_HEADER"
|
|
5602
|
-
law: "Page tabs are the header's tabs; a Tabs that is the first element of a Page body
|
|
5607
|
+
law: "Page tabs are the header's tabs; a Tabs that is the first element of a Page body is a page tab bar drawn outside the band."
|
|
5603
5608
|
kinds: [lint]
|
|
5604
5609
|
gates: [pre-commit, settle, land, ci]
|
|
5605
5610
|
failureCodes: ["FE_PAGE_TABS_OUTSIDE_HEADER"]
|
|
@@ -5622,7 +5627,7 @@ rules:
|
|
|
5622
5627
|
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."
|
|
5623
5628
|
- id: R325
|
|
5624
5629
|
code: "FE_ROUTE_PAGE_OUTSIDE_PAGE"
|
|
5625
|
-
law: "A page owner draws one Grammar Page with nothing beside it; a sibling of the Page
|
|
5630
|
+
law: "A page owner draws one Grammar Page with nothing beside it; a sibling of the Page re-decides the inset and rhythm Page owns, and PageHeader and PageContainer are Page's parts, not exports to import from the Grammar."
|
|
5626
5631
|
kinds: [lint]
|
|
5627
5632
|
gates: [pre-commit, settle, land, ci]
|
|
5628
5633
|
failureCodes: ["FE_ROUTE_PAGE_OUTSIDE_PAGE"]
|
|
@@ -5958,7 +5963,7 @@ rules:
|
|
|
5958
5963
|
ruling: "Mascot takes AssetSlotProps (assetSlot, assetSha256, assetPrompt) and emits them as data-asset-* on its root; the lint requires assetSlot on every Mascot."
|
|
5959
5964
|
- id: R340
|
|
5960
5965
|
code: "FE_RAISED_SURFACE_IN_SURFACE"
|
|
5961
|
-
law: "A
|
|
5966
|
+
law: "A surface never stands inside another surface (owner decision, dialogs and drawers included): a SurfaceCard that a SurfaceCard, a Pane surface, or a Dialog, Drawer, AlertDialog or Popover of the same file encloses is refused. The Grammar has one depth, raised."
|
|
5962
5967
|
kinds: [lint]
|
|
5963
5968
|
gates: [pre-commit, settle, land, ci]
|
|
5964
5969
|
failureCodes: ["FE_RAISED_SURFACE_IN_SURFACE"]
|
|
@@ -5977,8 +5982,8 @@ rules:
|
|
|
5977
5982
|
- id: case-1
|
|
5978
5983
|
date: "2026-10-31"
|
|
5979
5984
|
source: owner
|
|
5980
|
-
quote: "A list inside another surface is outlined (BOUNDARY-5, DNA-COMMON-NO-RAISED-SURFACE-IN-SURFACE);
|
|
5981
|
-
ruling: "
|
|
5985
|
+
quote: "A list inside another surface is outlined (BOUNDARY-5, DNA-COMMON-NO-RAISED-SURFACE-IN-SURFACE); the list SurfaceCard takes its depth from the surface context (grammar 0.16.2)."
|
|
5986
|
+
ruling: "A list SurfaceCard resolves its depth from the surface context like every SurfaceCard kind; an app that forces depth=\"top\" on a surface nested in another surface raises a surface inside a surface, so the lint refuses it."
|
|
5982
5987
|
- id: R341
|
|
5983
5988
|
code: "FE_FREESTYLE_REASON_MISSING"
|
|
5984
5989
|
law: "A Grammar Freestyle region, the one sanctioned place where an app writes its own plain elements and CSS because no Grammar component fits, names its reason: a non-empty string literal saying why. The rules that judge hand-written elements, classes, styles and generated art leave the raw elements of a Freestyle subtree alone; a Grammar component inside it is judged as before. An element with a spread attribute is not judged."
|
|
@@ -6028,6 +6033,7 @@ rules:
|
|
|
6028
6033
|
- id: R343
|
|
6029
6034
|
code: "HFS_CHECK_CACHE_FORGED"
|
|
6030
6035
|
editions: ["lite", "full"]
|
|
6036
|
+
docs: [contexts/hfs/README.md#rule-notes]
|
|
6031
6037
|
law: "An app's committed check cache (.starcistacks/cache/) is trusted in CI only as it stands at the trusted base; a record whose key matches the tree and whose findings differ from the inherited or computed result is HFS_CHECK_CACHE_FORGED (a hard blocker at any count), a touched key without a current record is HFS_CHECK_CACHE_STALE and a missing cache is HFS_CHECK_CACHE_MISSING (both report-only warnings)."
|
|
6032
6038
|
kinds: [codemod]
|
|
6033
6039
|
gates: [ci]
|
|
@@ -6140,7 +6146,7 @@ rules:
|
|
|
6140
6146
|
ruling: "One padding system for everything placed in a PaneGrid: the Pane publishes the inset and the PaneSection applies it, so no band adds its own."
|
|
6141
6147
|
- id: R348
|
|
6142
6148
|
code: "FE_PANE_GRID_FRAME"
|
|
6143
|
-
law: "A Grammar PaneGrid is the one child of `Page chrome=\"panes\"` or stands inside a Pane; a PaneGrid drawn in a
|
|
6149
|
+
law: "A Grammar PaneGrid is the one child of `Page chrome=\"panes\"` or stands inside a Pane; a PaneGrid drawn in a SurfaceCard of any kind, a Page of another chrome, or wrapped inside `Page chrome=\"panes\"` re-decides the frame and the bottom edge that Page owns. A grid whose frame lives in another file is judged where the frame is, and a Freestyle subtree is not judged."
|
|
6144
6150
|
kinds: [lint]
|
|
6145
6151
|
gates: [pre-commit, settle, land, ci]
|
|
6146
6152
|
failureCodes: ["FE_PANE_GRID_FRAME"]
|
|
@@ -6210,7 +6216,7 @@ rules:
|
|
|
6210
6216
|
ruling: "One spacing scale with no 16; every container holds its content 12 in, and an app names relations through role props, never spacing utilities."
|
|
6211
6217
|
- id: R351
|
|
6212
6218
|
code: "FE_TABLE_IN_CARD"
|
|
6213
|
-
law: "A DataTable is not placed inside a SurfaceCard, on the page or in a pane: plain rows with a few facts are a
|
|
6219
|
+
law: "A DataTable is not placed inside a SurfaceCard, on the page or in a pane: plain rows with a few facts are a SurfaceCard kind=list with the label outside and the note in its footer, and a table that belongs to a card is an owner decision named in this rule. The rule reads the JSX of the file: a DataTable enclosed by a SurfaceCard element, or rendered by a component of the same file that a SurfaceCard encloses."
|
|
6214
6220
|
kinds: [lint]
|
|
6215
6221
|
gates: [pre-commit, settle, land, ci]
|
|
6216
6222
|
failureCodes: ["FE_TABLE_IN_CARD"]
|
|
@@ -6230,10 +6236,10 @@ rules:
|
|
|
6230
6236
|
date: "2026-11-01"
|
|
6231
6237
|
source: owner
|
|
6232
6238
|
quote: "quá phức tạp mới nhét bảng vào card"
|
|
6233
|
-
ruling: "Plain rows are a
|
|
6239
|
+
ruling: "Plain rows are a SurfaceCard kind=\"list\" with a bottom note in its footer; a DataTable is not put in a SurfaceCard."
|
|
6234
6240
|
- id: R352
|
|
6235
6241
|
code: "FE_COMMAND_FILLS_HEADER"
|
|
6236
|
-
law: "A page or pane command hugs its label and sits at the end of its header row: a Grammar Button with width=\"fill\" in the actions of a
|
|
6242
|
+
law: "A page or pane command hugs its label and sits at the end of its header row: a Grammar Button with width=\"fill\" in the actions of a Page header or the labelEnd of a SurfaceCard is refused; width=\"fill\" is for a form submit, an auth panel and a phone action bar, which a file cannot show and the rule does not judge."
|
|
6237
6243
|
kinds: [lint]
|
|
6238
6244
|
gates: [pre-commit, settle, land, ci]
|
|
6239
6245
|
failureCodes: ["FE_COMMAND_FILLS_HEADER"]
|
|
@@ -6279,7 +6285,7 @@ rules:
|
|
|
6279
6285
|
ruling: "A filter over a collection is a FilterButtonGroup of sm toggle buttons; the TagGroup has no selection."
|
|
6280
6286
|
- id: R354
|
|
6281
6287
|
code: "FE_HEADING_ABOVE_CARD"
|
|
6282
|
-
law: "A card has a label by design, so a
|
|
6288
|
+
law: "A card has a label by design, so a heading over it is wrong: a Section (not kind=\"list\") whose only child is one SurfaceCard (of any kind), and a card whose ariaLabel is the Section title, are refused; the title moves to the label, the count to fact, the action to labelEnd and the note to footer. Several cards under one Section stay legal."
|
|
6283
6289
|
kinds: [lint]
|
|
6284
6290
|
gates: [pre-commit, settle, land, ci]
|
|
6285
6291
|
failureCodes: ["FE_HEADING_ABOVE_CARD"]
|
|
@@ -6299,4 +6305,223 @@ rules:
|
|
|
6299
6305
|
date: "2026-11-01"
|
|
6300
6306
|
source: owner
|
|
6301
6307
|
quote: "bản thân card đã có label, vốn là mục đích thiết kế"
|
|
6302
|
-
ruling: "A card carries its own title, count, action and note; a
|
|
6308
|
+
ruling: "A card carries its own title, count, action and note; a Section around a single card is refused."
|
|
6309
|
+
- id: R355
|
|
6310
|
+
code: "FE_SURFACE_CARD_KIND"
|
|
6311
|
+
law: "A SurfaceCard is one component whose kind the compiler checks, and what a kind draws is not drawn by hand around it: a card of the body kind holds no raw ul, ol, li or dl, no Divider, and no Heading standing as a second title, and a Section whose only child is one SurfaceCard frames one subject twice."
|
|
6312
|
+
kinds: [lint]
|
|
6313
|
+
gates: [pre-commit, settle, land, ci]
|
|
6314
|
+
failureCodes: ["FE_SURFACE_CARD_KIND"]
|
|
6315
|
+
enforcers:
|
|
6316
|
+
- {kind: eslint-grammar-core, id: surface-card-kind}
|
|
6317
|
+
editions: [lite, full]
|
|
6318
|
+
proves:
|
|
6319
|
+
- packages/eslint-grammar-core/src/surface-card.spec.ts
|
|
6320
|
+
exemplifiedBy:
|
|
6321
|
+
- {path: packages/eslint-grammar-core/src/surface-card.spec.ts, editions: [lite, full], codes: [FE_SURFACE_CARD_KIND]}
|
|
6322
|
+
witness:
|
|
6323
|
+
- {path: packages/eslint-grammar-core/src/surface-card.spec.ts, editions: [lite, full], codes: [FE_SURFACE_CARD_KIND]}
|
|
6324
|
+
- id: R356
|
|
6325
|
+
code: "FE_SURFACE_NESTING"
|
|
6326
|
+
law: "Across files, a card is not drawn inside a card, a Pane surface or an overlay, a DataTable is not drawn inside a card, a PaneGrid stands only in Page chrome=\"panes\" or a Pane, a Section or Heading holds more than one card, and a labelled card is not repeated in the peer-card layout Grid kind=\"cards\": the render graph replaces every component by what it draws and every children by what its caller handed it."
|
|
6327
|
+
kinds: [lint]
|
|
6328
|
+
gates: [pre-commit, settle, land, ci]
|
|
6329
|
+
failureCodes: ["FE_SURFACE_NESTING"]
|
|
6330
|
+
enforcers:
|
|
6331
|
+
- {kind: machine, id: surface-nesting, at: scripts/hfs/architecture/surface-nesting.mjs}
|
|
6332
|
+
editions: [lite, full]
|
|
6333
|
+
docs: [contexts/ui/components/card.md]
|
|
6334
|
+
templateNa: "A template draws no card, table or grid."
|
|
6335
|
+
proves:
|
|
6336
|
+
- {path: packages/hfs/src/surface-nesting.spec.mts, editions: [lite, full], codes: [FE_SURFACE_NESTING]}
|
|
6337
|
+
exemplifiedBy:
|
|
6338
|
+
- {path: packages/hfs/src/surface-nesting.spec.mts, editions: [lite, full], codes: [FE_SURFACE_NESTING]}
|
|
6339
|
+
witness:
|
|
6340
|
+
- {path: packages/hfs/src/surface-nesting.spec.mts, editions: [lite, full], codes: [FE_SURFACE_NESTING]}
|
|
6341
|
+
cases:
|
|
6342
|
+
- id: case-1
|
|
6343
|
+
date: "2026-11-01"
|
|
6344
|
+
source: owner
|
|
6345
|
+
quote: "Surface in surface is banned totally, dialogs and drawers included."
|
|
6346
|
+
ruling: "The one-file card laws (R340, R348, R351, R354, R366) are closed across files by the render graph; the machine reports the inner element and the chain that led to it."
|
|
6347
|
+
- id: R357
|
|
6348
|
+
code: "FE_TEXT_BLOCK"
|
|
6349
|
+
law: "Text holds a run of words: a block component or block element inside it is refused."
|
|
6350
|
+
kinds: [lint]
|
|
6351
|
+
gates: [pre-commit, settle, land, ci]
|
|
6352
|
+
failureCodes: ["FE_TEXT_BLOCK"]
|
|
6353
|
+
enforcers:
|
|
6354
|
+
- {kind: eslint-grammar-core, id: text-inline-only}
|
|
6355
|
+
editions: [lite, full]
|
|
6356
|
+
docs: [contexts/ui/components/text.md]
|
|
6357
|
+
templateNa: "A template writes no Text with a block inside."
|
|
6358
|
+
proves:
|
|
6359
|
+
- {path: packages/eslint-grammar-core/src/text-rules.spec.ts, editions: [lite, full], codes: [FE_TEXT_BLOCK]}
|
|
6360
|
+
exemplifiedBy:
|
|
6361
|
+
- {path: packages/eslint-grammar-core/src/text-rules.spec.ts, editions: [lite, full], codes: [FE_TEXT_BLOCK]}
|
|
6362
|
+
witness:
|
|
6363
|
+
- {path: packages/eslint-grammar-core/src/text-rules.spec.ts, editions: [lite, full], codes: [FE_TEXT_BLOCK]}
|
|
6364
|
+
cases:
|
|
6365
|
+
- id: case-1
|
|
6366
|
+
date: "2026-11-01"
|
|
6367
|
+
source: owner
|
|
6368
|
+
quote: "A card carries its own label; text is inline."
|
|
6369
|
+
ruling: "Text is inline only, so a layout cannot hide inside a paragraph."
|
|
6370
|
+
- id: R358
|
|
6371
|
+
code: "FE_TEXT_HEADING"
|
|
6372
|
+
law: "A strong Text that is the first child of a group and is followed only by blocks is a hand heading: the group takes a Section title, a card label or Heading."
|
|
6373
|
+
kinds: [lint]
|
|
6374
|
+
gates: [pre-commit, settle, land, ci]
|
|
6375
|
+
failureCodes: ["FE_TEXT_HEADING"]
|
|
6376
|
+
enforcers:
|
|
6377
|
+
- {kind: eslint-grammar-core, id: no-text-heading}
|
|
6378
|
+
editions: [lite, full]
|
|
6379
|
+
docs: [contexts/ui/components/text.md]
|
|
6380
|
+
templateNa: "A template writes no heading by hand."
|
|
6381
|
+
proves:
|
|
6382
|
+
- {path: packages/eslint-grammar-core/src/text-rules.spec.ts, editions: [lite, full], codes: [FE_TEXT_HEADING]}
|
|
6383
|
+
exemplifiedBy:
|
|
6384
|
+
- {path: packages/eslint-grammar-core/src/text-rules.spec.ts, editions: [lite, full], codes: [FE_TEXT_HEADING]}
|
|
6385
|
+
witness:
|
|
6386
|
+
- {path: packages/eslint-grammar-core/src/text-rules.spec.ts, editions: [lite, full], codes: [FE_TEXT_HEADING]}
|
|
6387
|
+
cases:
|
|
6388
|
+
- id: case-1
|
|
6389
|
+
date: "2026-11-01"
|
|
6390
|
+
source: owner
|
|
6391
|
+
quote: "No free heading in apps."
|
|
6392
|
+
ruling: "A heading is a Section title, a card label or Heading, never a strong Text."
|
|
6393
|
+
- id: R359
|
|
6394
|
+
code: "FE_ONE_HIGHLIGHT"
|
|
6395
|
+
law: "A render states one highlighted thing: a second isHighlight on a Grammar element in one component render is refused."
|
|
6396
|
+
kinds: [lint]
|
|
6397
|
+
gates: [pre-commit, settle, land, ci]
|
|
6398
|
+
failureCodes: ["FE_ONE_HIGHLIGHT"]
|
|
6399
|
+
enforcers:
|
|
6400
|
+
- {kind: eslint-grammar-core, id: one-highlight-per-render}
|
|
6401
|
+
editions: [lite, full]
|
|
6402
|
+
docs: [contexts/ui/components/card.md]
|
|
6403
|
+
templateNa: "A template highlights nothing."
|
|
6404
|
+
proves:
|
|
6405
|
+
- {path: packages/eslint-grammar-core/src/text-rules.spec.ts, editions: [lite, full], codes: [FE_ONE_HIGHLIGHT]}
|
|
6406
|
+
exemplifiedBy:
|
|
6407
|
+
- {path: packages/eslint-grammar-core/src/text-rules.spec.ts, editions: [lite, full], codes: [FE_ONE_HIGHLIGHT]}
|
|
6408
|
+
witness:
|
|
6409
|
+
- {path: packages/eslint-grammar-core/src/text-rules.spec.ts, editions: [lite, full], codes: [FE_ONE_HIGHLIGHT]}
|
|
6410
|
+
cases:
|
|
6411
|
+
- id: case-1
|
|
6412
|
+
date: "2026-11-01"
|
|
6413
|
+
source: owner
|
|
6414
|
+
quote: "One accent fill per render."
|
|
6415
|
+
ruling: "When two things are highlighted neither is."
|
|
6416
|
+
- id: R361
|
|
6417
|
+
code: "FE_RENDER_PROP_ACROSS_SERVER"
|
|
6418
|
+
law: "A function is not handed from a server file to a client component: React serialises props across that line and a function is not serialisable; pass data, or move the callback into the client branch that owns the interaction."
|
|
6419
|
+
kinds: [lint]
|
|
6420
|
+
gates: [pre-commit, settle, land, ci]
|
|
6421
|
+
failureCodes: ["FE_RENDER_PROP_ACROSS_SERVER"]
|
|
6422
|
+
enforcers:
|
|
6423
|
+
- {kind: machine, id: render-prop-across-server, at: scripts/hfs/architecture/render-prop-across-server.mjs}
|
|
6424
|
+
editions: [lite, full]
|
|
6425
|
+
docs: [contexts/code/fe/function.md]
|
|
6426
|
+
templateNa: "A template hands no function across the server line."
|
|
6427
|
+
proves:
|
|
6428
|
+
- {path: packages/hfs/src/surface-nesting.spec.mts, editions: [lite, full], codes: [FE_RENDER_PROP_ACROSS_SERVER]}
|
|
6429
|
+
exemplifiedBy:
|
|
6430
|
+
- {path: packages/hfs/src/surface-nesting.spec.mts, editions: [lite, full], codes: [FE_RENDER_PROP_ACROSS_SERVER]}
|
|
6431
|
+
witness:
|
|
6432
|
+
- {path: packages/hfs/src/surface-nesting.spec.mts, editions: [lite, full], codes: [FE_RENDER_PROP_ACROSS_SERVER]}
|
|
6433
|
+
cases:
|
|
6434
|
+
- id: case-1
|
|
6435
|
+
date: "2026-11-01"
|
|
6436
|
+
source: owner
|
|
6437
|
+
quote: "a block takes data"
|
|
6438
|
+
ruling: "A render callback is for a client branch; a block takes data and renders it itself."
|
|
6439
|
+
- id: R362
|
|
6440
|
+
code: "FE_GRAMMAR_TIER_STATE"
|
|
6441
|
+
law: "In the Grammar package a leaf and a composite hold no useState, useReducer or effect, and a component's own props and its ReactNode slots stay inside its tier's budget (leaf 8, branch 12, composite 12, frame 10 props; 3 slots). Both run as counting rules: their findings are the tier refactor list."
|
|
6442
|
+
kinds: [lint]
|
|
6443
|
+
gates: [pre-commit, settle, land, ci]
|
|
6444
|
+
failureCodes: ["FE_GRAMMAR_TIER_STATE", "FE_GRAMMAR_PROP_BUDGET"]
|
|
6445
|
+
enforcers:
|
|
6446
|
+
- {kind: runtime, id: tier-state, at: packages/grammar/scripts/lint/eslint-plugin.mts}
|
|
6447
|
+
- {kind: runtime, id: prop-budget, at: packages/grammar/scripts/lint/eslint-plugin.mts}
|
|
6448
|
+
editions: [lite, full]
|
|
6449
|
+
docs: [contexts/grammars/README.md#grammar-lint]
|
|
6450
|
+
templateNa: "A template ships no Grammar source."
|
|
6451
|
+
proves:
|
|
6452
|
+
- {path: packages/grammar/scripts/lint/eslint-plugin.mts, editions: [lite, full], codes: [FE_GRAMMAR_TIER_STATE, FE_GRAMMAR_PROP_BUDGET]}
|
|
6453
|
+
exemplifiedBy:
|
|
6454
|
+
- {path: packages/grammar/scripts/lint/eslint-plugin.mts, editions: [lite, full], codes: [FE_GRAMMAR_TIER_STATE, FE_GRAMMAR_PROP_BUDGET]}
|
|
6455
|
+
witness:
|
|
6456
|
+
- {path: packages/grammar/scripts/lint/eslint-plugin.mts, editions: [lite, full], codes: [FE_GRAMMAR_TIER_STATE, FE_GRAMMAR_PROP_BUDGET]}
|
|
6457
|
+
cases:
|
|
6458
|
+
- id: case-1
|
|
6459
|
+
date: "2026-11-02"
|
|
6460
|
+
source: owner
|
|
6461
|
+
quote: "The tier binding of the Grammar is a ladder; a leaf draws and a branch behaves."
|
|
6462
|
+
ruling: "State sits in branches; budgets keep a role from growing into two."
|
|
6463
|
+
- id: R363
|
|
6464
|
+
code: "FE_GRAMMAR_LITERAL"
|
|
6465
|
+
law: "A Grammar component holds no hardcoded colour, length, utility class or numeric style value (`no-literal-look`); its stylesheets read named roles and ramps."
|
|
6466
|
+
kinds: [lint]
|
|
6467
|
+
gates: [pre-commit, settle, land, ci]
|
|
6468
|
+
failureCodes: ["FE_GRAMMAR_LITERAL"]
|
|
6469
|
+
enforcers:
|
|
6470
|
+
- {kind: runtime, id: no-literal-look, at: packages/grammar/scripts/lint/eslint-plugin.mts}
|
|
6471
|
+
editions: [lite, full]
|
|
6472
|
+
docs: [contexts/grammars/README.md#grammar-lint]
|
|
6473
|
+
templateNa: "A template ships no Grammar source."
|
|
6474
|
+
proves:
|
|
6475
|
+
- {path: packages/grammar/scripts/lint/eslint-plugin.mts, editions: [lite, full], codes: [FE_GRAMMAR_LITERAL]}
|
|
6476
|
+
exemplifiedBy:
|
|
6477
|
+
- {path: packages/grammar/scripts/lint/eslint-plugin.mts, editions: [lite, full], codes: [FE_GRAMMAR_LITERAL]}
|
|
6478
|
+
witness:
|
|
6479
|
+
- {path: packages/grammar/scripts/lint/eslint-plugin.mts, editions: [lite, full], codes: [FE_GRAMMAR_LITERAL]}
|
|
6480
|
+
cases:
|
|
6481
|
+
- id: case-1
|
|
6482
|
+
date: "2026-11-02"
|
|
6483
|
+
source: owner
|
|
6484
|
+
quote: "A component names a role and the stylesheet gives it a value."
|
|
6485
|
+
ruling: "Literals live in the ramps."
|
|
6486
|
+
- id: R366
|
|
6487
|
+
code: "FE_LABEL_IN_GRID"
|
|
6488
|
+
law: "A SurfaceCard with a label repeated in a Grid kind=cards is a tile drawn with the wrong anatomy: a peer you recognise and open names itself inside (SurfaceCard kind=tile), and peers compared by a fact are one kind=list card."
|
|
6489
|
+
kinds: [lint]
|
|
6490
|
+
gates: [pre-commit, settle, land, ci]
|
|
6491
|
+
failureCodes: ["FE_LABEL_IN_GRID"]
|
|
6492
|
+
enforcers:
|
|
6493
|
+
- {kind: eslint-grammar-core, id: no-label-in-grid}
|
|
6494
|
+
editions: [lite, full]
|
|
6495
|
+
proves:
|
|
6496
|
+
- packages/eslint-grammar-core/src/surface-card.spec.ts
|
|
6497
|
+
exemplifiedBy:
|
|
6498
|
+
- {path: packages/eslint-grammar-core/src/surface-card.spec.ts, editions: [lite, full], codes: [FE_LABEL_IN_GRID]}
|
|
6499
|
+
witness:
|
|
6500
|
+
- {path: packages/eslint-grammar-core/src/surface-card.spec.ts, editions: [lite, full], codes: [FE_LABEL_IN_GRID]}
|
|
6501
|
+
- id: R367
|
|
6502
|
+
code: "FE_SWR_DEFAULT_ARRAY"
|
|
6503
|
+
law: "A loading state is drawn from the page's own reads: a hook folds a read to [] only while it also returns that read's isLoading, a slot takes its loading and error from the reads it draws and never from another slot, a background refresh (isValidating) never drives a loading flag or a status, a placeholder is not the ready data with its lists emptied, and an app draws no skeleton by hand (`no-hand-skeleton`): the Grammar components draw their loading structure from `isLoading`."
|
|
6504
|
+
kinds: [lint]
|
|
6505
|
+
gates: [pre-commit, settle, land, ci]
|
|
6506
|
+
failureCodes: ["FE_SWR_DEFAULT_ARRAY", "FE_FOREIGN_SLOT_STATUS", "FE_VALIDATING_AS_LOADING", "FE_PLACEHOLDER_OBJECT", "FE_HAND_SKELETON"]
|
|
6507
|
+
enforcers:
|
|
6508
|
+
- {kind: eslint-fe, id: no-swr-default-array}
|
|
6509
|
+
- {kind: eslint-fe, id: no-foreign-slot-status}
|
|
6510
|
+
- {kind: eslint-fe, id: no-validating-as-loading}
|
|
6511
|
+
- {kind: eslint-fe, id: no-placeholder-object}
|
|
6512
|
+
- {kind: eslint-fe, id: no-hand-skeleton}
|
|
6513
|
+
editions: [lite, full]
|
|
6514
|
+
docs: [contexts/ui/loading.md]
|
|
6515
|
+
templateNa: "A template draws no loading state of its own."
|
|
6516
|
+
proves:
|
|
6517
|
+
- {path: packages/eslint-canon-fe/src/skeleton-states.spec.ts, editions: [lite, full], codes: [FE_SWR_DEFAULT_ARRAY, FE_FOREIGN_SLOT_STATUS, FE_VALIDATING_AS_LOADING, FE_PLACEHOLDER_OBJECT, FE_HAND_SKELETON]}
|
|
6518
|
+
exemplifiedBy:
|
|
6519
|
+
- {path: packages/eslint-canon-fe/src/skeleton-states.spec.ts, editions: [lite, full], codes: [FE_SWR_DEFAULT_ARRAY, FE_FOREIGN_SLOT_STATUS, FE_VALIDATING_AS_LOADING, FE_PLACEHOLDER_OBJECT, FE_HAND_SKELETON]}
|
|
6520
|
+
witness:
|
|
6521
|
+
- {path: packages/eslint-canon-fe/src/skeleton-states.spec.ts, editions: [lite, full], codes: [FE_SWR_DEFAULT_ARRAY, FE_FOREIGN_SLOT_STATUS, FE_VALIDATING_AS_LOADING, FE_PLACEHOLDER_OBJECT, FE_HAND_SKELETON]}
|
|
6522
|
+
cases:
|
|
6523
|
+
- id: case-1
|
|
6524
|
+
date: "2026-11-01"
|
|
6525
|
+
source: owner
|
|
6526
|
+
quote: "The skeleton is the ready structure with its data missing; it is drawn from isLoading alone and from the reads the region shows."
|
|
6527
|
+
ruling: "Several reads feed one frame: loading is any read loading with no data; a key change reloads only the region keyed by it; a refresh keeps the content."
|
|
@@ -203,13 +203,20 @@ tiers:
|
|
|
203
203
|
feature: {mayImport: [feature, components, hooks, foundation, modules, package], mayImportKinds: {feature: {from: [pages, layouts], to: [overlays]}}} # pages and layouts compose overlays and blocks; data goes through hooks/swr
|
|
204
204
|
# A component never reaches data: config, routes, i18n and types are foundation; every other module and the api
|
|
205
205
|
# transport are reached through a hook (owner decision 2026-09-29).
|
|
206
|
-
components: {mayImport: [components, hooks, foundation, package], lowerLayerOnly: true} # blocks, composites, branches, leaves
|
|
206
|
+
components: {mayImport: [components, hooks, foundation, package], lowerLayerOnly: true} # blocks, frames, composites, branches, leaves
|
|
207
207
|
# Across domains a hook is imported only through the other domain's public index (hooks/<domain>/index.ts), never deep.
|
|
208
208
|
hooks: {mayImport: [hooks, foundation, modules, transport, package], mayImportSlots: {transport: {from: [fe.hooks.swr]}}} # only hooks/swr imports an operation
|
|
209
209
|
foundation: {mayImport: [foundation, package], acyclic: true} # modules/{config,i18n,routes,types}
|
|
210
210
|
modules: {mayImport: [foundation, modules, transport, package], acyclic: true}
|
|
211
211
|
transport: {mayImport: [foundation, modules, package]} # modules/api: the one fetch
|
|
212
212
|
package: {mayImport: [package]}
|
|
213
|
+
# A component library (packages/grammar): src/core/<tier>/<Name>/ and the data-tier stamp equal the tier folder. A tier imports nothing
|
|
214
|
+
# above itself (FE_GRAMMAR_TIER_DIRECTION); a shared untiered module under src/core is followed, so one hop of indirection hides nothing.
|
|
215
|
+
grammar:
|
|
216
|
+
leaf: {mayImport: [leaf]} # one element or one vendor part
|
|
217
|
+
branch: {mayImport: [leaf, branch]} # a leaf plus its own interaction state
|
|
218
|
+
composite: {mayImport: [leaf, branch, composite]} # a fixed arrangement of lower tiers
|
|
219
|
+
frame: {mayImport: [leaf, branch, composite, frame]} # owns a region; the one tier that takes children
|
|
213
220
|
crossOwner: every import across owners targets the owner's public entry (index.ts or index.tsx); never export *.
|
|
214
221
|
crossApp: apps never import each other; shared code is a packages/<pkg> slot.
|
|
215
222
|
|
|
@@ -2270,12 +2277,12 @@ slots:
|
|
|
2270
2277
|
- id: fe.components
|
|
2271
2278
|
since: 3.1.0
|
|
2272
2279
|
profiles: [fe]
|
|
2273
|
-
path: "apps/<app>/src/components/{blocks,composites,branches,leaves}/<name>/"
|
|
2280
|
+
path: "apps/<app>/src/components/{blocks,frames,composites,branches,leaves}/<name>/"
|
|
2274
2281
|
presence: optional
|
|
2275
2282
|
tracked: tracked
|
|
2276
2283
|
tier: components
|
|
2277
2284
|
owner: true
|
|
2278
|
-
layers: [blocks, composites, branches, leaves] # a layer imports only the layers after it
|
|
2285
|
+
layers: [blocks, frames, composites, branches, leaves] # a layer imports only the layers after it
|
|
2279
2286
|
requires: [index.tsx]
|
|
2280
2287
|
allows: [index.tsx, component.tsx, classNames.ts]
|
|
2281
2288
|
roles: {entry: index.tsx, drawing: component.tsx, styles: classNames.ts} # the connected entry, the pure drawing half and the styling declarations of a component owner
|
|
@@ -2618,12 +2625,12 @@ slots:
|
|
|
2618
2625
|
tracked: tracked
|
|
2619
2626
|
tier: package
|
|
2620
2627
|
owner: true
|
|
2621
|
-
kinds: [composites, branches, leaves] # the layer folders under src/ (a package owns no blocks)
|
|
2628
|
+
kinds: [frames, composites, branches, leaves] # the layer folders under src/ (a package owns no blocks)
|
|
2622
2629
|
roles: {entry: index.tsx, drawing: component.tsx, styles: classNames.ts}
|
|
2623
2630
|
requires: [package.json, src/index.ts, tsconfig.json]
|
|
2624
2631
|
tests: none
|
|
2625
2632
|
budget: {component.tsx: 300, index.tsx: 200, dataHooks: 6, useState: 6}
|
|
2626
|
-
why: built to dist with explicit named exports; only composites, branches and leaves; no unused export; no colour literal
|
|
2633
|
+
why: built to dist with explicit named exports; only frames, composites, branches and leaves; no unused export; no colour literal
|
|
2627
2634
|
rules: [FE_PACKAGE_SHAPE, HFS_UNUSED_EXPORT, FE_STYLE_TOKEN_ONLY]
|
|
2628
2635
|
- id: fe.package.api
|
|
2629
2636
|
since: 3.1.0
|
|
@@ -364,7 +364,7 @@ rules:
|
|
|
364
364
|
cases:
|
|
365
365
|
- id: case-1
|
|
366
366
|
when: "A Grammar component (runtime package)"
|
|
367
|
-
write: "packages/grammar/src/core/<
|
|
367
|
+
write: "packages/grammar/src/core/<leaf|branch|composite|frame>/<Name>/index.tsx"
|
|
368
368
|
- id: case-2
|
|
369
369
|
when: "A Grammar component's class strings and spec (runtime package)"
|
|
370
370
|
write: "sibling classNames.ts and index.spec.tsx"
|