pasika 0.1.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -78
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +132 -0
- package/dist/enforcement/coverage.d.ts +51 -0
- package/dist/enforcement/coverage.js +210 -0
- package/dist/enforcement/docs-check.d.ts +17 -0
- package/dist/enforcement/docs-check.js +159 -0
- package/dist/enforcement/normalize.d.ts +11 -0
- package/dist/enforcement/normalize.js +21 -0
- package/dist/enforcement/parse-docs.d.ts +58 -0
- package/dist/enforcement/parse-docs.js +94 -0
- package/dist/enforcement/types.d.ts +57 -0
- package/dist/enforcement/types.js +59 -0
- package/dist/eslint/pasika/index.d.ts +13 -1
- package/dist/eslint/pasika/index.js +24 -11
- package/dist/eslint/pasika/project/ccf.d.ts +48 -0
- package/dist/eslint/pasika/project/ccf.js +119 -0
- package/dist/eslint/pasika/project/index.d.ts +21 -0
- package/dist/eslint/pasika/project/index.js +139 -0
- package/dist/eslint/pasika/project/parse-module.d.ts +27 -0
- package/dist/eslint/pasika/project/parse-module.js +128 -0
- package/dist/eslint/pasika/rules/component-placement.d.ts +11 -0
- package/dist/eslint/pasika/rules/component-placement.js +75 -0
- package/dist/eslint/pasika/rules/enforce-barrel-exports.d.ts +9 -0
- package/dist/eslint/pasika/rules/enforce-barrel-exports.js +78 -0
- package/dist/eslint/pasika/rules/enforce-cn-merge.d.ts +9 -0
- package/dist/eslint/pasika/rules/enforce-cn-merge.js +84 -0
- package/dist/eslint/pasika/rules/enforce-cva-variant-props.d.ts +9 -0
- package/dist/eslint/pasika/rules/enforce-cva-variant-props.js +81 -0
- package/dist/eslint/pasika/rules/filename-case.js +10 -0
- package/dist/eslint/pasika/rules/import-boundaries.d.ts +2 -0
- package/dist/eslint/pasika/rules/{organization-imports.js → import-boundaries.js} +62 -19
- package/dist/eslint/pasika/rules/no-arbitrary-tailwind.d.ts +9 -0
- package/dist/eslint/pasika/rules/no-arbitrary-tailwind.js +97 -0
- package/dist/eslint/pasika/rules/no-mixed-concerns.d.ts +9 -0
- package/dist/eslint/pasika/rules/no-mixed-concerns.js +84 -0
- package/dist/eslint/pasika/rules/support-file-placement.d.ts +15 -0
- package/dist/eslint/pasika/rules/support-file-placement.js +70 -0
- package/enforcement/registry.json +1139 -0
- package/package.json +21 -15
- package/claude/hooks/.vulyk +0 -3
- package/claude/hooks/AGENTS.md +0 -3
- package/claude/hooks/CLAUDE.md +0 -1
- package/claude/hooks/claude-hooks.md +0 -30
- package/claude/hooks/notification.sh +0 -38
- package/claude/hooks/protect-files.sh +0 -21
- package/claude/hooks/status-line/index.js +0 -57
- package/claude/scripts/render-settings.ts +0 -223
- package/claude/settings.base.json +0 -38
- package/dist/claude/scripts/render-settings.js +0 -145
- package/dist/eslint/pasika/rules/organization-imports.d.ts +0 -2
- package/dist/eslint.config.js +0 -7
- package/dist/scripts/pasika.js +0 -59
- package/docs/agent-conventions.md +0 -27
- package/docs/claude/hooks.md +0 -30
- package/docs/code-organization-guide/code-organization-guide.md +0 -69
- package/docs/code-organization-guide/references/application-architecture-reference.md +0 -149
- package/docs/code-organization-guide/rules/component-placement-rule.md +0 -119
- package/docs/code-organization-guide/rules/configuration-rule.md +0 -42
- package/docs/code-organization-guide/rules/constants-rule.md +0 -74
- package/docs/code-organization-guide/rules/exports-and-imports-rule.md +0 -84
- package/docs/code-organization-guide/rules/folder-nesting-rule.md +0 -82
- package/docs/code-organization-guide/rules/hook-extraction-rule.md +0 -141
- package/docs/code-organization-guide/rules/interactive-component-rule.md +0 -97
- package/docs/code-organization-guide/rules/jsx-hygiene-rule.md +0 -67
- package/docs/code-organization-guide/rules/locales-rule.md +0 -53
- package/docs/code-organization-guide/rules/nameable-visual-concept-rule.md +0 -66
- package/docs/code-organization-guide/rules/no-mixed-concerns-rule.md +0 -63
- package/docs/code-organization-guide/rules/repeated-structure-rule.md +0 -93
- package/docs/code-organization-guide/rules/smart-vs-dumb-component-rule.md +0 -112
- package/docs/code-organization-guide/rules/sole-state-owner-rule.md +0 -101
- package/docs/code-organization-guide/rules/types-and-schemas-rule.md +0 -139
- package/docs/code-organization-guide/rules/utilities-rule.md +0 -86
- package/docs/documentation-guide/_templates/grouped-reference.md +0 -11
- package/docs/documentation-guide/_templates/guide.md +0 -19
- package/docs/documentation-guide/_templates/rule.md +0 -21
- package/docs/documentation-guide/_templates/single-lookup-reference.md +0 -5
- package/docs/documentation-guide/documentation-guide.md +0 -13
- package/docs/documentation-guide/references/documentation-types-reference.md +0 -9
- package/docs/documentation-guide/rules/guide-creation-rule.md +0 -113
- package/docs/documentation-guide/rules/reference-creation-rule.md +0 -132
- package/docs/documentation-guide/rules/rule-creation-rule.md +0 -81
- package/docs/documentation-guide/rules/template-usage-rule.md +0 -49
- package/docs/shadcn-theme.md +0 -121
- package/docs/styling-guide/rules/arbitrary-value-rule.md +0 -31
- package/docs/styling-guide/rules/class-composition-rule.md +0 -52
- package/docs/styling-guide/rules/component-ui-state-rule.md +0 -53
- package/docs/styling-guide/rules/component-variant-rule.md +0 -125
- package/docs/styling-guide/rules/global-stylesheet-rule.md +0 -67
- package/docs/styling-guide/rules/theme-and-utility-definition-rule.md +0 -86
- package/docs/styling-guide/styling-guide.md +0 -14
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
# Smart vs Dumb Component Rule
|
|
2
|
-
|
|
3
|
-
Without a file-name convention, a component's smart vs dumb ownership is invisible to reviewers from the tree alone. Without a `data-testid` matching the file's casing, tests hardcode DOM identities that break on rename or restructure.
|
|
4
|
-
|
|
5
|
-
- A smart component MUST fetch data, or define `handle*` callbacks and pass them to children as `on*` props.
|
|
6
|
-
- A dumb component MUST NOT fetch data or define `handle*` callbacks for children.
|
|
7
|
-
- A smart component file name MUST be `PascalCase.tsx`.
|
|
8
|
-
- A dumb component file name MUST be `kebab-case.tsx`.
|
|
9
|
-
- A smart component with one outer DOM element in every rendered result MUST set `data-testid` on that element, and its value MUST match the component name in `PascalCase`.
|
|
10
|
-
- A smart component without one outer DOM element in every rendered result MAY omit `data-testid`.
|
|
11
|
-
- A dumb component MAY set `data-testid` on its root element, and the value MUST be `kebab-case`.
|
|
12
|
-
- [Next.js App Router routing files](https://nextjs.org/docs/app/getting-started/project-structure#routing-files) MUST use their required kebab-case names and are exempt from smart/dumb file-name and `data-testid` requirements.
|
|
13
|
-
|
|
14
|
-
## Incorrect — Smart Component Uses a Dumb Name
|
|
15
|
-
|
|
16
|
-
```tsx
|
|
17
|
-
// src/features/social/social-stats-panel.tsx
|
|
18
|
-
"use client";
|
|
19
|
-
|
|
20
|
-
import { useSocialStats } from "./hooks/use-social-stats";
|
|
21
|
-
import { PlatformCard } from "./platform-card";
|
|
22
|
-
import { locales } from "@/locales";
|
|
23
|
-
|
|
24
|
-
export function SocialStatsPanel(): React.JSX.Element {
|
|
25
|
-
const { stats, isLoading } = useSocialStats();
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div data-testid="social-stats-panel">
|
|
29
|
-
{isLoading ? (
|
|
30
|
-
<p>{locales.social.loading}</p>
|
|
31
|
-
) : (
|
|
32
|
-
stats.map((stat) => <PlatformCard key={stat.platform} data={stat} />)
|
|
33
|
-
)}
|
|
34
|
-
</div>
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Why: fetching data makes the component smart, but its file name and `data-testid` use dumb-component casing.
|
|
40
|
-
|
|
41
|
-
## Correct — Smart Component Uses a Smart Name
|
|
42
|
-
|
|
43
|
-
```tsx
|
|
44
|
-
// src/features/social/SocialStatsPanel.tsx
|
|
45
|
-
"use client";
|
|
46
|
-
|
|
47
|
-
import { useSocialStats } from "./hooks/use-social-stats";
|
|
48
|
-
import { PlatformCard } from "./platform-card";
|
|
49
|
-
import { locales } from "@/locales";
|
|
50
|
-
|
|
51
|
-
export function SocialStatsPanel(): React.JSX.Element {
|
|
52
|
-
const { stats, isLoading } = useSocialStats();
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div data-testid="SocialStatsPanel">
|
|
56
|
-
{isLoading ? (
|
|
57
|
-
<p>{locales.social.loading}</p>
|
|
58
|
-
) : (
|
|
59
|
-
stats.map((stat) => <PlatformCard key={stat.platform} data={stat} />)
|
|
60
|
-
)}
|
|
61
|
-
</div>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Why: fetching data makes the component smart, so its file name and `data-testid` use `PascalCase`.
|
|
67
|
-
|
|
68
|
-
## Incorrect — Dumb Component Uses a Smart Name
|
|
69
|
-
|
|
70
|
-
```tsx
|
|
71
|
-
// src/features/social/PlatformCard.tsx
|
|
72
|
-
|
|
73
|
-
export function PlatformCard({
|
|
74
|
-
data,
|
|
75
|
-
onFollowClick,
|
|
76
|
-
}: {
|
|
77
|
-
data: PlatformStat;
|
|
78
|
-
onFollowClick: () => void;
|
|
79
|
-
}): React.JSX.Element {
|
|
80
|
-
return (
|
|
81
|
-
<article data-testid="PlatformCard">
|
|
82
|
-
<h3>{data.platform}</h3>
|
|
83
|
-
<FollowButton onFollowClick={onFollowClick} />
|
|
84
|
-
</article>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Why: the component fetches no data and only receives a child callback, so it is dumb. Its file name and `data-testid` use smart-component casing.
|
|
90
|
-
|
|
91
|
-
## Correct — Dumb Component Uses a Dumb Name
|
|
92
|
-
|
|
93
|
-
```tsx
|
|
94
|
-
// src/features/social/platform-card.tsx
|
|
95
|
-
|
|
96
|
-
export function PlatformCard({
|
|
97
|
-
data,
|
|
98
|
-
onFollowClick,
|
|
99
|
-
}: {
|
|
100
|
-
data: PlatformStat;
|
|
101
|
-
onFollowClick: () => void;
|
|
102
|
-
}): React.JSX.Element {
|
|
103
|
-
return (
|
|
104
|
-
<article data-testid="platform-card">
|
|
105
|
-
<h3>{data.platform}</h3>
|
|
106
|
-
<FollowButton onFollowClick={onFollowClick} />
|
|
107
|
-
</article>
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Why: the component is dumb, so its file name and `data-testid` use `kebab-case`.
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# Sole State Owner Rule
|
|
2
|
-
|
|
3
|
-
Some blocks of elements are the only consumers of a state hook. This rule extracts those blocks into components that own the hook.
|
|
4
|
-
|
|
5
|
-
- A component MUST extract a named component when one part of its JSX contains every JSX expression, callback, and effect that reads one state hook's value or calls its updater.
|
|
6
|
-
|
|
7
|
-
## Incorrect — Parent Keeps Child-Only State
|
|
8
|
-
|
|
9
|
-
```tsx
|
|
10
|
-
// src/compositions/dashboard-view.tsx
|
|
11
|
-
import { locales } from "@/locales";
|
|
12
|
-
|
|
13
|
-
export function DashboardView(): React.JSX.Element {
|
|
14
|
-
const [isHelpOpen, setIsHelpOpen] = useState(false);
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<div>
|
|
18
|
-
<h1>{locales.dashboard}</h1>
|
|
19
|
-
<button onClick={() => setIsHelpOpen(true)} type="button">
|
|
20
|
-
{locales.help}
|
|
21
|
-
</button>
|
|
22
|
-
{isHelpOpen && (
|
|
23
|
-
<Modal onClose={() => setIsHelpOpen(false)}>
|
|
24
|
-
<HelpContent />
|
|
25
|
-
</Modal>
|
|
26
|
-
)}
|
|
27
|
-
<DashboardContent />
|
|
28
|
-
</div>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Why: the help button and modal are the only users of `isHelpOpen`, but the state lives in `DashboardView`.
|
|
34
|
-
|
|
35
|
-
## Correct — Child Owns Its State
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
src/compositions/
|
|
39
|
-
dashboard-view/
|
|
40
|
-
index.ts # re-exports only dashboard-view.tsx
|
|
41
|
-
dashboard-view.tsx
|
|
42
|
-
help-dialog/
|
|
43
|
-
index.ts # re-exports only help-dialog.tsx
|
|
44
|
-
help-dialog.tsx
|
|
45
|
-
help-trigger.tsx # exclusive child — imported directly
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
```tsx
|
|
49
|
-
// src/compositions/dashboard-view/help-dialog/help-dialog.tsx
|
|
50
|
-
import { HelpTrigger } from "./help-trigger";
|
|
51
|
-
|
|
52
|
-
export function HelpDialog(): React.JSX.Element {
|
|
53
|
-
const [isHelpOpen, setIsHelpOpen] = useState(false);
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<>
|
|
57
|
-
<HelpTrigger onOpen={() => setIsHelpOpen(true)} />
|
|
58
|
-
{isHelpOpen && (
|
|
59
|
-
<Modal onClose={() => setIsHelpOpen(false)}>
|
|
60
|
-
<HelpContent />
|
|
61
|
-
</Modal>
|
|
62
|
-
)}
|
|
63
|
-
</>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
```tsx
|
|
69
|
-
// src/compositions/dashboard-view/help-dialog/help-trigger.tsx
|
|
70
|
-
import { locales } from "@/locales";
|
|
71
|
-
|
|
72
|
-
type HelpTriggerProps = {
|
|
73
|
-
onOpen: () => void;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export function HelpTrigger({ onOpen }: HelpTriggerProps): React.JSX.Element {
|
|
77
|
-
return (
|
|
78
|
-
<button onClick={onOpen} type="button">
|
|
79
|
-
{locales.help}
|
|
80
|
-
</button>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
```tsx
|
|
86
|
-
// src/compositions/dashboard-view/dashboard-view.tsx
|
|
87
|
-
import { HelpDialog } from "./help-dialog";
|
|
88
|
-
import { locales } from "@/locales";
|
|
89
|
-
|
|
90
|
-
export function DashboardView(): React.JSX.Element {
|
|
91
|
-
return (
|
|
92
|
-
<div>
|
|
93
|
-
<h1>{locales.dashboard}</h1>
|
|
94
|
-
<HelpDialog />
|
|
95
|
-
<DashboardContent />
|
|
96
|
-
</div>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Why: `HelpDialog` owns the help button, modal, and `isHelpOpen` state. `DashboardView` only composes it.
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
# Types and Schemas Rule
|
|
2
|
-
|
|
3
|
-
Types and schemas are easy to bury in component files or scatter across the project. This rule keeps them close to one component and gives independently used ones a consistent location.
|
|
4
|
-
|
|
5
|
-
- A type or schema declared in a component MUST stay in that component file until another file imports it without the component where it is defined.
|
|
6
|
-
- Importing a type or schema alongside the component that defines it MUST NOT require extraction.
|
|
7
|
-
- A type or schema declared outside a component MUST stay in its file until another file needs it without using the code in that file.
|
|
8
|
-
- Extracted types and schemas MUST live in their matching `types/` or `schemas/` folder at the closest common folder (CCF) of their consumers.
|
|
9
|
-
- When a type or schema's CCF is `src/features/`, it MUST move to `src/types/` or `src/schemas/`.
|
|
10
|
-
- A `types/` or `schemas/` folder MUST either define its exports directly in `index.ts` or group related types and schemas in kebab-case files that `index.ts` named-re-exports.
|
|
11
|
-
- Consumers MUST import an extracted type or schema through the `index.ts` in that type or schema's `types/` or `schemas/` folder.
|
|
12
|
-
- A type or schema used only to implement one configuration module MUST live in that module's `types/` or `schemas/` folder.
|
|
13
|
-
- A type MAY stay in `src/config/<module>/` when its meaning is derived from the configuration that it parameterizes, even when consumers exist outside the config module.
|
|
14
|
-
|
|
15
|
-
## Incorrect — Feature and Composition Type Kept in a Feature
|
|
16
|
-
|
|
17
|
-
```text
|
|
18
|
-
src/
|
|
19
|
-
├── compositions/
|
|
20
|
-
│ └── BillingDashboard.tsx
|
|
21
|
-
├── features/
|
|
22
|
-
│ └── billing/
|
|
23
|
-
│ ├── invoice.tsx
|
|
24
|
-
│ └── types/
|
|
25
|
-
│ ├── billing.ts
|
|
26
|
-
│ └── index.ts
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
// src/features/billing/invoice.tsx
|
|
31
|
-
import type { InvoiceStatus } from "./types";
|
|
32
|
-
|
|
33
|
-
// src/compositions/BillingDashboard.tsx
|
|
34
|
-
import type { InvoiceStatus } from "@/features/billing/types";
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Why: the feature and composition have `src/` as their CCF, but the type remains in the billing feature.
|
|
38
|
-
|
|
39
|
-
## Correct — Feature and Composition Type in `src/types/`
|
|
40
|
-
|
|
41
|
-
```text
|
|
42
|
-
src/
|
|
43
|
-
├── compositions/
|
|
44
|
-
│ └── BillingDashboard.tsx
|
|
45
|
-
├── features/
|
|
46
|
-
│ └── billing/
|
|
47
|
-
│ └── invoice.tsx
|
|
48
|
-
└── types/
|
|
49
|
-
├── billing.ts
|
|
50
|
-
└── index.ts
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
// src/types/billing.ts
|
|
55
|
-
export type InvoiceStatus = "draft" | "paid";
|
|
56
|
-
export type PaymentMethod = "card" | "bank";
|
|
57
|
-
|
|
58
|
-
// src/types/index.ts
|
|
59
|
-
export type { InvoiceStatus, PaymentMethod } from "./billing";
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
```tsx
|
|
63
|
-
// src/features/billing/invoice.tsx
|
|
64
|
-
import type { InvoiceStatus } from "@/types";
|
|
65
|
-
|
|
66
|
-
// src/compositions/BillingDashboard.tsx
|
|
67
|
-
import type { InvoiceStatus } from "@/types";
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Why: the feature and composition have `src/` as their CCF, so the type lives in `src/types/`.
|
|
71
|
-
|
|
72
|
-
## Incorrect — Type Imported Independently from a Component
|
|
73
|
-
|
|
74
|
-
```tsx
|
|
75
|
-
// src/features/billing/invoice.tsx
|
|
76
|
-
export type DateRange = { from: Date; to: Date };
|
|
77
|
-
|
|
78
|
-
export function Invoice({ range }: { range: DateRange }): React.JSX.Element {
|
|
79
|
-
return <InvoiceView range={range} />;
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
// src/features/billing/hooks/use-billing-filter.ts
|
|
85
|
-
import type { DateRange } from "../invoice";
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Why: the hook reaches into a component file for a type it uses independently.
|
|
89
|
-
|
|
90
|
-
## Correct — Independently Used Type Extracted
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
// src/features/billing/types/index.ts
|
|
94
|
-
export type DateRange = { from: Date; to: Date };
|
|
95
|
-
|
|
96
|
-
// src/features/billing/hooks/use-billing-filter.ts
|
|
97
|
-
import { type DateRange } from "../types";
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Why: the component and hook can use the billing feature's types index independently.
|
|
101
|
-
|
|
102
|
-
## Incorrect — Schema Imported Independently from a Component
|
|
103
|
-
|
|
104
|
-
```tsx
|
|
105
|
-
// src/features/billing/invoice-form.tsx
|
|
106
|
-
import { z } from "zod";
|
|
107
|
-
|
|
108
|
-
export const invoiceSchema = z.object({
|
|
109
|
-
amount: z.number().positive(),
|
|
110
|
-
});
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
```ts
|
|
114
|
-
// src/features/billing/hooks/use-invoice-draft.ts
|
|
115
|
-
import { invoiceSchema } from "../invoice-form";
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Why: the hook reaches into a component file for a schema it uses independently.
|
|
119
|
-
|
|
120
|
-
## Correct — Independently Used Schema Extracted
|
|
121
|
-
|
|
122
|
-
```ts
|
|
123
|
-
// src/features/billing/schemas/index.ts
|
|
124
|
-
import { z } from "zod";
|
|
125
|
-
|
|
126
|
-
export const invoiceSchema = z.object({
|
|
127
|
-
amount: z.number().positive(),
|
|
128
|
-
});
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
```tsx
|
|
132
|
-
// src/features/billing/invoice-form.tsx
|
|
133
|
-
import { invoiceSchema } from "./schemas";
|
|
134
|
-
|
|
135
|
-
// src/features/billing/hooks/use-invoice-draft.ts
|
|
136
|
-
import { invoiceSchema } from "../schemas";
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
Why: the component and hook can use the billing feature's schemas index independently.
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# Utilities Rule
|
|
2
|
-
|
|
3
|
-
Pure functions should not be hidden in component files. This rule extracts them to a predictable `utils/` folder and keeps their imports direct.
|
|
4
|
-
|
|
5
|
-
- A pure function MUST be extracted to `utils/`, even when it has one consumer.
|
|
6
|
-
- An extracted utility MUST live in the `utils/` folder at the closest common folder (CCF) of its consumers.
|
|
7
|
-
- When a utility's CCF is `src/features/`, it MUST move to `src/utils/`.
|
|
8
|
-
- A utility MUST be imported directly without a barrel.
|
|
9
|
-
- A utility file that exports one function MUST have a name in that function's kebab-case form.
|
|
10
|
-
- Utilities that are used together MAY be grouped in a file with a kebab-case name.
|
|
11
|
-
- A utility used only to implement one configuration module MUST live in that module's `utils/` folder.
|
|
12
|
-
|
|
13
|
-
## Incorrect — Pure Function Left Beside Its Consumer
|
|
14
|
-
|
|
15
|
-
```tsx
|
|
16
|
-
// src/features/billing/invoice.tsx
|
|
17
|
-
const calculateInvoiceTotal = (items: InvoiceItem[]): number => {
|
|
18
|
-
return items.reduce((total, item) => total + item.amount, 0);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export function Invoice({ items }: InvoiceProps): React.JSX.Element {
|
|
22
|
-
const total = calculateInvoiceTotal(items);
|
|
23
|
-
return <InvoiceTotal value={total} />;
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Why: the pure function is mixed into a component file.
|
|
28
|
-
|
|
29
|
-
## Correct — Pure Function in a Directly Imported Utility File
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
// src/features/billing/utils/calculate-invoice-total.ts
|
|
33
|
-
export function calculateInvoiceTotal(items: InvoiceItem[]): number {
|
|
34
|
-
return items.reduce((total, item) => total + item.amount, 0);
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
```tsx
|
|
39
|
-
// src/features/billing/invoice.tsx
|
|
40
|
-
import { calculateInvoiceTotal } from "./utils/calculate-invoice-total";
|
|
41
|
-
|
|
42
|
-
export function Invoice({ items }: InvoiceProps): React.JSX.Element {
|
|
43
|
-
const total = calculateInvoiceTotal(items);
|
|
44
|
-
return <InvoiceTotal value={total} />;
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Why: the function has a focused utility file in the billing feature's `utils/` folder.
|
|
49
|
-
|
|
50
|
-
## Incorrect — Shared Utility Kept in a Feature
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
// src/features/billing/utils/format-retry-delay.ts
|
|
54
|
-
export function formatRetryDelay(milliseconds: number): string {
|
|
55
|
-
return `${milliseconds / 1000} seconds`;
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
```tsx
|
|
60
|
-
// src/features/billing/invoice.tsx
|
|
61
|
-
import { formatRetryDelay } from "./utils/format-retry-delay";
|
|
62
|
-
|
|
63
|
-
// src/compositions/billing-dashboard.tsx
|
|
64
|
-
import { formatRetryDelay } from "@/features/billing/utils/format-retry-delay";
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Why: the utility is imported by both a feature and a composition, so it cannot stay in the billing feature.
|
|
68
|
-
|
|
69
|
-
## Correct — Shared Utility in `src/utils/`
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
// src/utils/format-retry-delay.ts
|
|
73
|
-
export function formatRetryDelay(milliseconds: number): string {
|
|
74
|
-
return `${milliseconds / 1000} seconds`;
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
```tsx
|
|
79
|
-
// src/features/billing/invoice.tsx
|
|
80
|
-
import { formatRetryDelay } from "@/utils/format-retry-delay";
|
|
81
|
-
|
|
82
|
-
// src/compositions/billing-dashboard.tsx
|
|
83
|
-
import { formatRetryDelay } from "@/utils/format-retry-delay";
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Why: the utility's CCF is `src/`, so it lives in `src/utils/`.
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# [Topic] Reference
|
|
2
|
-
|
|
3
|
-
[1-2 short sentences naming exactly what readers can look up here and when they should use this reference.]
|
|
4
|
-
|
|
5
|
-
## [Lookup Block Name]
|
|
6
|
-
|
|
7
|
-
[1-2 short sentences explaining what this lookup block contains and why it is included.]
|
|
8
|
-
|
|
9
|
-
[Place this lookup block's content here.]
|
|
10
|
-
|
|
11
|
-
[Repeat this section for every remaining lookup block.]
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# [Topic] Guide
|
|
2
|
-
|
|
3
|
-
[1-2 short descriptive sentences explaining this guide's scope, underlying idea, and why it matters.]
|
|
4
|
-
|
|
5
|
-
## How To [Workflow]
|
|
6
|
-
|
|
7
|
-
[What does this accomplish? When should it run?]
|
|
8
|
-
|
|
9
|
-
1. [Step 1]
|
|
10
|
-
2. [Step 2]
|
|
11
|
-
|
|
12
|
-
## How To [Another Workflow]
|
|
13
|
-
|
|
14
|
-
[What does this accomplish? When should it run?]
|
|
15
|
-
|
|
16
|
-
1. [Step 1]
|
|
17
|
-
2. [Step 2]
|
|
18
|
-
|
|
19
|
-
[Repeat this section only when the guide has another real workflow. Otherwise delete it.]
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# [Rule Name] Rule
|
|
2
|
-
|
|
3
|
-
[What problem does this rule solve?]
|
|
4
|
-
|
|
5
|
-
- [Requirement using MUST, MUST NOT, SHOULD, SHOULD NOT, or MAY]
|
|
6
|
-
|
|
7
|
-
## Incorrect — [Concise example description]
|
|
8
|
-
|
|
9
|
-
```[lang]
|
|
10
|
-
// code
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Why: [What makes this incorrect?]
|
|
14
|
-
|
|
15
|
-
## Correct — [Concise example description]
|
|
16
|
-
|
|
17
|
-
```[lang]
|
|
18
|
-
// code
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Why: [What makes this correct?]
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# Documentation Guide
|
|
2
|
-
|
|
3
|
-
This guide explains how to create Rules, References, and Guides in this repository.
|
|
4
|
-
|
|
5
|
-
## How To Create a Document
|
|
6
|
-
|
|
7
|
-
Run this whenever you add a new doc to the repository.
|
|
8
|
-
|
|
9
|
-
1. Pick the document kind from the [Documentation Types Reference](references/documentation-types-reference.md) so you know the right template.
|
|
10
|
-
2. Follow the [Template Usage Rule](rules/template-usage-rule.md) so the doc has no leftover bracketed prompts.
|
|
11
|
-
3. For a Guide, follow the [Guide Creation Rule](rules/guide-creation-rule.md) so the doc has the correct shape, naming, and folder layout.
|
|
12
|
-
4. For a Rule, follow the [Rule Creation Rule](rules/rule-creation-rule.md) so the doc has the correct shape, naming, and vocabulary.
|
|
13
|
-
5. For a Reference, follow the [Reference Creation Rule](rules/reference-creation-rule.md) so the doc has the correct shape, naming, and folder layout.
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# Documentation Types Reference
|
|
2
|
-
|
|
3
|
-
Use this reference to look up the three standard Markdown document kinds in this repository.
|
|
4
|
-
|
|
5
|
-
| Kind | Shape | Template |
|
|
6
|
-
| --------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
7
|
-
| Guide | A workflow-oriented document with one or more `## How To [...]` sections. | `/_templates/guide.md` |
|
|
8
|
-
| Rule | An opinionated project rule that states required, forbidden, recommended, or optional behavior. | `/_templates/rule.md` |
|
|
9
|
-
| Reference | Lookup material. A lookup block is one self-contained set of facts for a reader's question. Use the single template for one lookup block and the grouped template for several blocks organized by topic. | `/_templates/single-lookup-reference.md` or `/_templates/grouped-reference.md` |
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
# Guide Creation Rule
|
|
2
|
-
|
|
3
|
-
Guides without a consistent creation process invent their own structure. This rule defines how to create a Guide document.
|
|
4
|
-
|
|
5
|
-
- Each How To step MUST be concise and use one sentence.
|
|
6
|
-
- A Guide MAY reference Rules, References, and other Guides.
|
|
7
|
-
- A Guide overview MUST contain one or two short descriptive sentences about the guide's scope and purpose, and MUST NOT contain instructions or links to other documentation.
|
|
8
|
-
- Each How To step MUST link at most one documentation file total, whether that file is a Rule, Reference, or Guide.
|
|
9
|
-
- When a step links a Rule, Reference, or Guide, the step MUST name the concrete decision or result the reader will have after reading it.
|
|
10
|
-
- A step that links another Guide MUST link directly to the relevant How To section.
|
|
11
|
-
- How To sections MUST NOT nest inside other How To sections.
|
|
12
|
-
- Guide file names MUST match the document title in kebab-case and use the `-guide` suffix (e.g., `foo-guide.md`).
|
|
13
|
-
- A Guide with private support files MUST become a folder named the same as its entry-point file, without the `.md` extension.
|
|
14
|
-
- Private Rule files for a Guide MUST live in a `rules/` subfolder inside the Guide's folder.
|
|
15
|
-
- Private Reference files for a Guide MUST live in a `references/` subfolder inside the Guide's folder.
|
|
16
|
-
|
|
17
|
-
## Incorrect — Private Documents Mixed at the Guide Root
|
|
18
|
-
|
|
19
|
-
```text
|
|
20
|
-
feature-workflow-guide/
|
|
21
|
-
├── feature-workflow-guide.md
|
|
22
|
-
├── naming-rule.md
|
|
23
|
-
└── option-reference.md
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Why: mixed Markdown files turn the doc folder into an undifferentiated list once the doc grows.
|
|
27
|
-
|
|
28
|
-
## Correct — Private Documents Grouped by Kind
|
|
29
|
-
|
|
30
|
-
```text
|
|
31
|
-
feature-workflow-guide/
|
|
32
|
-
├── feature-workflow-guide.md
|
|
33
|
-
├── rules/
|
|
34
|
-
│ └── naming-rule.md
|
|
35
|
-
└── references/
|
|
36
|
-
└── option-reference.md
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Why: private support files stay colocated with the entry point while being grouped by document kind.
|
|
40
|
-
|
|
41
|
-
## Incorrect — Step Links to Multiple Documents
|
|
42
|
-
|
|
43
|
-
```markdown
|
|
44
|
-
1. Follow the [Naming Rule](rules/naming-rule.md) and the [Layout Rule](rules/layout-rule.md) so the doc looks right.
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Why: step links two Rules and does not name a concrete decision or result.
|
|
48
|
-
|
|
49
|
-
## Correct — Step Links to One Document
|
|
50
|
-
|
|
51
|
-
```markdown
|
|
52
|
-
1. Follow the [Naming Rule](rules/naming-rule.md) so the doc has a predictable file name.
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Why: step links one Rule and names the concrete result the reader will have.
|
|
56
|
-
|
|
57
|
-
## Incorrect — Nested How To Section
|
|
58
|
-
|
|
59
|
-
```markdown
|
|
60
|
-
## How To Deploy
|
|
61
|
-
|
|
62
|
-
1. Build the project.
|
|
63
|
-
### How To Build
|
|
64
|
-
1. Run the build script.
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Why: a How To section nests inside a step of another How To section.
|
|
68
|
-
|
|
69
|
-
## Correct — Flat How To Sections
|
|
70
|
-
|
|
71
|
-
```markdown
|
|
72
|
-
## How To Deploy
|
|
73
|
-
|
|
74
|
-
1. Follow [How To Build](#how-to-build) to build the project.
|
|
75
|
-
|
|
76
|
-
## How To Build
|
|
77
|
-
|
|
78
|
-
1. Run the build script.
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Why: How To sections stay flat and reference each other via links.
|
|
82
|
-
|
|
83
|
-
## Incorrect — Multi-Sentence Step
|
|
84
|
-
|
|
85
|
-
```markdown
|
|
86
|
-
1. First, check the configuration file. Then verify all required fields are present. After that, confirm none of the fields contain invalid values.
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Why: step uses multiple sentences instead of one concise sentence.
|
|
90
|
-
|
|
91
|
-
## Correct — Concise Step
|
|
92
|
-
|
|
93
|
-
```markdown
|
|
94
|
-
1. Run the config check script.
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Why: step is one concise sentence with a concrete action.
|
|
98
|
-
|
|
99
|
-
## Incorrect — Guide Link Omits Its How To Section
|
|
100
|
-
|
|
101
|
-
```markdown
|
|
102
|
-
1. Follow the [Build Guide](build-guide.md) to build the project.
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
Why: link points at a Guide file but does not anchor to a specific How To section, so the reader lands on the guide's introduction and has to search for the relevant workflow.
|
|
106
|
-
|
|
107
|
-
## Correct — Guide Link Targets Its How To Section
|
|
108
|
-
|
|
109
|
-
```markdown
|
|
110
|
-
1. Follow [How To Build](build-guide.md#how-to-build) to build the project.
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Why: link anchors to the specific How To section, so the reader lands directly on the relevant workflow.
|