this string has no description
0
claude-roast.md
1 🔥 The FARE Website Roast
2
3 You wrote a 648-line, 27KB AGENTS.md that lovingly documents the One True Way
4 to handle errors, validate inputs, and shape return types... and then your
5 actual code reads like nobody on the team has ever opened it. The doc is a
6 beautiful constitution for a country that's in open civil war. Let's tour the
7 battlefield.
8
9 1. You preach tryCatch, then console.error your way through 19 actions
10
11 Your CLAUDE.md says, in bold, "never a bare console.error()". There are 19 of
12 them in src/actions/ alone. Some are even better — signOutAction.tsx:23 does
13 console.log("Deconnection réussie"), leaving a French success log in
14 production like a sticky note on a server rack. You built a captureActionError
15 helper, wrote three paragraphs about why it's superior, and then ignored it
16 everywhere except the files you happened to write last.
17
18 The tell: editArticleAction.tsx does error handling flawlessly. Eight sibling
19 actions break the exact rule it follows. That's not a style — that's
20 copy-paste archaeology. You can date the strata.
21
22 2. The discriminated union you mandated... doesn't exist
23
24 CLAUDE.md, line 96, capital letters energy: return { success: true } | {
25 success: false; error }, "never optional success?/error? fields."
26
27 Reality (grep doesn't lie):
28 ): Promise<{ error?: string; success?: boolean }> //
29 approveAssociationAction.tsx:14
30 Both fields optional, so if (result.success) console.log(result.error)
31 type-checks fine and means nothing. You wrote a rule specifically forbidding
32 this, have a TODO.md item #3 admitting you haven't done it, and shipped it
33 anyway. The type system is right there, begging to help you, and you put it in
34 a headlock with as unknown as 14 times — including undefined as unknown as
35 File in the adhesion form, which is just any wearing a fake moustache.
36
37 3. schema.prisma: zero indexes. ZERO.
38
39 @@index count: 0
40 Every foreign key — authorId, creatorId, categoryId, userId — unindexed. Every
41 WHERE on a relation is a full table scan waiting to happen. It's fine now
42 because your tables have twelve rows, but the day this student federation gets
43 popular, the database is going to fall over and the post-mortem is going to
44 be one line long.
45
46 And 17 columns default to "". email String @default("") means "missing" and
47 "empty" are the same value, so where: { email: "" } cheerfully returns every
48 junk record. You turned NULL — a perfectly good word for "nothing" — into a
49 scavenger hunt.
50
51 4. assocation
52
53 assocation String // schema.prisma:149
54 A typo. In the schema. Migrated to the database. Now load-bearing across 8
55 files — ticket.assocation in pages, cards, actions, and the test that asserts
56 it's spelled wrong. You had every opportunity to fix it and instead you
57 enshrined it. It's not a bug anymore, it's heritage. Future you will type
58 association, get undefined, and lose forty minutes to a missing 's'.
59
60 5. A 1,629-line form component
61
62 src/app/(public)/a-propos/adhesion/form.tsx — 1,629 lines. That's not a
63 component, that's a novella. Right behind it: a 944-line form, an 819-line
64 form, and a 753-line sidebar. The same 40-line <form.Field> render-prop
65 boilerplate is pasted verbatim 15+ times per file because extracting a
66 <FieldInput> would have been too easy. You have react-hook-form AND
67 @tanstack/react-form in package.json simultaneously — TODO.md #2 admits you're
68 mid-migration between two form libraries you haven't finished picking.
69
70 Meanwhile eventCard.tsx does hover effects via raw DOM mutation in
71 onMouseEnter (e.currentTarget.style.backgroundColor = ...) in a codebase whose
72 rules say "❌ Don't add inline styles." You're hand-rolling :hover in
73 JavaScript. CSS has done this for free since 1996.
74
75 6. The config quietly disables the safety nets
76
77 - tsconfig.json:5 → skipLibCheck: true
78 - next.config.mjs:65 → typescript: { ignoreBuildErrors: true }
79
80 The build will not fail on type errors. You've delegated all type safety to
81 "CI runs it separately," which means your production build is one red CI
82 checkmark away from shipping undefined is not a function. You also
83 console.log(formDataToString(formData)) inside editMemberButton.tsx — logging
84 raw member form data, including whatever PII a student federation collects,
85 straight to the console.
86
87 7. The test suite is a Potemkin village
88
89 50/50 server actions tested — genuinely good, credit where due. But it's 5.8%
90 component coverage (7 of ~120) and the action tests mock every boundary: DB,
91 email, captcha, storage. They prove your if statements branch correctly. They
92 prove nothing about whether Prisma's actual types match, whether the email
93 payload is real, or whether the upload works. It's a test suite that turns
94 green when the schema changes underneath it. Confidence theatre with a
95 50-action cast.
96
97 And TODO.md #1, marked HIGH PRIORITY: an unfixed XSS gap in
98 dangerouslySetInnerHTML rich-text rendering. The thing every web security
99 checklist screams about, parked in a markdown file with a 🟠 emoji and a vibe
100 of "this quarter, probably."
101
102 ---
103 The actual diagnosis
104
105 Here's the thing that should sting the most: you clearly know what good looks
106 like. The 648-line guide proves it. editArticleAction.tsx proves it. tryCatch,
107 the custom oxlint rule, the Sentry wrapper — these are the instincts of
108 someone who cares. The problem isn't ignorance.
109
110 The problem is you wrote the documentation of discipline instead of the
111 enforcement of it. Your local/no-try-catch rule is set to "warn" — a warning
112 nobody reads. Your discriminated-union mandate has no lint rule at all. So the
113 codebase did what every codebase does without a gate: it drifted toward the
114 path of least resistance, one copy-paste at a time, while the AGENTS.md sat in
115 the corner being aspirational.
116
117 You don't have a knowledge problem. You have a "make the linter angry instead
118 of the markdown sad" problem.
119
120 If you fix five things, fix these (highest pain ÷ effort):
121
122 1. Turn local/no-try-catch and a new "no optional success/error" rule into
123 errors, then pnpm lint --fix your way through the carnage. Let the robot
124 enforce what the doc only requests.
125 2. Add @@index to every FK in schema.prisma. One migration. Cheap insurance
126 against the day this matters.
127 3. Delete skipLibCheck/ignoreBuildErrors OR accept they're off and stop
128 pretending the build is a safety net.
129 4. Extract one <FieldInput> and one <DialogForm> wrapper — that's ~3,000 lines
130 of duplication gone in two components.
131 5. Do TODO.md #1 (the XSS one). It's been "HIGH PRIORITY" long enough to
132 qualify for tenure.
133
134 You built something real and ambitious here — full-stack, RBAC, the works.
135 It's just held together with discipline you wrote down instead of discipline
136 you ran. Wire the rules into CI and 70% of this roast evaporates on its own.
137
138 Now go make the linter mad. 🫡