// Strip "AI slop" from code and prose in this repo — over-engineered abstractions, defensive checks on trusted paths, narrating comments, dead error handlers, redundant types, useEffect-for-derived-data, raw MUI imports, whole-store Zustand subscriptions, throat-clearing prose. Use when reviewing your own diff before commit, when the user says "unslop this", or when refactoring AI-generated code in `web/`, `electron/`, `mobile/`, or `packages/`. Triggers on TypeScript/React/Zustand/MUI/TanStack Query files. Tailored to NodeTool's stack (React 19, Zustand 4.5, MUI v7 + ui_primitives, TanStack Query v5, Vitest/Jest, Drizzle).
Strip "AI slop" from code and prose in this repo — over-engineered abstractions, defensive checks on trusted paths, narrating comments, dead error handlers, redundant types, useEffect-for-derived-data, raw MUI imports, whole-store Zustand subscriptions, throat-clearing prose. Use when reviewing your own diff before commit, when the user says "unslop this", or when refactoring AI-generated code in `web/`, `electron/`, `mobile/`, or `packages/`. Triggers on TypeScript/React/Zustand/MUI/TanStack Query files. Tailored to NodeTool's stack (React 19, Zustand 4.5, MUI v7 + ui_primitives, TanStack Query v5, Vitest/Jest, Drizzle).
Unslop
A pre-commit pass that removes patterns LLMs add reflexively but humans wouldn't write. Apply it to your own diff before claiming a task is done. The goal isn't to shorten the diff — it's to delete code whose absence would not be missed.
After making changes, run git diff and read every added line through the lenses below.
For each "slop" you find, delete or rewrite it. Don't leave a // removed X comment behind.
Re-run npm run typecheck && npm run lint && npm run test — unslopping must not break anything.
If you delete an abstraction, also delete its tests, types, and exports.
Categories
#
Category
Lens
1
General code slop
"Would removing this confuse a future reader?"
2
Comment slop
"Does the WHY survive without the comment?"
3
Error-handling slop
"Can this error actually happen here?"
4
TypeScript slop
"Is this type narrowing/casting load-bearing?"
5
React 19 slop
"Is this hook earning its keep?"
6
Zustand slop
"Does this component re-render only when it should?"
7
MUI / styling slop
"Is this a primitive or a theme value?"
8
TanStack Query slop
"Why isn't this useQuery?"
9
Test slop
"Does this test catch a real regression?"
10
Prose slop
"Did a human write this sentence?"
1. General code slop
Hunt for:
Speculative abstractions. A Strategy interface with one implementation. A factory that returns one type. Three near-identical lines beat a premature helper.
Half-finished features behind unused flags or if (false) branches. Delete them.
Re-exports of things nothing imports. Run a search before claiming "this might be useful elsewhere".
Backwards-compatibility shims for code paths you just changed. If callers are all in this repo, update the callers.
_unused parameter renames for vars that could simply be removed.
// TODO for issues you could fix in this PR, or worse, // removed X — see commit Y.
Default to no comments. Only write one when the WHY is non-obvious — a hidden constraint, a workaround for a specific bug, a subtle invariant.
Delete on sight:
Comments that restate the next line: // increment counter, // loop over nodes, // return the result.
File or function "summary" docstrings that name-rephrase the identifier (/** Get node label. Returns the node's label. */).
"Added by" / "fixes #123" / "used by FooComponent" annotations — that belongs in the commit message and PR description, not the source.
Multi-line block comments above functions in this codebase. One short line max.
Section banners like // ============ HELPERS ============.
Bad:
/**
* Adds a node to the workflow.
* @paramnode - The node to add.
* @returnsvoid
*/// Used by NodeEditor.tsx (added in PR #2790)functionaddNode(node: Node): void {
this.nodes.push(node); // push the node
}
This repo uses React 19. Many older idioms are now slop.
Hunt for:
useEffect to derive state from props. Compute it during render. useMemo only if the computation is genuinely expensive or referential stability matters.
useEffect to sync to a parent prop — lift state up or use a key.
useCallback / useMemo "just in case" when the value isn't passed to a memoized child or used as a hook dep.
React.memo on every component. Only when profiling shows wasted renders on stable props.
forwardRef — React 19 passes ref as a normal prop. Drop the wrapper.
useState for values derived from one prop — just use the prop.
Inline arrow handlers passed to a React.memo'd child — wrap with useCallback or stop memoizing the child.
Empty useEffect(() => {}, []) or effects whose only job is setX(props.x).
Certainly! / Of course! / Absolutely! as a reply opener
"Not just X. Y." binary contrasts
"X. Y. Z." formulaic three-beats when one would do
Numbered lists padded to three items because three feels balanced
Style checks:
Active voice. Name the actor: "the workflow runner emits…", not "a message is emitted…".
Concrete verbs over hedges: "this fails when X" beats "this might cause issues with X".
One idea per sentence. Vary sentence length.
For commit messages: imperative mood, why over what. The diff already shows what.
Bad:
This PR delves into the node selection landscape and leverages a robust new approach to seamlessly handle the intuitive interaction. Worth noting that it lays the foundation for future enhancements.
Good:
Fixes a race where rapid clicks could leave two nodes marked selected. The selection store now tracks a single id instead of a Set; multi-select moved to a separate store.
Self-review checklist
Run through this before declaring a task done. Treat any "yes" as a slop sighting to fix.
Did I add a comment that restates the code, names the PR, or describes a removed feature?
Did I add a try/catch whose error path can't actually trigger here?
Did I write any, as any, or as unknown as to silence the compiler?
Did I add useEffect to compute a value from props/state I already have?
Did I useCallback/useMemo/React.memo without a memoized consumer or measurable cost?
Did I subscribe to a whole Zustand store (const s = useFooStore()) or skip shallow on a multi-key selector?
Did I import a raw MUI component into a non-primitive file, or hardcode a color/spacing?
Did I write a useEffect+fetch instead of useQuery?
Does any new test assert implementation rather than user-visible behavior?
Are there // TODO, // removed, // added by, or "useful elsewhere" leftovers?
Could three near-identical lines have been left as-is instead of becoming a helper?
Does my prose contain any banned openers or filler ("delve", "robust", "seamlessly", "Here's the thing")?
After all this, do typecheck, lint, and test still pass?
If everything is "no", the diff is unslopped.
Sources
Patterns synthesized from these community skills, adapted to NodeTool's stack: