@activatus/code-standards 2.0.0 → 3.0.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 +12 -13
- package/docs/Guides/_category_.json +2 -2
- package/docs/Guides/migrating.md +33 -16
- package/docs/Guides/setup.md +46 -18
- package/docs/Standards/_category_.json +2 -2
- package/docs/Standards/agent-skills.md +29 -29
- package/docs/Standards/agent-specs.md +17 -32
- package/docs/Standards/direnv.mdx +8 -8
- package/docs/Standards/documentation.md +5 -2
- package/docs/Standards/editorconfig.md +23 -21
- package/docs/Standards/eslint.md +593 -402
- package/docs/Standards/human-readable-names.md +61 -21
- package/docs/Standards/lint-errors.md +37 -5
- package/docs/Standards/prettier.md +35 -26
- package/docs/Standards/readable-code.md +30 -0
- package/docs/Standards/stylelint.md +26 -11
- package/docs/Standards/styling.md +9 -3
- package/docs/Standards/tech-stack.md +3 -1
- package/docs/Structure/_category_.json +2 -2
- package/docs/Structure/component-organization.md +9 -2
- package/docs/Structure/folder-structure.md +26 -13
- package/docs/Structure/naming-conventions.md +0 -2
- package/docs/intro.md +1 -3
- package/docs/ontology.md +21 -26
- package/docs/templates.mdx +15 -38
- package/docs/version-history.md +133 -6
- package/package.json +6 -2
- package/scripts/renameSymbol.mjs +188 -0
- package/static/templates/.editorconfig +8 -11
- package/static/templates/AGENTS.md +16 -13
- package/static/templates/docs/development/coding-standards.md +4 -0
- package/static/templates/docsSite/docusaurus.config.mjs +144 -0
- package/static/templates/docsSite/sidebars.mjs +7 -0
- package/static/templates/docsSite/src/css/custom.css +6 -0
- package/static/templates/docsSite/tsconfig.json +5 -5
- package/static/templates/eslint/base/eslint.config.mjs +95 -70
- package/static/templates/eslint/nextjs/eslint.config.mjs +170 -126
- package/static/templates/eslint/react/eslint.config.mjs +160 -125
- package/static/templates/eslint/typescript/eslint.config.mjs +143 -115
- package/static/templates/globals.css +9 -9
- package/static/templates/next.config.mjs +13 -10
- package/static/templates/prettier.config.mjs +26 -0
- package/static/templates/stylelint/css/stylelint.config.mjs +37 -25
- package/static/templates/stylelint/scss/stylelint.config.mjs +10 -7
- package/docs/Standards/branch-naming.md +0 -66
- package/docs/Standards/conventional-commits.md +0 -189
- package/static/templates/.commitlintrc.json +0 -3
- package/static/templates/docsSite/docusaurus.config.ts +0 -119
- package/static/templates/docsSite/sidebars.ts +0 -8
- package/static/templates/husky/commit-msg +0 -1
- package/static/templates/prettier.config.js +0 -17
|
@@ -19,17 +19,14 @@ This applies to everyone writing code, **including AI agents**. An agent copies
|
|
|
19
19
|
| `(e) =>`, `(item, i) =>` | `(event) =>`, `(item, index) =>` |
|
|
20
20
|
| `acc` in a `reduce` | `runningTotal` — what it actually holds |
|
|
21
21
|
| `args`, `params` (our own) | `commandArguments`, `searchFilters` |
|
|
22
|
-
| `inputRef` | `inputReference` |
|
|
23
|
-
| `inputRef` | `inputReference` |
|
|
24
22
|
| `cwd` (our own variable) | `workingDirectory` |
|
|
25
|
-
| `deps`, `
|
|
23
|
+
| `deps`, `env` | `dependencies`, `environment` |
|
|
26
24
|
| `timeoutMs` | `timeoutMilliseconds` |
|
|
27
25
|
| `MAX_RETRY_ATTEMPTS` | `MAXIMUM_RETRY_ATTEMPTS` |
|
|
28
26
|
| `size: "sm" \| "lg"` | `size: "small" \| "large"` |
|
|
29
27
|
| `<T>` | `<ItemType>` |
|
|
30
28
|
| `name="email1"` | `name="emailAddress"` |
|
|
31
29
|
| `helpers.ts`, `misc.ts` | `formatCurrency.ts` — named after what it does |
|
|
32
|
-
| `lib/` | `library/` |
|
|
33
30
|
| `helpers/`, `utilities/` | `utils/` |
|
|
34
31
|
| `useAuth`, `authStore` | `useAuthentication`, `authenticationStore` |
|
|
35
32
|
| `STRIPE_SK` | `STRIPE_SECRET_KEY` |
|
|
@@ -59,7 +56,6 @@ Everything that has a name:
|
|
|
59
56
|
| **API routes** | `/api/calendar-events` | `/api/ce`, `/api/data`, `/api/v2x` |
|
|
60
57
|
| **Environment variables** | `STRIPE_SECRET_KEY`, `DOCS_URL` | `SK`, `KEY2`, `URL_1` |
|
|
61
58
|
| **Test names** | `it("adds a thousands separator")` | `it("works")`, `it("test 1")` |
|
|
62
|
-
| **Branches** | `feat/invoice-export` | `fix/stuff`, `toms-branch` |
|
|
63
59
|
| **Doc files** | `architecture/background-jobs.md` | `arch/bg.md`, `notes2.md` |
|
|
64
60
|
| **Error messages** | `"Enter an email address"` | `"Invalid input"`, `"Error 42"` |
|
|
65
61
|
|
|
@@ -129,30 +125,33 @@ Form fields are where names slip most, and where they matter most: a field's nam
|
|
|
129
125
|
|
|
130
126
|
Some short names are not abbreviations. They are the word people actually say, and the long form would only make the name harder to read. **These are dictionary words, and stay short:**
|
|
131
127
|
|
|
132
|
-
| Word | Means
|
|
133
|
-
| ---------------------------- |
|
|
134
|
-
| `id` | A unique id
|
|
135
|
-
| `docs` | A project's documentation
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
128
|
+
| Word | Means | Example names |
|
|
129
|
+
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
|
|
130
|
+
| `id` | A unique id | `userId`, `user_id`, `DEFAULT_PROJECT_ID` |
|
|
131
|
+
| `docs` | A project's documentation | `docs/`, `docsSite/`, `DOCS_URL`, `pnpm docs:dev` |
|
|
132
|
+
| `lib` | The folder for outside connections and app-wide setup. Next.js and shadcn/ui use the same name | `lib/`, `lib/integrations/` |
|
|
133
|
+
| `props` | The values a component takes | `props`, `ButtonProps` |
|
|
134
|
+
| `ref` | A React ref: a handle on an element or a value kept between renders | `inputRef`, `useRef`, `scrollContainerRef` |
|
|
135
|
+
| `repo` | A repository: in GitHub's words, it "contains all of your project's files and each file's revision history" | `repoName`, `repoUrl` |
|
|
136
|
+
| `utils` | Reusable functions that are not components | `utils/`, `utils/formatCurrency/` |
|
|
137
|
+
| `app` | The application | `app/`, `appName` |
|
|
138
|
+
| `url` | A web address | `imageUrl`, `DOCS_URL` |
|
|
139
|
+
| `api` | An application programming interface | `api/`, `apiResponse` |
|
|
140
|
+
| `uat` | User acceptance testing | `uatEnvironment` |
|
|
141
|
+
| `pdf`, `html`, `css`, `json` | File formats | `invoicePdf`, `htmlContent`, `globals.css` |
|
|
142
|
+
| `readme` | A project's README file | `readmeContent` |
|
|
144
143
|
|
|
145
144
|
**Case them as words** in camelCase and PascalCase: `imageUrl`, `apiResponse`, `docsSite` — never `imageURL` or `APIResponse`.
|
|
146
145
|
|
|
147
|
-
**This list is the only back door.** A short name not on it gets written out in full. If people say the long form out loud — "environment", "
|
|
146
|
+
**This list is the only back door.** A short name not on it gets written out in full. If people say the long form out loud — "environment", "dependencies", "message" — write the long form.
|
|
148
147
|
|
|
149
148
|
### Adding a word to the dictionary
|
|
150
149
|
|
|
151
150
|
When a short name feels like a dictionary word, don't just use it — add it here first:
|
|
152
151
|
|
|
153
|
-
1. **Ask:**
|
|
152
|
+
1. **Ask:** is this the word people actually say, so the long form would only be harder to read? `docs`, `repo`: yes. `env`, `btn`, `msg`: no.
|
|
154
153
|
2. **Add a row** to the table above, in the same pull request as the code that uses it.
|
|
155
|
-
3. **If ESLint flags the word**, add it to `replacements` in the `unicorn/name-replacements` rule of the [ESLint config](/Standards/eslint), so the linter agrees: `replacements: { docs: false, props: false, utils: false }`.
|
|
154
|
+
3. **If ESLint flags the word**, add it to `replacements` in the `unicorn/name-replacements` rule of the [ESLint config](/Standards/eslint), so the linter agrees: `replacements: { docs: false, lib: false, props: false, ref: false, repo: false, utils: false }`.
|
|
156
155
|
|
|
157
156
|
The list is the record. If a word is not on it, it is not a dictionary word yet.
|
|
158
157
|
|
|
@@ -189,6 +188,47 @@ execFile("pnpm", commandArguments, { workingDirectory });
|
|
|
189
188
|
const cwd = process.cwd();
|
|
190
189
|
```
|
|
191
190
|
|
|
191
|
+
### When a name clashes
|
|
192
|
+
|
|
193
|
+
**Keep a third-party import's own name.** When it clashes with something the file declares itself, **rename our own thing first**. A clash usually means our name is too vague: give it a more specific full-word name that says what it actually is.
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
// ✅ Good — our function says what it makes, so Supabase's createClient keeps its name
|
|
197
|
+
import { createClient } from "@supabase/supabase-js";
|
|
198
|
+
|
|
199
|
+
export const createProjectDatabaseClient = () =>
|
|
200
|
+
createClient(DATABASE_URL, DATABASE_KEY);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
// ❌ Bad — the import is renamed to make room for our vague name
|
|
205
|
+
import { createClient as createSupabaseClient } from "@supabase/supabase-js";
|
|
206
|
+
|
|
207
|
+
export const createClient = () =>
|
|
208
|
+
createSupabaseClient(DATABASE_URL, DATABASE_KEY);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Rename the import only when our side can't change:**
|
|
212
|
+
|
|
213
|
+
| The clash | Why our side can't change | Rename the import to |
|
|
214
|
+
| -------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------- |
|
|
215
|
+
| **Two third-party names**, such as `Link` from `next/link` and the `Link` icon from `lucide-react` | Neither name is ours | The owner plus the original name: `Link as LucideLinkIcon` |
|
|
216
|
+
| **Our name is fixed by a framework**, such as a Next.js route's `GET` or `generateMetadata` | The framework finds it only by that exact name | The owner plus the original name |
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
// ✅ Good — two third-party names clash, so one import is renamed: owner plus original name
|
|
220
|
+
import Link from "next/link";
|
|
221
|
+
import { Link as LucideLinkIcon } from "lucide-react";
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
// ❌ Bad — renamed for taste, or shortened
|
|
226
|
+
import { createClient as makeDb } from "@supabase/supabase-js";
|
|
227
|
+
import { Link as Icon } from "lucide-react";
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Never rename an import for taste, and never shorten it.
|
|
231
|
+
|
|
192
232
|
---
|
|
193
233
|
|
|
194
234
|
## Examples in code
|
|
@@ -231,4 +271,4 @@ users.map((u) => u.name);
|
|
|
231
271
|
| CSS variables and CSS Module classes | Stylelint |
|
|
232
272
|
| **Everything else** — folders, props, form fields, union values, database columns, routes, environment variables, test names | **Code review and AI agents.** The linter cannot read these, so every agent and reviewer checks them |
|
|
233
273
|
|
|
234
|
-
ESLint allows the [dictionary](#dictionary) it would otherwise flag (`docs`, `props`, `utils`), and exactly
|
|
274
|
+
ESLint allows the [dictionary](#dictionary) words it would otherwise flag (`docs`, `lib`, `props`, `ref`, `repo`, `utils`), and exactly two third-party names as they are: `params` and `searchParams`. It still flags any of our own names built from an third-party name, such as `inputRef`.
|
|
@@ -132,8 +132,40 @@ Nothing else is switched off. If another rule seems impossible to satisfy, the c
|
|
|
132
132
|
|
|
133
133
|
Lint runs in three places, so a broken standard is caught before it reaches `main`:
|
|
134
134
|
|
|
135
|
-
| Where
|
|
136
|
-
|
|
|
137
|
-
| **AI agents**
|
|
138
|
-
| **Git hook** (`.husky/pre-commit`) |
|
|
139
|
-
| **CI**
|
|
135
|
+
| Where | When | What happens on an error |
|
|
136
|
+
| --------------------------------------------------------------- | ------------------------- | ------------------------------------------------------------------------ |
|
|
137
|
+
| **AI agents** | Before finishing any task | The agent fixes the error. Its work is not done until `pnpm lint` passes |
|
|
138
|
+
| **Git hook** ([`.husky/pre-commit`](#lint-before-every-commit)) | On every commit | The commit is refused until the errors are fixed |
|
|
139
|
+
| **CI** | On every change pushed | The change cannot be merged |
|
|
140
|
+
|
|
141
|
+
## Lint before every commit
|
|
142
|
+
|
|
143
|
+
A [Husky](https://typicode.github.io/husky/) git hook runs `pnpm lint` before every commit, so code that breaks the standards cannot be committed.
|
|
144
|
+
|
|
145
|
+
1. Install Husky:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pnpm add -D husky
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
2. Add the `prepare` script to `package.json`, so the hook is installed whenever someone runs `pnpm install`:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"scripts": {
|
|
156
|
+
"prepare": "husky"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
3. Copy the hook file [`.husky/pre-commit`](/templates#husky-pre-commit):
|
|
162
|
+
|
|
163
|
+
```text title=".husky/pre-commit"
|
|
164
|
+
pnpm lint
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
4. Run `pnpm install` once to switch it on.
|
|
168
|
+
|
|
169
|
+
**Add the hook last**, once the project already passes `pnpm lint`. Otherwise every commit is refused until the whole codebase is fixed.
|
|
170
|
+
|
|
171
|
+
How the project names commits and branches, and who may commit, is up to the project. See [Project workflow](/Standards/agent-specs#project-workflow).
|
|
@@ -15,23 +15,32 @@ pnpm add -D prettier prettier-plugin-tailwindcss
|
|
|
15
15
|
|
|
16
16
|
## Usage
|
|
17
17
|
|
|
18
|
-
Create `prettier.config.
|
|
18
|
+
Create `prettier.config.mjs` in your project root:
|
|
19
19
|
|
|
20
|
-
```js title="prettier.config.
|
|
20
|
+
```js title="prettier.config.mjs"
|
|
21
21
|
/** @type {import("prettier").Config & import("prettier-plugin-tailwindcss").PluginOptions} */
|
|
22
22
|
const config = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
// Short lines, so code reads well on a phone.
|
|
24
|
+
printWidth: 70,
|
|
25
|
+
// Tabs indent. A tab counts as 2 characters towards the line width.
|
|
26
|
+
useTabs: true,
|
|
27
|
+
tabWidth: 2,
|
|
28
|
+
singleQuote: false,
|
|
29
|
+
trailingComma: "all",
|
|
30
|
+
bracketSpacing: true,
|
|
31
|
+
bracketSameLine: false,
|
|
32
|
+
arrowParens: "always",
|
|
33
|
+
semi: true,
|
|
34
|
+
endOfLine: "lf",
|
|
35
|
+
plugins: ["prettier-plugin-tailwindcss"],
|
|
36
|
+
tailwindStylesheet: "./app/globals.css",
|
|
37
|
+
overrides: [
|
|
38
|
+
{
|
|
39
|
+
// YAML does not allow tabs, and Markdown reads a tab as four spaces.
|
|
40
|
+
files: ["*.md", "*.mdx", "*.yml", "*.yaml"],
|
|
41
|
+
options: { useTabs: false },
|
|
42
|
+
},
|
|
43
|
+
],
|
|
35
44
|
};
|
|
36
45
|
|
|
37
46
|
export default config;
|
|
@@ -66,18 +75,18 @@ The config includes [`prettier-plugin-tailwindcss`](https://github.com/tailwindl
|
|
|
66
75
|
|
|
67
76
|
## Settings reference
|
|
68
77
|
|
|
69
|
-
| Option
|
|
70
|
-
|
|
|
71
|
-
| `printWidth`
|
|
72
|
-
| `tabWidth`
|
|
73
|
-
| `useTabs`
|
|
74
|
-
| `singleQuote`
|
|
75
|
-
| `trailingComma`
|
|
76
|
-
| `bracketSpacing`
|
|
77
|
-
| `bracketSameLine` | `false`
|
|
78
|
-
| `arrowParens`
|
|
79
|
-
| `semi`
|
|
80
|
-
| `endOfLine`
|
|
78
|
+
| Option | Value | Rationale |
|
|
79
|
+
| ----------------- | ---------- | ----------------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `printWidth` | `70` | Short lines read well on a phone, where a lot of code is reviewed |
|
|
81
|
+
| `tabWidth` | `2` | How many characters a tab counts as towards the line width; matches `.editorconfig` |
|
|
82
|
+
| `useTabs` | `true` | Tabs indent code: each person sees the width they like, and files are smaller. Markdown and YAML use spaces |
|
|
83
|
+
| `singleQuote` | `false` | Double quotes are standard in JSX and HTML; one style for all |
|
|
84
|
+
| `trailingComma` | `"all"` | Cleaner diffs when adding items to the end of a list |
|
|
85
|
+
| `bracketSpacing` | `true` | `{ foo }` is more readable than `{foo}` |
|
|
86
|
+
| `bracketSameLine` | `false` | Closing `>` on its own line for JSX |
|
|
87
|
+
| `arrowParens` | `"always"` | `(x) => x` is consistent whether 0, 1, or many params |
|
|
88
|
+
| `semi` | `true` | Explicit semicolons avoid ASI footguns |
|
|
89
|
+
| `endOfLine` | `"lf"` | Unix line endings everywhere; matches `.editorconfig` |
|
|
81
90
|
|
|
82
91
|
## Editor integration
|
|
83
92
|
|
|
@@ -521,6 +521,36 @@ interface UserCardData {}
|
|
|
521
521
|
|
|
522
522
|
Use a named type instead of a bare `string` wherever the value has a fixed set of options.
|
|
523
523
|
|
|
524
|
+
### Name a type after what it is, not the job it does
|
|
525
|
+
|
|
526
|
+
A type name says what the thing is. **If a type only describes one field of a function's props, don't give it its own name**: write it inside the `Props`, so the `Props` is the only type.
|
|
527
|
+
|
|
528
|
+
```ts
|
|
529
|
+
// ✅ Good — the dependencies are written inside the one Props type
|
|
530
|
+
interface StartAgentRunProps {
|
|
531
|
+
ticketId: string;
|
|
532
|
+
dependencies: {
|
|
533
|
+
launchSession: (ticketId: string) => Promise<void>;
|
|
534
|
+
reportStatus: (status: AgentRunStatusType) => void;
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
```ts
|
|
540
|
+
// ❌ Bad — a second type named after the role it plays in one function
|
|
541
|
+
interface StartAgentRunDependenciesType {
|
|
542
|
+
launchSession: (ticketId: string) => Promise<void>;
|
|
543
|
+
reportStatus: (status: AgentRunStatusType) => void;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
interface StartAgentRunProps {
|
|
547
|
+
ticketId: string;
|
|
548
|
+
dependencies: StartAgentRunDependenciesType;
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Where that object is built somewhere else, don't annotate it with a separate type either. TypeScript checks it against `StartAgentRunProps` when it is passed in.
|
|
553
|
+
|
|
524
554
|
---
|
|
525
555
|
|
|
526
556
|
## Error messages say what happened and what to do
|
|
@@ -7,10 +7,10 @@ sidebar_label: "Stylelint"
|
|
|
7
7
|
|
|
8
8
|
Components are styled with [Tailwind CSS](/Standards/styling), and Stylelint lints the CSS that is still written by hand — `globals.css` and any CSS Modules. Start from the config below that matches the project:
|
|
9
9
|
|
|
10
|
-
| Config
|
|
11
|
-
|
|
|
10
|
+
| Config | Use when |
|
|
11
|
+
| --------------------------------------- | ----------------------------------------- |
|
|
12
12
|
| [Plain CSS](#plain-css-and-css-modules) | Plain CSS and CSS Modules (`.module.css`) |
|
|
13
|
-
| [SCSS](#scss)
|
|
13
|
+
| [SCSS](#scss) | SCSS / Sass |
|
|
14
14
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
@@ -34,7 +34,10 @@ Create `stylelint.config.mjs` in your project root:
|
|
|
34
34
|
/** @type {import("stylelint").Config} */
|
|
35
35
|
export default {
|
|
36
36
|
ignoreFiles: ["**/vendor/**"],
|
|
37
|
-
extends: [
|
|
37
|
+
extends: [
|
|
38
|
+
"stylelint-config-standard",
|
|
39
|
+
"stylelint-config-recess-order",
|
|
40
|
+
],
|
|
38
41
|
rules: {
|
|
39
42
|
"selector-max-id": 0,
|
|
40
43
|
"property-no-vendor-prefix": true,
|
|
@@ -42,7 +45,16 @@ export default {
|
|
|
42
45
|
"import-notation": "string",
|
|
43
46
|
"at-rule-no-unknown": [
|
|
44
47
|
true,
|
|
45
|
-
{
|
|
48
|
+
{
|
|
49
|
+
ignoreAtRules: [
|
|
50
|
+
"theme",
|
|
51
|
+
"utility",
|
|
52
|
+
"variant",
|
|
53
|
+
"custom-variant",
|
|
54
|
+
"source",
|
|
55
|
+
"plugin",
|
|
56
|
+
],
|
|
57
|
+
},
|
|
46
58
|
],
|
|
47
59
|
},
|
|
48
60
|
overrides: [
|
|
@@ -67,7 +79,10 @@ export default {
|
|
|
67
79
|
/** @type {import("stylelint").Config} */
|
|
68
80
|
export default {
|
|
69
81
|
ignoreFiles: ["**/vendor/**"],
|
|
70
|
-
extends: [
|
|
82
|
+
extends: [
|
|
83
|
+
"stylelint-config-standard-scss",
|
|
84
|
+
"stylelint-config-recess-order",
|
|
85
|
+
],
|
|
71
86
|
rules: {
|
|
72
87
|
"selector-max-id": 0,
|
|
73
88
|
"property-no-vendor-prefix": true,
|
|
@@ -104,11 +119,11 @@ Add a lint script to `package.json`:
|
|
|
104
119
|
|
|
105
120
|
## Extended configs
|
|
106
121
|
|
|
107
|
-
| Config
|
|
108
|
-
|
|
|
109
|
-
| `stylelint-config-standard`
|
|
110
|
-
| `stylelint-config-standard-scss` | SCSS
|
|
111
|
-
| `stylelint-config-recess-order`
|
|
122
|
+
| Config | Used by | Purpose |
|
|
123
|
+
| -------------------------------- | ------------------------- | ---------------------------------------- |
|
|
124
|
+
| `stylelint-config-standard` | Plain CSS and CSS Modules | Stylelint's recommended modern CSS rules |
|
|
125
|
+
| `stylelint-config-standard-scss` | SCSS | Standard rules + SCSS-specific rules |
|
|
126
|
+
| `stylelint-config-recess-order` | all | Enforces Bootstrap/RECESS property order |
|
|
112
127
|
|
|
113
128
|
## Property order
|
|
114
129
|
|
|
@@ -56,7 +56,9 @@ export const PageHeaderActions = () => {
|
|
|
56
56
|
<button className="rounded-md bg-brand-primary px-4 py-2 text-white">
|
|
57
57
|
Save
|
|
58
58
|
</button>
|
|
59
|
-
<button className="rounded-md px-4 py-2 text-text">
|
|
59
|
+
<button className="rounded-md px-4 py-2 text-text">
|
|
60
|
+
Cancel
|
|
61
|
+
</button>
|
|
60
62
|
</div>
|
|
61
63
|
);
|
|
62
64
|
};
|
|
@@ -71,13 +73,17 @@ export const PageHeader = () => {
|
|
|
71
73
|
<header className="grid grid-cols-[1fr_auto] items-center gap-4">
|
|
72
74
|
<div className="flex flex-col gap-1">
|
|
73
75
|
<h1 className="text-2xl font-semibold text-text">Invoices</h1>
|
|
74
|
-
<p className="text-sm text-text">
|
|
76
|
+
<p className="text-sm text-text">
|
|
77
|
+
All invoices for this account
|
|
78
|
+
</p>
|
|
75
79
|
</div>
|
|
76
80
|
<div className="flex items-center gap-2">
|
|
77
81
|
<button className="rounded-md bg-brand-primary px-4 py-2 text-white">
|
|
78
82
|
Save
|
|
79
83
|
</button>
|
|
80
|
-
<button className="rounded-md px-4 py-2 text-text">
|
|
84
|
+
<button className="rounded-md px-4 py-2 text-text">
|
|
85
|
+
Cancel
|
|
86
|
+
</button>
|
|
81
87
|
</div>
|
|
82
88
|
</header>
|
|
83
89
|
);
|
|
@@ -38,7 +38,9 @@ the code reads as though it were typed. Storyblok's story type is the one to wat
|
|
|
38
38
|
|
|
39
39
|
```ts
|
|
40
40
|
// storyblok-js-client
|
|
41
|
-
interface ISbStoryData<
|
|
41
|
+
interface ISbStoryData<
|
|
42
|
+
Content = ISbComponentType<string> & { [index: string]: any },
|
|
43
|
+
> {
|
|
42
44
|
content: Content;
|
|
43
45
|
// …
|
|
44
46
|
}
|
|
@@ -216,7 +216,11 @@ export const UserListClient = ({
|
|
|
216
216
|
<input value={searchText} onChange={handleSearchTextChange} />
|
|
217
217
|
<ul>
|
|
218
218
|
{visibleUsers.map((user) => (
|
|
219
|
-
<UserListItem
|
|
219
|
+
<UserListItem
|
|
220
|
+
key={user.id}
|
|
221
|
+
user={user}
|
|
222
|
+
onUserSelect={onUserSelect}
|
|
223
|
+
/>
|
|
220
224
|
))}
|
|
221
225
|
</ul>
|
|
222
226
|
</section>
|
|
@@ -301,7 +305,10 @@ interface UserListItemProps {
|
|
|
301
305
|
onUserSelect?: (user: UserType) => void;
|
|
302
306
|
}
|
|
303
307
|
|
|
304
|
-
export const UserListItem = ({
|
|
308
|
+
export const UserListItem = ({
|
|
309
|
+
user,
|
|
310
|
+
onUserSelect,
|
|
311
|
+
}: UserListItemProps) => {
|
|
305
312
|
const handleClick = useCallback(() => {
|
|
306
313
|
onUserSelect?.(user);
|
|
307
314
|
}, [onUserSelect, user]);
|
|
@@ -49,25 +49,25 @@ project-root/
|
|
|
49
49
|
│ │ ├── types.ts # Component types
|
|
50
50
|
│ │ └── index.ts # Export file
|
|
51
51
|
│ └── shared/ # Used by more than one page
|
|
52
|
-
├──
|
|
53
|
-
│ ├── permissions/ #
|
|
52
|
+
├── lib/ # Outside connections and app-wide setup
|
|
53
|
+
│ ├── permissions/ # Who may do what
|
|
54
54
|
│ │ ├── index.ts # Main definitions
|
|
55
55
|
│ │ ├── api.ts # API permissions
|
|
56
56
|
│ │ ├── page.ts # Page permissions
|
|
57
57
|
│ │ └── plan.ts # Plan permissions
|
|
58
|
-
│ ├── integrations/ #
|
|
58
|
+
│ ├── integrations/ # The only code that talks to outside services
|
|
59
59
|
│ │ ├── stripe.ts # Payment processing
|
|
60
60
|
│ │ └── supabase/ # Database integration
|
|
61
61
|
│ │ ├── client.ts
|
|
62
62
|
│ │ └── mock/
|
|
63
|
-
│ ├── middleware/ #
|
|
63
|
+
│ ├── middleware/ # Code that runs on every request
|
|
64
64
|
│ │ ├── cors.ts
|
|
65
65
|
│ │ ├── rateLimit.ts
|
|
66
66
|
│ │ └── session.ts
|
|
67
|
-
│ ├── validation/ #
|
|
68
|
-
│ ├── constants/ # Constants
|
|
69
|
-
│ ├── errors/ # Error handlers
|
|
70
|
-
│ └── types/ #
|
|
67
|
+
│ ├── validation/ # Schemas that check input
|
|
68
|
+
│ ├── constants/ # Constants shared across the app
|
|
69
|
+
│ ├── errors/ # Error types and handlers shared across the app
|
|
70
|
+
│ └── types/ # Types shared across the app; a component's own types stay in its types.ts
|
|
71
71
|
├── utils/ # Reusable functions used across the app
|
|
72
72
|
│ ├── formatCurrency/ # One folder per function
|
|
73
73
|
│ │ ├── formatCurrency.ts
|
|
@@ -79,10 +79,8 @@ project-root/
|
|
|
79
79
|
│ │ └── index.ts
|
|
80
80
|
│ └── index.ts # Exports every function
|
|
81
81
|
├── hooks/ # Custom React hooks
|
|
82
|
-
├── services/ # Business logic
|
|
82
|
+
├── services/ # Business logic: what the app does
|
|
83
83
|
├── stores/ # State management stores
|
|
84
|
-
├── types/ # TypeScript type definitions
|
|
85
|
-
├── constants/ # App constants
|
|
86
84
|
├── styles/ # Global styles
|
|
87
85
|
├── public/ # Static assets
|
|
88
86
|
├── tests/ # Test files
|
|
@@ -108,7 +106,7 @@ project-root/
|
|
|
108
106
|
project-root/
|
|
109
107
|
├── app/ # Next.js App Router
|
|
110
108
|
├── components/ # React components
|
|
111
|
-
├──
|
|
109
|
+
├── lib/ # Outside connections and app-wide setup
|
|
112
110
|
├── utils/ # Reusable functions
|
|
113
111
|
├── public/ # Static assets
|
|
114
112
|
├── docs/ # Project documentation (Markdown)
|
|
@@ -178,6 +176,21 @@ Other frameworks follow the same rule with their own folders. In a React app wit
|
|
|
178
176
|
|
|
179
177
|
---
|
|
180
178
|
|
|
179
|
+
## 🗂️ utils, services and lib {#utils-services-and-lib}
|
|
180
|
+
|
|
181
|
+
Three folders hold functions that are not components. Each function belongs in exactly one of them:
|
|
182
|
+
|
|
183
|
+
| Folder | What goes in it | Ask yourself | Examples |
|
|
184
|
+
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
185
|
+
| **`utils/`** | Small reusable functions with no side effects. The same input always gives the same output: formatting, calculating, sorting, checking | "Does it only work with what it is given?" | `formatCurrency`, `calculateInvoiceTotal`, `sortInvoicesByDate` |
|
|
186
|
+
| **`services/`** | The business logic: what the app actually does. Its rules and operations, which may read and write data | "Is this a rule or an action of the product itself?" | `createInvoice`, `decideNextActions`, `sendWelcomeEmail` |
|
|
187
|
+
| **`lib/`** | Code that connects the app to something outside it, and the app-wide setup around that: integration clients, permissions, middleware, validation schemas, constants, errors and shared types | "Does it talk to another system, or set up something the whole app uses?" | `lib/integrations/stripe/`, `lib/permissions/`, `lib/validation/` |
|
|
188
|
+
|
|
189
|
+
- **Pages, API routes and server actions stay thin.** They take the request, call a function in `services/`, and return the result. Business logic never lives in them.
|
|
190
|
+
- **`services/` uses `lib/` to reach the outside world.** A service that charges a card calls the Stripe client in `lib/integrations/stripe/`, never the Stripe package directly.
|
|
191
|
+
- **`utils/` uses nothing outside itself.** If a function needs a database, an API or the environment, it is not a util: it belongs in `services/` or `lib/`.
|
|
192
|
+
- **Group `services/` by the [Ontology](/ontology) word it works on**, one folder per function: `services/invoice/createInvoice/`.
|
|
193
|
+
|
|
181
194
|
## 🧰 Utils
|
|
182
195
|
|
|
183
196
|
**Every reusable function lives in a `utils/` folder**, never inside a UI file. Where it goes depends on who uses it:
|
|
@@ -310,7 +323,7 @@ Name documentation folders and files in **kebab-case** (`background-jobs.md`), n
|
|
|
310
323
|
```text
|
|
311
324
|
/root
|
|
312
325
|
├── eslint.config.mjs # ESLint configuration
|
|
313
|
-
├── prettier.config.
|
|
326
|
+
├── prettier.config.mjs # Prettier configuration
|
|
314
327
|
├── tsconfig.json # TypeScript configuration
|
|
315
328
|
├── next.config.mjs # Next.js configuration
|
|
316
329
|
├── package.json # Dependencies & scripts
|
|
@@ -295,7 +295,6 @@ Code files are camelCase or PascalCase. A short list of things are kebab-case in
|
|
|
295
295
|
| **Route folders** | `app/account-settings/` | The folder name becomes the URL |
|
|
296
296
|
| **Documentation files** | `docs/architecture/background-jobs.md` | The file name becomes the URL |
|
|
297
297
|
| **CSS variables** | `--color-brand-primary` | The CSS convention; required by Tailwind |
|
|
298
|
-
| **Branch names** | `feat/invoice-export` | The Git convention |
|
|
299
298
|
|
|
300
299
|
**The rule of thumb: if the name becomes a URL, it is kebab-case.**
|
|
301
300
|
|
|
@@ -314,7 +313,6 @@ Code files are camelCase or PascalCase. A short list of things are kebab-case in
|
|
|
314
313
|
| **Constant files** | camelCase / SCREAMING_SNAKE_CASE | `userRole.ts` / `API_URL.ts` |
|
|
315
314
|
| **CSS modules** | camelCase + `.module.css` | `userProfile.module.css` |
|
|
316
315
|
| **CSS variables** | kebab-case | `--color-brand-primary` |
|
|
317
|
-
| **Branches** | `type/kebab-case` | `feat/invoice-export` |
|
|
318
316
|
| **Documentation files** | kebab-case | `background-jobs.md` |
|
|
319
317
|
| **Variables** | camelCase | `userName` |
|
|
320
318
|
| **Functions** | camelCase | `getUserData` |
|
package/docs/intro.md
CHANGED
|
@@ -17,12 +17,10 @@ This site documents the shared code standards used across all Activatus projects
|
|
|
17
17
|
| Documentation | [Docs standard](/Standards/documentation) | How project docs are written and shown at `/docs` |
|
|
18
18
|
| ESLint | [`eslint.config.mjs`](/Standards/eslint) | Linting rules for JS/TS/React/Next.js |
|
|
19
19
|
| Fixing Lint Errors | [Error guide](/Standards/lint-errors) | What each lint error means and how to fix it |
|
|
20
|
-
| Prettier | [`prettier.config.
|
|
20
|
+
| Prettier | [`prettier.config.mjs`](/Standards/prettier) | Code formatting |
|
|
21
21
|
| Stylelint | [`stylelint.config.mjs`](/Standards/stylelint) | Linting rules for CSS/SCSS/CSS Modules |
|
|
22
22
|
| EditorConfig | [`.editorconfig`](/Standards/editorconfig) | Editor-level whitespace rules |
|
|
23
23
|
| direnv + nvm | [`.envrc` / `.nvmrc`](/Standards/direnv) | Automatic Node version switching on `cd` |
|
|
24
|
-
| Conventional Commits | [Commit spec](/Standards/conventional-commits) | Commit message format and tooling |
|
|
25
|
-
| Branch Naming | [Branch names](/Standards/branch-naming) | `type/short-description` branch names |
|
|
26
24
|
| AGENTS.md | [`AGENTS.md`](/Standards/agent-specs) | AI agent context and agent skills |
|
|
27
25
|
| CSS Styling | [Styling guide](/Standards/styling) | Tailwind CSS, flexbox-first layout and global colour variables |
|
|
28
26
|
| Tech Stack | [Tech stack](/Standards/tech-stack) | The frameworks, libraries and versions we build on |
|