@activatus/code-standards 0.0.0-stage → 2.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 (183) hide show
  1. package/README.md +200 -2
  2. package/docs/Guides/_category_.json +4 -0
  3. package/docs/Guides/component-library-workflow.md +182 -0
  4. package/docs/Guides/migrating.md +144 -0
  5. package/docs/Guides/setup.md +130 -0
  6. package/docs/Standards/_category_.json +4 -0
  7. package/docs/Standards/agent-skills.md +125 -0
  8. package/docs/Standards/agent-specs.md +89 -0
  9. package/docs/Standards/branch-naming.md +66 -0
  10. package/docs/Standards/conventional-commits.md +189 -0
  11. package/docs/Standards/direnv.mdx +249 -0
  12. package/docs/Standards/documentation.md +311 -0
  13. package/docs/Standards/editorconfig.md +72 -0
  14. package/docs/Standards/eslint.md +709 -0
  15. package/docs/Standards/human-readable-names.md +234 -0
  16. package/docs/Standards/lint-errors.md +139 -0
  17. package/docs/Standards/prettier.md +97 -0
  18. package/docs/Standards/readable-code.md +540 -0
  19. package/docs/Standards/stylelint.md +178 -0
  20. package/docs/Standards/styling.md +284 -0
  21. package/docs/Standards/tech-stack.md +156 -0
  22. package/docs/Structure/_category_.json +4 -0
  23. package/docs/Structure/api-routes.md +118 -0
  24. package/docs/Structure/component-organization.md +519 -0
  25. package/docs/Structure/folder-structure.md +353 -0
  26. package/docs/Structure/import-organization.md +158 -0
  27. package/docs/Structure/naming-conventions.md +336 -0
  28. package/docs/design-system-components.md +117 -0
  29. package/docs/intro.md +46 -0
  30. package/docs/ontology.md +132 -0
  31. package/docs/templates.mdx +419 -0
  32. package/docs/version-history.md +211 -0
  33. package/package.json +56 -3
  34. package/static/templates/.commitlintrc.json +3 -0
  35. package/static/templates/.editorconfig +28 -0
  36. package/static/templates/.envrc +25 -0
  37. package/static/templates/.nvmrc +1 -0
  38. package/static/templates/AGENTS.md +160 -0
  39. package/static/templates/decision-doc.md +19 -0
  40. package/static/templates/docs/ai/agent-harness.md +17 -0
  41. package/static/templates/docs/ai/agents.md +17 -0
  42. package/static/templates/docs/ai/architecture.md +17 -0
  43. package/static/templates/docs/ai/context.md +17 -0
  44. package/static/templates/docs/ai/model-routing.md +17 -0
  45. package/static/templates/docs/ai/models.md +17 -0
  46. package/static/templates/docs/ai/retrieval.md +17 -0
  47. package/static/templates/docs/ai/tools.md +17 -0
  48. package/static/templates/docs/architecture/authentication.md +17 -0
  49. package/static/templates/docs/architecture/backend.md +17 -0
  50. package/static/templates/docs/architecture/background-jobs.md +17 -0
  51. package/static/templates/docs/architecture/backups.md +17 -0
  52. package/static/templates/docs/architecture/caching.md +17 -0
  53. package/static/templates/docs/architecture/ci-cd.md +17 -0
  54. package/static/templates/docs/architecture/disaster-recovery.md +17 -0
  55. package/static/templates/docs/architecture/environments.md +17 -0
  56. package/static/templates/docs/architecture/frontend.md +17 -0
  57. package/static/templates/docs/architecture/hosting.md +17 -0
  58. package/static/templates/docs/architecture/observability.md +17 -0
  59. package/static/templates/docs/architecture/realtime.md +17 -0
  60. package/static/templates/docs/architecture/search.md +17 -0
  61. package/static/templates/docs/architecture/services.md +17 -0
  62. package/static/templates/docs/architecture/state-management.md +17 -0
  63. package/static/templates/docs/architecture/storage.md +17 -0
  64. package/static/templates/docs/architecture/system.md +17 -0
  65. package/static/templates/docs/business/buying-trigger.md +17 -0
  66. package/static/templates/docs/business/customer-value.md +17 -0
  67. package/static/templates/docs/business/customer.md +17 -0
  68. package/static/templates/docs/business/defensibility.md +17 -0
  69. package/static/templates/docs/business/growth.md +17 -0
  70. package/static/templates/docs/business/pricing.md +17 -0
  71. package/static/templates/docs/business/revenue-model.md +17 -0
  72. package/static/templates/docs/business/unit-economics.md +17 -0
  73. package/static/templates/docs/data/analytics.md +17 -0
  74. package/static/templates/docs/data/audit.md +17 -0
  75. package/static/templates/docs/data/database.md +17 -0
  76. package/static/templates/docs/data/indexing.md +17 -0
  77. package/static/templates/docs/data/migrations.md +17 -0
  78. package/static/templates/docs/data/model.md +17 -0
  79. package/static/templates/docs/data/retention.md +17 -0
  80. package/static/templates/docs/data/schema.md +17 -0
  81. package/static/templates/docs/deployment/approval.md +17 -0
  82. package/static/templates/docs/deployment/live-verification.md +17 -0
  83. package/static/templates/docs/deployment/process.md +17 -0
  84. package/static/templates/docs/deployment/rollback.md +17 -0
  85. package/static/templates/docs/design/design-system.md +17 -0
  86. package/static/templates/docs/design/errors.md +17 -0
  87. package/static/templates/docs/design/feedback.md +17 -0
  88. package/static/templates/docs/design/interactions.md +17 -0
  89. package/static/templates/docs/design/layout.md +17 -0
  90. package/static/templates/docs/design/navigation.md +17 -0
  91. package/static/templates/docs/design/principles.md +17 -0
  92. package/static/templates/docs/design/responsive.md +17 -0
  93. package/static/templates/docs/development/branching.md +17 -0
  94. package/static/templates/docs/development/code-quality.md +17 -0
  95. package/static/templates/docs/development/coding-standards.md +17 -0
  96. package/static/templates/docs/development/commits.md +17 -0
  97. package/static/templates/docs/development/merge-strategy.md +17 -0
  98. package/static/templates/docs/development/process.md +17 -0
  99. package/static/templates/docs/development/pull-requests.md +17 -0
  100. package/static/templates/docs/experience/accessibility.md +17 -0
  101. package/static/templates/docs/experience/aha-moment.md +17 -0
  102. package/static/templates/docs/experience/anti-experience.md +17 -0
  103. package/static/templates/docs/experience/friction.md +17 -0
  104. package/static/templates/docs/experience/outcomes.md +17 -0
  105. package/static/templates/docs/experience/principles.md +17 -0
  106. package/static/templates/docs/experience/retention.md +17 -0
  107. package/static/templates/docs/experience/sharing.md +17 -0
  108. package/static/templates/docs/overview/how-it-works.md +17 -0
  109. package/static/templates/docs/overview/what-is-it.md +17 -0
  110. package/static/templates/docs/overview/writing-docs.md +16 -0
  111. package/static/templates/docs/pitch/alternatives.md +17 -0
  112. package/static/templates/docs/pitch/assumptions.md +17 -0
  113. package/static/templates/docs/pitch/audience.md +17 -0
  114. package/static/templates/docs/pitch/concept.md +17 -0
  115. package/static/templates/docs/pitch/differentiators.md +17 -0
  116. package/static/templates/docs/pitch/outcomes.md +17 -0
  117. package/static/templates/docs/pitch/problem.md +17 -0
  118. package/static/templates/docs/pitch/product-market-fit.md +17 -0
  119. package/static/templates/docs/pitch/success-criteria.md +17 -0
  120. package/static/templates/docs/pitch/validation.md +17 -0
  121. package/static/templates/docs/pitch/value-proposition.md +17 -0
  122. package/static/templates/docs/pitch/vision.md +17 -0
  123. package/static/templates/docs/scope/core.md +17 -0
  124. package/static/templates/docs/scope/exclusions.md +17 -0
  125. package/static/templates/docs/scope/future.md +17 -0
  126. package/static/templates/docs/scope/launch.md +17 -0
  127. package/static/templates/docs/scope/post-launch.md +17 -0
  128. package/static/templates/docs/scope/priorities.md +17 -0
  129. package/static/templates/docs/scope/quality-bar.md +17 -0
  130. package/static/templates/docs/security/audit.md +17 -0
  131. package/static/templates/docs/security/data-access.md +17 -0
  132. package/static/templates/docs/security/logging.md +17 -0
  133. package/static/templates/docs/security/privacy.md +17 -0
  134. package/static/templates/docs/security/secrets.md +17 -0
  135. package/static/templates/docs/security/threat-model.md +17 -0
  136. package/static/templates/docs/testing/end-to-end.md +17 -0
  137. package/static/templates/docs/testing/integration.md +17 -0
  138. package/static/templates/docs/testing/regression.md +17 -0
  139. package/static/templates/docs/testing/security.md +17 -0
  140. package/static/templates/docs/testing/strategy.md +17 -0
  141. package/static/templates/docs/testing/test-data.md +17 -0
  142. package/static/templates/docs/testing/unit.md +17 -0
  143. package/static/templates/docs/uat/checks.md +17 -0
  144. package/static/templates/docs/uat/strategy.md +17 -0
  145. package/static/templates/docs/users/permissions.md +17 -0
  146. package/static/templates/docs/users/roles.md +17 -0
  147. package/static/templates/docs/users/user-types.md +17 -0
  148. package/static/templates/docs/workflows/assignment.md +17 -0
  149. package/static/templates/docs/workflows/bugfix.md +17 -0
  150. package/static/templates/docs/workflows/dependencies.md +17 -0
  151. package/static/templates/docs/workflows/deployment.md +17 -0
  152. package/static/templates/docs/workflows/development.md +17 -0
  153. package/static/templates/docs/workflows/planning.md +17 -0
  154. package/static/templates/docs/workflows/review.md +17 -0
  155. package/static/templates/docs/workflows/uat.md +17 -0
  156. package/static/templates/docsSite/docusaurus.config.ts +119 -0
  157. package/static/templates/docsSite/gitignore.txt +3 -0
  158. package/static/templates/docsSite/package.json.txt +42 -0
  159. package/static/templates/docsSite/pnpm-workspace.yaml +2 -0
  160. package/static/templates/docsSite/sidebars.ts +8 -0
  161. package/static/templates/docsSite/src/css/custom.css +1 -0
  162. package/static/templates/docsSite/src/pages/index.tsx +9 -0
  163. package/static/templates/docsSite/static/images/logo.svg +4 -0
  164. package/static/templates/docsSite/tsconfig.json +7 -0
  165. package/static/templates/eslint/base/eslint.config.mjs +79 -0
  166. package/static/templates/eslint/nextjs/eslint.config.mjs +137 -0
  167. package/static/templates/eslint/react/eslint.config.mjs +138 -0
  168. package/static/templates/eslint/typescript/eslint.config.mjs +126 -0
  169. package/static/templates/globals.css +16 -0
  170. package/static/templates/husky/commit-msg +1 -0
  171. package/static/templates/husky/pre-commit +1 -0
  172. package/static/templates/next.config.mjs +18 -0
  173. package/static/templates/prettier.config.js +17 -0
  174. package/static/templates/skills/codeReview.md +31 -0
  175. package/static/templates/skills/format.md +18 -0
  176. package/static/templates/skills/lint.md +21 -0
  177. package/static/templates/skills/lintFix.md +24 -0
  178. package/static/templates/skills/securityAudit.md +28 -0
  179. package/static/templates/skills/test.md +24 -0
  180. package/static/templates/skills/typescript.md +28 -0
  181. package/static/templates/skills/updateLayout.md +20 -0
  182. package/static/templates/stylelint/css/stylelint.config.mjs +28 -0
  183. package/static/templates/stylelint/scss/stylelint.config.mjs +10 -0
@@ -0,0 +1,540 @@
1
+ ---
2
+ sidebar_position: 0
3
+ ---
4
+
5
+ # Readable Code
6
+
7
+ Every file must be readable by a person who has never seen it before. Code is read far more often than it is written, so we optimise for the reader.
8
+
9
+ ## The rules
10
+
11
+ | Rule | Enforced by |
12
+ | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
13
+ | [No file longer than 300 lines](#no-file-longer-than-300-lines) | ESLint `max-lines` (test files exempt) |
14
+ | [Names are full English words](/Standards/human-readable-names) — everywhere, including form fields | ESLint for code names; code review and AI agents for the rest |
15
+ | [Arrow functions everywhere](#arrow-functions-everywhere) | ESLint `func-style`, `prefer-arrow-callback` |
16
+ | [Always `const`](#always-const) | ESLint `no-restricted-syntax`, `prefer-const`, `no-var` |
17
+ | [A function does one thing](#a-function-does-one-thing) | Code review |
18
+ | [Every function and component has a test file](#every-function-and-component-has-a-test-file) | Developers and AI agents — not linted |
19
+ | [Functions, never classes](#functions-never-classes) | ESLint `no-restricted-syntax` |
20
+ | [Return early, nest shallow](#return-early-nest-shallow) | ESLint `max-depth` |
21
+ | [No magic numbers or strings](#no-magic-numbers-or-strings) | ESLint `no-magic-numbers` (numbers), code review (strings) |
22
+ | [One parameter; always an object for more](#one-parameter-always-an-object-for-more) | ESLint `no-restricted-syntax` |
23
+ | [No nested ternaries](#no-nested-ternaries) | ESLint `no-nested-ternary` |
24
+ | [No boolean flag parameters](#no-boolean-flag-parameters) | Code review |
25
+ | [Comments explain why, not what](#comments-explain-why-not-what) | Code review |
26
+ | [No `any`, no `!`, no unexplained `as`](#no-any-no--no-unexplained-as) | ESLint `@typescript-eslint` rules |
27
+ | [One component per file](#one-component-per-file) | ESLint `react/no-multi-comp` |
28
+ | [Named exports only](#named-exports-only) | ESLint `import-x/no-default-export` |
29
+ | [Type names say what they are](#type-names-say-what-they-are) | ESLint `@typescript-eslint/naming-convention` |
30
+ | [Error messages say what happened and what to do](#error-messages-say-what-happened-and-what-to-do) | Code review |
31
+
32
+ See the [ESLint configuration](/Standards/eslint) for the rule setup, and [Fixing Lint Errors](/Standards/lint-errors) for what each error means and how to fix it.
33
+
34
+ ---
35
+
36
+ ## No file longer than 300 lines
37
+
38
+ A file over 300 lines is doing more than one job. Split it before it gets there:
39
+
40
+ - Move reusable functions into a [utils folder](/Structure/folder-structure#-utils)
41
+ - Move sub-components into [their own folders](/Structure/component-organization#nested-components)
42
+ - Move types into `types.ts`
43
+
44
+ Blank lines and comments do not count towards the limit. **Test files are exempt** — a thorough test file can run long.
45
+
46
+ ---
47
+
48
+ ## Names are full English words
49
+
50
+ **Every name in the project is made of whole, human-readable words** — not just variables, but folders, files, props, form fields, types, database columns, environment variables, everything.
51
+
52
+ This rule has its own page, because it applies everywhere: **[Human-Readable Names](/Standards/human-readable-names)**. Read it in full.
53
+
54
+ ---
55
+
56
+ ## Arrow functions everywhere
57
+
58
+ **Every function is an arrow function.** Components, utils, services, callbacks — all of them. Never use the `function` keyword.
59
+
60
+ One way of writing a function means nobody has to choose, and every file reads the same.
61
+
62
+ ```tsx
63
+ // ✅ Good
64
+ export const Button = ({ label }: ButtonProps) => {
65
+ return <button>{label}</button>;
66
+ };
67
+
68
+ export const formatCurrency = (amount: number) => {
69
+ return CURRENCY_FORMATTER.format(amount);
70
+ };
71
+
72
+ export const getUsers = async () => {
73
+ return database.users.findAll();
74
+ };
75
+ ```
76
+
77
+ ```tsx
78
+ // ❌ Bad
79
+ export function Button({ label }: ButtonProps) {
80
+ return <button>{label}</button>;
81
+ }
82
+
83
+ export const formatCurrency = function (amount: number) {
84
+ return CURRENCY_FORMATTER.format(amount);
85
+ };
86
+ ```
87
+
88
+ Three things to know:
89
+
90
+ - **Define before use.** An arrow function is written above the code that uses it. This is [enforced](/Structure/import-organization#3-define-before-use), and it means a mistake fails straight away instead of being hidden.
91
+ - **Generic components need a trailing comma** in `.tsx` files, so the type parameter is not read as JSX: `const List = <ItemType,>({ items }: ListProps<ItemType>) => { … }`.
92
+ - **Next.js files that need a default export** take two lines:
93
+
94
+ ```tsx title="app/users/page.tsx"
95
+ const Page = () => {
96
+ return <UserList />;
97
+ };
98
+
99
+ export default Page;
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Always `const`
105
+
106
+ **Declare every variable with `const`.** A `const` cannot be reassigned, so a reader knows its value is the same everywhere below. Never use `var`.
107
+
108
+ ```ts
109
+ // ✅ Good
110
+ const orderTotal = orderItems.reduce(
111
+ (runningTotal, orderItem) => runningTotal + orderItem.price,
112
+ 0,
113
+ );
114
+ const activeUsers = users.filter((user) => user.isActive);
115
+ ```
116
+
117
+ ```ts
118
+ // ❌ Bad
119
+ let orderTotal = 0;
120
+ for (const orderItem of orderItems) {
121
+ orderTotal += orderItem.price;
122
+ }
123
+
124
+ var activeUsers = [];
125
+ ```
126
+
127
+ ### When `let` is genuinely needed
128
+
129
+ Sometimes a value really must change. Use `let`, and **add a comment that says so** — the same way a type cast needs one. Without the comment, ESLint rejects the `let`:
130
+
131
+ ```ts
132
+ // eslint-disable-next-line no-restricted-syntax -- the retry count goes up on every failed attempt
133
+ let retryAttemptCount = 0;
134
+ ```
135
+
136
+ The comment is the point: it tells the reader "yes, this one changes, and here is why". Before using `let`, check whether `map`, `filter` or `reduce` with `const` does the job.
137
+
138
+ ---
139
+
140
+ ## A function does one thing
141
+
142
+ If you need the word "and" to describe what a function does, it is two functions.
143
+
144
+ ```ts
145
+ // ✅ Good — each function has one job
146
+ const validateEmailAddress = (emailAddress: string) => {
147
+ return EMAIL_ADDRESS_PATTERN.test(emailAddress);
148
+ };
149
+
150
+ const saveUser = (user: UserType) => {
151
+ return database.users.insert(user);
152
+ };
153
+
154
+ const sendWelcomeEmail = (user: UserType) => {
155
+ return emailService.send({
156
+ to: user.emailAddress,
157
+ template: WELCOME_EMAIL_TEMPLATE,
158
+ });
159
+ };
160
+ ```
161
+
162
+ ```ts
163
+ // ❌ Bad — validates, saves and sends an email
164
+ const handleUser = (user: UserType) => {
165
+ if (!EMAIL_ADDRESS_PATTERN.test(user.emailAddress)) {
166
+ throw new Error("Invalid email address");
167
+ }
168
+ database.users.insert(user);
169
+ emailService.send({
170
+ to: user.emailAddress,
171
+ template: WELCOME_EMAIL_TEMPLATE,
172
+ });
173
+ };
174
+ ```
175
+
176
+ Small single-purpose functions are easy to name, easy to test and easy to reuse.
177
+
178
+ ---
179
+
180
+ ## Every function and component has a test file
181
+
182
+ Every function and every component has a test file that tests its functionality. ESLint does not check this — it is a rule for everyone writing code, **including AI agents such as Claude Code**: when you create a function or component, create its test file in the same change.
183
+
184
+ **One test file tests one thing.** A test file covers exactly one function or one component, and is named after it (`formatCurrency.test.ts`, `Button.test.tsx`). Never combine tests for several functions in one file.
185
+
186
+ The function and its test live together in their own folder:
187
+
188
+ ```text
189
+ utils/
190
+ ├── formatCurrency/
191
+ │ ├── formatCurrency.ts
192
+ │ ├── formatCurrency.test.ts
193
+ │ └── index.ts
194
+ └── index.ts
195
+ ```
196
+
197
+ See [Utils](/Structure/folder-structure#-utils) for the folder layout and the `index.ts` files.
198
+
199
+ ```ts title="utils/formatCurrency/formatCurrency.test.ts"
200
+ import { describe, expect, it } from "vitest";
201
+
202
+ import { formatCurrency } from "./formatCurrency";
203
+
204
+ describe("formatCurrency", () => {
205
+ it("formats whole dollars with two decimal places", () => {
206
+ expect(formatCurrency(5)).toBe("$5.00");
207
+ });
208
+
209
+ it("adds a thousands separator", () => {
210
+ expect(formatCurrency(1250)).toBe("$1,250.00");
211
+ });
212
+ });
213
+ ```
214
+
215
+ Components keep their test beside the component file (`button/Button.tsx` and `button/Button.test.tsx`) — see [Component Organization](/Structure/component-organization).
216
+
217
+ ---
218
+
219
+ ## Functions, never classes
220
+
221
+ Write functions. Never use class definitions — not for components, not for services, not for utils.
222
+
223
+ ```ts
224
+ // ✅ Good
225
+ export const createUserService = (database: DatabaseType) => {
226
+ const findUserById = (userId: string) => {
227
+ return database.users.find(userId);
228
+ };
229
+
230
+ return { findUserById };
231
+ };
232
+ ```
233
+
234
+ ```ts
235
+ // ❌ Bad
236
+ export class UserService {
237
+ constructor(private database: DatabaseType) {}
238
+
239
+ findUserById(userId: string) {
240
+ return this.database.users.find(userId);
241
+ }
242
+ }
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Return early, nest shallow
248
+
249
+ Deal with the error and empty cases first and return. The main logic then sits at the left edge of the function, not buried inside three `if`s. Never nest more than three levels deep.
250
+
251
+ ```ts
252
+ // ✅ Good
253
+ const getDiscountedPrice = (order: OrderType) => {
254
+ if (!order.customer) {
255
+ return order.price;
256
+ }
257
+
258
+ if (!order.customer.isMember) {
259
+ return order.price;
260
+ }
261
+
262
+ return order.price * MEMBER_DISCOUNT_MULTIPLIER;
263
+ };
264
+ ```
265
+
266
+ ```ts
267
+ // ❌ Bad
268
+ const getDiscountedPrice = (order: OrderType) => {
269
+ if (order.customer) {
270
+ if (order.customer.isMember) {
271
+ return order.price * MEMBER_DISCOUNT_MULTIPLIER;
272
+ } else {
273
+ return order.price;
274
+ }
275
+ } else {
276
+ return order.price;
277
+ }
278
+ };
279
+ ```
280
+
281
+ ---
282
+
283
+ ## No magic numbers or strings
284
+
285
+ A bare number or string in the middle of the logic tells the reader nothing. Give it a name.
286
+
287
+ ```ts
288
+ // ✅ Good
289
+ const MAXIMUM_RETRY_ATTEMPTS = 3;
290
+ const ORDER_STATUS_SHIPPED = "shipped";
291
+
292
+ if (retryAttemptCount > MAXIMUM_RETRY_ATTEMPTS) {
293
+ return;
294
+ }
295
+
296
+ if (order.status === ORDER_STATUS_SHIPPED) {
297
+ sendShippingEmail(order);
298
+ }
299
+ ```
300
+
301
+ ```ts
302
+ // ❌ Bad
303
+ if (retryAttemptCount > 3) {
304
+ return;
305
+ }
306
+
307
+ if (order.status === "shipped") {
308
+ sendShippingEmail(order);
309
+ }
310
+ ```
311
+
312
+ `-1`, `0` and `1` are allowed. See [Naming Conventions](/Structure/naming-conventions#constants) for how to name constants.
313
+
314
+ ---
315
+
316
+ ## One parameter; always an object for more
317
+
318
+ A function takes **one** parameter. When it needs more than one value, that parameter is an object, so every value is named where the function is called.
319
+
320
+ ```ts
321
+ // ✅ Good — the call site explains itself
322
+ interface CreateUserProps {
323
+ name: string;
324
+ emailAddress: string;
325
+ isAdministrator: boolean;
326
+ }
327
+
328
+ const createUser = ({
329
+ name,
330
+ emailAddress,
331
+ isAdministrator,
332
+ }: CreateUserProps) => {
333
+ // ...
334
+ };
335
+
336
+ createUser({
337
+ name: "Sam Taylor",
338
+ emailAddress: "sam@example.com",
339
+ isAdministrator: false,
340
+ });
341
+ ```
342
+
343
+ ```ts
344
+ // ❌ Bad — what is false?
345
+ const createUser = (
346
+ name: string,
347
+ emailAddress: string,
348
+ isAdministrator: boolean,
349
+ ) => {
350
+ // ...
351
+ };
352
+
353
+ createUser("Sam Taylor", "sam@example.com", false);
354
+ ```
355
+
356
+ This applies to functions you define. Inline callbacks whose parameters are set by someone else are fine as they are — `orderItems.reduce((runningTotal, orderItem) => …)`, or a Next.js route handler.
357
+
358
+ ---
359
+
360
+ ## No nested ternaries
361
+
362
+ One ternary is fine. A ternary inside a ternary becomes an `if` or its own function.
363
+
364
+ ```ts
365
+ // ✅ Good
366
+ const getStatusLabel = (order: OrderType) => {
367
+ if (order.isCancelled) {
368
+ return "Cancelled";
369
+ }
370
+
371
+ return order.isShipped ? "Shipped" : "Processing";
372
+ };
373
+ ```
374
+
375
+ ```ts
376
+ // ❌ Bad
377
+ const statusLabel = order.isCancelled
378
+ ? "Cancelled"
379
+ : order.isShipped
380
+ ? "Shipped"
381
+ : "Processing";
382
+ ```
383
+
384
+ ---
385
+
386
+ ## No boolean flag parameters
387
+
388
+ A boolean that switches a function between two behaviours means it is two functions. Write both, and name them.
389
+
390
+ ```ts
391
+ // ✅ Good
392
+ sendEmail({ user });
393
+ sendUrgentEmail({ user });
394
+ ```
395
+
396
+ ```ts
397
+ // ❌ Bad — the function does two different things
398
+ sendEmail({ user, isUrgent: true });
399
+ ```
400
+
401
+ A boolean that is simply **data** — `isAdministrator` on a new user — is fine.
402
+
403
+ ---
404
+
405
+ ## Comments explain why, not what
406
+
407
+ The code already says what it does. A comment is for what the code cannot say: **why** it is written this way.
408
+
409
+ ```ts
410
+ // ✅ Good — explains a decision the reader could not guess
411
+ // The payment provider rejects requests that arrive less than one second apart.
412
+ await waitMilliseconds(PAYMENT_PROVIDER_COOLDOWN_MILLISECONDS);
413
+ ```
414
+
415
+ ```ts
416
+ // ❌ Bad — repeats the code
417
+ // Wait for one second
418
+ await waitMilliseconds(1000);
419
+ ```
420
+
421
+ - If a comment describes **what** the code does, rename things until the comment is not needed.
422
+ - **Never leave commented-out code.** Delete it — Git remembers.
423
+
424
+ ---
425
+
426
+ ## No `any`, no `!`, no unexplained `as`
427
+
428
+ All three switch the type checker off, so the reader can no longer trust the types.
429
+
430
+ | Avoid | Do instead |
431
+ | --------------------------------- | ---------------------------------------------------------------------- |
432
+ | `any` | `unknown`, then narrow it with a check |
433
+ | `user!.name` (non-null assertion) | Check that `user` exists first |
434
+ | `data as UserType` (type cast) | Validate the data. If a cast is truly needed, add a comment saying why |
435
+
436
+ ```ts
437
+ // ✅ Good
438
+ const getUserName = (user: UserType | undefined) => {
439
+ if (!user) {
440
+ return UNKNOWN_USER_NAME;
441
+ }
442
+
443
+ return user.name;
444
+ };
445
+ ```
446
+
447
+ ```ts
448
+ // ❌ Bad
449
+ const getUserName = (user: any) => {
450
+ return user!.name as string;
451
+ };
452
+ ```
453
+
454
+ `as const` is fine. See the [ESLint configuration](/Standards/eslint#typescript-eslintconsistent-type-assertions-never) for how to record the reason for a cast.
455
+
456
+ ---
457
+
458
+ ## One component per file
459
+
460
+ A file holds one component. A second component — however small — gets [its own folder](/Structure/component-organization#nested-components).
461
+
462
+ ---
463
+
464
+ ## Named exports only
465
+
466
+ Export things by name. A named export has the same name everywhere it is imported, so it can be searched for and renamed safely.
467
+
468
+ ```ts
469
+ // ✅ Good
470
+ export const formatCurrency = (amount: number) => {
471
+ // ...
472
+ };
473
+
474
+ import { formatCurrency } from "@/utils";
475
+ ```
476
+
477
+ ```ts
478
+ // ❌ Bad — every importer can call it something different
479
+ const formatCurrency = (amount: number) => {
480
+ // ...
481
+ };
482
+
483
+ export default formatCurrency;
484
+
485
+ import format from "@/utils/formatCurrency";
486
+ ```
487
+
488
+ The only default exports are the ones a tool demands: Next.js route files (`page.tsx`, `layout.tsx`), config files and Storybook stories.
489
+
490
+ ---
491
+
492
+ ## Type names say what they are
493
+
494
+ Every type name ends in **`Props`** or **`Type`**, so a reader can tell a type from a value at a glance.
495
+
496
+ | Suffix | Use for | Example |
497
+ | ------- | ------------------------------------------------------ | -------------------------------- |
498
+ | `Props` | The single object parameter of a component or function | `ButtonProps`, `CreateUserProps` |
499
+ | `Type` | Everything else | `UserType`, `OrderStatusType` |
500
+
501
+ ```ts
502
+ // ✅ Good
503
+ interface UserType {
504
+ id: string;
505
+ name: string;
506
+ }
507
+
508
+ type OrderStatusType = "processing" | "shipped" | "cancelled";
509
+
510
+ interface UserCardProps {
511
+ user: UserType;
512
+ }
513
+ ```
514
+
515
+ ```ts
516
+ // ❌ Bad
517
+ interface User {}
518
+ type OrderStatus = "processing" | "shipped" | "cancelled";
519
+ interface UserCardData {}
520
+ ```
521
+
522
+ Use a named type instead of a bare `string` wherever the value has a fixed set of options.
523
+
524
+ ---
525
+
526
+ ## Error messages say what happened and what to do
527
+
528
+ An error message is read by someone in the middle of a problem. Tell them what went wrong and how to fix it.
529
+
530
+ ```ts
531
+ // ✅ Good
532
+ throw new Error(
533
+ `Invoice total must be greater than zero, but received ${invoiceTotal}. Check the line item prices.`,
534
+ );
535
+ ```
536
+
537
+ ```ts
538
+ // ❌ Bad
539
+ throw new Error("Invalid input");
540
+ ```