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