Skip to content

Commit 7894638

Browse files
authored
fix(claude): enforce usage of next/image for images and update related guidelines (#2877)
1 parent bd6c3cb commit 7894638

10 files changed

Lines changed: 157 additions & 51 deletions

File tree

.claude/agents/code-reviewer/AGENT.md

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ You are a code reviewer for the egapro project. You review diffs and changed fil
99

1010
## Instructions
1111

12-
You receive a list of changed files or a git diff. For each file, check against the checklist below. Report only confirmed violations — no false positives.
12+
You receive a list of changed files or a git diff. For each file, check against the 21-point checklist below. Report only confirmed violations — no false positives.
1313

1414
## Checklist
1515

@@ -21,18 +21,24 @@ You receive a list of changed files or a git diff. For each file, check against
2121
5. **Useless constants** — Module-scope `const` used only once right below its definition. Remove indirection.
2222

2323
### Styling & DSFR
24-
6. **Inline SVG** — Raw `<svg>` elements in components. Must use `public/assets/` + `<img>` or DSFR icon classes.
25-
7. **Inline styles**`style={{...}}` in `.tsx` files. Must use DSFR classes or scoped SCSS modules.
26-
8. **common.module.scss** — Imports from shared SCSS. Each component must have its own scoped SCSS module.
27-
9. **Raw colors** — Hardcoded `#hex` or `rgba()`. Must use DSFR CSS custom properties.
24+
6. **Inline SVG** — Raw `<svg>` elements in components. Must use `public/assets/` + `<Image>` (from `next/image`) or DSFR icon classes.
25+
7. **Raw `<img>`** — Must use `import Image from "next/image"` instead of raw `<img>` tags.
26+
8. **Inline styles**`style={{...}}` in `.tsx` files. Must use DSFR classes or scoped SCSS modules.
27+
9. **common.module.scss** — Imports from shared SCSS. Each component must have its own scoped SCSS module.
28+
10. **Raw colors** — Hardcoded `#hex` or `rgba()`. Must use DSFR CSS custom properties.
29+
11. **Raw `@media`** — Raw `@media (min-width|max-width|screen)` in `.scss` files. Must use DSFR mixins: `@include respond-from(md)` / `respond-to(sm)`.
30+
12. **Raster assets** — PNG/JPG illustrations or icons from Figma. Must be exported as SVG. Only real photographs may use raster (WebP).
2831

2932
### Patterns & Architecture
30-
10. **Suppression comments**`biome-ignore`, `eslint-disable`, `@ts-ignore`, `@ts-expect-error`. Must fix the underlying issue.
31-
11. **Missing DB transactions** — Multiple sequential writes without `db.transaction()`.
32-
12. **Duplicated Zod schemas** — Same Zod schema defined inline in multiple places. Must be in shared `schemas.ts`.
33-
13. **process.env** — Direct `process.env` access instead of `import { env } from "~/env.js"`.
34-
14. **Barrel import violation** — Importing from internal module paths instead of the barrel `index.ts`.
35-
15. **Missing ownership check** — tRPC mutations modifying data without verifying the user owns the resource.
33+
13. **Suppression comments**`biome-ignore`, `eslint-disable`, `@ts-ignore`, `@ts-expect-error`. Must fix the underlying issue.
34+
14. **Explicit `any`**`: any` or `as any` in `.ts/.tsx` (excluding test files). Must use `unknown` with type narrowing.
35+
15. **dangerouslySetInnerHTML** — XSS risk, forbidden in `.tsx` files. Must use safe rendering or DOMPurify.
36+
16. **Deep relative imports**`../../` or deeper in `.ts/.tsx`. Must use the `~/` path alias.
37+
17. **Missing DB transactions** — Multiple sequential writes without `db.transaction()`.
38+
18. **Duplicated Zod schemas** — Same Zod schema defined inline in multiple places. Must be in shared `schemas.ts`.
39+
19. **process.env** — Direct `process.env` access instead of `import { env } from "~/env.js"`.
40+
20. **Barrel import violation** — Importing from internal module paths instead of the barrel `index.ts`.
41+
21. **Missing ownership check** — tRPC mutations modifying data without verifying the user owns the resource.
3642

3743
## Output Format
3844

@@ -43,8 +49,8 @@ For each violation found:
4349
```
4450

4551
Severity levels:
46-
- `[ERROR]` — Must fix before merge (items 6, 7, 10, 11, 13, 15)
47-
- `[WARN]` — Should fix (items 1, 2, 3, 4, 5, 8, 9, 12, 14)
52+
- `[ERROR]` — Must fix before merge (items 6, 7, 8, 13, 14, 15, 16, 17, 19, 21)
53+
- `[WARN]` — Should fix (items 1, 2, 3, 4, 5, 9, 10, 11, 12, 18, 20)
4854

4955
End with:
5056
- `PASS` — No violations

.claude/agents/rgaa-auditor/AGENT.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ You receive a list of files to audit. Read each file and check against all crite
1414
## RGAA Checklist
1515

1616
### 1. Images (RGAA theme 1)
17-
- Every `<img>` has an `alt` attribute
17+
- All images use `import Image from "next/image"` — raw `<img>` is forbidden (blocked by hook)
18+
- Every `<Image>` has an `alt` prop
1819
- Decorative images use `alt=""`
1920
- Informative images have a descriptive `alt` (not "image", "photo", "icon")
2021
- DSFR icons use `aria-hidden="true"` when decorative

.claude/hooks/block-bad-patterns.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ check_pattern '\.(tsx|jsx)$' \
5050
# Inline SVG — JSX files only (DsfrPictogram is the only allowed SVG wrapper)
5151
check_pattern '\.(tsx|jsx)$' \
5252
'<svg[[:space:]>]' \
53-
'Inline <svg> is forbidden. Use DsfrPictogram, public/assets/*.svg + <img>, or DSFR icon classes (fr-icon-*).' \
53+
'Inline <svg> is forbidden. Use DsfrPictogram, public/assets/*.svg + <Image> (next/image), or DSFR icon classes (fr-icon-*).' \
5454
'shared/DsfrPictogram\.tsx'
5555

5656
# Direct process.env — use ~/env.js instead (exclude env.js, instrumentation, next.config)
@@ -80,4 +80,10 @@ check_pattern '\.(ts|tsx)$' \
8080
'Explicit `any` type is forbidden. Use `unknown` with type narrowing instead.' \
8181
'(__tests__|\.test\.|\.spec\.)'
8282

83+
# Raw <img> tags — use next/image Image component instead (allow test files)
84+
check_pattern '\.(tsx|jsx)$' \
85+
'<img[[:space:]>]' \
86+
'Raw <img> is forbidden. Use: import Image from "next/image".' \
87+
'(__tests__|\.test\.|\.spec\.|setup\.ts)'
88+
8389
exit 0

.claude/rules/automation.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ Blocks edits containing forbidden patterns:
2020
|---|---|---|
2121
| `biome-ignore`, `eslint-disable`, `@ts-ignore`, `@ts-expect-error` | `.ts/.tsx/.js/.jsx` | Fix the underlying issue |
2222
| `style={` | `.tsx/.jsx` | Use DSFR classes or a scoped SCSS module |
23-
| `<svg>` | `.tsx/.jsx` | Use `DsfrPictogram` for DSFR artwork, `public/assets/*.svg` + `<img>`, or DSFR icon classes (`fr-icon-*`) |
23+
| `<svg>` | `.tsx/.jsx` | Use `DsfrPictogram` for DSFR artwork, `public/assets/*.svg` + `<Image>`, or DSFR icon classes (`fr-icon-*`) |
24+
| `<img>` | `.tsx/.jsx` (excl. test files) | Use `import Image from "next/image"` |
2425
| `process.env` | `.ts/.tsx` (excl. `env.js`, `instrumentation.ts`, `next.config`, `trpc/react.tsx`) | `import { env } from "~/env.js"` |
2526
| `../../` (or deeper) | `.ts/.tsx` | Use `~/` path alias |
2627
| `@media` (width/screen) | `.scss` | Use DSFR mixins: `@include respond-from(md)` / `respond-to(sm)` |
@@ -69,11 +70,13 @@ Before reporting ANY task as done, launch **3 parallel agents**:
6970

7071
If any fails → fix → re-run. Only report completion when all 3 pass.
7172

73+
**Bonus: Next.js runtime check** — if the dev server is running, also call `nextjs_call(get_errors)` via the `next-devtools` MCP to catch runtime/compilation errors not visible in `pnpm typecheck`.
74+
7275
### Gate 2 — RGAA (always)
7376

7477
Verify **inline while writing** AND audit all created/modified files after implementation:
7578
- `<input>` → associated `<label>` via `htmlFor`/`id`
76-
- `<img>` descriptive `alt` (or `alt=""` if decorative)
79+
- Images → `import Image from "next/image"` (raw `<img>` blocked by hook), descriptive `alt` (or `alt=""` if decorative)
7780
- Decorative icons → `aria-hidden="true"`
7881
- `target="_blank"``<NewTabNotice />` present
7982
- Modals → `role="dialog"` + `aria-modal="true"` + `aria-labelledby`

.claude/rules/react-components.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,50 @@ return <div>{activeItems.map(i => <ActiveItem key={i.id} item={i} />)}</div>
2222
## No inline SVG (blocked by hook)
2323

2424
Never paste raw `<svg>` markup in `.tsx` components — the edit will be rejected.
25-
Place all SVG files in `public/assets/` and reference them via `<img src="/assets/icon.svg" alt="..." />` or use DSFR icon classes (`fr-icon-*`).
25+
Place all SVG files in `public/assets/` and reference them via `<Image>` from `next/image`, or use DSFR icon classes (`fr-icon-*`).
26+
27+
## Images: always use `next/image` (blocked by hook)
28+
29+
Raw `<img>` tags are forbidden in `.tsx` files — use `import Image from "next/image"` instead.
30+
Next.js `Image` provides automatic optimization (lazy loading, responsive sizing, format conversion).
31+
32+
```tsx
33+
// FORBIDDEN
34+
<img src="/assets/illustration.svg" alt="Illustration" />
35+
36+
// CORRECT
37+
import Image from "next/image";
38+
<Image src="/assets/illustration.svg" alt="Illustration" width={200} height={150} />
39+
```
40+
41+
- Decorative images: `alt=""`
42+
- Informative images: descriptive `alt` (not "image", "photo", "icon")
43+
- SVG from `public/assets/`: use `width` + `height` props
44+
- Remote images: declare the domain in `next.config.js` `images.remotePatterns`
45+
46+
## Figma assets: always SVG, never PNG/JPG
47+
48+
When extracting illustrations, icons, or graphics from Figma (`get_design_context`), **always export as SVG**.
49+
PNG and JPG raster formats are forbidden for illustrations and icons — they are not scalable and produce larger files.
50+
51+
| Asset type | Format | Where to store |
52+
|---|---|---|
53+
| Illustration / graphic | **SVG** | `public/assets/{module}/` |
54+
| Icon | **DSFR icon class** preferred, SVG fallback | `fr-icon-*` or `public/assets/icons/` |
55+
| Photo (real photograph) | **WebP** (exception: only format where SVG is impossible) | `public/assets/{module}/` |
56+
57+
```tsx
58+
// FORBIDDEN — raster export from Figma
59+
<Image src="/assets/illustration.png" alt="..." width={400} height={300} />
60+
61+
// CORRECT — vector export from Figma
62+
<Image src="/assets/illustration.svg" alt="..." width={400} height={300} />
63+
```
64+
65+
When calling `get_design_context` from the Figma MCP, if the returned asset download URLs point to PNG/JPG:
66+
1. Re-export manually as SVG from Figma, or
67+
2. Ask the user to export the asset as SVG from the Figma file
68+
3. Only accept PNG/JPG for actual photographs that cannot be vectorized
2669

2770
## .map() over 5 lines
2871

.claude/skills/audit-rgaa/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Split the files into batches of ~10 and launch parallel agents. Each agent:
3131
3. Reports findings with `[SEVERITY] RGAA-{theme}.{criterion} file:line — description`
3232

3333
The RGAA auditor agent checks:
34-
- **Theme 1**: Images — alt, decorative, icon accessibility
34+
- **Theme 1**: Images — `next/image` mandatory (raw `<img>` forbidden), alt, decorative, icon accessibility
3535
- **Theme 2**: Frames — iframe titles
3636
- **Theme 3**: Colors — color-only information, contrast via DSFR tokens
3737
- **Theme 5**: Tables — caption, th scope, no layout tables
@@ -48,6 +48,7 @@ The RGAA auditor agent checks:
4848

4949
Fix all `[ERROR]` findings automatically:
5050
- Missing `<label>` → add `<label htmlFor="...">` with matching `id`
51+
- Raw `<img>` → replace with `import Image from "next/image"` + `<Image>`
5152
- Missing `alt` → add descriptive `alt` (or `alt=""` if decorative)
5253
- Missing `aria-hidden="true"` on decorative icons → add it
5354
- Missing `<NewTabNotice />` → import and add after link text

.claude/skills/create-page/SKILL.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ Otherwise, code only the applicable parts in order:
8585
- Each component < 200 lines, `"use client"` only where needed
8686

8787
6. Run `pnpm typecheck` to validate foundations compile.
88+
7. If dev server is running, use `nextjs_call(get_errors)` to verify no runtime errors.
8889

8990
### Phase 3 — Pages (parallel agents)
9091

@@ -111,6 +112,8 @@ Each agent MUST follow:
111112
- **Server Components** by default, `"use client"` only for hooks/events/browser APIs
112113
- **DSFR classes** for all styling (no inline styles, no raw colors)
113114
- **DSFR MCP** to verify HTML structure before writing
115+
- **Figma assets as SVG**: export all illustrations/icons from Figma as SVG (never PNG/JPG). Store in `public/assets/{module}/`. Only accept raster for real photographs
116+
- **`next/image`**: all images use `import Image from "next/image"` (raw `<img>` blocked by hook)
114117
- **Accessibility**: labels, alt, aria attributes, semantic HTML, NewTabNotice
115118
- **English code**, French user-facing text
116119
- **Unit tests**: test observable behavior, mock boundaries only

.claude/skills/review-pr/SKILL.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ Get the diff and changed files:
2929
git diff origin/master...HEAD --name-only
3030
git diff origin/master...HEAD
3131
```
32-
Run the code review against the 15-point checklist in `.claude/agents/code-reviewer/AGENT.md`.
32+
Run the code review against the 21-point checklist in `.claude/agents/code-reviewer/AGENT.md`.
33+
This checklist covers all rules from `.claude/rules/` and `block-bad-patterns.sh`.
3334

3435
### Step 2 — Analyze findings
3536

@@ -46,12 +47,15 @@ For each **unresolved** issue (human comments + agent errors):
4647
2. Apply the fix following project conventions (see `CLAUDE.md` and `.claude/rules/`)
4748
3. If the fix is ambiguous, list options and ask for clarification
4849

49-
### Step 4 — Validate (parallel agents)
50+
### Step 4 — Quality gates (parallel agents)
5051

51-
Launch 3 parallel validation agents (same as `/validate`):
52-
- `pnpm typecheck`
53-
- `pnpm test`
54-
- `pnpm lint:check && pnpm format:check`
52+
Launch **5 parallel agents** (all mandatory per automation rules):
53+
54+
1. **Validation: typecheck**`pnpm typecheck`
55+
2. **Validation: tests**`pnpm test`
56+
3. **Validation: lint+format**`pnpm lint:check && pnpm format:check`
57+
4. **RGAA audit** — delegate to `rgaa-auditor` agent on all changed `.tsx` files (skip if no `.tsx` in diff)
58+
5. **Security audit** — delegate to `security-auditor` agent on all changed `.ts/.tsx` in `server/`, `routers/`, or tRPC (skip if no server files in diff)
5559

5660
### Step 5 — Report
5761

@@ -65,9 +69,15 @@ Launch 3 parallel validation agents (same as `/validate`):
6569
| Already resolved | @reviewer | ... | ... | Fixed in abc123 |
6670
| Needs clarification | @reviewer | ... | ... | Asked user |
6771
68-
### Automated Code Review: [PASS | NEEDS WORK | MINOR]
72+
### Automated Code Review (21-point checklist): [PASS | NEEDS WORK | MINOR]
6973
[Agent findings here]
7074
75+
### RGAA Audit: [PASS | NEEDS WORK | MINOR | SKIPPED — no .tsx files]
76+
[RGAA findings here]
77+
78+
### Security Audit: [SECURE | VULNERABLE | SKIPPED — no server files]
79+
[Security findings here]
80+
7181
### Validation: [PASS | FAIL]
7282
[Validation results here]
7383
```

CLAUDE.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@ Never create a git commit, unless the user explicitly requests it.
6262

6363
---
6464

65+
## MCP Servers (`.mcp.json`)
66+
67+
Three MCP servers are configured and **must be used** in the relevant contexts:
68+
69+
| Server | When to use | Key tools |
70+
|---|---|---|
71+
| `next-devtools` | Debugging, runtime errors, route inspection, Next.js docs | `nextjs_index`, `nextjs_call`, `nextjs_docs`, `browser_eval` |
72+
| `dsfr` | Before writing any DSFR HTML | `get_component_doc`, `search_components`, `get_color_tokens` |
73+
| `figma` | When implementing from a Figma design | `get_design_context`, `get_screenshot` |
74+
75+
**Next.js DevTools** is particularly important: use `nextjs_docs` to look up Next.js APIs (never guess from memory), and use `nextjs_call(get_errors)` to check runtime/compilation errors after changes.
76+
77+
See `packages/app/CLAUDE.md` for detailed usage instructions per MCP server.
78+
79+
---
80+
6581
## Useful root scripts
6682

6783
```bash
@@ -110,7 +126,7 @@ When creating multiple pages/screens, follow this 4-phase approach:
110126

111127
| Agent | Role | Triggered by |
112128
|---|---|---|
113-
| `code-reviewer` | 15-point code quality checklist | `/review-pr`, PR gate |
129+
| `code-reviewer` | 21-point code quality checklist | `/review-pr`, PR gate |
114130
| `rgaa-auditor` | 13-theme RGAA accessibility audit | `/audit-rgaa`, RGAA gate |
115131
| `security-auditor` | OWASP Top 10 + RGS security review | `/audit-secu`, security gate |
116132

@@ -119,7 +135,7 @@ When creating multiple pages/screens, follow this 4-phase approach:
119135
| Skill | Purpose |
120136
|---|---|
121137
| `/validate` | Force run all quality checks (3 parallel agents) |
122-
| `/review-pr` | Deep PR review: GH comments + code-reviewer agent + auto-fix |
138+
| `/review-pr` | Deep PR review: GH comments + code-reviewer + RGAA + security + auto-fix |
123139
| `/audit-rgaa` | Deep 13-theme RGAA audit with detailed report + auto-fix |
124140
| `/audit-secu` | Deep OWASP + RGS security audit with detailed report + auto-fix |
125141
| `/create-page` | Create pages from Figma (4-phase parallelized workflow) |

0 commit comments

Comments
 (0)