Skip to content

release/v1.4.1#255

Merged
KinjiKawaguchi merged 4 commits intomainfrom
release/v1.4.1
Apr 10, 2026
Merged

release/v1.4.1#255
KinjiKawaguchi merged 4 commits intomainfrom
release/v1.4.1

Conversation

@KinjiKawaguchi
Copy link
Copy Markdown
Member

@KinjiKawaguchi KinjiKawaguchi commented Apr 10, 2026


Open with Devin

KinjiKawaguchi and others added 4 commits April 10, 2026 21:50
## Why

CODEOWNERS の `@araaki12345` を現室長である
`@KikyoNanakusa`(https://github.com/KikyoNanakusa)に差し替える。

## What

- `CODEOWNERS` の1行を更新: `@araaki12345` → `@KikyoNanakusa`
<!-- devin-review-badge-begin -->

---

<a href="https://app.devin.ai/review/su-its/typing/pull/253"
target="_blank">
  <picture>
<source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img
src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1"
alt="Open with Devin">
  </picture>
</a>
<!-- devin-review-badge-end -->

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Why

依存アップグレード(Next.js 16 / React 19 / ESLint 9 フラットコンフィグ等)で `yarn build` /
`yarn lint` / CI インストールが壊れていたので、ビルドチェーンを再び通るようにする。併せて調査中に判明した Tailwind /
shadcn スタック一式や、役目を終えた依存の残骸も掃除する。

## What

### Next.js 16 / ESLint 9 アップグレード対応

- `next lint` 廃止に伴う ESLint フラットコンフィグ移行(`eslint.config.mjs`)
- Next.js 16 の purity lint / set-state-in-effect ルールに合わせた
`GameTyping.tsx` / `RankingTabs.tsx` / `game/page.tsx` の修正
- `tsconfig.json` を Next.js の自動補正(`jsx: react-jsx` /
`.next/dev/types/**` の include 追加)に追従

### Tailwind / shadcn スタック撤去

実態として `@tailwind` ディレクティブも Tailwind
ユーティリティクラスも使われていないため、関連パッケージ・設定ファイルを一式削除:

- **dependencies** 削除: `clsx`, `tailwind-merge`, `tailwindcss-animate`,
`@radix-ui/react-slot`, `@radix-ui/react-toast`,
`class-variance-authority`, `lucide-react`
- **devDependencies** 削除: `tailwindcss`, `@tailwindcss/postcss`,
`postcss`, `autoprefixer`
- **ファイル削除**: `tailwind.config.ts`, `postcss.config.js`,
`src/libs/shadcn/`
- autoprefixing は Next.js 組み込みの PostCSS パイプライン(`postcss-preset-env`)に委譲

### 依存整備

- `@testing-library/react` の peer 要件である `@testing-library/dom` を明示追加
- `prettier` を `dependencies` → `devDependencies` に分類修正
- `@types/node` / `eslint` / `prettier` / `tsx` の patch / minor 追従
- `@next/codemod` が自動挿入した `@types/react` / `@types/react-dom` の
`resolutions` ブロックを削除(React 19 エコシステム成熟により不要、むしろ古い `19.0.1` / `19.0.2`
に固定される副作用が出ていた)
- `openapi-typescript` v6 → v7 bump に合わせて `v1.d.ts` を v7 format で再生成
- `RankingTabs.tsx` の未使用 `useCallback` import 削除
- Yarn 4(Corepack)で `yarn.lock` をクリーン再生成

## How

- `tsconfig.json` の差分(`jsx` 変更・include 追加)は Next.js 16 が `next build`
時に自動で書き戻すもので、手動の設計変更ではない。ログ上も `jsx was set to react-jsx (next.js uses
the React automatic runtime)` として明示される。
- `postcss.config.js` を削除することで Next.js 組み込みの PostCSS
デフォルトにフォールバックする構成。最終出力 CSS(ベンダープレフィックス等)は従前と同じ。
- ESLint は `^9.39.4` 固定(v10 非対応): `eslint-plugin-react` が ESLint v10
で削除されたレガシー API(`context.getFilename()`)に依存しているため、上流修正
([jsx-eslint/eslint-plugin-react#3979](jsx-eslint/eslint-plugin-react#3979))
が公開されるまで v9 に留める。参考:
[vercel/next.js#89764](vercel/next.js#89764)
- lint は 0 errors(既存の `<img>` 6 件と 1 件の `react-hooks/exhaustive-deps`
警告のみ残る。いずれも今回のスコープ外)

## Validation

- `corepack yarn install --immutable`
- `corepack yarn format:ci`
- `corepack yarn lint`
- `corepack yarn build`
- `corepack yarn test --runInBand`

## References

- [Next.js 16 upgrade
guide](https://nextjs.org/docs/app/guides/upgrading/version-16)
- [Next.js ESLint migration / flat
config](https://nextjs.org/docs/app/api-reference/config/eslint)
- [Next.js
codemods](https://nextjs.org/docs/app/guides/upgrading/codemods)
- [Tailwind CSS v4 upgrade
guide](https://tailwindcss.com/docs/upgrade-guide)
- [vercel/next.js#89764 — ESLint v10
incompatibility](vercel/next.js#89764)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@KinjiKawaguchi KinjiKawaguchi merged commit 7ca482a into main Apr 10, 2026
2 checks passed
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

Open in Devin Review


// 開始時刻と処理フラグの参照
const startTimeRef = useRef<number>(Date.now());
const startTimeRef = useRef<number>(0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: startTimeRef initialization change relies on useEffect ordering guarantees

The change from useRef<number>(Date.now()) to useRef<number>(0) at typing-app/src/components/templates/GameTyping.tsx:34 moves the timer start initialization to a useEffect at line 193-196. This works correctly because: (1) the timer interval callback (line 110-121) won't fire until 100ms after mount, well after all effects complete; (2) keypress handlers only fire on user input, also after mount; (3) the completion check effect at line 127-132 checks stats.typeIndex === subjectText.length - 1, which is false on mount for any text longer than 1 character (all sample texts are 1000+ chars). The one theoretical edge case is if subjectText.length === 1, the completion effect would fire before the init effect (due to declaration order), reading startTimeRef.current = 0 and computing a wildly incorrect elapsed time (~55 years). This is unrealistic given the actual text files but worth noting as a fragile ordering dependency.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +60 to +91
useEffect(() => {
let isCancelled = false;

const fetchData = async () => {
const { data, error } = await client.GET("/scores/ranking", {
params: {
query: {
sort_by: sortBy,
start: rankingStartFrom,
limit: LIMIT,
},
},
},
});
if (data) {
setScoreRankings(data.rankings);
setTotalRankingCount(data.total_count);
} else {
showErrorToast(error);
}
}, [sortBy, rankingStartFrom]);
});

useEffect(() => {
fetchData();
}, [fetchData]);
if (isCancelled) {
return;
}

if (data) {
setScoreRankings(data.rankings);
setTotalRankingCount(data.total_count);
} else {
showErrorToast(error);
}
};

void fetchData();

return () => {
isCancelled = true;
};
}, [refreshKey, sortBy, rankingStartFrom]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: RankingTabs refactor fixes a pre-existing stale-data bug on refresh

The old code called fetchData() directly in the refresh button handler after setRankingStartFrom(1). Since fetchData was a useCallback capturing rankingStartFrom in its closure, the direct call would use the stale (pre-update) value of rankingStartFrom, not the newly-set value of 1. The new approach using refreshKey state to trigger a useEffect ensures the fetch always uses the committed state values. This is a correctness improvement, not just a refactor.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

const filenames = fs.readdirSync("public/texts/");

const getRandomSubjectText = () => {
const randomFilename = filenames[Math.floor(Math.random() * filenames.length)] ?? filenames[0];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: game/page.tsx: nullish coalescing fallback is ineffective when filenames is empty

At line 7, filenames[Math.floor(Math.random() * filenames.length)] ?? filenames[0] — the ?? filenames[0] fallback exists to satisfy noUncheckedIndexedAccess: true in tsconfig. However, if filenames is empty, Math.floor(Math.random() * 0) yields 0, filenames[0] is undefined, and the fallback filenames[0] is also undefined, so fs.readFileSync('public/texts/undefined') would throw. This is a pre-existing issue (the old code would also fail on an empty directory) and is unrealistic in practice since public/texts/ contains sample files.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread typing-app/package.json
"jest": "^30.3.0",
"jest-environment-jsdom": "^30.3.0",
"openapi-typescript": "7.13.0",
"prettier": "^3.8.2",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: prettier moved from dependencies to devDependencies

In the old package.json, prettier was listed under dependencies. It's now correctly placed in devDependencies at line 38. This is a proper fix — prettier is a development tool and should not be bundled as a production dependency.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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