Skip to content

Commit 65c9d73

Browse files
pierre@redtrash.frpierre@redtrash.fr
authored andcommitted
Doc: ajoute doc/STYLE_GUIDE.md (chantier 2.2)
13 sections couvrant les conventions effectivement suivies par la codebase : langues (commentaires anglais / UI française / doc anglaise), nommage des fichiers par couche (frontend + backend), identifiants, imports, patterns React, patterns backend, commentaires, CSS, tests, lint, workflow git. Tables ancrées sur ce qui existe vraiment dans le repo plutôt que sur des règles inventées. Une déviation est explicitement signalée : services/adminService.ts porte un suffixe `Service` que les autres services n'ont pas — les nouveaux services doivent suivre la forme non suffixée. Annonce doc/adr/ et CONTRIBUTING.md comme planned, cohérent avec ARCHITECTURE.md.
1 parent 1cd13b4 commit 65c9d73

2 files changed

Lines changed: 324 additions & 0 deletions

File tree

‎doc/STYLE_GUIDE.md‎

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
# VortexFlow — Style Guide
2+
3+
Conventions actually followed by the codebase. When this document and the code
4+
disagree, the code wins — open a PR to fix the doc, not the code (unless the
5+
code is the deviation, in which case fix the code).
6+
7+
Sections marked **(deviation)** point at known inconsistencies that are being
8+
tracked, not patterns to follow.
9+
10+
---
11+
12+
## 1. Languages
13+
14+
| Surface | Language |
15+
|---|---|
16+
| Code comments | English |
17+
| User-facing strings (UI labels, errors shown to users) | French |
18+
| Documentation (`*.md`) | English |
19+
| Commit messages | French (matches the existing log) |
20+
| Variable names | English |
21+
22+
Existing code has French comments in places (e.g. older `LoginPage.tsx`).
23+
Don't bulk-translate; convert opportunistically when you're already touching
24+
the file. Don't introduce new French comments.
25+
26+
---
27+
28+
## 2. File naming — Frontend (`frontend/src/`)
29+
30+
| Layer | Path | Naming | Example |
31+
|---|---|---|---|
32+
| React component | `components/<area>/` | `PascalCase.tsx` | `LoginPage.tsx`, `GraphRenderer3D.tsx` |
33+
| Component test (colocated) | same dir | `PascalCase.test.tsx` | `LoginPage.test.tsx` |
34+
| Component CSS (colocated) | same dir | `PascalCase.css` | `AdminPanel.css` |
35+
| React Context | `context/` | `XxxContext.tsx` | `AuthContext.tsx`, `GraphContext.tsx` |
36+
| Service | `services/` | `camelCase.ts` | `api.ts`, `errorHandler.ts`, `websocket.ts` |
37+
| Type definitions | `types/index.ts` | one file, named exports | `types/index.ts` |
38+
| Ambient module declarations | `@types/` | `*.d.ts` | `3d-force-graph.d.ts` |
39+
| Test stubs | `test-stubs/` | `camelCase.ts` | `empty.ts` |
40+
41+
**Components are organized by feature area** (`auth/`, `graphs/`, `admin/`,
42+
`dashboard/`, `user/`, `common/`, `layout/`), not by component type. Don't
43+
introduce a `components/buttons/` or `components/forms/` directory.
44+
45+
**(deviation)** — `services/adminService.ts` uses a `Service` suffix; the
46+
others (`api.ts`, `errorHandler.ts`, `websocket.ts`) don't. New services
47+
should follow the **non-suffixed** form: `feature.ts`, not `featureService.ts`.
48+
49+
There is currently no `hooks/` directory. Custom hooks live inside contexts
50+
(`AuthContext` exports `useAuth`, etc.). If you write a reusable hook that
51+
isn't context-bound, create `hooks/useXxx.ts`.
52+
53+
---
54+
55+
## 3. File naming — Backend (`backend/src/`)
56+
57+
| Layer | Path | Naming | Example |
58+
|---|---|---|---|
59+
| Express route module | `routes/` | `kebab-case.js` (lowercase, hyphens for compounds) | `auth.js`, `import-export.js` |
60+
| Sequelize model | `models/` | `PascalCase.js` (matches the model name) | `User.js`, `GraphShare.js` |
61+
| Models barrel | `models/index.js` | the **only** place that wires associations | |
62+
| Middleware | `middleware/` | `camelCase.js` | `asyncHandler.js`, `errorHandler.js`, `auth.js` |
63+
| Utility | `utils/` | `camelCase.js` | `dotValidator.js`, `fileUpload.js`, `logger.js` |
64+
| Service | `services/` | `camelCase.js` | `emailService.js` |
65+
| Sequelize config | `config/` | one file per concern | `database.js` |
66+
67+
Tests live **outside** `src/`, mirrored by layer:
68+
69+
```
70+
backend/tests/
71+
setup.js (Jest setupFilesAfterEnv)
72+
unit/
73+
models/<Name>.test.js
74+
middleware/<name>.test.js
75+
services/<name>.test.js
76+
utils/<name>.test.js
77+
integration/
78+
routes/<route>.test.js
79+
```
80+
81+
---
82+
83+
## 4. Identifier naming
84+
85+
| Kind | Style | Example |
86+
|---|---|---|
87+
| Variables, function parameters | `camelCase` | `userId`, `dotContent` |
88+
| Functions | `camelCase` | `validateSession`, `setupAdminUser` |
89+
| React components, Sequelize models, classes, TS interfaces | `PascalCase` | `GraphRenderer3D`, `User`, `ApiService` |
90+
| Module-scope constants | `SCREAMING_SNAKE_CASE` | `API_BASE_URL`, `NO_NAV_PATHS`, `DEFAULT_GRAPH_OPTIONS` |
91+
| Database columns | `snake_case` | `user_id`, `dot_code`, `permission_level`, `start_time` |
92+
| URL path segments | `kebab-case` | `/api/import-export`, `/validate-dot`, `/parse-dot` |
93+
| Environment variables | `SCREAMING_SNAKE_CASE` | `SESSION_SECRET`, `VITE_API_URL`, `REDIS_URL` |
94+
| Vite-exposed env vars | `VITE_*` prefix (mandatory) | `VITE_API_URL`, `VITE_WS_URL` |
95+
96+
**Don't** mirror DB column names into JS variables. Sequelize already maps
97+
`user_id` ↔ `userId` via model attributes; use the camelCase form
98+
everywhere in JS.
99+
100+
---
101+
102+
## 5. Imports
103+
104+
### Frontend (ES modules, TypeScript)
105+
106+
Order, **without blank lines between groups** (current style):
107+
108+
```ts
109+
// 1. React + react-router
110+
import React, { useState } from 'react';
111+
import { useNavigate } from 'react-router-dom';
112+
113+
// 2. Third-party (MUI, axios, …)
114+
import { Box, Button } from '@mui/material';
115+
116+
// 3. Internal — absolute-style (services, contexts, types)
117+
import apiService from '../../services/api';
118+
import { useAuth } from '../../context/AuthContext';
119+
import type { User } from '../../types';
120+
121+
// 4. Internal — same-folder relative
122+
import { LoginForm } from './LoginForm';
123+
```
124+
125+
`type` imports use the `import type` form when only types are needed.
126+
127+
### Backend (CommonJS)
128+
129+
Order, no blank lines between groups:
130+
131+
```js
132+
const express = require('express');
133+
const { body, validationResult } = require('express-validator');
134+
const { User } = require('../models');
135+
const { authRateLimit, validateSession } = require('../middleware/auth');
136+
const { asyncHandler } = require('../middleware/errorHandler');
137+
const logger = require('../utils/logger');
138+
139+
const router = express.Router();
140+
```
141+
142+
`router` declaration goes **last** in the import block, blank-line-separated
143+
from the requires.
144+
145+
---
146+
147+
## 6. React patterns
148+
149+
- **Functional components only.** `React.FC<Props>` (or `FC<Props>` after
150+
named import) is the existing style. Don't introduce class components.
151+
- **Local state** → `useState`. Multiple linked fields with reducer logic →
152+
`useReducer`. Cross-route state → an existing Context.
153+
- **Side effects** → `useEffect` with an explicit dependency array. If you
154+
intentionally pin a partial deps list (e.g. load-on-mount), add:
155+
```ts
156+
// eslint-disable-next-line react-hooks/exhaustive-deps
157+
// <one-line reason>
158+
```
159+
See `AdminPanel.tsx` load effects for prior art.
160+
- **Provider tree** is fixed and ordered:
161+
`ErrorBoundary → ThemeProvider → Router → AuthProvider → GraphProvider → SimulationProvider → NotificationProvider`.
162+
Don't add a 5th application-wide provider without a justification documented
163+
in an ADR (planned in `doc/adr/`).
164+
- **Lazy load heavy routes** with `React.lazy` + `<Suspense>`. The current
165+
lazy split is `GraphEditor`, `GraphViewer`, `AdminPanel` — keep these
166+
lazy, and lazy-load any new heavy page.
167+
- **Auth guard** uses `<ProtectedRoute>` from `App.tsx`. Don't reimplement
168+
it per route.
169+
170+
---
171+
172+
## 7. Backend patterns
173+
174+
- **All async route handlers wrapped in `asyncHandler`** (`middleware/asyncHandler.js`)
175+
to forward thrown errors to the central error middleware. Naked `async (req, res) =>`
176+
in routes is a lint-pass smell — wrap it.
177+
- **Validation** → `express-validator`'s `body() / query() / param()` chain
178+
inside the route definition. The result of `validationResult(req)` is
179+
formatted by the errorHandler. `joi` is also installed and used at config
180+
boundaries (env-var validation, where it fits better).
181+
- **Errors** → `throw` (or `next(err)`); never `res.status(500).json(...)`
182+
inline. Let `middleware/errorHandler.js` shape the response.
183+
- **Logging** → `logger` from `utils/logger.js` (Winston). **No `console.log`
184+
/ `console.error` in `src/`.** `morgan` is piped into the same logger.
185+
- **Sessions** → never read or write the session map directly. Use
186+
`validateSession` middleware to populate `req.user`, then read `req.user.id`,
187+
`req.user.role`.
188+
- **Sequelize associations** are wired in `models/index.js`. When you add a
189+
model, register its associations there, not inside the model file.
190+
- **DB columns are `snake_case`**, but Sequelize attributes are exposed as
191+
`camelCase` to JS — use the camelCase form in code (`user.firstName`,
192+
not `user.first_name`).
193+
194+
---
195+
196+
## 8. Comments
197+
198+
- **English only** in new code.
199+
- **WHY, not WHAT.** Don't restate what the code does. Document:
200+
- hidden constraints (e.g. "must run before X because Y")
201+
- invariants the code relies on
202+
- non-obvious workarounds with a link or commit ref
203+
- load-bearing behaviors (`GraphRenderer3D.tsx` has many — preserve them)
204+
- **JSDoc** for public route handlers and exported helpers, with at least
205+
the path/method (for routes) and a one-line description. Existing
206+
`routes/auth.js` is a good template.
207+
- **No emojis in code or commit messages.** Emojis in user-facing strings
208+
(UI text, notifications) are fine.
209+
- **No AI attribution lines** in any artifact (commits, PRs, comments).
210+
211+
---
212+
213+
## 9. CSS / styling
214+
215+
- **MUI `sx` prop is the default** for component-scoped styling. Use it for
216+
layout, spacing, simple conditional styles.
217+
- **`.css` file colocated with the component** only when `sx` isn't enough:
218+
global selectors, complex animations, third-party widget overrides. See
219+
`AdminPanel.css` as the reference case.
220+
- **Theme tokens** (colors, typography, spacing scale, scrollbar) live in
221+
`App.tsx`'s `createTheme`. Don't hardcode `#4caf50` / `#ff6b35` in
222+
components — read from theme.
223+
- The dark theme is the only theme. Don't add a light-theme path without a
224+
product decision documented in an ADR.
225+
226+
---
227+
228+
## 10. Tests
229+
230+
### Backend (Jest)
231+
232+
- File naming: `*.test.js` only. No `*.spec.js`.
233+
- Location: `tests/unit/<layer>/<Name>.test.js` or `tests/integration/routes/<route>.test.js`.
234+
- `tests/setup.js` is auto-loaded via `setupFilesAfterEnv` (configured in
235+
`backend/package.json`) and silences the Winston logger globally — put
236+
cross-suite mocks there.
237+
- For modules that install module-level timers (e.g. `utils/fileUpload.js`'s
238+
1h `setInterval`), call `jest.useFakeTimers()` **before** `require` to
239+
avoid keeping the event loop alive after tests finish.
240+
241+
### Frontend (Vitest + React Testing Library + jsdom)
242+
243+
- File naming: `*.test.ts` / `*.test.tsx`, **colocated** with the source.
244+
- Use `vi.fn()` / `vi.mock()` — not `jest.*`. `vitest/globals` exposes
245+
`describe`, `test`, `expect`, `beforeEach`, `afterEach`, `vi` without
246+
imports (configured in `vite.config.ts` `test.globals: true`).
247+
- **Form submit** assertions: prefer `fireEvent.submit(form)` over
248+
`userEvent.click(submitButton)` — under jsdom + Vitest, the click fires
249+
but the form's native `submit` event doesn't.
250+
- **Tests that mount `GraphRenderer3D`** (or anything using
251+
`ResizeObserver` / `three-spritetext`) must stub these at the top of the
252+
file: jsdom doesn't ship `ResizeObserver`, and `three-spritetext` pulls
253+
Three.js modules. `3d-force-graph` is best mocked as a chainable spy
254+
that returns `this` — see `GraphRenderer3D.test.tsx` for the pattern.
255+
- The renderer's parser calls `fetch(${VITE_API_URL}/public/parse-dot)`,
256+
so stub `globalThis.fetch` to bypass the backend.
257+
- **Don't render `App` from a test** without first mocking `services/api`
258+
— the auth probe runs on mount and will hang the test.
259+
260+
---
261+
262+
## 11. Lint & format
263+
264+
| Tool | Status |
265+
|---|---|
266+
| Frontend ESLint (`eslint.config.js`, ESLint 9 flat config) | Wired, repo lint-clean. Keep it that way — CI will fail otherwise. |
267+
| Backend ESLint (`.eslintrc.json`, `eslint:recommended` only) | Minimal. Migration to a stricter preset is planned. |
268+
| Prettier | **Not yet wired up.** Don't introduce a personal Prettier config. |
269+
| `.editorconfig` | **Absent.** Indentation in the codebase is 2 spaces — match it. |
270+
| `unused-vars` (frontend) | `@typescript-eslint/no-unused-vars` ignores caught errors named `_`, `err`, `error` — keep error bindings even when unused, for stack traces. |
271+
272+
**Before committing**, both packages must lint-clean:
273+
274+
```bash
275+
( cd backend && npm run lint )
276+
( cd frontend && npm run lint )
277+
```
278+
279+
---
280+
281+
## 12. Git workflow
282+
283+
- **Commit messages**: French, present tense, **no body unless useful**.
284+
- `<scope>: <subject>` for scoped changes (e.g. `GraphRenderer3D: …`,
285+
`CI: …`, `Doc: …`).
286+
- `<subject>` for repo-wide changes (e.g. `Ignore les artefacts runtime`).
287+
- First line ≤ ~70 chars. Body wrapped at ~72 if added.
288+
- **No AI attribution** anywhere: no `Co-Authored-By`, no `Generated with
289+
Claude Code`, no `Anthropic` mention in commits, PR descriptions, comments,
290+
or code.
291+
- **Branches**: working directly on `main` is current practice for this solo
292+
repo. If branches are introduced, use `feature/<slug>`, `fix/<slug>`,
293+
`chore/<slug>`.
294+
- **Daily changelog** under `doc/changelog/YYYY-MM-DD.md`. One line per file
295+
modified, listing the actual change. If a change is reverted within the
296+
day, **delete the line** rather than logging "added then removed".
297+
- **Never commit secrets**. `.env` files are gitignored and must stay so.
298+
299+
---
300+
301+
## 13. Pointers
302+
303+
- Architecture overview → [`/ARCHITECTURE.md`](../ARCHITECTURE.md)
304+
- Backend topic guides → [`/backend/doc/`](../backend/doc/)
305+
- DOT 3D specification → [`/doc/dot-3d/`](./dot-3d/)
306+
- Architecture decisions → `doc/adr/` *(planned, chantier 2.3)*
307+
- Contribution workflow → `CONTRIBUTING.md` *(planned, chantier 2.1)*

‎doc/changelog/2026-05-10.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,23 @@ bundle-split / CI changes that landed last commits.
4545
pointing at the 5 living topic guides (API, AUTH, CONFIG, DEPLOY,
4646
DEV), a stack/routes summary, a quickstart, and test/lint commands.
4747
Now defers to ARCHITECTURE.md at the repo root for the big picture.
48+
## Documentation pass — chantier 2.2 (style guide)
49+
50+
- [doc/STYLE_GUIDE.md] (new) Coding conventions actually followed by the
51+
codebase. 13 sections: languages (English code / French UI / English
52+
docs), frontend vs backend file naming tables (per layer with paths
53+
+ naming pattern + examples), identifier naming, imports ordering,
54+
React patterns (provider tree, lazy-load, ProtectedRoute), backend
55+
patterns (asyncHandler wrap, validator, never `console.log`,
56+
Sequelize associations centralized), comments rules (English, WHY
57+
not WHAT, no AI attribution), CSS (MUI sx default, theme tokens),
58+
tests (Jest backend / Vitest frontend specifics including
59+
`globalThis.fetch` stub, jsdom limitations), lint status, git
60+
workflow (French commits, no AI attribution, daily changelog).
61+
Flagged one deviation: `services/adminService.ts` uses a `Service`
62+
suffix the other services don't — new services should follow the
63+
unsuffixed form.
64+
4865
## Code fixes (chantier 3.7 — hardcoded URL)
4966

5067
- [frontend/src/components/graphs/GraphRenderer3D.tsx] Replaced the

0 commit comments

Comments
 (0)