Skip to content

Make npm run lint work in fast-qa - #258

Open
Hotragn wants to merge 1 commit into
tinyfish-io:mainfrom
Hotragn:hotragn/fix-fast-qa-lint
Open

Make npm run lint work in fast-qa#258
Hotragn wants to merge 1 commit into
tinyfish-io:mainfrom
Hotragn:hotragn/fix-fast-qa-lint

Conversation

@Hotragn

@Hotragn Hotragn commented Aug 18, 2026

Copy link
Copy Markdown

Completes #248. The seventh recipe, and the one with a different root cause from the six in #249 and #250.

The bug

$ cd fast-qa && npm run lint
> next lint
Invalid project directory provided, no such directory: .../fast-qa/lint
exit 1

fast-qa is on Next 16.1.3, and next lint was removed in Next 16 — the CLI parses lint as a directory argument. Same wall tenders-finder hit.

But swapping the script isn't enough here. fast-qa already ships an eslint.config.mjs that imports eslint/config, eslint-config-next/core-web-vitals and eslint-config-next/typescript, while declaring neither eslint nor eslint-config-next:

"devDependencies": {
  "@tailwindcss/postcss": "^4",
  "@types/node": "^22",
  "@types/react": "^19",
  "@types/react-dom": "^19",
  "postcss": "^8.5",
  "tailwindcss": "^4",
  "tw-animate-css": "^1.3.3",
  "typescript": "^5"
}

So renaming the script alone just moves the failure from "command not found" to "cannot resolve eslint". I tried exactly that while working on #250, backed it out, and reported it on #248 rather than shipping a half-fix.

The fix

eslint: ^9 and eslint-config-next: 16.1.3 added as devDependencies, matching silicon-signal — which is also on Next 16.1.3 and pins eslint-config-next to the same version. Script moves to eslint, the idiom in the other Next 16 recipes. The existing eslint.config.mjs is untouched and now actually resolves.

The 4 errors its first-ever lint run surfaced

app/api/execute-tests/route.ts:43 — three prefer-const violations on one destructure. Only parallelLimit is reassigned (clamped on the next line), so that stays let and the rest become const:

const { testCases, websiteUrl, settings } = body;
let { parallelLimit = 3 } = body;

components/qa/project-dialog.tsx:39react-hooks/set-state-in-effect. An effect was pushing four pieces of form state whenever the project prop changed:

useEffect(() => {
  setName(project?.name || '');
  setWebsiteUrl(project?.websiteUrl || '');
  setDescription(project?.description || '');
  setUrlError(null);
}, [project]);

There's exactly one call site, so rather than the render-phase adjustment dance I gave the dialog a key and let React remount it:

<ProjectDialog key={editingProject?.id ?? 'new'} ... />

The useState(project?.name || '') initialisers already did this work; the effect existed only because the component never remounted. With the key, the effect and its import both go away.

I walked the four paths to confirm behaviour is unchanged — open-for-create, open-for-edit, switching directly between two projects, and close (which resets editingProject to undefined). Each changes the key and remounts with the right values. 'new' can't collide with a real id either: generateId() returns `${Date.now()}-${random}`, so ids always start with a digit.

The manual field reset at the end of handleSave is now redundant but harmless, so I left it rather than widen the diff.

Verification

cd fast-qa && rm -rf node_modules && npm ci
npm run lint      # exit 0
npx tsc --noEmit  # exit 0

One pre-existing warning left alone — lib/hooks.ts:305, exhaustive-deps missing modifiers. Adding it risks changing render behaviour, which isn't a call to make inside a lint repair. Same treatment as the summer-school-finder warning in #250.

On the lockfile

The lock diff is large — 369 package entries added — because this recipe is getting ESLint for the first time and that pulls in the whole toolchain (typescript-eslint, the Next plugin, the import/jsx-a11y/react-hooks plugins and their transitives). I checked it isn't hiding anything else: the lock was already in sync with package.json before this change, unlike tutor-finder in #253, and regenerating with --package-lock-only instead of a full install produced the same result (369 vs 363), so the churn is inherent rather than incidental drift I introduced.

Independent of #249, #250 and #253.

The seventh recipe from tinyfish-io#248, and the one tinyfish-io#249 and tinyfish-io#250 could not cover.

npm run lint fails because fast-qa is on Next 16.1.3 and next lint was
removed in Next 16 -- the CLI reads "lint" as a directory argument:

  Invalid project directory provided, no such directory: .../fast-qa/lint

Renaming the script is not enough here. fast-qa already ships an
eslint.config.mjs importing eslint/config, eslint-config-next/core-web-vitals
and eslint-config-next/typescript, but declares neither eslint nor
eslint-config-next, so the config references three packages the recipe has
no dependency on. Changing only the script moves the failure from "command
not found" to "cannot resolve eslint".

Adds eslint ^9 and eslint-config-next 16.1.3 as devDependencies, matching
silicon-signal, which is also on Next 16.1.3 and pins the same version. The
existing config file is untouched and now resolves.

Turning lint on for the first time surfaced 4 errors, fixed here:

- execute-tests/route.ts destructured five names as let when only
  parallelLimit is reassigned; split so the rest are const.
- project-dialog.tsx pushed four pieces of form state from an effect
  whenever the project prop changed, tripping
  react-hooks/set-state-in-effect. There is one call site, so the dialog
  now takes a key and React remounts it, which is what the existing
  useState initialisers already did. The effect and its import are gone.
  Checked all four paths -- create, edit, switching projects, close -- and
  'new' cannot collide with a real id since generateId() starts with a
  timestamp.

Verified from scratch: rm -rf node_modules && npm ci, then npm run lint and
tsc --noEmit, all exit 0. One pre-existing exhaustive-deps warning in
lib/hooks.ts left alone, as adding the dependency risks changing render
behaviour.

The lock grows by 369 entries because this recipe is getting ESLint for the
first time. It was already in sync beforehand, and --package-lock-only
produces the same result, so the churn is the toolchain rather than drift.
@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 184f073f-efd8-4e00-8a00-5af07ebe4f29

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant