@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.
Files changed (51) hide show
  1. package/README.md +12 -13
  2. package/docs/Guides/_category_.json +2 -2
  3. package/docs/Guides/migrating.md +33 -16
  4. package/docs/Guides/setup.md +46 -18
  5. package/docs/Standards/_category_.json +2 -2
  6. package/docs/Standards/agent-skills.md +29 -29
  7. package/docs/Standards/agent-specs.md +17 -32
  8. package/docs/Standards/direnv.mdx +8 -8
  9. package/docs/Standards/documentation.md +5 -2
  10. package/docs/Standards/editorconfig.md +23 -21
  11. package/docs/Standards/eslint.md +593 -402
  12. package/docs/Standards/human-readable-names.md +61 -21
  13. package/docs/Standards/lint-errors.md +37 -5
  14. package/docs/Standards/prettier.md +35 -26
  15. package/docs/Standards/readable-code.md +30 -0
  16. package/docs/Standards/stylelint.md +26 -11
  17. package/docs/Standards/styling.md +9 -3
  18. package/docs/Standards/tech-stack.md +3 -1
  19. package/docs/Structure/_category_.json +2 -2
  20. package/docs/Structure/component-organization.md +9 -2
  21. package/docs/Structure/folder-structure.md +26 -13
  22. package/docs/Structure/naming-conventions.md +0 -2
  23. package/docs/intro.md +1 -3
  24. package/docs/ontology.md +21 -26
  25. package/docs/templates.mdx +15 -38
  26. package/docs/version-history.md +133 -6
  27. package/package.json +6 -2
  28. package/scripts/renameSymbol.mjs +188 -0
  29. package/static/templates/.editorconfig +8 -11
  30. package/static/templates/AGENTS.md +16 -13
  31. package/static/templates/docs/development/coding-standards.md +4 -0
  32. package/static/templates/docsSite/docusaurus.config.mjs +144 -0
  33. package/static/templates/docsSite/sidebars.mjs +7 -0
  34. package/static/templates/docsSite/src/css/custom.css +6 -0
  35. package/static/templates/docsSite/tsconfig.json +5 -5
  36. package/static/templates/eslint/base/eslint.config.mjs +95 -70
  37. package/static/templates/eslint/nextjs/eslint.config.mjs +170 -126
  38. package/static/templates/eslint/react/eslint.config.mjs +160 -125
  39. package/static/templates/eslint/typescript/eslint.config.mjs +143 -115
  40. package/static/templates/globals.css +9 -9
  41. package/static/templates/next.config.mjs +13 -10
  42. package/static/templates/prettier.config.mjs +26 -0
  43. package/static/templates/stylelint/css/stylelint.config.mjs +37 -25
  44. package/static/templates/stylelint/scss/stylelint.config.mjs +10 -7
  45. package/docs/Standards/branch-naming.md +0 -66
  46. package/docs/Standards/conventional-commits.md +0 -189
  47. package/static/templates/.commitlintrc.json +0 -3
  48. package/static/templates/docsSite/docusaurus.config.ts +0 -119
  49. package/static/templates/docsSite/sidebars.ts +0 -8
  50. package/static/templates/husky/commit-msg +0 -1
  51. 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`, `repo`, `env` | `dependencies`, `repository`, `environment` |
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 | Example names |
133
- | ---------------------------- | ------------------------------------------ | ------------------------------------------------- |
134
- | `id` | A unique id | `userId`, `user_id`, `DEFAULT_PROJECT_ID` |
135
- | `docs` | A project's documentation | `docs/`, `docsSite/`, `DOCS_URL`, `pnpm docs:dev` |
136
- | `props` | The values a component takes | `props`, `ButtonProps` |
137
- | `utils` | Reusable functions that are not components | `utils/`, `utils/formatCurrency/` |
138
- | `app` | The application | `app/`, `appName` |
139
- | `url` | A web address | `imageUrl`, `DOCS_URL` |
140
- | `api` | An application programming interface | `api/`, `apiResponse` |
141
- | `uat` | User acceptance testing | `uatEnvironment` |
142
- | `pdf`, `html`, `css`, `json` | File formats | `invoicePdf`, `htmlContent`, `globals.css` |
143
- | `readme` | A project's README file | `readmeContent` |
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", "repository", "application" — write the long form.
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:** would a non-developer say this word out loud, and know what it means? `docs`: yes. `env`, `repo`, `btn`: no.
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 three third-party names as they are — `ref`, `params` and `searchParams`. It still flags any of our own names built from an third-party name, such as `inputRef`.
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 | 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`) | When a person commits | The commit is refused until the errors are fixed |
139
- | **CI** | On every pull request | The pull request cannot be merged |
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.js` in your project root:
18
+ Create `prettier.config.mjs` in your project root:
19
19
 
20
- ```js title="prettier.config.js"
20
+ ```js title="prettier.config.mjs"
21
21
  /** @type {import("prettier").Config & import("prettier-plugin-tailwindcss").PluginOptions} */
22
22
  const config = {
23
- printWidth: 100,
24
- tabWidth: 2,
25
- useTabs: false,
26
- singleQuote: false,
27
- trailingComma: "all",
28
- bracketSpacing: true,
29
- bracketSameLine: false,
30
- arrowParens: "always",
31
- semi: true,
32
- endOfLine: "lf",
33
- plugins: ["prettier-plugin-tailwindcss"],
34
- tailwindStylesheet: "./app/globals.css",
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 | Value | Rationale |
70
- | --- | --- | --- |
71
- | `printWidth` | `100` | Wider than 80 but avoids the illegibility of unconstrained lines |
72
- | `tabWidth` | `2` | Standard for JS/TS; matches `.editorconfig` |
73
- | `useTabs` | `false` | Spaces are consistent across all editors |
74
- | `singleQuote` | `false` | Double quotes are standard in JSX and HTML; one style for all |
75
- | `trailingComma` | `"all"` | Cleaner diffs when adding items to the end of a list |
76
- | `bracketSpacing` | `true` | `{ foo }` is more readable than `{foo}` |
77
- | `bracketSameLine` | `false` | Closing `>` on its own line for JSX |
78
- | `arrowParens` | `"always"` | `(x) => x` is consistent whether 0, 1, or many params |
79
- | `semi` | `true` | Explicit semicolons avoid ASI footguns |
80
- | `endOfLine` | `"lf"` | Unix line endings everywhere; matches `.editorconfig` |
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 | Use when |
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) | SCSS / Sass |
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: ["stylelint-config-standard", "stylelint-config-recess-order"],
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
- { ignoreAtRules: ["theme", "utility", "variant", "custom-variant", "source", "plugin"] },
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: ["stylelint-config-standard-scss", "stylelint-config-recess-order"],
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 | Used by | Purpose |
108
- | --- | --- | --- |
109
- | `stylelint-config-standard` | Plain CSS and CSS Modules | Stylelint's recommended modern CSS rules |
110
- | `stylelint-config-standard-scss` | SCSS | Standard rules + SCSS-specific rules |
111
- | `stylelint-config-recess-order` | all | Enforces Bootstrap/RECESS property 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">Cancel</button>
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">All invoices for this account</p>
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">Cancel</button>
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<Content = ISbComponentType<string> & { [index: string]: any }> {
41
+ interface ISbStoryData<
42
+ Content = ISbComponentType<string> & { [index: string]: any },
43
+ > {
42
44
  content: Content;
43
45
  // …
44
46
  }
@@ -1,4 +1,4 @@
1
1
  {
2
- "label": "Structure",
3
- "position": 7
2
+ "label": "Structure",
3
+ "position": 7
4
4
  }
@@ -216,7 +216,11 @@ export const UserListClient = ({
216
216
  <input value={searchText} onChange={handleSearchTextChange} />
217
217
  <ul>
218
218
  {visibleUsers.map((user) => (
219
- <UserListItem key={user.id} user={user} onUserSelect={onUserSelect} />
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 = ({ user, onUserSelect }: UserListItemProps) => {
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
- ├── library/ # Core libraries & integrations
53
- │ ├── permissions/ # Permission files (grouped)
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/ # External services (grouped)
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/ # Middleware concerns (grouped)
63
+ │ ├── middleware/ # Code that runs on every request
64
64
  │ │ ├── cors.ts
65
65
  │ │ ├── rateLimit.ts
66
66
  │ │ └── session.ts
67
- │ ├── validation/ # Validation schemas
68
- │ ├── constants/ # Constants
69
- │ ├── errors/ # Error handlers
70
- │ └── types/ # Type definitions
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 services
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
- ├── library/ # Core libraries & integrations
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.js # Prettier configuration
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.js`](/Standards/prettier) | Code formatting |
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 |