diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl
index 84e4d35..0259c83 100644
--- a/.beads/interactions.jsonl
+++ b/.beads/interactions.jsonl
@@ -36,3 +36,4 @@
{"id":"int-9cb5a663c6e65593625066b25976439c","kind":"field_change","created_at":"2026-09-11T19:15:51.529868089Z","actor":"Shinuwa","issue_id":"sokko-g-5i3","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Library category now derives from anchor navigation; same-page hash/history navigation works; malformed anchors handled and obsolete RAF/scroll timers cleaned up. Existing library coverage and two new behavioral E2E pass. npm run check:full passes with 32 E2E; targeted set-state-in-effect lint passes."}}
{"id":"int-4ece504edad6e21c4829485c33215835","kind":"field_change","created_at":"2026-09-12T07:06:41.579702638Z","actor":"Shinuwa","issue_id":"sokko-g-739","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Tutorial visibility now follows toolbox identity and request transitions before commit; tour keyed by toolbox resets step and start latch. Two StrictMode behavioral tests cover identity changes, one-time flag consumption, dismissal and pending requests. check:full passes with 34 E2E; targeted set-state-in-effect lint passes."}}
{"id":"int-c7fa4c94461f676392625658508ae231","kind":"field_change","created_at":"2026-09-12T10:15:48.480122568Z","actor":"Shinuwa","issue_id":"sokko-g-5rq","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Notepad now separates page identity from focused editing, replaces the editor DOM on page/module changes, preserves selection during local echoes and applies external updates after blur without stealing focus. Removed obsolete selection helpers and timestamp-only echo detection. check:full passes with 36 E2E; global set-state-in-effect diagnostic passes."}}
+{"id":"int-e79db6c1131fb3c5c57dde09917ef735","kind":"field_change","created_at":"2026-09-12T10:50:44.387707762Z","actor":"Shinuwa","issue_id":"sokko-g-8po","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Enabled app-wide StrictMode, restored recommended react-hooks/set-state-in-effect rule, documented development checks and added an effective ESLint configuration regression test. npm run check:full passed: lint, 101 Node tests, build and 36 E2E. All C10 subsets complete."}}
diff --git a/README.md b/README.md
index 77f805c..cce76d1 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,12 @@ chargement initial des outils par Vite. Le parallélisme reste ajustable avec
`npm run test:e2e -- --workers=4`. Les traces des tests en échec sont conservées
dans `test-results` et consultables depuis le rapport Playwright.
+L'application est enveloppée dans `StrictMode` : en développement (y compris les
+E2E sur Vite), React rejoue certains rendus et cycles d'effets pour détecter les
+effets de bord et les nettoyages manquants. Ces contrôles supplémentaires ne
+s'exécutent pas en production. Les règles recommandées React Hooks, dont
+`react-hooks/set-state-in-effect`, sont actives dans ESLint.
+
## Contenu éditable
Les contenus maintenus à la main sont regroupés dans `website/public/data`.
diff --git a/eslint.config.js b/eslint.config.js
index 08dc48a..34297b6 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -34,7 +34,6 @@ export default [
},
rules: {
...reactHooks.configs.recommended.rules,
- "react-hooks/set-state-in-effect": "off",
"no-unused-vars": ["error", {
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
diff --git a/tests/react-lint.test.mjs b/tests/react-lint.test.mjs
new file mode 100644
index 0000000..830bad2
--- /dev/null
+++ b/tests/react-lint.test.mjs
@@ -0,0 +1,12 @@
+// Rôle : vérifie que les contrôles React critiques restent actifs dans la configuration ESLint effective.
+import { test } from "node:test";
+import assert from "node:assert/strict";
+import { ESLint } from "eslint";
+
+test("React effect and purity checks are enabled for application sources", async () => {
+ const eslint = new ESLint();
+ const config = await eslint.calculateConfigForFile("website/src/main.jsx");
+ for (const rule of ["react-hooks/set-state-in-effect", "react-hooks/purity", "react-hooks/rules-of-hooks", "react-hooks/exhaustive-deps"]) {
+ assert.ok(config.rules[rule][0] > 0, `${rule} must remain enabled`);
+ }
+});
diff --git a/website/src/main.jsx b/website/src/main.jsx
index 052e8af..7637db6 100644
--- a/website/src/main.jsx
+++ b/website/src/main.jsx
@@ -1,6 +1,6 @@
// Rôle : point d'entrée React, assemble données, routes, shell et overlays.
/* eslint-disable react-refresh/only-export-components -- point d'entrée Vite sans export de composant. */
-import { lazy, Suspense, useCallback, useEffect, useState } from "react";
+import { lazy, StrictMode, Suspense, useCallback, useEffect, useState } from "react";
import { createRoot } from "react-dom/client";
import "./styles/main.scss";
import { AppOverlays } from "./components/AppOverlays.jsx";
@@ -175,4 +175,4 @@ function App() {
);
}
-createRoot(document.querySelector("#app")).render();
+createRoot(document.querySelector("#app")).render();