pasika 0.1.4 → 0.1.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.
Files changed (44) hide show
  1. package/dist/eslint/pasika/index.js +3 -0
  2. package/dist/eslint/pasika/rules/filename-case.d.ts +2 -0
  3. package/dist/eslint/pasika/rules/filename-case.js +90 -0
  4. package/docs/agent-conventions.md +6 -0
  5. package/docs/code-organization-guide/code-organization-guide.md +24 -20
  6. package/docs/code-organization-guide/references/application-architecture-reference.md +61 -19
  7. package/docs/code-organization-guide/rules/component-placement-rule.md +9 -25
  8. package/docs/code-organization-guide/rules/configuration-rule.md +9 -9
  9. package/docs/code-organization-guide/rules/constants-rule.md +5 -8
  10. package/docs/code-organization-guide/rules/exports-and-imports-rule.md +10 -7
  11. package/docs/code-organization-guide/rules/folder-nesting-rule.md +1 -2
  12. package/docs/code-organization-guide/rules/hook-extraction-rule.md +4 -5
  13. package/docs/code-organization-guide/rules/interactive-component-rule.md +7 -10
  14. package/docs/code-organization-guide/rules/jsx-hygiene-rule.md +2 -3
  15. package/docs/code-organization-guide/rules/locales-rule.md +2 -4
  16. package/docs/code-organization-guide/rules/no-mixed-concerns-rule.md +0 -1
  17. package/docs/code-organization-guide/rules/repeated-structure-rule.md +1 -1
  18. package/docs/code-organization-guide/rules/smart-vs-dumb-component-rule.md +2 -6
  19. package/docs/code-organization-guide/rules/sole-state-owner-rule.md +1 -2
  20. package/docs/code-organization-guide/rules/types-and-schemas-rule.md +7 -6
  21. package/docs/code-organization-guide/rules/utilities-rule.md +1 -2
  22. package/docs/documentation-guide/_templates/grouped-reference.md +11 -0
  23. package/docs/documentation-guide/_templates/guide.md +1 -1
  24. package/docs/documentation-guide/_templates/single-lookup-reference.md +5 -0
  25. package/docs/documentation-guide/references/documentation-types-reference.md +6 -6
  26. package/docs/documentation-guide/rules/guide-creation-rule.md +1 -0
  27. package/docs/documentation-guide/rules/reference-creation-rule.md +1 -1
  28. package/docs/documentation-guide/rules/template-usage-rule.md +2 -1
  29. package/docs/styling-guide/rules/arbitrary-value-rule.md +31 -0
  30. package/docs/styling-guide/rules/class-composition-rule.md +30 -10
  31. package/docs/styling-guide/rules/component-ui-state-rule.md +53 -0
  32. package/docs/styling-guide/rules/component-variant-rule.md +35 -38
  33. package/docs/styling-guide/rules/global-stylesheet-rule.md +67 -0
  34. package/docs/styling-guide/rules/theme-and-utility-definition-rule.md +39 -82
  35. package/docs/styling-guide/styling-guide.md +8 -11
  36. package/package.json +1 -1
  37. package/docs/code-organization-guide/rules/native-prop-forwarding-rule.md +0 -38
  38. package/docs/documentation-guide/_templates/reference.md +0 -17
  39. package/docs/styling-guide/rules/color-role-naming-rule.md +0 -115
  40. package/docs/styling-guide/rules/component-state-rule.md +0 -26
  41. package/docs/styling-guide/rules/global-style-system-rule.md +0 -63
  42. package/docs/styling-guide/rules/style-placement-rule.md +0 -49
  43. package/docs/styling-guide/rules/tailwind-utility-rule.md +0 -48
  44. package/docs/styling-guide/rules/theme-token-rule.md +0 -32
@@ -1,14 +1,17 @@
1
+ import { filenameCaseRule } from "./rules/filename-case.js";
1
2
  import { organizationImportsRule } from "./rules/organization-imports.js";
2
3
  export const pasikaConfig = {
3
4
  files: ["src/**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}"],
4
5
  plugins: {
5
6
  pasika: {
6
7
  rules: {
8
+ "filename-case": filenameCaseRule,
7
9
  "organization-imports": organizationImportsRule,
8
10
  },
9
11
  },
10
12
  },
11
13
  rules: {
14
+ "pasika/filename-case": "error",
12
15
  "pasika/organization-imports": "error",
13
16
  },
14
17
  };
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "eslint";
2
+ export declare const filenameCaseRule: Rule.RuleModule;
@@ -0,0 +1,90 @@
1
+ import path from "node:path";
2
+ /**
3
+ * Next.js App Router routing files that are exempt from filename-case checks.
4
+ * https://nextjs.org/docs/app/getting-started/project-structure#routing-files
5
+ */
6
+ const NEXT_ROUTING_FILES = new Set([
7
+ "page",
8
+ "layout",
9
+ "loading",
10
+ "error",
11
+ "not-found",
12
+ "route",
13
+ "template",
14
+ "default",
15
+ "middleware",
16
+ "instrumentation",
17
+ ]);
18
+ /** Suffixes that form a compound extension with the real extension (e.g. .example.tsx, .test.ts). */
19
+ const COMPOUND_SUFFIXES = new Set(["example", "test", "spec", "stories"]);
20
+ /**
21
+ * Checks whether a string is in kebab-case.
22
+ * Allows lowercase letters, digits, and hyphens (but not leading/trailing/double hyphens).
23
+ */
24
+ function isKebabCase(str) {
25
+ return /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(str);
26
+ }
27
+ /**
28
+ * Checks whether a string is in PascalCase.
29
+ * Starts with an uppercase letter, followed by alphanumeric characters.
30
+ */
31
+ function isPascalCase(str) {
32
+ return /^[A-Z][A-Za-z0-9]*$/.test(str);
33
+ }
34
+ export const filenameCaseRule = {
35
+ meta: {
36
+ schema: [],
37
+ type: "problem",
38
+ docs: {
39
+ description: "Enforce pasika filename conventions: kebab-case by default, PascalCase for smart .tsx components.",
40
+ },
41
+ },
42
+ create(context) {
43
+ const filename = context.filename;
44
+ if (!filename) {
45
+ return {};
46
+ }
47
+ // Only check files under src/
48
+ const normalized = filename.replace(/\\/g, "/");
49
+ if (!normalized.includes("/src/")) {
50
+ return {};
51
+ }
52
+ const ext = path.extname(filename);
53
+ let base = path.basename(filename, ext);
54
+ // Strip compound suffix (e.g. ".example", ".test", ".stories", ".spec") to get the real base name
55
+ const baseExt = path.extname(base);
56
+ if (baseExt && COMPOUND_SUFFIXES.has(baseExt.slice(1))) {
57
+ base = path.basename(base, baseExt);
58
+ }
59
+ // Next.js routing files are exempt
60
+ if (NEXT_ROUTING_FILES.has(base)) {
61
+ return {};
62
+ }
63
+ // .tsx components may be PascalCase (smart) or kebab-case (dumb)
64
+ if (ext === ".tsx") {
65
+ if (!(isPascalCase(base) || isKebabCase(base))) {
66
+ return report(context, filename, ext);
67
+ }
68
+ return {};
69
+ }
70
+ // Everything else must be kebab-case
71
+ if (!isKebabCase(base)) {
72
+ return report(context, filename, ext);
73
+ }
74
+ return {};
75
+ },
76
+ };
77
+ function report(context, filename, ext) {
78
+ return {
79
+ Program(node) {
80
+ const convention = ext === ".tsx"
81
+ ? "Component files must be PascalCase.tsx (smart) or kebab-case.tsx (dumb)."
82
+ : "Non-component files must use kebab-case.";
83
+ context.report({
84
+ node,
85
+ loc: { line: 1, column: 0 },
86
+ message: `Filename "${path.basename(filename)}" does not match pasika conventions. ${convention}`,
87
+ });
88
+ },
89
+ };
90
+ }
@@ -13,6 +13,12 @@ Repo-wide engineering rules and commit hygiene for AI agents.
13
13
  - Never use `eslint-disable` directives. Fix issues properly.
14
14
  - Never commit with the `--no-verify` flag.
15
15
 
16
+ ## Browser Verification
17
+
18
+ - Use `agent-browser` to verify browser behavior when a task requires it.
19
+ - Before using `agent-browser`, run `agent-browser upgrade`.
20
+ - Run `agent-browser --help` for available commands and `agent-browser <command> --help` for command-specific usage.
21
+
16
22
  ## Vulyk
17
23
 
18
24
  - Make Vulyk registry changes with Vulyk commands, not by editing generated files.
@@ -2,64 +2,68 @@
2
2
 
3
3
  This guide covers how to organize code in the repository's `src/` tree — placement, extraction, and module conventions — so structure stays consistent across contributors and reviewers.
4
4
 
5
- For every JavaScript or TypeScript module covered by this guide, also follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md).
6
-
7
5
  ## How To Organize a Component
8
6
 
9
7
  Use this no matter whether you are adding a new component or extracting from existing code; the steps below apply in any case.
10
8
 
11
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to choose the right layer and scope.
9
+ 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
12
10
  2. Pick the component's placement per the [Component Placement Rule](rules/component-placement-rule.md).
13
11
  3. Classify the component as smart or dumb per the [Smart vs Dumb Component Rule](rules/smart-vs-dumb-component-rule.md).
14
- 4. Follow the [Native Prop Forwarding Rule](rules/native-prop-forwarding-rule.md) when a `src/shared/` component renders the same intrinsic root element in every rendered result, so its native prop contract remains available.
15
- 5. Keep each component file to exactly one React component per the [No Mixed Concerns Rule](rules/no-mixed-concerns-rule.md).
16
- 6. Extract interactive elements per the [Interactive Component Rule](rules/interactive-component-rule.md).
17
- 7. Extract sole-state-owner blocks per the [Sole State Owner Rule](rules/sole-state-owner-rule.md).
18
- 8. Extract repeated structures per the [Repeated Structure Rule](rules/repeated-structure-rule.md).
19
- 9. Extract a group of elements when one clear component name describes it per the [Nameable Visual Concept Rule](rules/nameable-visual-concept-rule.md).
20
- 10. For every component extracted in steps 6–9, repeat steps 2–5.
21
- 11. Repeat steps 6–9 for an extracted component only when its JSX has an applicable trigger; stop when no new component is required.
22
- 12. Nest a component when it gains exclusive children per the [Folder Nesting Rule](rules/folder-nesting-rule.md).
23
- 13. Keep the component's JSX clean per the [JSX Hygiene Rule](rules/jsx-hygiene-rule.md).
12
+ 4. Keep each component file to exactly one React component per the [No Mixed Concerns Rule](rules/no-mixed-concerns-rule.md).
13
+ 5. Extract interactive elements per the [Interactive Component Rule](rules/interactive-component-rule.md).
14
+ 6. Extract sole-state-owner blocks per the [Sole State Owner Rule](rules/sole-state-owner-rule.md).
15
+ 7. Extract repeated structures per the [Repeated Structure Rule](rules/repeated-structure-rule.md).
16
+ 8. Extract a group of elements when one clear component name describes it per the [Nameable Visual Concept Rule](rules/nameable-visual-concept-rule.md).
17
+ 9. For every component extracted in steps 5–8, repeat steps 2–4.
18
+ 10. Repeat steps 5–8 for an extracted component only when one of those extraction conditions applies to its JSX; stop when no new component is required.
19
+ 11. Nest a component when it gains exclusive children per the [Folder Nesting Rule](rules/folder-nesting-rule.md).
20
+ 12. Keep the component's JSX clean per the [JSX Hygiene Rule](rules/jsx-hygiene-rule.md).
21
+ 13. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so every component module has predictable exports and import paths.
24
22
 
25
23
  ## How To Organize a Type or Schema
26
24
 
27
25
  Use this when adding or moving a type or schema.
28
26
 
29
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to choose the right layer and scope.
27
+ 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
30
28
  2. Follow the [Types and Schemas Rule](rules/types-and-schemas-rule.md).
29
+ 3. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so the type or schema module has predictable exports and import paths.
31
30
 
32
31
  ## How To Organize a Constant
33
32
 
34
33
  Use this when adding or moving a constant.
35
34
 
36
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to choose the right layer and scope.
35
+ 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
37
36
  2. Follow the [Constants Rule](rules/constants-rule.md).
37
+ 3. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so the constant module has predictable exports and import paths.
38
38
 
39
39
  ## How To Organize a Utility
40
40
 
41
41
  Use this when adding, extracting, or moving a pure function.
42
42
 
43
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to choose the right layer and scope.
43
+ 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
44
44
  2. Follow the [Utilities Rule](rules/utilities-rule.md).
45
+ 3. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so the utility module has predictable exports and import paths.
45
46
 
46
47
  ## How To Organize Configuration
47
48
 
48
- Use this when adding or moving an application configuration object.
49
+ Use this when adding or moving an application configuration module.
49
50
 
50
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to choose the right layer and scope.
51
+ 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
51
52
  2. Follow the [Configuration Rule](rules/configuration-rule.md).
53
+ 3. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so the configuration module has predictable exports and import paths.
52
54
 
53
55
  ## How To Organize a Custom Hook
54
56
 
55
57
  Use this when extracting or moving a custom hook.
56
58
 
57
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to choose the right layer and scope.
59
+ 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
58
60
  2. Follow the [Hook Extraction Rule](rules/hook-extraction-rule.md).
61
+ 3. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so the hook module has predictable exports and import paths.
59
62
 
60
63
  ## How To Organize a Locale String
61
64
 
62
65
  Use this when adding or moving user-facing text.
63
66
 
64
- 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to choose the right layer and scope.
67
+ 1. Read the [Application Architecture Reference](references/application-architecture-reference.md) to identify where the item belongs in `src/`.
65
68
  2. Follow the [Locales Rule](rules/locales-rule.md).
69
+ 3. Follow the [Exports and Imports Rule](rules/exports-and-imports-rule.md) so the locale module has predictable exports and import paths.
@@ -4,25 +4,72 @@ Use this reference to look up the canonical shape of the repository's `src/` tre
4
4
 
5
5
  ## Layer Model
6
6
 
7
- This section describes the repository's four layers, root support scope, and dependency direction. Use it to decide where a component or support file belongs and which layers it can import from.
7
+ This section describes the repository's five layers and their dependency direction. Use it to decide where a component or support file belongs and which layers it can import from.
8
8
 
9
- | Layer or scope | Path | Permitted contents | Imports from |
9
+ | Layer | Path | Permitted contents | Imports from |
10
10
  | -------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
11
- | `app` | `src/app/` | Next.js App Router framework-convention files and assets, plus styles required by routing files | compositions, features, shared, root |
11
+ | `app` | `src/app/` | [Next.js App Router routing files](https://nextjs.org/docs/app/getting-started/project-structure#routing-files), [metadata assets](https://nextjs.org/docs/app/getting-started/project-structure#metadata-file-conventions), and [styles required by routing files](https://nextjs.org/docs/app/getting-started/css) | compositions, features, shared, root |
12
12
  | `compositions` | `src/compositions/` | Components and support files (`hooks/`, `types/`, `schemas/`, `constants/`, `utils/`) | compositions, features, shared, root |
13
13
  | `features` | `src/features/<feature>/` | Components and support files (`hooks/`, `types/`, `schemas/`, `constants/`, `utils/`). `src/features/` itself holds feature folders only — never components or support folders | same feature, shared, root |
14
14
  | `shared` | `src/shared/` | Components and support files (`hooks/`, `types/`, `schemas/`, `constants/`, `utils/`) | shared, root |
15
- | `root` | `src/` | App-wide support files (`hooks/`, `types/`, `schemas/`, `constants/`, `utils/`), `locales/`, and `config/` | root support folders; a configuration module may also import files in its own `src/config/<config-name>/` directory |
16
-
17
- Root support folders are `hooks/`, `types/`, `schemas/`, `constants/`, `utils/`, `config/`, and `locales/`.
15
+ | `root` | `src/` | App-wide support folders: `hooks/`, `types/`, `schemas/`, `constants/`, `utils/`, `config/`, and `locales/` | root |
18
16
 
19
17
  ## Closest Common Folder (CCF)
20
18
 
21
- The CCF is the deepest eligible folder that contains every consumer of an item.
19
+ The CCF determines the placement of components, hooks, types, schemas, constants, and utilities. It is the closest folder shared by the files under `src/` that use the item.
20
+
21
+ ```text
22
+ src/features/payments/payment-card.tsx
23
+ src/features/payments/payment-history.tsx
24
+ └─ both import StatusBadge
25
+
26
+ CCF: src/features/payments/
27
+ Location: src/features/payments/status-badge.tsx
28
+ ```
29
+
30
+ For components, imports from `src/app/` and configuration modules do not affect the CCF. A component used by `src/app/` and a feature therefore stays with the feature.
31
+
32
+ ```text
33
+ src/app/products/page.tsx ─┐
34
+ src/features/products/product-card.tsx ─┴─ imports ProductPrice
22
35
 
23
- - Eligible folders are a nested component directory, a feature folder, a configuration-object directory, `src/features/`, `src/compositions/`, `src/shared/`, and `src/`.
24
- - Support folders and `src/app/` do not determine the CCF. A consumer in a support folder counts at its enclosing eligible component, feature, composition, shared, configuration-object, or root scope. A Next.js route-file consumer counts at `src/` unless a rule explicitly excludes it. `src/features/` is a non-placeable CCF boundary; the relevant placement rule maps it to a destination.
25
- - A consumer is a module under `src/` that uses the item. For initial placement only, include the origin module that contains the item before extraction; omit it after extraction. Apply a rule's import exclusions before calculating the CCF, then use it for placement unless that rule states otherwise.
36
+ CCF and location: src/features/products/product-price.tsx
37
+ ```
38
+
39
+ A component with no consumers outside `src/app/` or configuration modules stays in the feature it represents or supports. When no existing feature applies, it creates a feature folder.
40
+
41
+ ```text
42
+ src/app/search/page.tsx → SearchForm → src/features/search/search-form.tsx
43
+
44
+ src/app/account/page.tsx ─┐
45
+ src/app/orders/page.tsx ─┴→ AccountNav → src/features/account/account-nav.tsx
46
+ ```
47
+
48
+ When a component has consumers both inside and outside `src/compositions/`, only the consumers outside `src/compositions/` count. When all consumers are in `src/compositions/`, that folder counts.
49
+
50
+ ```text
51
+ src/compositions/checkout.tsx ─┐
52
+ src/features/payments/payment-summary.tsx ─┴→ Total → src/features/payments/total.tsx
53
+
54
+ src/compositions/checkout.tsx ─┐
55
+ src/compositions/receipt.tsx ─┴→ Total → src/compositions/total.tsx
56
+ ```
57
+
58
+ A component stays at a nested component folder, feature folder, `src/compositions/`, or `src/shared/`. A CCF of `src/features/` places it in `src/shared/`.
59
+
60
+ ```text
61
+ src/features/payments/payment-card.tsx ─┐
62
+ src/features/orders/order-card.tsx ─┴→ StatusBadge → src/shared/status-badge.tsx
63
+ ```
64
+
65
+ For a support file imported by a file under `src/app/`, use the matching root support folder, such as `src/hooks/` or `src/utils/`. Otherwise, the CCF passes through a support folder to its parent folder, and the file lives in the matching support folder directly below that parent.
66
+
67
+ ```text
68
+ src/app/products/page.tsx → useSearch → src/hooks/use-search.ts
69
+ src/features/payments/hooks/use-payment.ts → formatAmount → src/features/payments/utils/format-amount.ts
70
+ src/features/payments/payment-card.tsx ─┐
71
+ src/features/orders/order-card.tsx ─┴→ formatDate → src/utils/format-date.ts
72
+ ```
26
73
 
27
74
  ## Application Structure
28
75
 
@@ -30,10 +77,7 @@ This tree shows the canonical directory layout and naming conventions for files
30
77
 
31
78
  ```
32
79
  src/
33
- ├── app/ # Next.js framework-convention files, assets, and route styles
34
- │ ├── globals.css # Global stylesheet imported by the root layout
35
- │ ├── layout.tsx # Root layout
36
- │ └── <route>/ # Next.js App Router routing files
80
+ ├── app/ # Next.js framework-convention files, assets, and route styles
37
81
  ├── compositions/ # Components stay flat siblings by default
38
82
  │ ├── <ComponentA>.tsx # Smart component (PascalCase-named)
39
83
  │ ├── <component-b>.tsx # Dumb component (kebab-case-named)
@@ -94,13 +138,11 @@ src/
94
138
  ├── schemas/ # App-wide validation schemas
95
139
  ├── constants/ # App-wide constants
96
140
  ├── utils/ # App-wide pure functions
97
- ├── config/ # Configuration objects that centralize application behavior
98
- │ └── <config-name>/ # One folder per configuration object
99
- │ ├── index.ts # Config object
100
- │ ├── hooks/ # Config-only custom hooks
141
+ ├── config/ # Configuration modules that centralize application behavior
142
+ │ └── <config-name>/ # One folder per configuration module
143
+ │ ├── index.ts # Configuration-module entry point
101
144
  │ ├── types/ # Config-only TypeScript types
102
145
  │ ├── schemas/ # Config-only validation schemas
103
- │ ├── constants/ # Config-only constants
104
146
  │ └── utils/ # Config-only pure functions
105
147
  └── locales/ # App-wide locale strings
106
148
  └── index.ts # Single locale-registration file
@@ -2,28 +2,12 @@
2
2
 
3
3
  Without clear placement, it is hard to tell where a component belongs and reuse can create tangled dependencies. This rule gives each component a specific place in the application structure.
4
4
 
5
- - A routing UI file is a Next.js App Router UI file, excluding request handlers and metadata files. A Next.js `layout.tsx` routing UI file MAY render the layout required by its route segment and `children`. Any other routing UI file MAY import root support modules and directly render one feature component, one composition, or one shared component, but MUST NOT own state, callbacks, effects, or page layout.
6
- - A routing UI file other than `layout.tsx` that needs state, callbacks, effects, or page layout MUST delegate that work to a component in `src/compositions/`.
7
- - Any other component that imports from two or more feature folders MUST live in `src/compositions/` and is, by definition, a composition.
8
- - A component whose only consumers are routing files and that does not import from two or more feature folders MUST live in the feature folder whose behavior it implements. A component implements a feature when it reads that feature's data or exposes that feature's actions. If it implements no feature and only one routing file imports it, it MUST introduce a new feature folder for that route; if routing files for two or more routes import it, it MUST live in `src/shared/`. This rule does not apply to Next.js routing files themselves.
9
- - A component with at least one consumer other than a routing file MUST live in the closest common folder (CCF) of its non-routing consumers.
10
- - A new component with no consumers and fewer than two feature-folder imports MUST start in the existing feature folder it belongs to, or in a new feature folder when it introduces a new feature. A new component with no feature-specific behavior or data MAY start in `src/shared/` only when it imports no feature folders; otherwise it starts in the feature it imports from.
11
- - Imports from routing files in `src/app/` MUST NOT count as component consumers or affect CCF calculation.
12
- - Imports from configuration modules MUST NOT count as component consumers or affect CCF calculation.
13
- - When a component is imported both inside and outside `src/compositions/`, its CCF MUST be calculated only from imports outside `src/compositions/`.
14
- - When a component is imported only in `src/compositions/`, its CCF MUST be calculated from those imports.
15
- - When a component’s CCF is `src/features/`, it MUST live in `src/shared/`.
16
- - When a component’s CCF is `src/`, it MUST live in `src/shared/`.
17
- - When a component’s CCF is a nested component directory, it MUST start flat in that directory unless the Folder Nesting Rule requires nesting.
18
- - When a component’s CCF is a feature folder, `src/compositions/`, or `src/shared/`, it MUST start flat in that folder unless the Folder Nesting Rule requires nesting.
5
+ - A component that imports from two or more feature folders MUST live in `src/compositions/` and is, by definition, a composition.
6
+ - A component with no consumers outside `src/app/` or configuration modules, and that does not import from two or more feature folders, MUST live in the feature folder it represents or supports. If no existing feature applies, it MUST introduce a new feature folder.
7
+ - A component with at least one consumer outside `src/app/` and configuration modules MUST live in its CCF, calculated without imports from `src/app/` or configuration modules.
8
+ - When calculating a component's CCF, consumers under `src/compositions/` MUST count only when no consumer is outside `src/compositions/`.
9
+ - A component whose CCF is `src/features/` MUST live in `src/shared/`.
19
10
  - `src/app/` MUST contain [Next.js App Router framework-convention files and assets](https://nextjs.org/docs/app/getting-started/project-structure#routing-files), plus styles required by routing files, but MUST NOT contain ordinary components or support folders.
20
- - A JavaScript or TypeScript module under `src/app/` MUST NOT import modules under `src/` except from `src/compositions/`, `src/features/`, `src/shared/`, and root support folders.
21
- - A JavaScript or TypeScript module under `src/compositions/` MUST NOT import modules under `src/` except from `src/compositions/`, feature folders, `src/shared/`, and root support folders.
22
- - A JavaScript or TypeScript module in a feature folder MUST NOT import modules under `src/` except from the same feature folder, `src/shared/`, and root support folders.
23
- - A JavaScript or TypeScript module under `src/shared/` MUST NOT import modules under `src/` except from `src/shared/` and root support folders.
24
- - A JavaScript or TypeScript module in a root support folder MUST NOT import modules under `src/` except from root support folders. Any module under `src/config/<config-name>/` MAY also import files under that same configuration-object directory.
25
- - ESLint MUST enforce these import restrictions for JavaScript and TypeScript modules under `src/`.
26
-
27
11
  ## Incorrect — Cross-Feature Component Duplicated
28
12
 
29
13
  ```text
@@ -82,9 +66,9 @@ src/app/contact/
82
66
  └── page.tsx
83
67
  ```
84
68
 
85
- Why: the route folder contains ordinary component structure even though `src/app/` is reserved for framework routing files.
69
+ Why: the route folder contains ordinary component structure even though `src/app/` is reserved for framework-convention files, assets, and styles.
86
70
 
87
- ## Correct — Thin Route Imports a Page Composition
71
+ ## Correct — `src/app/` Imports a Page Composition
88
72
 
89
73
  ```text
90
74
  src/
@@ -107,7 +91,7 @@ import { locales } from "@/locales";
107
91
 
108
92
  export function ContactPageContent(): React.JSX.Element {
109
93
  return (
110
- <main data-testid="contact-page-content">
94
+ <main>
111
95
  <header>
112
96
  <h1>{locales.contactPageTitle}</h1>
113
97
  <p>{locales.contactPageDescription}</p>
@@ -132,4 +116,4 @@ export default function Page(): React.JSX.Element {
132
116
  }
133
117
  ```
134
118
 
135
- Why: the routing file remains thin while the page composition provides the contact page layout and combines components from two feature folders.
119
+ Why: `src/app/` contains the routing file while the composition provides the contact page layout and combines components from two feature folders.
@@ -1,13 +1,13 @@
1
1
  # Configuration Rule
2
2
 
3
- Configuration objects centralize values that control application behavior. This rule keeps each object and the files that only support it together in `src/config/`.
3
+ Configuration modules centralize values that control application behavior. This rule keeps each module and the files that only support it together in `src/config/`.
4
4
 
5
- - A configuration object is an app-wide object that selects or parameterizes application behavior. Feature options and domain values are not configuration objects.
6
- - All configuration objects MUST live in `src/config/`.
7
- - A configuration object MUST use `src/config/<config-name>/index.ts`.
8
- - A configuration support file is a hook, type, schema, constant, or utility used only to implement one configuration object; config-only extraction applies even with one consumer.
9
- - A configuration support file MUST live in its matching dedicated folder under `src/config/<config-name>/`.
10
- - A configuration support file remains in that configuration-object directory while every consumer is in that directory; it MUST move to its matching root support folder at the root CCF when any consumer is outside it.
5
+ - A configuration module is an app-wide module that selects or parameterizes application behavior.
6
+ - All configuration modules MUST live in `src/config/`.
7
+ - A configuration module MUST be one `src/config/<config-name>/` folder with `index.ts` as its entry point.
8
+ - A type, schema, or utility used only to implement one configuration module MUST be extracted even with one consumer.
9
+ - An extracted configuration type, schema, or utility MUST live in its matching dedicated folder under `src/config/<config-name>/`.
10
+ - An extracted configuration type, schema, or utility MUST move to its matching root support folder when a consumer outside its configuration module imports it.
11
11
 
12
12
  ## Incorrect — Supporting Schema Outside Its Config Folder
13
13
 
@@ -20,7 +20,7 @@ src/config/
20
20
 
21
21
  Why: the schema sits outside the `home-feed/` configuration folder.
22
22
 
23
- ## Correct — Configuration Object with Its Supporting Schema
23
+ ## Correct — Configuration Module with Its Supporting Schema
24
24
 
25
25
  ```text
26
26
  src/config/
@@ -39,4 +39,4 @@ export const homeFeedConfig = homeFeedConfigSchema.parse({
39
39
  });
40
40
  ```
41
41
 
42
- Why: the configuration object and its supporting schema are grouped in the same configuration folder.
42
+ Why: the configuration module and its supporting schema are grouped in the same configuration folder.
@@ -1,16 +1,13 @@
1
1
  # Constants Rule
2
2
 
3
- Duplicated constants are hard to keep in sync, while extracting every single-use value creates unnecessary files. This rule keeps reused constants in one module and leaves single-use values close to their consumer.
3
+ Duplicated constants are hard to keep in sync, while extracting every single-use value creates unnecessary files. This rule keeps reused constants in one file and leaves single-use values close to their consumer.
4
4
 
5
- - A constant is an exported or reused fixed domain value; local literals and implementation values are not constants for this rule. A configuration object is not a constant for this rule.
6
- - Except for a constant used only to implement one configuration object, a constant MUST stay in the file that uses it until another file imports it.
7
- - A constant used only to implement one configuration object MUST live in that object's `constants/` folder.
5
+ - A value MUST remain in its declaring component or file until another file imports it independently; it MUST then be extracted as a constant.
8
6
  - Extracted constants MUST live in a `constants/` folder at the closest common folder (CCF) of their consumers.
9
- - A `constants/index.ts` MAY define its exports directly.
10
- - Consumers MUST import extracted constants through the `constants/index.ts` at their scope.
11
- - Constants that are used together MAY be grouped in a file with a kebab-case name and named-re-exported from `constants/index.ts`.
12
- - When a `constants/` folder uses grouped files, its `index.ts` MUST only named-re-export those files.
7
+ - Consumers MUST import an extracted constant through the `index.ts` in that constant's `constants/` folder.
8
+ - A `constants/` folder MUST either define its constants directly in `index.ts` or group related constants in kebab-case files that `index.ts` named-re-exports.
13
9
  - When a constant's CCF is `src/features/`, it MUST move to `src/constants/`.
10
+ - A constant MAY live in `src/config/<module>/` instead of a `constants/` folder when a developer determines that it configures application behavior and is best understood alongside the configuration that parameterizes it, even when consumers exist outside the config module.
14
11
 
15
12
  ## Incorrect — Constant Imported Without `constants/index.ts`
16
13
 
@@ -1,12 +1,15 @@
1
1
  # Exports and Imports Rule
2
2
 
3
- Without consistent export and import styles, it is harder to tell what a file contains and how other files should import from it. This rule gives each file type a predictable export style and keeps import paths consistent.
4
-
5
- - Static JavaScript and TypeScript modules under `src/` MUST use named exports unless a framework or third-party package requires a different export style for a specific file.
6
- - Static imports under `src/` MUST use relative paths for the same folder, a direct subfolder, or one folder up.
7
- - Static imports under `src/` MUST use the `@/*` alias for anything beyond one folder up.
8
- - ESLint MUST enforce this rule's export and static import restrictions under `src/`.
9
-
3
+ Without consistent exports, import paths, and layer boundaries, it is harder to tell what a file contains and which files may depend on it. This rule gives each file a predictable export style and keeps imports consistent with the application structure.
4
+
5
+ - A file that exports values MUST use named exports unless a framework or third-party package requires a different export style for that file.
6
+ - Imports MUST use relative paths for the same folder, a direct subfolder, or one folder up.
7
+ - Imports MUST use the `@/*` alias for anything beyond one folder up.
8
+ - A file under `src/compositions/` MUST NOT import from `src/app/`.
9
+ - A file in a feature folder MUST NOT import from another feature folder, `src/compositions/`, or `src/app/`.
10
+ - A file under `src/shared/` MUST NOT import from `src/app/`, `src/compositions/`, or a feature folder.
11
+ - A file in the `root` layer MUST NOT import from `src/app/`, `src/compositions/`, a feature folder, or `src/shared/`.
12
+ - A configuration module MUST import only from root support folders and its own files.
10
13
  ## Incorrect — Single-Export Utility Uses a Default Export
11
14
 
12
15
  ```ts
@@ -2,8 +2,7 @@
2
2
 
3
3
  Without nesting, exclusive children can look reusable and their relationship to the parent is easy to miss in review. This rule groups them with their parent and keeps them out of the folder's public API.
4
4
 
5
- - An exclusive child component is imported only by its parent component or descendants in the parent's folder. A flat component MUST be nested in a folder with the same name when it gains one or more exclusive child components.
6
- - A child that gains a consumer outside its parent's folder MUST move according to the Component Placement Rule, including that rule's CCF consumer exclusions.
5
+ - An exclusive child component is imported only by its parent component. A component MUST stay flat until it has one or more exclusive child components, then MUST be nested in a folder with the same name.
7
6
  - A component MUST NOT be nested only because it has support files.
8
7
  - A nested component's support files MUST live in its folder.
9
8
  - The nested folder's `index.ts` MUST named-re-export the nested component and MUST NOT re-export its exclusive children.
@@ -3,22 +3,21 @@
3
3
  Keeping every hook inline makes components bloated, while extracting every hook adds indirection without benefit. This rule defines concrete reuse and imperative-complexity triggers for extraction.
4
4
 
5
5
  - A custom hook MUST be extracted to its own file when two or more consumers use it.
6
- - A custom hook used only to implement one configuration object MUST live in that object's `hooks/` folder.
7
6
  - A custom hook with exactly one consumer MUST be extracted when it contains two or more imperative categories and can be described as one coherent behavior.
8
7
  - An extracted custom hook MUST live in a `hooks/` folder at the closest common folder (CCF) of its consumers.
9
8
  - When a custom hook's CCF is `src/features/`, it MUST move to `src/hooks/`.
10
9
  - The imperative categories MUST be subscriptions, external I/O and persistence, DOM manipulation, or resource lifecycle.
11
- - One operation MAY count in only one imperative category; use its primary purpose when categories overlap.
10
+ - Each operation MUST count toward only one imperative category.
12
11
  - Subscriptions MUST include event listeners and registration or cleanup APIs such as `on()` and `off()`.
13
12
  - External I/O and persistence MUST include network requests, asynchronous reads or writes, and browser storage.
14
13
  - DOM manipulation MUST include imperative APIs such as `focus()`, `classList`, observers, or imperative rendering.
15
14
  - Resource lifecycle MUST include setup and teardown APIs such as `load()`, `destroy()`, or `dispose()`.
16
- - A custom hook with one consumer that does not meet the two-category threshold MUST stay inline in its consumer module, unless it is used only to implement one configuration object.
15
+ - A custom hook with one consumer that does not meet the two-category threshold MUST stay inline in its consumer file.
17
16
 
18
17
  ## Incorrect — Two Imperative Categories Left Inline
19
18
 
20
19
  ```tsx
21
- // src/features/player/Player.tsx
20
+ // src/features/player/player.tsx
22
21
  export function Player({ src }: PlayerProps): React.JSX.Element {
23
22
  useEffect(() => {
24
23
  player.on("play", handlePlay);
@@ -58,7 +57,7 @@ export function usePlayerSetup(src: string): void {
58
57
  ```
59
58
 
60
59
  ```tsx
61
- // src/features/player/Player.tsx
60
+ // src/features/player/player.tsx
62
61
  import { usePlayerSetup } from "./hooks/use-player-setup";
63
62
 
64
63
  export function Player({ src }: PlayerProps): React.JSX.Element {
@@ -2,7 +2,7 @@
2
2
 
3
3
  Large component files need a clear way to decide what to extract first. This rule treats interactive elements as meaningful component boundaries instead of extracting arbitrary layout elements.
4
4
 
5
- - An [interactive HTML element](https://html.spec.whatwg.org/multipage/dom.html#interactive-content) MUST be extracted to a component with a descriptive name unless it is the top-level native element returned by the component that renders it in every rendered result.
5
+ - An [interactive HTML element](https://html.spec.whatwg.org/multipage/dom.html#interactive-content) MUST be extracted to a component with a descriptive name.
6
6
 
7
7
  ## Incorrect — Interactive Element Kept Inline
8
8
 
@@ -18,8 +18,8 @@ export function HeaderSection({
18
18
  searchPlaceholder: string;
19
19
  }): React.JSX.Element {
20
20
  return (
21
- <header className="flex items-center justify-between px-6 py-4">
22
- <h1 className="text-2xl">{locales.layout.headerTitle}</h1>
21
+ <header>
22
+ <h1>{locales.layout.headerTitle}</h1>
23
23
 
24
24
  <button onClick={onMenuClick} aria-label={locales.layout.openMenu}>
25
25
  <Icon name="menu" />
@@ -71,23 +71,20 @@ export function SearchField(props: SearchFieldProps): React.JSX.Element {
71
71
 
72
72
  import { MenuButton } from "./menu-button";
73
73
  import { SearchField } from "./search-field";
74
- import { cn } from "@/utils/cn";
75
74
  import { locales } from "@/locales";
76
75
 
77
- type HeaderSectionProps = React.ComponentProps<"header"> & {
76
+ type HeaderSectionProps = {
78
77
  onMenuClick: () => void;
79
78
  searchPlaceholder: string;
80
79
  };
81
80
 
82
81
  export function HeaderSection({
83
- className,
84
82
  onMenuClick,
85
83
  searchPlaceholder,
86
- ...props
87
84
  }: HeaderSectionProps): React.JSX.Element {
88
85
  return (
89
- <header {...props} className={cn("flex items-center justify-between px-6 py-4", className)}>
90
- <h1 className="text-2xl">{locales.layout.headerTitle}</h1>
86
+ <header>
87
+ <h1>{locales.layout.headerTitle}</h1>
91
88
 
92
89
  <MenuButton onClick={onMenuClick} aria-label={locales.layout.openMenu} />
93
90
 
@@ -97,4 +94,4 @@ export function HeaderSection({
97
94
  }
98
95
  ```
99
96
 
100
- Why: each interactive element now has its own descriptive component and forwards native props, leaving `HeaderSection` to compose them.
97
+ Why: each interactive element now has its own descriptive component, leaving `HeaderSection` to compose them.
@@ -3,7 +3,7 @@
3
3
  JSX should show the component's structure, not its calculations. This rule moves complex expressions before `return` while keeping simple JSX readable.
4
4
 
5
5
  - Arithmetic, chained built-in method calls, calls to functions declared outside the component, nested ternaries, and conditions containing two or more logical operators MUST be extracted before `return`, including in JSX attributes.
6
- - An inline expression MAY contain one condition with at most one logical operator, one ternary, or one built-in method call. `cn()` is an explicit exception to the external-function-call restriction. Calls inside an event-handler callback are not JSX expressions.
6
+ - An inline expression MAY contain one condition with up to one logical operator, one ternary, or one built-in method call. `cn()` MAY be called inline. An event handler MAY make one call inline.
7
7
 
8
8
  ## Incorrect — Computation in JSX
9
9
 
@@ -40,7 +40,6 @@ const total = calculateTotal(items);
40
40
  const canShowAdmin = isLoggedIn && hasPermission && isOwner && featureEnabled;
41
41
  const publishDate = new Date(post.publishedAt).toLocaleDateString();
42
42
  const scorePercent = Math.round(score * 100);
43
- const daysAgoLabel = locales.daysAgo;
44
43
 
45
44
  let statusView = <Content />;
46
45
  if (isLoading) {
@@ -51,7 +50,7 @@ if (isLoading) {
51
50
 
52
51
  return (
53
52
  <div>
54
- <span>{daysSinceUpdate} {daysAgoLabel}</span>
53
+ <span>{daysSinceUpdate} {locales.daysAgo}</span>
55
54
  <ul>{activeItems.map(renderItem)}</ul>
56
55
  <p>
57
56
  {updatedLabel} — {total}